diff options
Diffstat (limited to 'servers')
444 files changed, 237576 insertions, 0 deletions
diff --git a/servers/Makefile.in b/servers/Makefile.in new file mode 100644 index 0000000..515019d --- /dev/null +++ b/servers/Makefile.in @@ -0,0 +1,17 @@ +# servers Makefile.in for OpenLDAP +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +SUBDIRS= slapd + diff --git a/servers/slapd/DB_CONFIG b/servers/slapd/DB_CONFIG new file mode 100644 index 0000000..d0f2c68 --- /dev/null +++ b/servers/slapd/DB_CONFIG @@ -0,0 +1,28 @@ +# $OpenLDAP$ +# Example DB_CONFIG file for use with slapd(8) BDB/HDB databases. +# +# See the Oracle Berkeley DB documentation +# <http://www.oracle.com/technology/documentation/berkeley-db/db/ref/env/db_config.html> +# for detail description of DB_CONFIG syntax and semantics. +# +# Hints can also be found in the OpenLDAP Software FAQ +# <http://www.openldap.org/faq/index.cgi?file=2> +# in particular: +# <http://www.openldap.org/faq/index.cgi?file=1075> + +# Note: most DB_CONFIG settings will take effect only upon rebuilding +# the DB environment. + +# one 0.25 GB cache +set_cachesize 0 268435456 1 + +# Data Directory +#set_data_dir db + +# Transaction Log settings +set_lg_regionmax 262144 +set_lg_bsize 2097152 +#set_lg_dir logs + +# Note: special DB_CONFIG flags are no longer needed for "quick" +# slapadd(8) or slapindex(8) access (see their -q option). diff --git a/servers/slapd/Makefile.in b/servers/slapd/Makefile.in new file mode 100644 index 0000000..afc992f --- /dev/null +++ b/servers/slapd/Makefile.in @@ -0,0 +1,460 @@ +## Makefile.in for slapd +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +SLAPTOOLS=slapadd slapcat slapdn slapindex slappasswd slaptest slapauth slapacl slapschema +PROGRAMS=slapd $(SLAPTOOLS) +XPROGRAMS=sslapd libbackends.a .backend liboverlays.a +XSRCS=version.c + +SUBDIRS=back-* shell-backends slapi overlays + +NT_SRCS = nt_svc.c +NT_OBJS = nt_svc.o ../../libraries/liblutil/slapdmsg.res + +SRCS = main.c globals.c bconfig.c config.c daemon.c \ + connection.c search.c filter.c add.c cr.c \ + attr.c entry.c backend.c result.c operation.c \ + dn.c compare.c modify.c delete.c modrdn.c ch_malloc.c \ + value.c ava.c bind.c unbind.c abandon.c filterentry.c \ + phonetic.c acl.c str2filter.c aclparse.c init.c user.c \ + lock.c controls.c extended.c passwd.c \ + schema.c schema_check.c schema_init.c schema_prep.c \ + schemaparse.c ad.c at.c mr.c syntax.c oc.c saslauthz.c \ + oidm.c starttls.c index.c sets.c referral.c root_dse.c \ + sasl.c module.c mra.c mods.c sl_malloc.c zn_malloc.c limits.c \ + operational.c matchedValues.c cancel.c syncrepl.c \ + backglue.c backover.c ctxcsn.c ldapsync.c frontend.c \ + slapadd.c slapcat.c slapcommon.c slapdn.c slapindex.c \ + slappasswd.c slaptest.c slapauth.c slapacl.c component.c \ + aci.c alock.c txn.c slapschema.c \ + $(@PLAT@_SRCS) + +OBJS = main.o globals.o bconfig.o config.o daemon.o \ + connection.o search.o filter.o add.o cr.o \ + attr.o entry.o backend.o backends.o result.o operation.o \ + dn.o compare.o modify.o delete.o modrdn.o ch_malloc.o \ + value.o ava.o bind.o unbind.o abandon.o filterentry.o \ + phonetic.o acl.o str2filter.o aclparse.o init.o user.o \ + lock.o controls.o extended.o passwd.o \ + schema.o schema_check.o schema_init.o schema_prep.o \ + schemaparse.o ad.o at.o mr.o syntax.o oc.o saslauthz.o \ + oidm.o starttls.o index.o sets.o referral.o root_dse.o \ + sasl.o module.o mra.o mods.o sl_malloc.o zn_malloc.o limits.o \ + operational.o matchedValues.o cancel.o syncrepl.o \ + backglue.o backover.o ctxcsn.o ldapsync.o frontend.o \ + slapadd.o slapcat.o slapcommon.o slapdn.o slapindex.o \ + slappasswd.o slaptest.o slapauth.o slapacl.o component.o \ + aci.o alock.o txn.o slapschema.o \ + $(@PLAT@_OBJS) + +LDAP_INCDIR= ../../include -I$(srcdir) -I$(srcdir)/slapi -I. +LDAP_LIBDIR= ../../libraries + +SLAP_DIR= +SLAPD_STATIC_DEPENDS=@SLAPD_NO_STATIC@ libbackends.a liboverlays.a +SLAPD_STATIC_BACKENDS=@SLAPD_STATIC_BACKENDS@ +SLAPD_DYNAMIC_BACKENDS=@SLAPD_DYNAMIC_BACKENDS@ + +SLAPI_LIBS=@LIBSLAPI@ @SLAPI_LIBS@ + +XDEFS = $(MODULES_CPPFLAGS) +XLDFLAGS = $(MODULES_LDFLAGS) + +XLIBS = $(SLAPD_STATIC_DEPENDS) $(SLAPD_L) $(MODULES_LIBS) +XXLIBS = $(SLAPD_LIBS) $(SECURITY_LIBS) $(LUTIL_LIBS) +XXXLIBS = $(LTHREAD_LIBS) $(SLAPI_LIBS) + +BUILD_OPT = "--enable-slapd" +BUILD_SRV = @BUILD_SLAPD@ + +all-local-srv: all-cffiles + +NT_SLAPD_DEPENDS = slapd.exp +NT_SLAPD_OBJECTS = slapd.exp symdummy.o $(OBJS) version.o + +UNIX_SLAPD_DEPENDS = $(SLAPD_STATIC_DEPENDS) version.o $(SLAPD_L) +UNIX_SLAPD_OBJECTS = $(OBJS) version.o + +SLAPD_DEPENDS = $(@PLAT@_SLAPD_DEPENDS) +SLAPD_OBJECTS = $(@PLAT@_SLAPD_OBJECTS) + +# Notes about slapd for Windows +# ============================= +# slapd.exe must export all of its global symbols, just like a DLL. +# The purpose of this is to allow dynamic modules (dynamic backends +# or external dynamic modules) to bind with the symbols at run-time. +# +# Exporting symbols from an .EXE is a bit tricky and involves multiple +# steps. First a .DEF file must be generated. The .DEF file indicates +# the set of symbols that are to be exported. Many times, it's possible +# to manually create this file with an editor. However, with slapd, +# we want to export EVERY global symbol that it knows about (NOT including +# symbols that are imported from other DLLs). The set of symbols to +# export INCLUDES symbols from all static libraries that slapd gets +# linked with, e.g. avl, lunicode, lutil, etc. This list +# will also include liblber and libldap_r if they were built as static +# libraries. ALSO included will be symbols from other STATIC libraries +# outside the domain of the OpenLDAP source tree, e.g. regex, ltdl, +# crypto, ssl, sasl, etc. (If these libraries are dynamic, we won't want +# to include their symbols in the list). The correct set of symbols +# CAN be determined at build time. The slapd.def target automatically +# determines the correct set of symbols and generates the slapd.def file. +# +# The slapd.def file, serving multiple purposes, will: +# +# 1) be used to generate libslapd.a, the import library for slapd.exe. +# +# 2) be used to generate the symdummy.c file. +# +# 3) be used to help create slapd.exp, the binary-formated slapd export file. +# +# The import library is used by dynamic modules at link time. With this +# library, dynamic modules indicate to the linker that it will resolve +# these symbols from the slapd.exe binary at run-time. Of course, whenever +# a module imports dynamic symbols, those symbols should be marked with +# the __declspec(dllimport) directive in the header files that the dynamic +# modules build with. In OpenLDAP, this is handled automatically in the +# header files. (See ldap_cdefs.h for an explanation). Writers of +# dynamic backend modules should keep in mind that slapd.exe might export +# other global symbols that are not part of OpenLDAP (e.g. regex, ltdl, +# crypto, ssl, sasl, etc.) When a writer actually uses (i.e. imports) these +# symbols, he must verify that the header files from these external packages +# include a mechanism to mark imported symbols with the __declspec(dllimport) +# directive. Whether or not such a mechanism exists, the writer must be +# able to include these directives appropriately when their symbols are +# being imported from slapd.exe. The directive is not completely necessary +# for functions, but it is required for variables. +# +# The symdummy.c file basically references EVERY symbol available to slapd.exe, +# including symbols that slapd.exe never actually referenced. The file +# is compiled and included at link time. Without this object file, slapd.exe +# would NOT export symbols that it never referenced. The reason that these +# symbols must still be exported is because a dynamic module may want to +# use a symbol even if it had not been referenced by slapd.exe. +# + +# +# slapd.def REALLY depends upon all slapd objects and all static libraries +# included in $(LIBS), including static libraries outside of OpenLDAP. +# When slapd.def is built, the absolute paths to all static libraries +# (both inside and outside of OpenLDAP) are generated. We don't have +# any way to include this generated list as a dependency of slapd.def (sigh). +# Thus, we do the best we can by depending on version.o, which depends +# on its own very long list of dependencies. +# +slapd.def: libbackends.a liboverlays.a version.o + @for i in XX $(LDFLAGS) ; do \ + path=`expr "$$i" : "-L\(.*\)"`; \ + if test $$? != 0; then continue; fi; \ + paths="$$paths $$path"; \ + done; \ + objs=""; \ + for i in $(OBJS) version.o $(LIBS) ; do \ + obj="" ; \ + case $$i in \ + -l*) \ + done="" ;\ + base=`expr "$$i" : "-l\(.*\)"`; \ + for p in . $$paths ; do \ + for ext in la dll dll.a a ; do \ + path=$$p/lib$$base.$$ext; \ + test ! -f $$path && continue; \ + if test $$ext = la ; then \ + for t in dlname old_library ; do \ + line=`grep "^$$t=" $$path`; \ + lib=`expr "$$line" : "[^']*'\(.*\)'"`; \ + test -n "$$lib" && test -f $$p/$$lib && \ + path=$$p/$$lib && break; \ + done; \ + test $$t = dlname && ext=dll; \ + test $$t = old_library && ext=a; \ + fi; \ + if test $$ext = a ; then \ + obj=$$path; \ + fi; \ + done=done; \ + break; \ + done; \ + test -n "$$done" && break; \ + done; \ + test -z "$$obj" && continue; \ + ;; \ + *.la) \ + if test -n "$(LTSTATIC)"; then \ + base=`expr "$$i" : ".*/\(.*\).la"`; \ + path=`expr "$$i" : "\(.*/\).*"`; \ + obj=$$path.libs/$$base.a; \ + fi; \ + ;; \ + *.dll.a) \ + ;; \ + *.o | *.a) \ + obj=$$i; \ + esac; \ + objs="$$objs $$obj"; \ + done; \ + echo dlltool --exclude-symbols main,ServiceMain@8 --export-all-symbols \ + --output-def $@.tmp $$objs; \ + dlltool --exclude-symbols main,ServiceMain@8 --export-all-symbols \ + --output-def $@.tmp $$objs; + echo EXPORTS > $@ + $(SED) -e 1,2d -e 's/ @ [0-9][0-9]*//' -e '/\.refptr\./d' $@.tmp | sort >> $@ + $(RM) $@.tmp + +symdummy.c: slapd.def + $(RM) $@ + @echo "generating $@..."; \ + echo "static void never_called() {" > $@.tmp; \ + cat $< | while read line; \ + do \ + set dummy $$line; \ + case $$# in \ + 3) \ + echo "int $$2();" >> $@; \ + echo "$$2();" >> $@.tmp; \ + ;; \ + 4) \ + echo "extern int $$2;" >> $@; \ + echo "$$2 = 0;" >> $@.tmp; \ + ;; \ + esac; \ + done; \ + echo "" >> $@; \ + echo "}" >> $@.tmp; \ + cat $@.tmp >> $@; \ + $(RM) $@.tmp + +libslapd.a: symdummy.o + dlltool --dllname slapd.exe --input-def slapd.def --output-lib $@ + +slapd.exp: libslapd.a + @echo $(LTLINK) -Wl,--base-file,slapd.base -o slapd \ + $(OBJS) symdummy.o version.o $(LIBS) $(WRAP_LIBS); \ + $(LTLINK) -Wl,--base-file,slapd.base -o slapd \ + $(OBJS) symdummy.o version.o $(LIBS) $(WRAP_LIBS) + $(RM) slapd.exe + @echo dlltool --dllname slapd.exe --input-def slapd.def \ + --base-file slapd.base --output-exp $@; \ + dlltool --dllname slapd.exe --input-def slapd.def \ + --base-file slapd.base --output-exp $@; \ + echo $(LTLINK) -Wl,--base-file,slapd.base -o slapd $@ \ + $(OBJS) symdummy.o version.o $(LIBS) $(WRAP_LIBS); \ + $(LTLINK) -Wl,--base-file,slapd.base -o slapd $@ \ + $(OBJS) symdummy.o version.o $(LIBS) $(WRAP_LIBS) + $(RM) slapd.exe + @echo dlltool --dllname slapd.exe --input-def slapd.def \ + --base-file slapd.base --output-exp $@; \ + dlltool --dllname slapd.exe --input-def slapd.def \ + --base-file slapd.base --output-exp $@ + +slapi/libslapi.la: FORCE + (cd slapi; $(MAKE) $(MFLAGS) all) + +slapd: $(SLAPD_DEPENDS) @LIBSLAPI@ + $(LTLINK) -o $@ $(SLAPD_OBJECTS) $(LIBS) \ + $(WRAP_LIBS) + $(RM) $(SLAPTOOLS) + for i in $(SLAPTOOLS); do \ + $(LN_S) slapd$(EXEEXT) $$i$(EXEEXT); done + + +sslapd: version.o + $(LTLINK) -static -o $@ $(OBJS) version.o $(LIBS) $(WRAP_LIBS) + +dummy $(SLAPD_DYNAMIC_BACKENDS): slapd + cd $@; $(MAKE) $(MFLAGS) all + @touch $@ + +dynamic_overlays: slapd + cd overlays; $(MAKE) $(MFLAGS) dynamic + +# +# In Windows, dynamic backends have to be built after slapd. For this +# reason, we only build static backends now and dynamic backends later. +# +.backend: FORCE + @if test -n "$(SLAPD_STATIC_BACKENDS)"; then \ + echo "building static backends..."; \ + for i in XX $(SLAPD_STATIC_BACKENDS); do \ + if test $$i != XX; then \ + echo " "; echo " cd $$i; $(MAKE) $(MFLAGS) all"; \ + ( cd $$i; $(MAKE) $(MFLAGS) all ); \ + if test $$? != 0; then exit 1; fi; \ + fi; \ + done; \ + echo " "; \ + fi + +libbackends.a: .backend + @$(RM) -r tmp + @$(MKDIR) tmp + @-for i in back-*/*.a; do \ + ( \ + cd tmp; \ + $(AR) x ../$$i; \ + pre=`echo $$i | $(SED) -e 's/\/.*$$//' -e 's/back-//'`; \ + for j in *.o; do \ + mv $$j $${pre}$$j; \ + done; \ + $(AR) ruv libbackends.a *.o 2>&1 | grep -v truncated; \ + $(RM) *.o __.SYMDEF ________64ELEL_ ; \ + echo "added backend library $$i"; \ + echo ""; \ + ); \ + done + @mv -f tmp/libbackends.a ./libbackends.a + @$(RM) -r tmp + @if test ! -z "$(RANLIB)" ; then \ + $(RANLIB) libbackends.a; \ + fi + @ls -l libbackends.a; echo "" + +liboverlays.a: FORCE + cd overlays; $(MAKE) $(MFLAGS) static + +version.c: Makefile + @-$(RM) $@ + $(MKVERSION) -s -n Versionstr slapd > $@ + +version.o: version.c $(OBJS) $(SLAPD_LIBDEPEND) + +backends.o: backends.c $(srcdir)/slap.h + +depend-local-srv: FORCE + @for i in $(SUBDIRS); do \ + if test -d $$i && test -f $$i/Makefile ; then \ + echo; echo " cd $$i; $(MAKE) $(MFLAGS) depend"; \ + ( cd $$i; $(MAKE) $(MFLAGS) depend ); \ + if test $$? != 0 ; then exit 1; fi ; \ + fi; \ + done + @echo "" + +clean-local: + $(RM) *.exp *.def *.base *.a *.objs symdummy.c + +veryclean-local: + $(RM) backends.c + +clean-local-srv: FORCE + @for i in $(SUBDIRS); do \ + if test -d $$i && test -f $$i/Makefile ; then \ + echo; echo " cd $$i; $(MAKE) $(MFLAGS) clean"; \ + ( cd $$i; $(MAKE) $(MFLAGS) clean ); \ + if test $$? != 0 ; then exit 1; fi ; \ + fi; \ + done + $(RM) *.tmp all-cffiles + +veryclean-local-srv: FORCE + @for i in $(SUBDIRS); do \ + if test -d $$i && test -f $$i/Makefile ; then \ + echo; echo " cd $$i; $(MAKE) $(MFLAGS) clean"; \ + ( cd $$i; $(MAKE) $(MFLAGS) veryclean ); \ + fi; \ + done + +install-dbc-maybe: install-dbc-@BUILD_BDB@ install-dbc-@BUILD_HDB@ + +install-dbc-yes: install-db-config +install-dbc-mod: install-db-config +install-dbc-no: + +install-local-srv: install-slapd install-tools \ + install-conf install-dbc-maybe install-schema install-tools + +install-slapd: FORCE + -$(MKDIR) $(DESTDIR)$(libexecdir) + -$(MKDIR) $(DESTDIR)$(localstatedir)/run + $(LTINSTALL) $(INSTALLFLAGS) $(STRIP) -m 755 \ + slapd$(EXEEXT) $(DESTDIR)$(libexecdir) + @for i in $(SUBDIRS); do \ + if test -d $$i && test -f $$i/Makefile ; then \ + echo; echo " cd $$i; $(MAKE) $(MFLAGS) install"; \ + ( cd $$i; $(MAKE) $(MFLAGS) install ); \ + if test $$? != 0 ; then exit 1; fi ; \ + fi; \ + done + +all-cffiles: slapd $(SLAPD_DYNAMIC_BACKENDS) dynamic_overlays + @if test $(PLAT) = NT; then \ + sysconfdir=`cygpath -w $(sysconfdir) | \ + $(SED) -e 's/\\\\/\\\\\\\\\\\\\\\\/g'`; \ + localstatedir=`cygpath -w $(localstatedir) | \ + $(SED) -e 's/\\\\/\\\\\\\\\\\\\\\\/g'`; \ + moduledir=`cygpath -w $(moduledir) | \ + $(SED) -e 's/\\\\/\\\\\\\\\\\\\\\\/g'`; \ + else \ + sysconfdir=$(sysconfdir); \ + localstatedir=$(localstatedir); \ + moduledir=$(moduledir); \ + fi; \ + $(SED) -e "s;%SYSCONFDIR%;$$sysconfdir;" \ + -e "s;%LOCALSTATEDIR%;$$localstatedir;" \ + -e "s;%MODULEDIR%;$$moduledir;" \ + $(srcdir)/slapd.conf > slapd.conf.tmp ; \ + $(SED) -e "s;%SYSCONFDIR%;$$sysconfdir;" \ + -e "s;%LOCALSTATEDIR%;$$localstatedir;" \ + -e "s;%MODULEDIR%;$$moduledir;" \ + $(srcdir)/slapd.ldif > slapd.ldif.tmp ; \ + touch all-cffiles + +install-schema: FORCE + @if test -d $(DESTDIR)$(schemadir) ; then \ + echo "MOVING EXISTING SCHEMA DIR to $(DESTDIR)$(schemadir).$$$$" ; \ + mv $(DESTDIR)$(schemadir) $(DESTDIR)$(schemadir).$$$$ ; \ + fi + $(MKDIR) $(DESTDIR)$(schemadir) + @SD=$(DESTDIR)$(schemadir) ; \ + files=`cd $(srcdir)/schema ; echo README *.ldif *.schema` ; \ + for i in $$files ; do \ + echo $(INSTALL) $(INSTALLFLAGS) -m 444 schema/$$i $$SD/$$i ; \ + $(INSTALL) $(INSTALLFLAGS) -m 444 $(srcdir)/schema/$$i $$SD/$$i ; \ + done + +install-conf: FORCE + @-$(MKDIR) $(DESTDIR)$(sysconfdir) + $(INSTALL) $(INSTALLFLAGS) -m 600 slapd.conf.tmp $(DESTDIR)$(sysconfdir)/slapd.conf.default + if test ! -f $(DESTDIR)$(sysconfdir)/slapd.conf; then \ + echo "installing slapd.conf in $(sysconfdir)"; \ + echo "$(INSTALL) $(INSTALLFLAGS) -m 600 slapd.conf.tmp $(DESTDIR)$(sysconfdir)/slapd.conf"; \ + $(INSTALL) $(INSTALLFLAGS) -m 600 slapd.conf.tmp $(DESTDIR)$(sysconfdir)/slapd.conf; \ + else \ + echo "PRESERVING EXISTING CONFIGURATION FILE $(DESTDIR)$(sysconfdir)/slapd.conf" ; \ + fi + $(INSTALL) $(INSTALLFLAGS) -m 600 slapd.ldif.tmp $(DESTDIR)$(sysconfdir)/slapd.ldif.default + if test ! -f $(DESTDIR)$(sysconfdir)/slapd.ldif; then \ + echo "installing slapd.ldif in $(sysconfdir)"; \ + echo "$(INSTALL) $(INSTALLFLAGS) -m 600 slapd.ldif.tmp $(DESTDIR)$(sysconfdir)/slapd.ldif"; \ + $(INSTALL) $(INSTALLFLAGS) -m 600 slapd.ldif.tmp $(DESTDIR)$(sysconfdir)/slapd.ldif; \ + else \ + echo "PRESERVING EXISTING CONFIGURATION FILE $(DESTDIR)$(sysconfdir)/slapd.ldif" ; \ + fi + +install-db-config: FORCE + @-$(MKDIR) $(DESTDIR)$(localstatedir) $(DESTDIR)$(sysconfdir) + @-$(INSTALL) -m 700 -d $(DESTDIR)$(localstatedir)/openldap-data + $(INSTALL) $(INSTALLFLAGS) -m 600 $(srcdir)/DB_CONFIG \ + $(DESTDIR)$(localstatedir)/openldap-data/DB_CONFIG.example + $(INSTALL) $(INSTALLFLAGS) -m 600 $(srcdir)/DB_CONFIG \ + $(DESTDIR)$(sysconfdir)/DB_CONFIG.example + +install-tools: FORCE + -$(MKDIR) $(DESTDIR)$(sbindir) + for i in $(SLAPTOOLS); do \ + $(RM) $(DESTDIR)$(sbindir)/$$i$(EXEEXT); \ + $(LN_S) -f $(DESTDIR)$(libexecdir)/slapd$(EXEEXT) $(DESTDIR)$(sbindir)/$$i$(EXEEXT); \ + done + diff --git a/servers/slapd/abandon.c b/servers/slapd/abandon.c new file mode 100644 index 0000000..a7da99d --- /dev/null +++ b/servers/slapd/abandon.c @@ -0,0 +1,141 @@ +/* abandon.c - decode and handle an ldap abandon operation */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/socket.h> + +#include "slap.h" + +int +do_abandon( Operation *op, SlapReply *rs ) +{ + ber_int_t id; + Operation *o; + const char *msg; + + Debug( LDAP_DEBUG_TRACE, "%s do_abandon\n", + op->o_log_prefix, 0, 0 ); + + /* + * Parse the abandon request. It looks like this: + * + * AbandonRequest := MessageID + */ + + if ( ber_scanf( op->o_ber, "i", &id ) == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, "%s do_abandon: ber_scanf failed\n", + op->o_log_prefix, 0, 0 ); + send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" ); + return SLAPD_DISCONNECT; + } + + Statslog( LDAP_DEBUG_STATS, "%s ABANDON msg=%ld\n", + op->o_log_prefix, (long) id, 0, 0, 0 ); + + if( get_ctrls( op, rs, 0 ) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s do_abandon: get_ctrls failed\n", + op->o_log_prefix, 0, 0 ); + return rs->sr_err; + } + + Debug( LDAP_DEBUG_ARGS, "%s do_abandon: id=%ld\n", + op->o_log_prefix, (long) id, 0 ); + + if( id <= 0 ) { + Debug( LDAP_DEBUG_ANY, "%s do_abandon: bad msgid %ld\n", + op->o_log_prefix, (long) id, 0 ); + return LDAP_SUCCESS; + } + + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + + /* Find the operation being abandoned. */ + LDAP_STAILQ_FOREACH( o, &op->o_conn->c_ops, o_next ) { + if ( o->o_msgid == id ) { + break; + } + } + + if ( o == NULL ) { + msg = "not found"; + /* The operation is not active. Just discard it if found. */ + LDAP_STAILQ_FOREACH( o, &op->o_conn->c_pending_ops, o_next ) { + if ( o->o_msgid == id ) { + msg = "discarded"; + /* FIXME: This traverses c_pending_ops yet again. */ + LDAP_STAILQ_REMOVE( &op->o_conn->c_pending_ops, + o, Operation, o_next ); + LDAP_STAILQ_NEXT(o, o_next) = NULL; + op->o_conn->c_n_ops_pending--; + slap_op_free( o, NULL ); + break; + } + } + + } else if ( o->o_tag == LDAP_REQ_BIND + || o->o_tag == LDAP_REQ_UNBIND + || o->o_tag == LDAP_REQ_ABANDON ) { + msg = "cannot be abandoned"; + +#if 0 /* Would break o_abandon used as "suppress response" flag, ITS#6138 */ + } else if ( o->o_abandon ) { + msg = "already being abandoned"; +#endif + + } else { + msg = "found"; + /* Set the o_abandon flag in the to-be-abandoned operation. + * The backend can periodically check this flag and abort the + * operation at a convenient time. However it should "send" + * the response anyway, with result code SLAPD_ABANDON. + * The functions in result.c will intercept the message. + */ + o->o_abandon = 1; + op->orn_msgid = id; + op->o_bd = frontendDB; + rs->sr_err = frontendDB->be_abandon( op, rs ); + } + + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + + Debug( LDAP_DEBUG_TRACE, "%s do_abandon: op=%ld %s\n", + op->o_log_prefix, (long) id, msg ); + return rs->sr_err; +} + +int +fe_op_abandon( Operation *op, SlapReply *rs ) +{ + LDAP_STAILQ_FOREACH( op->o_bd, &backendDB, be_next ) { + if ( op->o_bd->be_abandon ) { + (void)op->o_bd->be_abandon( op, rs ); + } + } + + return LDAP_SUCCESS; +} diff --git a/servers/slapd/aci.c b/servers/slapd/aci.c new file mode 100644 index 0000000..49bae62 --- /dev/null +++ b/servers/slapd/aci.c @@ -0,0 +1,1835 @@ +/* aci.c - routines to parse and check acl's */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#ifdef SLAPD_ACI_ENABLED + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/regex.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/unistd.h> + +#include "slap.h" +#include "lber_pvt.h" +#include "lutil.h" + +/* use most appropriate size */ +#define ACI_BUF_SIZE 1024 + +/* move to "stable" when no longer experimental */ +#define SLAPD_ACI_SYNTAX "1.3.6.1.4.1.4203.666.2.1" + +/* change this to "OpenLDAPset" */ +#define SLAPD_ACI_SET_ATTR "template" + +typedef enum slap_aci_scope_t { + SLAP_ACI_SCOPE_ENTRY = 0x1, + SLAP_ACI_SCOPE_CHILDREN = 0x2, + SLAP_ACI_SCOPE_SUBTREE = ( SLAP_ACI_SCOPE_ENTRY | SLAP_ACI_SCOPE_CHILDREN ) +} slap_aci_scope_t; + +enum { + ACI_BV_ENTRY, + ACI_BV_CHILDREN, + ACI_BV_ONELEVEL, + ACI_BV_SUBTREE, + + ACI_BV_BR_ENTRY, + ACI_BV_BR_CHILDREN, + ACI_BV_BR_ALL, + + ACI_BV_ACCESS_ID, + ACI_BV_PUBLIC, + ACI_BV_USERS, + ACI_BV_SELF, + ACI_BV_DNATTR, + ACI_BV_GROUP, + ACI_BV_ROLE, + ACI_BV_SET, + ACI_BV_SET_REF, + + ACI_BV_GRANT, + ACI_BV_DENY, + + ACI_BV_GROUP_CLASS, + ACI_BV_GROUP_ATTR, + ACI_BV_ROLE_CLASS, + ACI_BV_ROLE_ATTR, + + ACI_BV_SET_ATTR, + + ACI_BV_LAST +}; + +static const struct berval aci_bv[] = { + /* scope */ + BER_BVC("entry"), + BER_BVC("children"), + BER_BVC("onelevel"), + BER_BVC("subtree"), + + /* */ + BER_BVC("[entry]"), + BER_BVC("[children]"), + BER_BVC("[all]"), + + /* type */ + BER_BVC("access-id"), + BER_BVC("public"), + BER_BVC("users"), + BER_BVC("self"), + BER_BVC("dnattr"), + BER_BVC("group"), + BER_BVC("role"), + BER_BVC("set"), + BER_BVC("set-ref"), + + /* actions */ + BER_BVC("grant"), + BER_BVC("deny"), + + /* schema */ + BER_BVC(SLAPD_GROUP_CLASS), + BER_BVC(SLAPD_GROUP_ATTR), + BER_BVC(SLAPD_ROLE_CLASS), + BER_BVC(SLAPD_ROLE_ATTR), + + BER_BVC(SLAPD_ACI_SET_ATTR), + + BER_BVNULL +}; + +static AttributeDescription *slap_ad_aci; + +static int +OpenLDAPaciValidate( + Syntax *syntax, + struct berval *val ); + +static int +OpenLDAPaciPretty( + Syntax *syntax, + struct berval *val, + struct berval *out, + void *ctx ); + +static int +OpenLDAPaciNormalize( + slap_mask_t use, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *out, + void *ctx ); + +#define OpenLDAPaciMatch octetStringMatch + +static int +aci_list_map_rights( + struct berval *list ) +{ + struct berval bv; + slap_access_t mask; + int i; + + ACL_INIT( mask ); + for ( i = 0; acl_get_part( list, i, ',', &bv ) >= 0; i++ ) { + if ( bv.bv_len <= 0 ) { + continue; + } + + switch ( *bv.bv_val ) { + case 'x': + /* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt does not + * define any equivalent to the AUTH right, so I've just used + * 'x' for now. + */ + ACL_PRIV_SET(mask, ACL_PRIV_AUTH); + break; + case 'd': + /* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt defines + * the right 'd' to mean "delete"; we hijack it to mean + * "disclose" for consistency wuith the rest of slapd. + */ + ACL_PRIV_SET(mask, ACL_PRIV_DISCLOSE); + break; + case 'c': + ACL_PRIV_SET(mask, ACL_PRIV_COMPARE); + break; + case 's': + /* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt defines + * the right 's' to mean "set", but in the examples states + * that the right 's' means "search". The latter definition + * is used here. + */ + ACL_PRIV_SET(mask, ACL_PRIV_SEARCH); + break; + case 'r': + ACL_PRIV_SET(mask, ACL_PRIV_READ); + break; + case 'w': + ACL_PRIV_SET(mask, ACL_PRIV_WRITE); + break; + default: + break; + } + + } + + return mask; +} + +static int +aci_list_has_attr( + struct berval *list, + const struct berval *attr, + struct berval *val ) +{ + struct berval bv, left, right; + int i; + + for ( i = 0; acl_get_part( list, i, ',', &bv ) >= 0; i++ ) { + if ( acl_get_part(&bv, 0, '=', &left ) < 0 + || acl_get_part( &bv, 1, '=', &right ) < 0 ) + { + if ( ber_bvstrcasecmp( attr, &bv ) == 0 ) { + return(1); + } + + } else if ( val == NULL ) { + if ( ber_bvstrcasecmp( attr, &left ) == 0 ) { + return(1); + } + + } else { + if ( ber_bvstrcasecmp( attr, &left ) == 0 ) { + /* FIXME: this is also totally undocumented! */ + /* this is experimental code that implements a + * simple (prefix) match of the attribute value. + * the ACI draft does not provide for aci's that + * apply to specific values, but it would be + * nice to have. If the <attr> part of an aci's + * rights list is of the form <attr>=<value>, + * that means the aci applies only to attrs with + * the given value. Furthermore, if the attr is + * of the form <attr>=<value>*, then <value> is + * treated as a prefix, and the aci applies to + * any value with that prefix. + * + * Ideally, this would allow r.e. matches. + */ + if ( acl_get_part( &right, 0, '*', &left ) < 0 + || right.bv_len <= left.bv_len ) + { + if ( ber_bvstrcasecmp( val, &right ) == 0 ) { + return 1; + } + + } else if ( val->bv_len >= left.bv_len ) { + if ( strncasecmp( val->bv_val, left.bv_val, left.bv_len ) == 0 ) { + return(1); + } + } + } + } + } + + return 0; +} + +static slap_access_t +aci_list_get_attr_rights( + struct berval *list, + const struct berval *attr, + struct berval *val ) +{ + struct berval bv; + slap_access_t mask; + int i; + + /* loop through each rights/attr pair, skip first part (action) */ + ACL_INIT(mask); + for ( i = 1; acl_get_part( list, i + 1, ';', &bv ) >= 0; i += 2 ) { + if ( aci_list_has_attr( &bv, attr, val ) == 0 ) { + Debug( LDAP_DEBUG_ACL, + " <= aci_list_get_attr_rights " + "test %s for %s -> failed\n", + bv.bv_val, attr->bv_val, 0 ); + continue; + } + + Debug( LDAP_DEBUG_ACL, + " <= aci_list_get_attr_rights " + "test %s for %s -> ok\n", + bv.bv_val, attr->bv_val, 0 ); + + if ( acl_get_part( list, i, ';', &bv ) < 0 ) { + Debug( LDAP_DEBUG_ACL, + " <= aci_list_get_attr_rights " + "test no rights\n", + 0, 0, 0 ); + continue; + } + + mask |= aci_list_map_rights( &bv ); + Debug( LDAP_DEBUG_ACL, + " <= aci_list_get_attr_rights " + "rights %s to mask 0x%x\n", + bv.bv_val, mask, 0 ); + } + + return mask; +} + +static int +aci_list_get_rights( + struct berval *list, + struct berval *attr, + struct berval *val, + slap_access_t *grant, + slap_access_t *deny ) +{ + struct berval perm, actn, baseattr; + slap_access_t *mask; + int i, found; + + if ( attr == NULL || BER_BVISEMPTY( attr ) ) { + attr = (struct berval *)&aci_bv[ ACI_BV_ENTRY ]; + + } else if ( acl_get_part( attr, 0, ';', &baseattr ) > 0 ) { + attr = &baseattr; + } + found = 0; + ACL_INIT(*grant); + ACL_INIT(*deny); + /* loop through each permissions clause */ + for ( i = 0; acl_get_part( list, i, '$', &perm ) >= 0; i++ ) { + if ( acl_get_part( &perm, 0, ';', &actn ) < 0 ) { + continue; + } + + if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_GRANT ], &actn ) == 0 ) { + mask = grant; + + } else if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_DENY ], &actn ) == 0 ) { + mask = deny; + + } else { + continue; + } + + *mask |= aci_list_get_attr_rights( &perm, attr, val ); + *mask |= aci_list_get_attr_rights( &perm, &aci_bv[ ACI_BV_BR_ALL ], NULL ); + + if ( *mask != ACL_PRIV_NONE ) { + found = 1; + } + } + + return found; +} + +static int +aci_group_member ( + struct berval *subj, + const struct berval *defgrpoc, + const struct berval *defgrpat, + Operation *op, + Entry *e, + int nmatch, + regmatch_t *matches +) +{ + struct berval subjdn; + struct berval grpoc; + struct berval grpat; + ObjectClass *grp_oc = NULL; + AttributeDescription *grp_ad = NULL; + const char *text; + int rc; + + /* format of string is "{group|role}/objectClassValue/groupAttrName" */ + if ( acl_get_part( subj, 0, '/', &subjdn ) < 0 ) { + return 0; + } + + if ( acl_get_part( subj, 1, '/', &grpoc ) < 0 ) { + grpoc = *defgrpoc; + } + + if ( acl_get_part( subj, 2, '/', &grpat ) < 0 ) { + grpat = *defgrpat; + } + + rc = slap_bv2ad( &grpat, &grp_ad, &text ); + if ( rc != LDAP_SUCCESS ) { + rc = 0; + goto done; + } + rc = 0; + + grp_oc = oc_bvfind( &grpoc ); + + if ( grp_oc != NULL && grp_ad != NULL ) { + char buf[ ACI_BUF_SIZE ]; + struct berval bv, ndn; + AclRegexMatches amatches = { 0 }; + + amatches.dn_count = nmatch; + AC_MEMCPY( amatches.dn_data, matches, sizeof( amatches.dn_data ) ); + + bv.bv_len = sizeof( buf ) - 1; + bv.bv_val = (char *)&buf; + if ( acl_string_expand( &bv, &subjdn, + &e->e_nname, NULL, &amatches ) ) + { + rc = LDAP_OTHER; + goto done; + } + + if ( dnNormalize( 0, NULL, NULL, &bv, &ndn, op->o_tmpmemctx ) == LDAP_SUCCESS ) + { + rc = ( backend_group( op, e, &ndn, &op->o_ndn, + grp_oc, grp_ad ) == 0 ); + slap_sl_free( ndn.bv_val, op->o_tmpmemctx ); + } + } + +done: + return rc; +} + +static int +aci_mask( + Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + struct berval *aci, + int nmatch, + regmatch_t *matches, + slap_access_t *grant, + slap_access_t *deny, + slap_aci_scope_t asserted_scope ) +{ + struct berval bv, + scope, + perms, + type, + opts, + sdn; + int rc; + + ACL_INIT( *grant ); + ACL_INIT( *deny ); + + assert( !BER_BVISNULL( &desc->ad_cname ) ); + + /* parse an aci of the form: + oid # scope # action;rights;attr;rights;attr + $ action;rights;attr;rights;attr # type # subject + + [NOTE: the following comment is very outdated, + as the draft version it refers to (Ando, 2004-11-20)]. + + See draft-ietf-ldapext-aci-model-04.txt section 9.1 for + a full description of the format for this attribute. + Differences: "this" in the draft is "self" here, and + "self" and "public" is in the position of type. + + <scope> = {entry|children|subtree} + <type> = {public|users|access-id|subtree|onelevel|children| + self|dnattr|group|role|set|set-ref} + + This routine now supports scope={ENTRY,CHILDREN} + with the semantics: + - ENTRY applies to "entry" and "subtree"; + - CHILDREN applies to "children" and "subtree" + */ + + /* check that the aci has all 5 components */ + if ( acl_get_part( aci, 4, '#', NULL ) < 0 ) { + return 0; + } + + /* check that the aci family is supported */ + /* FIXME: the OID is ignored? */ + if ( acl_get_part( aci, 0, '#', &bv ) < 0 ) { + return 0; + } + + /* check that the scope matches */ + if ( acl_get_part( aci, 1, '#', &scope ) < 0 ) { + return 0; + } + + /* note: scope can be either ENTRY or CHILDREN; + * they respectively match "entry" and "children" in bv + * both match "subtree" */ + switch ( asserted_scope ) { + case SLAP_ACI_SCOPE_ENTRY: + if ( ber_bvcmp( &scope, &aci_bv[ ACI_BV_ENTRY ] ) != 0 + && ber_bvstrcasecmp( &scope, &aci_bv[ ACI_BV_SUBTREE ] ) != 0 ) + { + return 0; + } + break; + + case SLAP_ACI_SCOPE_CHILDREN: + if ( ber_bvcmp( &scope, &aci_bv[ ACI_BV_CHILDREN ] ) != 0 + && ber_bvstrcasecmp( &scope, &aci_bv[ ACI_BV_SUBTREE ] ) != 0 ) + { + return 0; + } + break; + + case SLAP_ACI_SCOPE_SUBTREE: + /* TODO: add assertion? */ + return 0; + } + + /* get the list of permissions clauses, bail if empty */ + if ( acl_get_part( aci, 2, '#', &perms ) <= 0 ) { + assert( 0 ); + return 0; + } + + /* check if any permissions allow desired access */ + if ( aci_list_get_rights( &perms, &desc->ad_cname, val, grant, deny ) == 0 ) { + return 0; + } + + /* see if we have a DN match */ + if ( acl_get_part( aci, 3, '#', &type ) < 0 ) { + assert( 0 ); + return 0; + } + + /* see if we have a public (i.e. anonymous) access */ + if ( ber_bvcmp( &aci_bv[ ACI_BV_PUBLIC ], &type ) == 0 ) { + return 1; + } + + /* otherwise require an identity */ + if ( BER_BVISNULL( &op->o_ndn ) || BER_BVISEMPTY( &op->o_ndn ) ) { + return 0; + } + + /* see if we have a users access */ + if ( ber_bvcmp( &aci_bv[ ACI_BV_USERS ], &type ) == 0 ) { + return 1; + } + + /* NOTE: this may fail if a DN contains a valid '#' (unescaped); + * just grab all the berval up to its end (ITS#3303). + * NOTE: the problem could be solved by providing the DN with + * the embedded '#' encoded as hexpairs: "cn=Foo#Bar" would + * become "cn=Foo\23Bar" and be safely used by aci_mask(). */ +#if 0 + if ( acl_get_part( aci, 4, '#', &sdn ) < 0 ) { + return 0; + } +#endif + sdn.bv_val = type.bv_val + type.bv_len + STRLENOF( "#" ); + sdn.bv_len = aci->bv_len - ( sdn.bv_val - aci->bv_val ); + + /* get the type options, if any */ + if ( acl_get_part( &type, 1, '/', &opts ) > 0 ) { + opts.bv_len = type.bv_len - ( opts.bv_val - type.bv_val ); + type.bv_len = opts.bv_val - type.bv_val - 1; + + } else { + BER_BVZERO( &opts ); + } + + if ( ber_bvcmp( &aci_bv[ ACI_BV_ACCESS_ID ], &type ) == 0 ) { + return dn_match( &op->o_ndn, &sdn ); + + } else if ( ber_bvcmp( &aci_bv[ ACI_BV_SUBTREE ], &type ) == 0 ) { + return dnIsSuffix( &op->o_ndn, &sdn ); + + } else if ( ber_bvcmp( &aci_bv[ ACI_BV_ONELEVEL ], &type ) == 0 ) { + struct berval pdn; + + dnParent( &sdn, &pdn ); + + return dn_match( &op->o_ndn, &pdn ); + + } else if ( ber_bvcmp( &aci_bv[ ACI_BV_CHILDREN ], &type ) == 0 ) { + return ( !dn_match( &op->o_ndn, &sdn ) && dnIsSuffix( &op->o_ndn, &sdn ) ); + + } else if ( ber_bvcmp( &aci_bv[ ACI_BV_SELF ], &type ) == 0 ) { + return dn_match( &op->o_ndn, &e->e_nname ); + + } else if ( ber_bvcmp( &aci_bv[ ACI_BV_DNATTR ], &type ) == 0 ) { + Attribute *at; + AttributeDescription *ad = NULL; + const char *text; + + rc = slap_bv2ad( &sdn, &ad, &text ); + assert( rc == LDAP_SUCCESS ); + + rc = 0; + for ( at = attrs_find( e->e_attrs, ad ); + at != NULL; + at = attrs_find( at->a_next, ad ) ) + { + if ( attr_valfind( at, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + &op->o_ndn, NULL, op->o_tmpmemctx ) == 0 ) + { + rc = 1; + break; + } + } + + return rc; + + } else if ( ber_bvcmp( &aci_bv[ ACI_BV_GROUP ], &type ) == 0 ) { + struct berval oc, + at; + + if ( BER_BVISNULL( &opts ) ) { + oc = aci_bv[ ACI_BV_GROUP_CLASS ]; + at = aci_bv[ ACI_BV_GROUP_ATTR ]; + + } else { + if ( acl_get_part( &opts, 0, '/', &oc ) < 0 ) { + assert( 0 ); + } + + if ( acl_get_part( &opts, 1, '/', &at ) < 0 ) { + at = aci_bv[ ACI_BV_GROUP_ATTR ]; + } + } + + if ( aci_group_member( &sdn, &oc, &at, op, e, nmatch, matches ) ) + { + return 1; + } + + } else if ( ber_bvcmp( &aci_bv[ ACI_BV_ROLE ], &type ) == 0 ) { + struct berval oc, + at; + + if ( BER_BVISNULL( &opts ) ) { + oc = aci_bv[ ACI_BV_ROLE_CLASS ]; + at = aci_bv[ ACI_BV_ROLE_ATTR ]; + + } else { + if ( acl_get_part( &opts, 0, '/', &oc ) < 0 ) { + assert( 0 ); + } + + if ( acl_get_part( &opts, 1, '/', &at ) < 0 ) { + at = aci_bv[ ACI_BV_ROLE_ATTR ]; + } + } + + if ( aci_group_member( &sdn, &oc, &at, op, e, nmatch, matches ) ) + { + return 1; + } + + } else if ( ber_bvcmp( &aci_bv[ ACI_BV_SET ], &type ) == 0 ) { + if ( acl_match_set( &sdn, op, e, NULL ) ) { + return 1; + } + + } else if ( ber_bvcmp( &aci_bv[ ACI_BV_SET_REF ], &type ) == 0 ) { + if ( acl_match_set( &sdn, op, e, (struct berval *)&aci_bv[ ACI_BV_SET_ATTR ] ) ) { + return 1; + } + + } else { + /* it passed normalization! */ + assert( 0 ); + } + + return 0; +} + +static int +aci_init( void ) +{ + /* OpenLDAP eXperimental Syntax */ + static slap_syntax_defs_rec aci_syntax_def = { + "( 1.3.6.1.4.1.4203.666.2.1 DESC 'OpenLDAP Experimental ACI' )", + SLAP_SYNTAX_HIDE, + NULL, + OpenLDAPaciValidate, + OpenLDAPaciPretty + }; + static slap_mrule_defs_rec aci_mr_def = { + "( 1.3.6.1.4.1.4203.666.4.2 NAME 'OpenLDAPaciMatch' " + "SYNTAX 1.3.6.1.4.1.4203.666.2.1 )", + SLAP_MR_HIDE | SLAP_MR_EQUALITY, NULL, + NULL, OpenLDAPaciNormalize, OpenLDAPaciMatch, + NULL, NULL, + NULL + }; + static struct { + char *name; + char *desc; + slap_mask_t flags; + AttributeDescription **ad; + } aci_at = { + "OpenLDAPaci", "( 1.3.6.1.4.1.4203.666.1.5 " + "NAME 'OpenLDAPaci' " + "DESC 'OpenLDAP access control information (experimental)' " + "EQUALITY OpenLDAPaciMatch " + "SYNTAX 1.3.6.1.4.1.4203.666.2.1 " + "USAGE directoryOperation )", + SLAP_AT_HIDE, + &slap_ad_aci + }; + + int rc; + + /* ACI syntax */ + rc = register_syntax( &aci_syntax_def ); + if ( rc != 0 ) { + return rc; + } + + /* ACI equality rule */ + rc = register_matching_rule( &aci_mr_def ); + if ( rc != 0 ) { + return rc; + } + + /* ACI attribute */ + rc = register_at( aci_at.desc, aci_at.ad, 0 ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "aci_init: at_register failed\n", 0, 0, 0 ); + return rc; + } + + /* install flags */ + (*aci_at.ad)->ad_type->sat_flags |= aci_at.flags; + + return rc; +} + +static int +dynacl_aci_parse( + const char *fname, + int lineno, + const char *opts, + slap_style_t sty, + const char *right, + void **privp ) +{ + AttributeDescription *ad = NULL; + const char *text = NULL; + + if ( sty != ACL_STYLE_REGEX && sty != ACL_STYLE_BASE ) { + fprintf( stderr, "%s: line %d: " + "inappropriate style \"%s\" in \"aci\" by clause\n", + fname, lineno, style_strings[sty] ); + return -1; + } + + if ( right != NULL && *right != '\0' ) { + if ( slap_str2ad( right, &ad, &text ) != LDAP_SUCCESS ) { + fprintf( stderr, + "%s: line %d: aci \"%s\": %s\n", + fname, lineno, right, text ); + return -1; + } + + } else { + ad = slap_ad_aci; + } + + if ( !is_at_syntax( ad->ad_type, SLAPD_ACI_SYNTAX) ) { + fprintf( stderr, "%s: line %d: " + "aci \"%s\": inappropriate syntax: %s\n", + fname, lineno, right, + ad->ad_type->sat_syntax_oid ); + return -1; + } + + *privp = (void *)ad; + + return 0; +} + +static int +dynacl_aci_unparse( void *priv, struct berval *bv ) +{ + AttributeDescription *ad = ( AttributeDescription * )priv; + char *ptr; + + assert( ad != NULL ); + + bv->bv_val = ch_malloc( STRLENOF(" aci=") + ad->ad_cname.bv_len + 1 ); + ptr = lutil_strcopy( bv->bv_val, " aci=" ); + ptr = lutil_strcopy( ptr, ad->ad_cname.bv_val ); + bv->bv_len = ptr - bv->bv_val; + + return 0; +} + +static int +dynacl_aci_mask( + void *priv, + Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + int nmatch, + regmatch_t *matches, + slap_access_t *grantp, + slap_access_t *denyp ) +{ + AttributeDescription *ad = ( AttributeDescription * )priv; + Attribute *at; + slap_access_t tgrant, tdeny, grant, deny; +#ifdef LDAP_DEBUG + char accessmaskbuf[ACCESSMASK_MAXLEN]; + char accessmaskbuf1[ACCESSMASK_MAXLEN]; +#endif /* LDAP_DEBUG */ + + if ( BER_BVISEMPTY( &e->e_nname ) ) { + /* no ACIs in the root DSE */ + return -1; + } + + /* start out with nothing granted, nothing denied */ + ACL_INIT(tgrant); + ACL_INIT(tdeny); + + /* get the aci attribute */ + at = attr_find( e->e_attrs, ad ); + if ( at != NULL ) { + int i; + + /* the aci is an multi-valued attribute. The + * rights are determined by OR'ing the individual + * rights given by the acis. + */ + for ( i = 0; !BER_BVISNULL( &at->a_nvals[i] ); i++ ) { + if ( aci_mask( op, e, desc, val, &at->a_nvals[i], + nmatch, matches, &grant, &deny, + SLAP_ACI_SCOPE_ENTRY ) != 0 ) + { + tgrant |= grant; + tdeny |= deny; + } + } + + Debug( LDAP_DEBUG_ACL, " <= aci_mask grant %s deny %s\n", + accessmask2str( tgrant, accessmaskbuf, 1 ), + accessmask2str( tdeny, accessmaskbuf1, 1 ), 0 ); + } + + /* If the entry level aci didn't contain anything valid for the + * current operation, climb up the tree and evaluate the + * acis with scope set to subtree + */ + if ( tgrant == ACL_PRIV_NONE && tdeny == ACL_PRIV_NONE ) { + struct berval parent_ndn; + + dnParent( &e->e_nname, &parent_ndn ); + while ( !BER_BVISEMPTY( &parent_ndn ) ){ + int i; + BerVarray bvals = NULL; + int ret, stop; + + /* to solve the chicken'n'egg problem of accessing + * the OpenLDAPaci attribute, the direct access + * to the entry's attribute is unchecked; however, + * further accesses to OpenLDAPaci values in the + * ancestors occur through backend_attribute(), i.e. + * with the identity of the operation, requiring + * further access checking. For uniformity, this + * makes further requests occur as the rootdn, if + * any, i.e. searching for the OpenLDAPaci attribute + * is considered an internal search. If this is not + * acceptable, then the same check needs be performed + * when accessing the entry's attribute. */ + struct berval save_o_dn, save_o_ndn; + + if ( !BER_BVISNULL( &op->o_bd->be_rootndn ) ) { + save_o_dn = op->o_dn; + save_o_ndn = op->o_ndn; + + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + } + + Debug( LDAP_DEBUG_ACL, " checking ACI of \"%s\"\n", parent_ndn.bv_val, 0, 0 ); + ret = backend_attribute( op, NULL, &parent_ndn, ad, &bvals, ACL_AUTH ); + + if ( !BER_BVISNULL( &op->o_bd->be_rootndn ) ) { + op->o_dn = save_o_dn; + op->o_ndn = save_o_ndn; + } + + switch ( ret ) { + case LDAP_SUCCESS : + stop = 0; + if ( !bvals ) { + break; + } + + for ( i = 0; !BER_BVISNULL( &bvals[i] ); i++ ) { + if ( aci_mask( op, e, desc, val, + &bvals[i], + nmatch, matches, + &grant, &deny, + SLAP_ACI_SCOPE_CHILDREN ) != 0 ) + { + tgrant |= grant; + tdeny |= deny; + /* evaluation stops as soon as either a "deny" or a + * "grant" directive matches. + */ + if ( tgrant != ACL_PRIV_NONE || tdeny != ACL_PRIV_NONE ) { + stop = 1; + } + } + Debug( LDAP_DEBUG_ACL, "<= aci_mask grant %s deny %s\n", + accessmask2str( tgrant, accessmaskbuf, 1 ), + accessmask2str( tdeny, accessmaskbuf1, 1 ), 0 ); + } + break; + + case LDAP_NO_SUCH_ATTRIBUTE: + /* just go on if the aci-Attribute is not present in + * the current entry + */ + Debug( LDAP_DEBUG_ACL, "no such attribute\n", 0, 0, 0 ); + stop = 0; + break; + + case LDAP_NO_SUCH_OBJECT: + /* We have reached the base object */ + Debug( LDAP_DEBUG_ACL, "no such object\n", 0, 0, 0 ); + stop = 1; + break; + + default: + stop = 1; + break; + } + + if ( stop ) { + break; + } + dnParent( &parent_ndn, &parent_ndn ); + } + } + + *grantp = tgrant; + *denyp = tdeny; + + return 0; +} + +/* need to register this at some point */ +static slap_dynacl_t dynacl_aci = { + "aci", + dynacl_aci_parse, + dynacl_aci_unparse, + dynacl_aci_mask, + NULL, + NULL, + NULL +}; + +int +dynacl_aci_init( void ) +{ + int rc; + + rc = aci_init(); + + if ( rc == 0 ) { + rc = slap_dynacl_register( &dynacl_aci ); + } + + return rc; +} + + +/* ACI syntax validation */ + +/* + * Matches given berval to array of bervals + * Returns: + * >=0 if one if the array elements equals to this berval + * -1 if string was not found in array + */ +static int +bv_getcaseidx( + struct berval *bv, + const struct berval *arr[] ) +{ + int i; + + if ( BER_BVISEMPTY( bv ) ) { + return -1; + } + + for ( i = 0; arr[ i ] != NULL ; i++ ) { + if ( ber_bvstrcasecmp( bv, arr[ i ] ) == 0 ) { + return i; + } + } + + return -1; +} + + +/* Returns what have left in input berval after current sub */ +static void +bv_get_tail( + struct berval *val, + struct berval *sub, + struct berval *tail ) +{ + int head_len; + + tail->bv_val = sub->bv_val + sub->bv_len; + head_len = (unsigned long) tail->bv_val - (unsigned long) val->bv_val; + tail->bv_len = val->bv_len - head_len; +} + + +/* + * aci is accepted in following form: + * oid#scope#rights#type#subject + * Where: + * oid := numeric OID (currently ignored) + * scope := entry|children|subtree + * rights := right[[$right]...] + * right := (grant|deny);action + * action := perms;attrs[[;perms;attrs]...] + * perms := perm[[,perm]...] + * perm := c|s|r|w|x + * attrs := attribute[[,attribute]..]|"[all]" + * attribute := attributeType|attributeType=attributeValue|attributeType=attributeValuePrefix* + * type := public|users|self|dnattr|group|role|set|set-ref| + * access_id|subtree|onelevel|children + */ +static int +OpenLDAPaciValidatePerms( + struct berval *perms ) +{ + ber_len_t i; + + for ( i = 0; i < perms->bv_len; ) { + switch ( perms->bv_val[ i ] ) { + case 'x': + case 'd': + case 'c': + case 's': + case 'r': + case 'w': + break; + + default: + Debug( LDAP_DEBUG_ACL, "aciValidatePerms: perms needs to be one of x,d,c,s,r,w in '%s'\n", perms->bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + + if ( ++i == perms->bv_len ) { + return LDAP_SUCCESS; + } + + while ( i < perms->bv_len && perms->bv_val[ i ] == ' ' ) + i++; + + assert( i != perms->bv_len ); + + if ( perms->bv_val[ i ] != ',' ) { + Debug( LDAP_DEBUG_ACL, "aciValidatePerms: missing comma in '%s'\n", perms->bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + + do { + i++; + } while ( perms->bv_val[ i ] == ' ' ); + } + + return LDAP_SUCCESS; +} + +static const struct berval *ACIgrantdeny[] = { + &aci_bv[ ACI_BV_GRANT ], + &aci_bv[ ACI_BV_DENY ], + NULL +}; + +static int +OpenLDAPaciValidateRight( + struct berval *action ) +{ + struct berval bv = BER_BVNULL; + int i; + + /* grant|deny */ + if ( acl_get_part( action, 0, ';', &bv ) < 0 || + bv_getcaseidx( &bv, ACIgrantdeny ) == -1 ) + { + Debug( LDAP_DEBUG_ACL, "aciValidateRight: '%s' must be either 'grant' or 'deny'\n", bv.bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + + for ( i = 0; acl_get_part( action, i + 1, ';', &bv ) >= 0; i++ ) { + if ( i & 1 ) { + /* perms */ + if ( OpenLDAPaciValidatePerms( &bv ) != LDAP_SUCCESS ) + { + return LDAP_INVALID_SYNTAX; + } + + } else { + /* attr */ + AttributeDescription *ad; + const char *text; + struct berval attr, left, right; + int j; + + /* could be "[all]" or an attribute description */ + if ( ber_bvstrcasecmp( &bv, &aci_bv[ ACI_BV_BR_ALL ] ) == 0 ) { + continue; + } + + + for ( j = 0; acl_get_part( &bv, j, ',', &attr ) >= 0; j++ ) + { + ad = NULL; + text = NULL; + if ( acl_get_part( &attr, 0, '=', &left ) < 0 + || acl_get_part( &attr, 1, '=', &right ) < 0 ) + { + if ( slap_bv2ad( &attr, &ad, &text ) != LDAP_SUCCESS ) + { + Debug( LDAP_DEBUG_ACL, "aciValidateRight: unknown attribute: '%s'\n", attr.bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + } else { + if ( slap_bv2ad( &left, &ad, &text ) != LDAP_SUCCESS ) + { + Debug( LDAP_DEBUG_ACL, "aciValidateRight: unknown attribute: '%s'\n", left.bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + } + } + } + } + + /* "perms;attr" go in pairs */ + if ( i > 0 && ( i & 1 ) == 0 ) { + return LDAP_SUCCESS; + + } else { + Debug( LDAP_DEBUG_ACL, "aciValidateRight: perms:attr need to be pairs in '%s'\n", action->bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + + return LDAP_SUCCESS; +} + +static int +OpenLDAPaciNormalizeRight( + struct berval *action, + struct berval *naction, + void *ctx ) +{ + struct berval grantdeny, + perms = BER_BVNULL, + bv = BER_BVNULL; + int idx, + i; + + /* grant|deny */ + if ( acl_get_part( action, 0, ';', &grantdeny ) < 0 ) { + Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: missing ';' in '%s'\n", action->bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + idx = bv_getcaseidx( &grantdeny, ACIgrantdeny ); + if ( idx == -1 ) { + Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: '%s' must be grant or deny\n", grantdeny.bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + + ber_dupbv_x( naction, (struct berval *)ACIgrantdeny[ idx ], ctx ); + + for ( i = 1; acl_get_part( action, i, ';', &bv ) >= 0; i++ ) { + struct berval nattrs = BER_BVNULL; + int freenattrs = 1; + if ( i & 1 ) { + /* perms */ + if ( OpenLDAPaciValidatePerms( &bv ) != LDAP_SUCCESS ) + { + return LDAP_INVALID_SYNTAX; + } + perms = bv; + + } else { + /* attr */ + char *ptr; + + /* could be "[all]" or an attribute description */ + if ( ber_bvstrcasecmp( &bv, &aci_bv[ ACI_BV_BR_ALL ] ) == 0 ) { + nattrs = aci_bv[ ACI_BV_BR_ALL ]; + freenattrs = 0; + + } else { + AttributeDescription *ad = NULL; + AttributeDescription adstatic= { 0 }; + const char *text = NULL; + struct berval attr, left, right; + int j; + int len; + + for ( j = 0; acl_get_part( &bv, j, ',', &attr ) >= 0; j++ ) + { + ad = NULL; + text = NULL; + /* openldap 2.1 aci compabitibility [entry] -> entry */ + if ( ber_bvstrcasecmp( &attr, &aci_bv[ ACI_BV_BR_ENTRY ] ) == 0 ) { + ad = &adstatic; + adstatic.ad_cname = aci_bv[ ACI_BV_ENTRY ]; + + /* openldap 2.1 aci compabitibility [children] -> children */ + } else if ( ber_bvstrcasecmp( &attr, &aci_bv[ ACI_BV_BR_CHILDREN ] ) == 0 ) { + ad = &adstatic; + adstatic.ad_cname = aci_bv[ ACI_BV_CHILDREN ]; + + /* openldap 2.1 aci compabitibility [all] -> only [all] */ + } else if ( ber_bvstrcasecmp( &attr, &aci_bv[ ACI_BV_BR_ALL ] ) == 0 ) { + ber_memfree_x( nattrs.bv_val, ctx ); + nattrs = aci_bv[ ACI_BV_BR_ALL ]; + freenattrs = 0; + break; + + } else if ( acl_get_part( &attr, 0, '=', &left ) < 0 + || acl_get_part( &attr, 1, '=', &right ) < 0 ) + { + if ( slap_bv2ad( &attr, &ad, &text ) != LDAP_SUCCESS ) + { + ber_memfree_x( nattrs.bv_val, ctx ); + Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: unknown attribute: '%s'\n", attr.bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + + } else { + if ( slap_bv2ad( &left, &ad, &text ) != LDAP_SUCCESS ) + { + ber_memfree_x( nattrs.bv_val, ctx ); + Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: unknown attribute: '%s'\n", left.bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + } + + + len = nattrs.bv_len + ( !BER_BVISEMPTY( &nattrs ) ? STRLENOF( "," ) : 0 ) + + ad->ad_cname.bv_len; + nattrs.bv_val = ber_memrealloc_x( nattrs.bv_val, len + 1, ctx ); + ptr = &nattrs.bv_val[ nattrs.bv_len ]; + if ( !BER_BVISEMPTY( &nattrs ) ) { + *ptr++ = ','; + } + ptr = lutil_strncopy( ptr, ad->ad_cname.bv_val, ad->ad_cname.bv_len ); + ptr[ 0 ] = '\0'; + nattrs.bv_len = len; + } + + } + + naction->bv_val = ber_memrealloc_x( naction->bv_val, + naction->bv_len + STRLENOF( ";" ) + + perms.bv_len + STRLENOF( ";" ) + + nattrs.bv_len + 1, + ctx ); + + ptr = &naction->bv_val[ naction->bv_len ]; + ptr[ 0 ] = ';'; + ptr++; + ptr = lutil_strncopy( ptr, perms.bv_val, perms.bv_len ); + ptr[ 0 ] = ';'; + ptr++; + ptr = lutil_strncopy( ptr, nattrs.bv_val, nattrs.bv_len ); + ptr[ 0 ] = '\0'; + naction->bv_len += STRLENOF( ";" ) + perms.bv_len + + STRLENOF( ";" ) + nattrs.bv_len; + if ( freenattrs ) { + ber_memfree_x( nattrs.bv_val, ctx ); + } + } + } + + /* perms;attr go in pairs */ + if ( i > 1 && ( i & 1 ) ) { + return LDAP_SUCCESS; + + } else { + Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: perms:attr need to be pairs in '%s'\n", action->bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } +} + +static int +OpenLDAPaciValidateRights( + struct berval *actions ) + +{ + struct berval bv = BER_BVNULL; + int i; + + for ( i = 0; acl_get_part( actions, i, '$', &bv ) >= 0; i++ ) { + if ( OpenLDAPaciValidateRight( &bv ) != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + } + + return LDAP_SUCCESS; +} + +static int +OpenLDAPaciNormalizeRights( + struct berval *actions, + struct berval *nactions, + void *ctx ) + +{ + struct berval bv = BER_BVNULL; + int i; + + BER_BVZERO( nactions ); + for ( i = 0; acl_get_part( actions, i, '$', &bv ) >= 0; i++ ) { + int rc; + struct berval nbv; + + rc = OpenLDAPaciNormalizeRight( &bv, &nbv, ctx ); + if ( rc != LDAP_SUCCESS ) { + ber_memfree_x( nactions->bv_val, ctx ); + BER_BVZERO( nactions ); + return LDAP_INVALID_SYNTAX; + } + + if ( i == 0 ) { + *nactions = nbv; + + } else { + nactions->bv_val = ber_memrealloc_x( nactions->bv_val, + nactions->bv_len + STRLENOF( "$" ) + + nbv.bv_len + 1, + ctx ); + nactions->bv_val[ nactions->bv_len ] = '$'; + AC_MEMCPY( &nactions->bv_val[ nactions->bv_len + 1 ], + nbv.bv_val, nbv.bv_len + 1 ); + ber_memfree_x( nbv.bv_val, ctx ); + nactions->bv_len += STRLENOF( "$" ) + nbv.bv_len; + } + BER_BVZERO( &nbv ); + } + + return LDAP_SUCCESS; +} + +static const struct berval *OpenLDAPaciscopes[] = { + &aci_bv[ ACI_BV_ENTRY ], + &aci_bv[ ACI_BV_CHILDREN ], + &aci_bv[ ACI_BV_SUBTREE ], + + NULL +}; + +static const struct berval *OpenLDAPacitypes[] = { + /* DN-valued */ + &aci_bv[ ACI_BV_GROUP ], + &aci_bv[ ACI_BV_ROLE ], + +/* set to one past the last DN-valued type with options (/) */ +#define LAST_OPTIONAL 2 + + &aci_bv[ ACI_BV_ACCESS_ID ], + &aci_bv[ ACI_BV_SUBTREE ], + &aci_bv[ ACI_BV_ONELEVEL ], + &aci_bv[ ACI_BV_CHILDREN ], + +/* set to one past the last DN-valued type */ +#define LAST_DNVALUED 6 + + /* non DN-valued */ + &aci_bv[ ACI_BV_DNATTR ], + &aci_bv[ ACI_BV_PUBLIC ], + &aci_bv[ ACI_BV_USERS ], + &aci_bv[ ACI_BV_SELF ], + &aci_bv[ ACI_BV_SET ], + &aci_bv[ ACI_BV_SET_REF ], + + NULL +}; + +static int +OpenLDAPaciValidate( + Syntax *syntax, + struct berval *val ) +{ + struct berval oid = BER_BVNULL, + scope = BER_BVNULL, + rights = BER_BVNULL, + type = BER_BVNULL, + subject = BER_BVNULL; + int idx; + int rc; + + if ( BER_BVISEMPTY( val ) ) { + Debug( LDAP_DEBUG_ACL, "aciValidatet: value is empty\n", 0, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + + /* oid */ + if ( acl_get_part( val, 0, '#', &oid ) < 0 || + numericoidValidate( NULL, &oid ) != LDAP_SUCCESS ) + { + /* NOTE: the numericoidValidate() is rather pedantic; + * I'd replace it with X-ORDERED VALUES so that + * it's guaranteed values are maintained and used + * in the desired order */ + Debug( LDAP_DEBUG_ACL, "aciValidate: invalid oid '%s'\n", oid.bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + + /* scope */ + if ( acl_get_part( val, 1, '#', &scope ) < 0 || + bv_getcaseidx( &scope, OpenLDAPaciscopes ) == -1 ) + { + Debug( LDAP_DEBUG_ACL, "aciValidate: invalid scope '%s'\n", scope.bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + + /* rights */ + if ( acl_get_part( val, 2, '#', &rights ) < 0 || + OpenLDAPaciValidateRights( &rights ) != LDAP_SUCCESS ) + { + return LDAP_INVALID_SYNTAX; + } + + /* type */ + if ( acl_get_part( val, 3, '#', &type ) < 0 ) { + Debug( LDAP_DEBUG_ACL, "aciValidate: missing type in '%s'\n", val->bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + idx = bv_getcaseidx( &type, OpenLDAPacitypes ); + if ( idx == -1 ) { + struct berval isgr; + + if ( acl_get_part( &type, 0, '/', &isgr ) < 0 ) { + Debug( LDAP_DEBUG_ACL, "aciValidate: invalid type '%s'\n", type.bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + + idx = bv_getcaseidx( &isgr, OpenLDAPacitypes ); + if ( idx == -1 || idx >= LAST_OPTIONAL ) { + Debug( LDAP_DEBUG_ACL, "aciValidate: invalid type '%s'\n", isgr.bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + } + + /* subject */ + bv_get_tail( val, &type, &subject ); + if ( subject.bv_val[ 0 ] != '#' ) { + Debug( LDAP_DEBUG_ACL, "aciValidate: missing subject in '%s'\n", val->bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + + if ( idx >= LAST_DNVALUED ) { + if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_DNATTR ] ) { + AttributeDescription *ad = NULL; + const char *text = NULL; + + rc = slap_bv2ad( &subject, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ACL, "aciValidate: unknown dn attribute '%s'\n", subject.bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + + if ( ad->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName ) { + /* FIXME: allow nameAndOptionalUID? */ + Debug( LDAP_DEBUG_ACL, "aciValidate: wrong syntax for dn attribute '%s'\n", subject.bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + } + + /* not a DN */ + return LDAP_SUCCESS; + + } else if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_GROUP ] + || OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_ROLE ] ) + { + /* do {group|role}/oc/at check */ + struct berval ocbv = BER_BVNULL, + atbv = BER_BVNULL; + + ocbv.bv_val = ber_bvchr( &type, '/' ); + if ( ocbv.bv_val != NULL ) { + ocbv.bv_val++; + ocbv.bv_len = type.bv_len + - ( ocbv.bv_val - type.bv_val ); + + atbv.bv_val = ber_bvchr( &ocbv, '/' ); + if ( atbv.bv_val != NULL ) { + AttributeDescription *ad = NULL; + const char *text = NULL; + int rc; + + atbv.bv_val++; + atbv.bv_len = type.bv_len + - ( atbv.bv_val - type.bv_val ); + ocbv.bv_len = atbv.bv_val - ocbv.bv_val - 1; + + rc = slap_bv2ad( &atbv, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ACL, "aciValidate: unknown group attribute '%s'\n", atbv.bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + } + + if ( oc_bvfind( &ocbv ) == NULL ) { + Debug( LDAP_DEBUG_ACL, "aciValidate: unknown group '%s'\n", ocbv.bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + } + } + + if ( BER_BVISEMPTY( &subject ) ) { + /* empty DN invalid */ + Debug( LDAP_DEBUG_ACL, "aciValidate: missing dn in '%s'\n", val->bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + + subject.bv_val++; + subject.bv_len--; + + /* FIXME: pass DN syntax? */ + rc = dnValidate( NULL, &subject ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ACL, "aciValidate: invalid dn '%s'\n", subject.bv_val, 0, 0 ); + } + return rc; +} + +static int +OpenLDAPaciPrettyNormal( + struct berval *val, + struct berval *out, + void *ctx, + int normalize ) +{ + struct berval oid = BER_BVNULL, + scope = BER_BVNULL, + rights = BER_BVNULL, + nrights = BER_BVNULL, + type = BER_BVNULL, + ntype = BER_BVNULL, + subject = BER_BVNULL, + nsubject = BER_BVNULL; + int idx, + rc = LDAP_SUCCESS, + freesubject = 0, + freetype = 0; + char *ptr; + + BER_BVZERO( out ); + + if ( BER_BVISEMPTY( val ) ) { + Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: value is empty\n", 0, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + + /* oid: if valid, it's already normalized */ + if ( acl_get_part( val, 0, '#', &oid ) < 0 || + numericoidValidate( NULL, &oid ) != LDAP_SUCCESS ) + { + Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid oid '%s'\n", oid.bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + + /* scope: normalize by replacing with OpenLDAPaciscopes */ + if ( acl_get_part( val, 1, '#', &scope ) < 0 ) { + Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: missing scope in '%s'\n", val->bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + idx = bv_getcaseidx( &scope, OpenLDAPaciscopes ); + if ( idx == -1 ) { + Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid scope '%s'\n", scope.bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + scope = *OpenLDAPaciscopes[ idx ]; + + /* rights */ + if ( acl_get_part( val, 2, '#', &rights ) < 0 ) { + Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: missing rights in '%s'\n", val->bv_val, 0, 0 ); + return LDAP_INVALID_SYNTAX; + } + if ( OpenLDAPaciNormalizeRights( &rights, &nrights, ctx ) + != LDAP_SUCCESS ) + { + return LDAP_INVALID_SYNTAX; + } + + /* type */ + if ( acl_get_part( val, 3, '#', &type ) < 0 ) { + Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: missing type in '%s'\n", val->bv_val, 0, 0 ); + rc = LDAP_INVALID_SYNTAX; + goto cleanup; + } + idx = bv_getcaseidx( &type, OpenLDAPacitypes ); + if ( idx == -1 ) { + struct berval isgr; + + if ( acl_get_part( &type, 0, '/', &isgr ) < 0 ) { + Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid type '%s'\n", type.bv_val, 0, 0 ); + rc = LDAP_INVALID_SYNTAX; + goto cleanup; + } + + idx = bv_getcaseidx( &isgr, OpenLDAPacitypes ); + if ( idx == -1 || idx >= LAST_OPTIONAL ) { + Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid type '%s'\n", isgr.bv_val, 0, 0 ); + rc = LDAP_INVALID_SYNTAX; + goto cleanup; + } + } + ntype = *OpenLDAPacitypes[ idx ]; + + /* subject */ + bv_get_tail( val, &type, &subject ); + + if ( BER_BVISEMPTY( &subject ) || subject.bv_val[ 0 ] != '#' ) { + Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: missing subject in '%s'\n", val->bv_val, 0, 0 ); + rc = LDAP_INVALID_SYNTAX; + goto cleanup; + } + + subject.bv_val++; + subject.bv_len--; + + if ( idx < LAST_DNVALUED ) { + /* FIXME: pass DN syntax? */ + if ( normalize ) { + rc = dnNormalize( 0, NULL, NULL, + &subject, &nsubject, ctx ); + } else { + rc = dnPretty( NULL, &subject, &nsubject, ctx ); + } + + if ( rc == LDAP_SUCCESS ) { + freesubject = 1; + + } else { + Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid subject dn '%s'\n", subject.bv_val, 0, 0 ); + goto cleanup; + } + + if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_GROUP ] + || OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_ROLE ] ) + { + /* do {group|role}/oc/at check */ + struct berval ocbv = BER_BVNULL, + atbv = BER_BVNULL; + + ocbv.bv_val = ber_bvchr( &type, '/' ); + if ( ocbv.bv_val != NULL ) { + ObjectClass *oc = NULL; + AttributeDescription *ad = NULL; + const char *text = NULL; + int rc; + struct berval bv; + + bv.bv_len = ntype.bv_len; + + ocbv.bv_val++; + ocbv.bv_len = type.bv_len - ( ocbv.bv_val - type.bv_val ); + + atbv.bv_val = ber_bvchr( &ocbv, '/' ); + if ( atbv.bv_val != NULL ) { + atbv.bv_val++; + atbv.bv_len = type.bv_len + - ( atbv.bv_val - type.bv_val ); + ocbv.bv_len = atbv.bv_val - ocbv.bv_val - 1; + + rc = slap_bv2ad( &atbv, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: unknown group attribute '%s'\n", atbv.bv_val, 0, 0 ); + rc = LDAP_INVALID_SYNTAX; + goto cleanup; + } + + bv.bv_len += STRLENOF( "/" ) + ad->ad_cname.bv_len; + } + + oc = oc_bvfind( &ocbv ); + if ( oc == NULL ) { + Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid group '%s'\n", ocbv.bv_val, 0, 0 ); + rc = LDAP_INVALID_SYNTAX; + goto cleanup; + } + + bv.bv_len += STRLENOF( "/" ) + oc->soc_cname.bv_len; + bv.bv_val = ber_memalloc_x( bv.bv_len + 1, ctx ); + + ptr = bv.bv_val; + ptr = lutil_strncopy( ptr, ntype.bv_val, ntype.bv_len ); + ptr[ 0 ] = '/'; + ptr++; + ptr = lutil_strncopy( ptr, + oc->soc_cname.bv_val, + oc->soc_cname.bv_len ); + if ( ad != NULL ) { + ptr[ 0 ] = '/'; + ptr++; + ptr = lutil_strncopy( ptr, + ad->ad_cname.bv_val, + ad->ad_cname.bv_len ); + } + ptr[ 0 ] = '\0'; + + ntype = bv; + freetype = 1; + } + } + + } else if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_DNATTR ] ) { + AttributeDescription *ad = NULL; + const char *text = NULL; + int rc; + + rc = slap_bv2ad( &subject, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: unknown dn attribute '%s'\n", subject.bv_val, 0, 0 ); + rc = LDAP_INVALID_SYNTAX; + goto cleanup; + } + + if ( ad->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName ) { + /* FIXME: allow nameAndOptionalUID? */ + Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: wrong syntax for dn attribute '%s'\n", subject.bv_val, 0, 0 ); + rc = LDAP_INVALID_SYNTAX; + goto cleanup; + } + + nsubject = ad->ad_cname; + + } else if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_SET ] + || OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_SET_REF ] ) + { + /* NOTE: dunno how to normalize it... */ + nsubject = subject; + } + + + out->bv_len = + oid.bv_len + STRLENOF( "#" ) + + scope.bv_len + STRLENOF( "#" ) + + nrights.bv_len + STRLENOF( "#" ) + + ntype.bv_len + STRLENOF( "#" ) + + nsubject.bv_len; + + out->bv_val = ber_memalloc_x( out->bv_len + 1, ctx ); + ptr = lutil_strncopy( out->bv_val, oid.bv_val, oid.bv_len ); + ptr[ 0 ] = '#'; + ptr++; + ptr = lutil_strncopy( ptr, scope.bv_val, scope.bv_len ); + ptr[ 0 ] = '#'; + ptr++; + ptr = lutil_strncopy( ptr, nrights.bv_val, nrights.bv_len ); + ptr[ 0 ] = '#'; + ptr++; + ptr = lutil_strncopy( ptr, ntype.bv_val, ntype.bv_len ); + ptr[ 0 ] = '#'; + ptr++; + if ( !BER_BVISNULL( &nsubject ) ) { + ptr = lutil_strncopy( ptr, nsubject.bv_val, nsubject.bv_len ); + } + ptr[ 0 ] = '\0'; + +cleanup:; + if ( freesubject ) { + ber_memfree_x( nsubject.bv_val, ctx ); + } + + if ( freetype ) { + ber_memfree_x( ntype.bv_val, ctx ); + } + + if ( !BER_BVISNULL( &nrights ) ) { + ber_memfree_x( nrights.bv_val, ctx ); + } + + return rc; +} + +static int +OpenLDAPaciPretty( + Syntax *syntax, + struct berval *val, + struct berval *out, + void *ctx ) +{ + return OpenLDAPaciPrettyNormal( val, out, ctx, 0 ); +} + +static int +OpenLDAPaciNormalize( + slap_mask_t use, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *out, + void *ctx ) +{ + return OpenLDAPaciPrettyNormal( val, out, ctx, 1 ); +} + +#if SLAPD_ACI_ENABLED == SLAPD_MOD_DYNAMIC +/* + * FIXME: need config and Makefile.am code to ease building + * as dynamic module + */ +int +init_module( int argc, char *argv[] ) +{ + return dynacl_aci_init(); +} +#endif /* SLAPD_ACI_ENABLED == SLAPD_MOD_DYNAMIC */ + +#endif /* SLAPD_ACI_ENABLED */ + diff --git a/servers/slapd/acl.c b/servers/slapd/acl.c new file mode 100644 index 0000000..a09e282 --- /dev/null +++ b/servers/slapd/acl.c @@ -0,0 +1,2689 @@ +/* acl.c - routines to parse and check acl's */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/regex.h> +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "sets.h" +#include "lber_pvt.h" +#include "lutil.h" + +#define ACL_BUF_SIZE 1024 /* use most appropriate size */ + +static const struct berval acl_bv_ip_eq = BER_BVC( "IP=" ); +#ifdef LDAP_PF_INET6 +static const struct berval acl_bv_ipv6_eq = BER_BVC( "IP=[" ); +#endif /* LDAP_PF_INET6 */ +#ifdef LDAP_PF_LOCAL +static const struct berval acl_bv_path_eq = BER_BVC("PATH="); +#endif /* LDAP_PF_LOCAL */ + +static AccessControl * slap_acl_get( + AccessControl *ac, int *count, + Operation *op, Entry *e, + AttributeDescription *desc, + struct berval *val, + AclRegexMatches *matches, + slap_mask_t *mask, + AccessControlState *state ); + +static slap_control_t slap_acl_mask( + AccessControl *ac, + AccessControl *prev, + slap_mask_t *mask, + Operation *op, Entry *e, + AttributeDescription *desc, + struct berval *val, + AclRegexMatches *matches, + int count, + AccessControlState *state, + slap_access_t access ); + +static int regex_matches( + struct berval *pat, char *str, + struct berval *dn_matches, struct berval *val_matches, + AclRegexMatches *matches); + +typedef struct AclSetCookie { + SetCookie asc_cookie; +#define asc_op asc_cookie.set_op + Entry *asc_e; +} AclSetCookie; + + +SLAP_SET_GATHER acl_set_gather; +SLAP_SET_GATHER acl_set_gather2; + +/* + * access_allowed - check whether op->o_ndn is allowed the requested access + * to entry e, attribute attr, value val. if val is null, access to + * the whole attribute is assumed (all values). + * + * This routine loops through all access controls and calls + * slap_acl_mask() on each applicable access control. + * The loop exits when a definitive answer is reached or + * or no more controls remain. + * + * returns: + * 0 access denied + * 1 access granted + * + * Notes: + * - can be legally called with op == NULL + * - can be legally called with op->o_bd == NULL + */ + +int +slap_access_always_allowed( + Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + slap_access_t access, + AccessControlState *state, + slap_mask_t *maskp ) +{ + assert( maskp != NULL ); + + /* assign all */ + ACL_LVL_ASSIGN_MANAGE( *maskp ); + + return 1; +} + +#define MATCHES_DNMAXCOUNT(m) \ + ( sizeof ( (m)->dn_data ) / sizeof( *(m)->dn_data ) ) +#define MATCHES_VALMAXCOUNT(m) \ + ( sizeof ( (m)->val_data ) / sizeof( *(m)->val_data ) ) +#define MATCHES_MEMSET(m) do { \ + memset( (m)->dn_data, '\0', sizeof( (m)->dn_data ) ); \ + memset( (m)->val_data, '\0', sizeof( (m)->val_data ) ); \ + (m)->dn_count = MATCHES_DNMAXCOUNT( (m) ); \ + (m)->val_count = MATCHES_VALMAXCOUNT( (m) ); \ +} while ( 0 /* CONSTCOND */ ) + +int +slap_access_allowed( + Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + slap_access_t access, + AccessControlState *state, + slap_mask_t *maskp ) +{ + int ret = 1; + int count; + AccessControl *a, *prev; + +#ifdef LDAP_DEBUG + char accessmaskbuf[ACCESSMASK_MAXLEN]; +#endif + slap_mask_t mask; + slap_control_t control; + slap_access_t access_level; + const char *attr; + AclRegexMatches matches; + AccessControlState acl_state = ACL_STATE_INIT; + static AccessControlState state_init = ACL_STATE_INIT; + + assert( op != NULL ); + assert( e != NULL ); + assert( desc != NULL ); + assert( maskp != NULL ); + + access_level = ACL_LEVEL( access ); + attr = desc->ad_cname.bv_val; + + assert( attr != NULL ); + + ACL_INIT( mask ); + + /* grant database root access */ + if ( be_isroot( op ) ) { + Debug( LDAP_DEBUG_ACL, "<= root access granted\n", 0, 0, 0 ); + mask = ACL_LVL_MANAGE; + goto done; + } + + /* + * no-user-modification operational attributes are ignored + * by ACL_WRITE checking as any found here are not provided + * by the user + * + * NOTE: but they are not ignored for ACL_MANAGE, because + * if we get here it means a non-root user is trying to + * manage data, so we need to check its privileges. + */ + if ( access_level == ACL_WRITE_ + && is_at_no_user_mod( desc->ad_type ) + && desc != slap_schema.si_ad_entry + && desc != slap_schema.si_ad_children ) + { + Debug( LDAP_DEBUG_ACL, "NoUserMod Operational attribute:" + " %s access granted\n", + attr, 0, 0 ); + goto done; + } + + /* use backend default access if no backend acls */ + if ( op->o_bd->be_acl == NULL && frontendDB->be_acl == NULL ) { + int i; + + Debug( LDAP_DEBUG_ACL, + "=> slap_access_allowed: backend default %s " + "access %s to \"%s\"\n", + access2str( access ), + op->o_bd->be_dfltaccess >= access_level ? "granted" : "denied", + op->o_dn.bv_val ? op->o_dn.bv_val : "(anonymous)" ); + ret = op->o_bd->be_dfltaccess >= access_level; + + mask = ACL_PRIV_LEVEL; + for ( i = ACL_NONE; i <= op->o_bd->be_dfltaccess; i++ ) { + ACL_PRIV_SET( mask, ACL_ACCESS2PRIV( i ) ); + } + + goto done; + } + + ret = 0; + control = ACL_BREAK; + + if ( state == NULL ) + state = &acl_state; + if ( state->as_desc == desc && + state->as_access == access && + state->as_vd_acl_present ) + { + a = state->as_vd_acl; + count = state->as_vd_acl_count; + if ( state->as_fe_done ) + state->as_fe_done--; + ACL_PRIV_ASSIGN( mask, state->as_vd_mask ); + } else { + *state = state_init; + + a = NULL; + count = 0; + ACL_PRIV_ASSIGN( mask, *maskp ); + } + + MATCHES_MEMSET( &matches ); + prev = a; + + while ( ( a = slap_acl_get( a, &count, op, e, desc, val, + &matches, &mask, state ) ) != NULL ) + { + int i; + int dnmaxcount = MATCHES_DNMAXCOUNT( &matches ); + int valmaxcount = MATCHES_VALMAXCOUNT( &matches ); + regmatch_t *dn_data = matches.dn_data; + regmatch_t *val_data = matches.val_data; + + /* DN matches */ + for ( i = 0; i < dnmaxcount && dn_data[i].rm_eo > 0; i++ ) { + char *data = e->e_ndn; + + Debug( LDAP_DEBUG_ACL, "=> match[dn%d]: %d %d ", i, + (int)dn_data[i].rm_so, + (int)dn_data[i].rm_eo ); + if ( dn_data[i].rm_so <= dn_data[0].rm_eo ) { + int n; + for ( n = dn_data[i].rm_so; + n < dn_data[i].rm_eo; n++ ) { + Debug( LDAP_DEBUG_ACL, "%c", + data[n], 0, 0 ); + } + } + Debug( LDAP_DEBUG_ACL, "\n", 0, 0, 0 ); + } + + /* val matches */ + for ( i = 0; i < valmaxcount && val_data[i].rm_eo > 0; i++ ) { + char *data = val->bv_val; + + Debug( LDAP_DEBUG_ACL, "=> match[val%d]: %d %d ", i, + (int)val_data[i].rm_so, + (int)val_data[i].rm_eo ); + if ( val_data[i].rm_so <= val_data[0].rm_eo ) { + int n; + for ( n = val_data[i].rm_so; + n < val_data[i].rm_eo; n++ ) { + Debug( LDAP_DEBUG_ACL, "%c", + data[n], 0, 0 ); + } + } + Debug( LDAP_DEBUG_ACL, "\n", 0, 0, 0 ); + } + + control = slap_acl_mask( a, prev, &mask, op, + e, desc, val, &matches, count, state, access ); + + if ( control != ACL_BREAK ) { + break; + } + + MATCHES_MEMSET( &matches ); + prev = a; + } + + if ( ACL_IS_INVALID( mask ) ) { + Debug( LDAP_DEBUG_ACL, + "=> slap_access_allowed: \"%s\" (%s) invalid!\n", + e->e_dn, attr, 0 ); + ACL_PRIV_ASSIGN( mask, *maskp ); + + } else if ( control == ACL_BREAK ) { + Debug( LDAP_DEBUG_ACL, + "=> slap_access_allowed: no more rules\n", 0, 0, 0 ); + + goto done; + } + + ret = ACL_GRANT( mask, access ); + + Debug( LDAP_DEBUG_ACL, + "=> slap_access_allowed: %s access %s by %s\n", + access2str( access ), ret ? "granted" : "denied", + accessmask2str( mask, accessmaskbuf, 1 ) ); + +done: + ACL_PRIV_ASSIGN( *maskp, mask ); + return ret; +} + +int +fe_access_allowed( + Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + slap_access_t access, + AccessControlState *state, + slap_mask_t *maskp ) +{ + BackendDB *be_orig; + int rc; + + /* + * NOTE: control gets here if FIXME + * if an appropriate backend cannot be selected for the operation, + * we assume that the frontend should handle this + * FIXME: should select_backend() take care of this, + * and return frontendDB instead of NULL? maybe for some value + * of the flags? + */ + be_orig = op->o_bd; + + if ( op->o_bd == NULL ) { + op->o_bd = select_backend( &op->o_req_ndn, 0 ); + if ( op->o_bd == NULL ) + op->o_bd = frontendDB; + } + rc = slap_access_allowed( op, e, desc, val, access, state, maskp ); + op->o_bd = be_orig; + + return rc; +} + +int +access_allowed_mask( + Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + slap_access_t access, + AccessControlState *state, + slap_mask_t *maskp ) +{ + int ret = 1; + int be_null = 0; + +#ifdef LDAP_DEBUG + char accessmaskbuf[ACCESSMASK_MAXLEN]; +#endif + slap_mask_t mask; + slap_access_t access_level; + const char *attr; + + assert( e != NULL ); + assert( desc != NULL ); + + access_level = ACL_LEVEL( access ); + + assert( access_level > ACL_NONE ); + + ACL_INIT( mask ); + if ( maskp ) ACL_INVALIDATE( *maskp ); + + attr = desc->ad_cname.bv_val; + + assert( attr != NULL ); + + if ( op ) { + if ( op->o_acl_priv != ACL_NONE ) { + access = op->o_acl_priv; + + } else if ( op->o_is_auth_check && + ( access_level == ACL_SEARCH || access_level == ACL_READ ) ) + { + access = ACL_AUTH; + + } else if ( get_relax( op ) && access_level == ACL_WRITE_ && + desc == slap_schema.si_ad_entry ) + { + access = ACL_MANAGE; + } + } + + if ( state != NULL ) { + if ( state->as_desc == desc && + state->as_access == access && + state->as_result != -1 && + !state->as_vd_acl_present ) + { + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: result was in cache (%s)\n", + attr, 0, 0 ); + return state->as_result; + } else { + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: result not in cache (%s)\n", + attr, 0, 0 ); + } + } + + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: %s access to \"%s\" \"%s\" requested\n", + access2str( access ), e->e_dn, attr ); + + if ( op == NULL ) { + /* no-op call */ + goto done; + } + + if ( op->o_bd == NULL ) { + op->o_bd = LDAP_STAILQ_FIRST( &backendDB ); + be_null = 1; + + /* FIXME: experimental; use first backend rules + * iff there is no global_acl (ITS#3100) + */ + if ( frontendDB->be_acl != NULL ) { + op->o_bd = frontendDB; + } + } + assert( op->o_bd != NULL ); + + /* this is enforced in backend_add() */ + if ( op->o_bd->bd_info->bi_access_allowed ) { + /* delegate to backend */ + ret = op->o_bd->bd_info->bi_access_allowed( op, e, + desc, val, access, state, &mask ); + + } else { + /* use default (but pass through frontend + * for global ACL overlays) */ + ret = frontendDB->bd_info->bi_access_allowed( op, e, + desc, val, access, state, &mask ); + } + + if ( !ret ) { + if ( ACL_IS_INVALID( mask ) ) { + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: \"%s\" (%s) invalid!\n", + e->e_dn, attr, 0 ); + ACL_INIT( mask ); + + } else { + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: no more rules\n", 0, 0, 0 ); + + goto done; + } + } + + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: %s access %s by %s\n", + access2str( access ), ret ? "granted" : "denied", + accessmask2str( mask, accessmaskbuf, 1 ) ); + +done: + if ( state != NULL ) { + state->as_access = access; + state->as_result = ret; + state->as_desc = desc; + } + if ( be_null ) op->o_bd = NULL; + if ( maskp ) ACL_PRIV_ASSIGN( *maskp, mask ); + return ret; +} + + +/* + * slap_acl_get - return the acl applicable to entry e, attribute + * attr. the acl returned is suitable for use in subsequent calls to + * acl_access_allowed(). + */ + +static AccessControl * +slap_acl_get( + AccessControl *a, + int *count, + Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + AclRegexMatches *matches, + slap_mask_t *mask, + AccessControlState *state ) +{ + const char *attr; + ber_len_t dnlen; + AccessControl *prev; + + assert( e != NULL ); + assert( count != NULL ); + assert( desc != NULL ); + assert( state != NULL ); + + attr = desc->ad_cname.bv_val; + + assert( attr != NULL ); + + if( a == NULL ) { + if( op->o_bd == NULL || op->o_bd->be_acl == NULL ) { + a = frontendDB->be_acl; + } else { + a = op->o_bd->be_acl; + } + prev = NULL; + + assert( a != NULL ); + if ( a == frontendDB->be_acl ) + state->as_fe_done = 1; + } else { + prev = a; + a = a->acl_next; + } + + dnlen = e->e_nname.bv_len; + + retry: + for ( ; a != NULL; prev = a, a = a->acl_next ) { + (*count) ++; + + if ( a != frontendDB->be_acl && state->as_fe_done ) + state->as_fe_done++; + + if ( a->acl_dn_pat.bv_len || ( a->acl_dn_style != ACL_STYLE_REGEX )) { + if ( a->acl_dn_style == ACL_STYLE_REGEX ) { + Debug( LDAP_DEBUG_ACL, "=> dnpat: [%d] %s nsub: %d\n", + *count, a->acl_dn_pat.bv_val, (int) a->acl_dn_re.re_nsub ); + if ( regexec ( &a->acl_dn_re, + e->e_ndn, + matches->dn_count, + matches->dn_data, 0 ) ) + continue; + + } else { + ber_len_t patlen; + + Debug( LDAP_DEBUG_ACL, "=> dn: [%d] %s\n", + *count, a->acl_dn_pat.bv_val, 0 ); + patlen = a->acl_dn_pat.bv_len; + if ( dnlen < patlen ) + continue; + + if ( a->acl_dn_style == ACL_STYLE_BASE ) { + /* base dn -- entire object DN must match */ + if ( dnlen != patlen ) + continue; + + } else if ( a->acl_dn_style == ACL_STYLE_ONE ) { + ber_len_t rdnlen = 0; + ber_len_t sep = 0; + + if ( dnlen <= patlen ) + continue; + + if ( patlen > 0 ) { + if ( !DN_SEPARATOR( e->e_ndn[dnlen - patlen - 1] ) ) + continue; + sep = 1; + } + + rdnlen = dn_rdnlen( NULL, &e->e_nname ); + if ( rdnlen + patlen + sep != dnlen ) + continue; + + } else if ( a->acl_dn_style == ACL_STYLE_SUBTREE ) { + if ( dnlen > patlen && !DN_SEPARATOR( e->e_ndn[dnlen - patlen - 1] ) ) + continue; + + } else if ( a->acl_dn_style == ACL_STYLE_CHILDREN ) { + if ( dnlen <= patlen ) + continue; + if ( !DN_SEPARATOR( e->e_ndn[dnlen - patlen - 1] ) ) + continue; + } + + if ( strcmp( a->acl_dn_pat.bv_val, e->e_ndn + dnlen - patlen ) != 0 ) + continue; + } + + Debug( LDAP_DEBUG_ACL, "=> acl_get: [%d] matched\n", + *count, 0, 0 ); + } + + if ( a->acl_attrs && !ad_inlist( desc, a->acl_attrs ) ) { + matches->dn_data[0].rm_so = -1; + matches->dn_data[0].rm_eo = -1; + matches->val_data[0].rm_so = -1; + matches->val_data[0].rm_eo = -1; + continue; + } + + /* Is this ACL only for a specific value? */ + if ( a->acl_attrval.bv_val ) { + if ( val == NULL ) { + continue; + } + + if ( !state->as_vd_acl_present ) { + state->as_vd_acl_present = 1; + state->as_vd_acl = prev; + state->as_vd_acl_count = *count - 1; + ACL_PRIV_ASSIGN ( state->as_vd_mask, *mask ); + } + + if ( a->acl_attrval_style == ACL_STYLE_REGEX ) { + Debug( LDAP_DEBUG_ACL, + "acl_get: valpat %s\n", + a->acl_attrval.bv_val, 0, 0 ); + if ( regexec ( &a->acl_attrval_re, + val->bv_val, + matches->val_count, + matches->val_data, 0 ) ) + { + continue; + } + + } else { + int match = 0; + const char *text; + Debug( LDAP_DEBUG_ACL, + "acl_get: val %s\n", + a->acl_attrval.bv_val, 0, 0 ); + + if ( a->acl_attrs[0].an_desc->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName ) { + if (value_match( &match, desc, + a->acl_attrval_mr, 0, + val, &a->acl_attrval, &text ) != LDAP_SUCCESS || + match ) + continue; + + } else { + ber_len_t patlen, vdnlen; + + patlen = a->acl_attrval.bv_len; + vdnlen = val->bv_len; + + if ( vdnlen < patlen ) + continue; + + if ( a->acl_attrval_style == ACL_STYLE_BASE ) { + if ( vdnlen > patlen ) + continue; + + } else if ( a->acl_attrval_style == ACL_STYLE_ONE ) { + ber_len_t rdnlen = 0; + + if ( !DN_SEPARATOR( val->bv_val[vdnlen - patlen - 1] ) ) + continue; + + rdnlen = dn_rdnlen( NULL, val ); + if ( rdnlen + patlen + 1 != vdnlen ) + continue; + + } else if ( a->acl_attrval_style == ACL_STYLE_SUBTREE ) { + if ( vdnlen > patlen && !DN_SEPARATOR( val->bv_val[vdnlen - patlen - 1] ) ) + continue; + + } else if ( a->acl_attrval_style == ACL_STYLE_CHILDREN ) { + if ( vdnlen <= patlen ) + continue; + + if ( !DN_SEPARATOR( val->bv_val[vdnlen - patlen - 1] ) ) + continue; + } + + if ( strcmp( a->acl_attrval.bv_val, val->bv_val + vdnlen - patlen ) ) + continue; + } + } + } + + if ( a->acl_filter != NULL ) { + ber_int_t rc = test_filter( NULL, e, a->acl_filter ); + if ( rc != LDAP_COMPARE_TRUE ) { + continue; + } + } + + Debug( LDAP_DEBUG_ACL, "=> acl_get: [%d] attr %s\n", + *count, attr, 0); + return a; + } + + if ( !state->as_fe_done ) { + state->as_fe_done = 1; + a = frontendDB->be_acl; + goto retry; + } + + Debug( LDAP_DEBUG_ACL, "<= acl_get: done.\n", 0, 0, 0 ); + return( NULL ); +} + +/* + * Record value-dependent access control state + */ +#define ACL_RECORD_VALUE_STATE do { \ + if( state && !state->as_vd_acl_present ) { \ + state->as_vd_acl_present = 1; \ + state->as_vd_acl = prev; \ + state->as_vd_acl_count = count - 1; \ + ACL_PRIV_ASSIGN( state->as_vd_mask, *mask ); \ + } \ + } while( 0 ) + +static int +acl_mask_dn( + Operation *op, + Entry *e, + struct berval *val, + AccessControl *a, + AclRegexMatches *matches, + slap_dn_access *bdn, + struct berval *opndn ) +{ + /* + * if access applies to the entry itself, and the + * user is bound as somebody in the same namespace as + * the entry, OR the given dn matches the dn pattern + */ + /* + * NOTE: styles "anonymous", "users" and "self" + * have been moved to enum slap_style_t, whose + * value is set in a_dn_style; however, the string + * is maintained in a_dn_pat. + */ + + if ( bdn->a_style == ACL_STYLE_ANONYMOUS ) { + if ( !BER_BVISEMPTY( opndn ) ) { + return 1; + } + + } else if ( bdn->a_style == ACL_STYLE_USERS ) { + if ( BER_BVISEMPTY( opndn ) ) { + return 1; + } + + } else if ( bdn->a_style == ACL_STYLE_SELF ) { + struct berval ndn, selfndn; + int level; + + if ( BER_BVISEMPTY( opndn ) || BER_BVISNULL( &e->e_nname ) ) { + return 1; + } + + level = bdn->a_self_level; + if ( level < 0 ) { + selfndn = *opndn; + ndn = e->e_nname; + level = -level; + + } else { + ndn = *opndn; + selfndn = e->e_nname; + } + + for ( ; level > 0; level-- ) { + if ( BER_BVISEMPTY( &ndn ) ) { + break; + } + dnParent( &ndn, &ndn ); + } + + if ( BER_BVISEMPTY( &ndn ) || !dn_match( &ndn, &selfndn ) ) + { + return 1; + } + + } else if ( bdn->a_style == ACL_STYLE_REGEX ) { + if ( !ber_bvccmp( &bdn->a_pat, '*' ) ) { + AclRegexMatches tmp_matches, + *tmp_matchesp = &tmp_matches; + int rc = 0; + regmatch_t *tmp_data; + + MATCHES_MEMSET( &tmp_matches ); + tmp_data = &tmp_matches.dn_data[0]; + + if ( a->acl_attrval_style == ACL_STYLE_REGEX ) + tmp_matchesp = matches; + else switch ( a->acl_dn_style ) { + case ACL_STYLE_REGEX: + if ( !BER_BVISNULL( &a->acl_dn_pat ) ) { + tmp_matchesp = matches; + break; + } + /* FALLTHRU: applies also to ACL_STYLE_REGEX when pattern is "*" */ + + case ACL_STYLE_BASE: + tmp_data[0].rm_so = 0; + tmp_data[0].rm_eo = e->e_nname.bv_len; + tmp_matches.dn_count = 1; + break; + + case ACL_STYLE_ONE: + case ACL_STYLE_SUBTREE: + case ACL_STYLE_CHILDREN: + tmp_data[0].rm_so = 0; + tmp_data[0].rm_eo = e->e_nname.bv_len; + tmp_data[1].rm_so = e->e_nname.bv_len - a->acl_dn_pat.bv_len; + tmp_data[1].rm_eo = e->e_nname.bv_len; + tmp_matches.dn_count = 2; + break; + + default: + /* error */ + rc = 1; + break; + } + + if ( rc ) { + return 1; + } + + if ( !regex_matches( &bdn->a_pat, opndn->bv_val, + &e->e_nname, NULL, tmp_matchesp ) ) + { + return 1; + } + } + + } else { + struct berval pat; + ber_len_t patlen, odnlen; + int got_match = 0; + + if ( e->e_dn == NULL ) + return 1; + + if ( bdn->a_expand ) { + struct berval bv; + char buf[ACL_BUF_SIZE]; + + AclRegexMatches tmp_matches, + *tmp_matchesp = &tmp_matches; + int rc = 0; + regmatch_t *tmp_data; + + MATCHES_MEMSET( &tmp_matches ); + tmp_data = &tmp_matches.dn_data[0]; + + bv.bv_len = sizeof( buf ) - 1; + bv.bv_val = buf; + + /* Expand value regex */ + if ( a->acl_attrval_style == ACL_STYLE_REGEX ) + tmp_matchesp = matches; + else switch ( a->acl_dn_style ) { + case ACL_STYLE_REGEX: + if ( !BER_BVISNULL( &a->acl_dn_pat ) ) { + tmp_matchesp = matches; + break; + } + /* FALLTHRU: applies also to ACL_STYLE_REGEX when pattern is "*" */ + + case ACL_STYLE_BASE: + tmp_data[0].rm_so = 0; + tmp_data[0].rm_eo = e->e_nname.bv_len; + tmp_matches.dn_count = 1; + break; + + case ACL_STYLE_ONE: + case ACL_STYLE_SUBTREE: + case ACL_STYLE_CHILDREN: + tmp_data[0].rm_so = 0; + tmp_data[0].rm_eo = e->e_nname.bv_len; + tmp_data[1].rm_so = e->e_nname.bv_len - a->acl_dn_pat.bv_len; + tmp_data[1].rm_eo = e->e_nname.bv_len; + tmp_matches.dn_count = 2; + break; + + default: + /* error */ + rc = 1; + break; + } + + if ( rc ) { + return 1; + } + + if ( acl_string_expand( &bv, &bdn->a_pat, + &e->e_nname, + val, tmp_matchesp ) ) + { + return 1; + } + + if ( dnNormalize(0, NULL, NULL, &bv, + &pat, op->o_tmpmemctx ) + != LDAP_SUCCESS ) + { + /* did not expand to a valid dn */ + return 1; + } + + } else { + pat = bdn->a_pat; + } + + patlen = pat.bv_len; + odnlen = opndn->bv_len; + if ( odnlen < patlen ) { + goto dn_match_cleanup; + + } + + if ( bdn->a_style == ACL_STYLE_BASE ) { + /* base dn -- entire object DN must match */ + if ( odnlen != patlen ) { + goto dn_match_cleanup; + } + + } else if ( bdn->a_style == ACL_STYLE_ONE ) { + ber_len_t rdnlen = 0; + + if ( odnlen <= patlen ) { + goto dn_match_cleanup; + } + + if ( !DN_SEPARATOR( opndn->bv_val[odnlen - patlen - 1] ) ) { + goto dn_match_cleanup; + } + + rdnlen = dn_rdnlen( NULL, opndn ); + if ( rdnlen - ( odnlen - patlen - 1 ) != 0 ) { + goto dn_match_cleanup; + } + + } else if ( bdn->a_style == ACL_STYLE_SUBTREE ) { + if ( odnlen > patlen && !DN_SEPARATOR( opndn->bv_val[odnlen - patlen - 1] ) ) { + goto dn_match_cleanup; + } + + } else if ( bdn->a_style == ACL_STYLE_CHILDREN ) { + if ( odnlen <= patlen ) { + goto dn_match_cleanup; + } + + if ( !DN_SEPARATOR( opndn->bv_val[odnlen - patlen - 1] ) ) { + goto dn_match_cleanup; + } + + } else if ( bdn->a_style == ACL_STYLE_LEVEL ) { + int level = bdn->a_level; + struct berval ndn; + + if ( odnlen <= patlen ) { + goto dn_match_cleanup; + } + + if ( level > 0 && !DN_SEPARATOR( opndn->bv_val[odnlen - patlen - 1] ) ) + { + goto dn_match_cleanup; + } + + ndn = *opndn; + for ( ; level > 0; level-- ) { + if ( BER_BVISEMPTY( &ndn ) ) { + goto dn_match_cleanup; + } + dnParent( &ndn, &ndn ); + if ( ndn.bv_len < patlen ) { + goto dn_match_cleanup; + } + } + + if ( ndn.bv_len != patlen ) { + goto dn_match_cleanup; + } + } + + got_match = !strcmp( pat.bv_val, &opndn->bv_val[ odnlen - patlen ] ); + +dn_match_cleanup:; + if ( pat.bv_val != bdn->a_pat.bv_val ) { + slap_sl_free( pat.bv_val, op->o_tmpmemctx ); + } + + if ( !got_match ) { + return 1; + } + } + + return 0; +} + +static int +acl_mask_dnattr( + Operation *op, + Entry *e, + struct berval *val, + AccessControl *a, + int count, + AccessControlState *state, + slap_mask_t *mask, + slap_dn_access *bdn, + struct berval *opndn ) +{ + Attribute *at; + struct berval bv; + int rc, match = 0; + const char *text; + const char *attr = bdn->a_at->ad_cname.bv_val; + + assert( attr != NULL ); + + if ( BER_BVISEMPTY( opndn ) ) { + return 1; + } + + Debug( LDAP_DEBUG_ACL, "<= check a_dn_at: %s\n", attr, 0, 0 ); + bv = *opndn; + + /* see if asker is listed in dnattr */ + for ( at = attrs_find( e->e_attrs, bdn->a_at ); + at != NULL; + at = attrs_find( at->a_next, bdn->a_at ) ) + { + if ( attr_valfind( at, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + &bv, NULL, op->o_tmpmemctx ) == 0 ) + { + /* found it */ + match = 1; + break; + } + } + + if ( match ) { + /* have a dnattr match. if this is a self clause then + * the target must also match the op dn. + */ + if ( bdn->a_self ) { + /* check if the target is an attribute. */ + if ( val == NULL ) return 1; + + /* target is attribute, check if the attribute value + * is the op dn. + */ + rc = value_match( &match, bdn->a_at, + bdn->a_at->ad_type->sat_equality, 0, + val, &bv, &text ); + /* on match error or no match, fail the ACL clause */ + if ( rc != LDAP_SUCCESS || match != 0 ) + return 1; + } + + } else { + /* no dnattr match, check if this is a self clause */ + if ( ! bdn->a_self ) + return 1; + + /* this is a self clause, check if the target is an + * attribute. + */ + if ( val == NULL ) + return 1; + + /* target is attribute, check if the attribute value + * is the op dn. + */ + rc = value_match( &match, bdn->a_at, + bdn->a_at->ad_type->sat_equality, 0, + val, &bv, &text ); + + /* on match error or no match, fail the ACL clause */ + if ( rc != LDAP_SUCCESS || match != 0 ) + return 1; + } + + return 0; +} + + +/* + * slap_acl_mask - modifies mask based upon the given acl and the + * requested access to entry e, attribute attr, value val. if val + * is null, access to the whole attribute is assumed (all values). + * + * returns 0 access NOT allowed + * 1 access allowed + */ + +static slap_control_t +slap_acl_mask( + AccessControl *a, + AccessControl *prev, + slap_mask_t *mask, + Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + AclRegexMatches *matches, + int count, + AccessControlState *state, + slap_access_t access ) +{ + int i; + Access *b; +#ifdef LDAP_DEBUG + char accessmaskbuf[ACCESSMASK_MAXLEN]; +#endif /* DEBUG */ + const char *attr; +#ifdef SLAP_DYNACL + slap_mask_t a2pmask = ACL_ACCESS2PRIV( access ); +#endif /* SLAP_DYNACL */ + + assert( a != NULL ); + assert( mask != NULL ); + assert( desc != NULL ); + + attr = desc->ad_cname.bv_val; + + assert( attr != NULL ); + + Debug( LDAP_DEBUG_ACL, + "=> acl_mask: access to entry \"%s\", attr \"%s\" requested\n", + e->e_dn, attr, 0 ); + + Debug( LDAP_DEBUG_ACL, + "=> acl_mask: to %s by \"%s\", (%s) \n", + val ? "value" : "all values", + op->o_ndn.bv_val ? op->o_ndn.bv_val : "", + accessmask2str( *mask, accessmaskbuf, 1 ) ); + + + b = a->acl_access; + i = 1; + + for ( ; b != NULL; b = b->a_next, i++ ) { + slap_mask_t oldmask, modmask; + + ACL_INVALIDATE( modmask ); + + /* check for the "self" modifier in the <access> field */ + if ( b->a_dn.a_self ) { + const char *dummy; + int rc, match = 0; + + ACL_RECORD_VALUE_STATE; + + /* must have DN syntax */ + if ( desc->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName && + !is_at_syntax( desc->ad_type, SLAPD_NAMEUID_SYNTAX )) continue; + + /* check if the target is an attribute. */ + if ( val == NULL ) continue; + + /* a DN must be present */ + if ( BER_BVISEMPTY( &op->o_ndn ) ) { + continue; + } + + /* target is attribute, check if the attribute value + * is the op dn. + */ + rc = value_match( &match, desc, + desc->ad_type->sat_equality, 0, + val, &op->o_ndn, &dummy ); + /* on match error or no match, fail the ACL clause */ + if ( rc != LDAP_SUCCESS || match != 0 ) + continue; + } + + /* AND <who> clauses */ + if ( !BER_BVISEMPTY( &b->a_dn_pat ) ) { + Debug( LDAP_DEBUG_ACL, "<= check a_dn_pat: %s\n", + b->a_dn_pat.bv_val, 0, 0); + /* + * if access applies to the entry itself, and the + * user is bound as somebody in the same namespace as + * the entry, OR the given dn matches the dn pattern + */ + /* + * NOTE: styles "anonymous", "users" and "self" + * have been moved to enum slap_style_t, whose + * value is set in a_dn_style; however, the string + * is maintained in a_dn_pat. + */ + + if ( acl_mask_dn( op, e, val, a, matches, + &b->a_dn, &op->o_ndn ) ) + { + continue; + } + } + + if ( !BER_BVISEMPTY( &b->a_realdn_pat ) ) { + struct berval ndn; + + Debug( LDAP_DEBUG_ACL, "<= check a_realdn_pat: %s\n", + b->a_realdn_pat.bv_val, 0, 0); + /* + * if access applies to the entry itself, and the + * user is bound as somebody in the same namespace as + * the entry, OR the given dn matches the dn pattern + */ + /* + * NOTE: styles "anonymous", "users" and "self" + * have been moved to enum slap_style_t, whose + * value is set in a_dn_style; however, the string + * is maintained in a_dn_pat. + */ + + if ( op->o_conn && !BER_BVISNULL( &op->o_conn->c_ndn ) ) + { + ndn = op->o_conn->c_ndn; + } else { + ndn = op->o_ndn; + } + + if ( acl_mask_dn( op, e, val, a, matches, + &b->a_realdn, &ndn ) ) + { + continue; + } + } + + if ( !BER_BVISEMPTY( &b->a_sockurl_pat ) ) { + if ( ! op->o_conn->c_listener ) { + continue; + } + Debug( LDAP_DEBUG_ACL, "<= check a_sockurl_pat: %s\n", + b->a_sockurl_pat.bv_val, 0, 0 ); + + if ( !ber_bvccmp( &b->a_sockurl_pat, '*' ) ) { + if ( b->a_sockurl_style == ACL_STYLE_REGEX) { + if ( !regex_matches( &b->a_sockurl_pat, op->o_conn->c_listener_url.bv_val, + &e->e_nname, val, matches ) ) + { + continue; + } + + } else if ( b->a_sockurl_style == ACL_STYLE_EXPAND ) { + struct berval bv; + char buf[ACL_BUF_SIZE]; + + bv.bv_len = sizeof( buf ) - 1; + bv.bv_val = buf; + if ( acl_string_expand( &bv, &b->a_sockurl_pat, &e->e_nname, val, matches ) ) + { + continue; + } + + if ( ber_bvstrcasecmp( &bv, &op->o_conn->c_listener_url ) != 0 ) + { + continue; + } + + } else { + if ( ber_bvstrcasecmp( &b->a_sockurl_pat, &op->o_conn->c_listener_url ) != 0 ) + { + continue; + } + } + } + } + + if ( !BER_BVISEMPTY( &b->a_domain_pat ) ) { + if ( !op->o_conn->c_peer_domain.bv_val ) { + continue; + } + Debug( LDAP_DEBUG_ACL, "<= check a_domain_pat: %s\n", + b->a_domain_pat.bv_val, 0, 0 ); + if ( !ber_bvccmp( &b->a_domain_pat, '*' ) ) { + if ( b->a_domain_style == ACL_STYLE_REGEX) { + if ( !regex_matches( &b->a_domain_pat, op->o_conn->c_peer_domain.bv_val, + &e->e_nname, val, matches ) ) + { + continue; + } + } else { + char buf[ACL_BUF_SIZE]; + + struct berval cmp = op->o_conn->c_peer_domain; + struct berval pat = b->a_domain_pat; + + if ( b->a_domain_expand ) { + struct berval bv; + + bv.bv_len = sizeof(buf) - 1; + bv.bv_val = buf; + + if ( acl_string_expand(&bv, &b->a_domain_pat, &e->e_nname, val, matches) ) + { + continue; + } + pat = bv; + } + + if ( b->a_domain_style == ACL_STYLE_SUBTREE ) { + int offset = cmp.bv_len - pat.bv_len; + if ( offset < 0 ) { + continue; + } + + if ( offset == 1 || ( offset > 1 && cmp.bv_val[ offset - 1 ] != '.' ) ) { + continue; + } + + /* trim the domain */ + cmp.bv_val = &cmp.bv_val[ offset ]; + cmp.bv_len -= offset; + } + + if ( ber_bvstrcasecmp( &pat, &cmp ) != 0 ) { + continue; + } + } + } + } + + if ( !BER_BVISEMPTY( &b->a_peername_pat ) ) { + if ( !op->o_conn->c_peer_name.bv_val ) { + continue; + } + Debug( LDAP_DEBUG_ACL, "<= check a_peername_path: %s\n", + b->a_peername_pat.bv_val, 0, 0 ); + if ( !ber_bvccmp( &b->a_peername_pat, '*' ) ) { + if ( b->a_peername_style == ACL_STYLE_REGEX ) { + if ( !regex_matches( &b->a_peername_pat, op->o_conn->c_peer_name.bv_val, + &e->e_nname, val, matches ) ) + { + continue; + } + + } else { + /* try exact match */ + if ( b->a_peername_style == ACL_STYLE_BASE ) { + if ( ber_bvstrcasecmp( &b->a_peername_pat, &op->o_conn->c_peer_name ) != 0 ) { + continue; + } + + } else if ( b->a_peername_style == ACL_STYLE_EXPAND ) { + struct berval bv; + char buf[ACL_BUF_SIZE]; + + bv.bv_len = sizeof( buf ) - 1; + bv.bv_val = buf; + if ( acl_string_expand( &bv, &b->a_peername_pat, &e->e_nname, val, matches ) ) + { + continue; + } + + if ( ber_bvstrcasecmp( &bv, &op->o_conn->c_peer_name ) != 0 ) { + continue; + } + + /* extract IP and try exact match */ + } else if ( b->a_peername_style == ACL_STYLE_IP ) { + char *port; + char buf[STRLENOF("255.255.255.255") + 1]; + struct berval ip; + unsigned long addr; + int port_number = -1; + + if ( strncasecmp( op->o_conn->c_peer_name.bv_val, + acl_bv_ip_eq.bv_val, + acl_bv_ip_eq.bv_len ) != 0 ) + continue; + + ip.bv_val = op->o_conn->c_peer_name.bv_val + acl_bv_ip_eq.bv_len; + ip.bv_len = op->o_conn->c_peer_name.bv_len - acl_bv_ip_eq.bv_len; + + port = strrchr( ip.bv_val, ':' ); + if ( port ) { + ip.bv_len = port - ip.bv_val; + ++port; + if ( lutil_atoi( &port_number, port ) != 0 ) + continue; + } + + /* the port check can be anticipated here */ + if ( b->a_peername_port != -1 && port_number != b->a_peername_port ) + continue; + + /* address longer than expected? */ + if ( ip.bv_len >= sizeof(buf) ) + continue; + + AC_MEMCPY( buf, ip.bv_val, ip.bv_len ); + buf[ ip.bv_len ] = '\0'; + + addr = inet_addr( buf ); + + /* unable to convert? */ + if ( addr == (unsigned long)(-1) ) + continue; + + if ( (addr & b->a_peername_mask) != b->a_peername_addr ) + continue; + +#ifdef LDAP_PF_INET6 + /* extract IPv6 and try exact match */ + } else if ( b->a_peername_style == ACL_STYLE_IPV6 ) { + char *port; + char buf[STRLENOF("FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF") + 1]; + struct berval ip; + struct in6_addr addr; + int port_number = -1; + + if ( strncasecmp( op->o_conn->c_peer_name.bv_val, + acl_bv_ipv6_eq.bv_val, + acl_bv_ipv6_eq.bv_len ) != 0 ) + continue; + + ip.bv_val = op->o_conn->c_peer_name.bv_val + acl_bv_ipv6_eq.bv_len; + ip.bv_len = op->o_conn->c_peer_name.bv_len - acl_bv_ipv6_eq.bv_len; + + port = strrchr( ip.bv_val, ']' ); + if ( port ) { + ip.bv_len = port - ip.bv_val; + ++port; + if ( port[0] == ':' && lutil_atoi( &port_number, ++port ) != 0 ) + continue; + } + + /* the port check can be anticipated here */ + if ( b->a_peername_port != -1 && port_number != b->a_peername_port ) + continue; + + /* address longer than expected? */ + if ( ip.bv_len >= sizeof(buf) ) + continue; + + AC_MEMCPY( buf, ip.bv_val, ip.bv_len ); + buf[ ip.bv_len ] = '\0'; + + if ( inet_pton( AF_INET6, buf, &addr ) != 1 ) + continue; + + /* check mask */ + if ( !slap_addr6_mask( &addr, &b->a_peername_mask6, &b->a_peername_addr6 ) ) + continue; +#endif /* LDAP_PF_INET6 */ + +#ifdef LDAP_PF_LOCAL + /* extract path and try exact match */ + } else if ( b->a_peername_style == ACL_STYLE_PATH ) { + struct berval path; + + if ( strncmp( op->o_conn->c_peer_name.bv_val, + acl_bv_path_eq.bv_val, + acl_bv_path_eq.bv_len ) != 0 ) + continue; + + path.bv_val = op->o_conn->c_peer_name.bv_val + + acl_bv_path_eq.bv_len; + path.bv_len = op->o_conn->c_peer_name.bv_len + - acl_bv_path_eq.bv_len; + + if ( ber_bvcmp( &b->a_peername_pat, &path ) != 0 ) + continue; + +#endif /* LDAP_PF_LOCAL */ + + /* exact match (very unlikely...) */ + } else if ( ber_bvcmp( &op->o_conn->c_peer_name, &b->a_peername_pat ) != 0 ) { + continue; + } + } + } + } + + if ( !BER_BVISEMPTY( &b->a_sockname_pat ) ) { + if ( BER_BVISNULL( &op->o_conn->c_sock_name ) ) { + continue; + } + Debug( LDAP_DEBUG_ACL, "<= check a_sockname_path: %s\n", + b->a_sockname_pat.bv_val, 0, 0 ); + if ( !ber_bvccmp( &b->a_sockname_pat, '*' ) ) { + if ( b->a_sockname_style == ACL_STYLE_REGEX) { + if ( !regex_matches( &b->a_sockname_pat, op->o_conn->c_sock_name.bv_val, + &e->e_nname, val, matches ) ) + { + continue; + } + + } else if ( b->a_sockname_style == ACL_STYLE_EXPAND ) { + struct berval bv; + char buf[ACL_BUF_SIZE]; + + bv.bv_len = sizeof( buf ) - 1; + bv.bv_val = buf; + if ( acl_string_expand( &bv, &b->a_sockname_pat, &e->e_nname, val, matches ) ) + { + continue; + } + + if ( ber_bvstrcasecmp( &bv, &op->o_conn->c_sock_name ) != 0 ) { + continue; + } + + } else { + if ( ber_bvstrcasecmp( &b->a_sockname_pat, &op->o_conn->c_sock_name ) != 0 ) { + continue; + } + } + } + } + + if ( b->a_dn_at != NULL ) { + if ( acl_mask_dnattr( op, e, val, a, + count, state, mask, + &b->a_dn, &op->o_ndn ) ) + { + continue; + } + } + + if ( b->a_realdn_at != NULL ) { + struct berval ndn; + + if ( op->o_conn && !BER_BVISNULL( &op->o_conn->c_ndn ) ) + { + ndn = op->o_conn->c_ndn; + } else { + ndn = op->o_ndn; + } + + if ( acl_mask_dnattr( op, e, val, a, + count, state, mask, + &b->a_realdn, &ndn ) ) + { + continue; + } + } + + if ( !BER_BVISEMPTY( &b->a_group_pat ) ) { + struct berval bv; + struct berval ndn = BER_BVNULL; + int rc; + + if ( op->o_ndn.bv_len == 0 ) { + continue; + } + + Debug( LDAP_DEBUG_ACL, "<= check a_group_pat: %s\n", + b->a_group_pat.bv_val, 0, 0 ); + + /* b->a_group is an unexpanded entry name, expanded it should be an + * entry with objectclass group* and we test to see if odn is one of + * the values in the attribute group + */ + /* see if asker is listed in dnattr */ + if ( b->a_group_style == ACL_STYLE_EXPAND ) { + char buf[ACL_BUF_SIZE]; + AclRegexMatches tmp_matches, + *tmp_matchesp = &tmp_matches; + regmatch_t *tmp_data; + + MATCHES_MEMSET( &tmp_matches ); + tmp_data = &tmp_matches.dn_data[0]; + + bv.bv_len = sizeof(buf) - 1; + bv.bv_val = buf; + + rc = 0; + + if ( a->acl_attrval_style == ACL_STYLE_REGEX ) + tmp_matchesp = matches; + else switch ( a->acl_dn_style ) { + case ACL_STYLE_REGEX: + if ( !BER_BVISNULL( &a->acl_dn_pat ) ) { + tmp_matchesp = matches; + break; + } + + /* FALLTHRU: applies also to ACL_STYLE_REGEX when pattern is "*" */ + case ACL_STYLE_BASE: + tmp_data[0].rm_so = 0; + tmp_data[0].rm_eo = e->e_nname.bv_len; + tmp_matches.dn_count = 1; + break; + + case ACL_STYLE_ONE: + case ACL_STYLE_SUBTREE: + case ACL_STYLE_CHILDREN: + tmp_data[0].rm_so = 0; + tmp_data[0].rm_eo = e->e_nname.bv_len; + + tmp_data[1].rm_so = e->e_nname.bv_len - a->acl_dn_pat.bv_len; + tmp_data[1].rm_eo = e->e_nname.bv_len; + tmp_matches.dn_count = 2; + break; + + default: + /* error */ + rc = 1; + break; + } + + if ( rc ) { + continue; + } + + if ( acl_string_expand( &bv, &b->a_group_pat, + &e->e_nname, val, + tmp_matchesp ) ) + { + continue; + } + + if ( dnNormalize( 0, NULL, NULL, &bv, &ndn, + op->o_tmpmemctx ) != LDAP_SUCCESS ) + { + /* did not expand to a valid dn */ + continue; + } + + bv = ndn; + + } else { + bv = b->a_group_pat; + } + + rc = backend_group( op, e, &bv, &op->o_ndn, + b->a_group_oc, b->a_group_at ); + + if ( ndn.bv_val ) { + slap_sl_free( ndn.bv_val, op->o_tmpmemctx ); + } + + if ( rc != 0 ) { + continue; + } + } + + if ( !BER_BVISEMPTY( &b->a_set_pat ) ) { + struct berval bv; + char buf[ACL_BUF_SIZE]; + + Debug( LDAP_DEBUG_ACL, "<= check a_set_pat: %s\n", + b->a_set_pat.bv_val, 0, 0 ); + + if ( b->a_set_style == ACL_STYLE_EXPAND ) { + AclRegexMatches tmp_matches, + *tmp_matchesp = &tmp_matches; + int rc = 0; + regmatch_t *tmp_data; + + MATCHES_MEMSET( &tmp_matches ); + tmp_data = &tmp_matches.dn_data[0]; + + bv.bv_len = sizeof( buf ) - 1; + bv.bv_val = buf; + + rc = 0; + + if ( a->acl_attrval_style == ACL_STYLE_REGEX ) + tmp_matchesp = matches; + else switch ( a->acl_dn_style ) { + case ACL_STYLE_REGEX: + if ( !BER_BVISNULL( &a->acl_dn_pat ) ) { + tmp_matchesp = matches; + break; + } + + /* FALLTHRU: applies also to ACL_STYLE_REGEX when pattern is "*" */ + case ACL_STYLE_BASE: + tmp_data[0].rm_so = 0; + tmp_data[0].rm_eo = e->e_nname.bv_len; + tmp_matches.dn_count = 1; + break; + + case ACL_STYLE_ONE: + case ACL_STYLE_SUBTREE: + case ACL_STYLE_CHILDREN: + tmp_data[0].rm_so = 0; + tmp_data[0].rm_eo = e->e_nname.bv_len; + tmp_data[1].rm_so = e->e_nname.bv_len - a->acl_dn_pat.bv_len; + tmp_data[1].rm_eo = e->e_nname.bv_len; tmp_matches.dn_count = 2; + break; + + default: + /* error */ + rc = 1; + break; + } + + if ( rc ) { + continue; + } + + if ( acl_string_expand( &bv, &b->a_set_pat, + &e->e_nname, val, + tmp_matchesp ) ) + { + continue; + } + + } else { + bv = b->a_set_pat; + } + + if ( acl_match_set( &bv, op, e, NULL ) == 0 ) { + continue; + } + } + + if ( b->a_authz.sai_ssf ) { + Debug( LDAP_DEBUG_ACL, "<= check a_authz.sai_ssf: ACL %u > OP %u\n", + b->a_authz.sai_ssf, op->o_ssf, 0 ); + if ( b->a_authz.sai_ssf > op->o_ssf ) { + continue; + } + } + + if ( b->a_authz.sai_transport_ssf ) { + Debug( LDAP_DEBUG_ACL, + "<= check a_authz.sai_transport_ssf: ACL %u > OP %u\n", + b->a_authz.sai_transport_ssf, op->o_transport_ssf, 0 ); + if ( b->a_authz.sai_transport_ssf > op->o_transport_ssf ) { + continue; + } + } + + if ( b->a_authz.sai_tls_ssf ) { + Debug( LDAP_DEBUG_ACL, + "<= check a_authz.sai_tls_ssf: ACL %u > OP %u\n", + b->a_authz.sai_tls_ssf, op->o_tls_ssf, 0 ); + if ( b->a_authz.sai_tls_ssf > op->o_tls_ssf ) { + continue; + } + } + + if ( b->a_authz.sai_sasl_ssf ) { + Debug( LDAP_DEBUG_ACL, + "<= check a_authz.sai_sasl_ssf: ACL %u > OP %u\n", + b->a_authz.sai_sasl_ssf, op->o_sasl_ssf, 0 ); + if ( b->a_authz.sai_sasl_ssf > op->o_sasl_ssf ) { + continue; + } + } + +#ifdef SLAP_DYNACL + if ( b->a_dynacl ) { + slap_dynacl_t *da; + slap_access_t tgrant, tdeny; + + Debug( LDAP_DEBUG_ACL, "<= check a_dynacl\n", + 0, 0, 0 ); + + /* this case works different from the others above. + * since dynamic ACL's themselves give permissions, we need + * to first check b->a_access_mask, the ACL's access level. + */ + /* first check if the right being requested + * is allowed by the ACL clause. + */ + if ( ! ACL_PRIV_ISSET( b->a_access_mask, a2pmask ) ) { + continue; + } + + /* start out with nothing granted, nothing denied */ + ACL_INVALIDATE(tgrant); + ACL_INVALIDATE(tdeny); + + for ( da = b->a_dynacl; da; da = da->da_next ) { + slap_access_t grant, + deny; + + ACL_INVALIDATE(grant); + ACL_INVALIDATE(deny); + + Debug( LDAP_DEBUG_ACL, " <= check a_dynacl: %s\n", + da->da_name, 0, 0 ); + + /* + * XXXmanu Only DN matches are supplied + * sending attribute values matches require + * an API update + */ + (void)da->da_mask( da->da_private, op, e, desc, + val, matches->dn_count, matches->dn_data, + &grant, &deny ); + + tgrant |= grant; + tdeny |= deny; + } + + /* remove anything that the ACL clause does not allow */ + tgrant &= b->a_access_mask & ACL_PRIV_MASK; + tdeny &= ACL_PRIV_MASK; + + /* see if we have anything to contribute */ + if( ACL_IS_INVALID(tgrant) && ACL_IS_INVALID(tdeny) ) { + continue; + } + + /* this could be improved by changing slap_acl_mask so that it can deal with + * by clauses that return grant/deny pairs. Right now, it does either + * additive or subtractive rights, but not both at the same time. So, + * we need to combine the grant/deny pair into a single rights mask in + * a smart way: if either grant or deny is "empty", then we use the + * opposite as is, otherwise we remove any denied rights from the grant + * rights mask and construct an additive mask. + */ + if (ACL_IS_INVALID(tdeny)) { + modmask = tgrant | ACL_PRIV_ADDITIVE; + + } else if (ACL_IS_INVALID(tgrant)) { + modmask = tdeny | ACL_PRIV_SUBSTRACTIVE; + + } else { + modmask = (tgrant & ~tdeny) | ACL_PRIV_ADDITIVE; + } + + } else +#endif /* SLAP_DYNACL */ + { + modmask = b->a_access_mask; + } + + Debug( LDAP_DEBUG_ACL, + "<= acl_mask: [%d] applying %s (%s)\n", + i, accessmask2str( modmask, accessmaskbuf, 1 ), + b->a_type == ACL_CONTINUE + ? "continue" + : b->a_type == ACL_BREAK + ? "break" + : "stop" ); + /* save old mask */ + oldmask = *mask; + + if( ACL_IS_ADDITIVE(modmask) ) { + /* add privs */ + ACL_PRIV_SET( *mask, modmask ); + + /* cleanup */ + ACL_PRIV_CLR( *mask, ~ACL_PRIV_MASK ); + + } else if( ACL_IS_SUBTRACTIVE(modmask) ) { + /* substract privs */ + ACL_PRIV_CLR( *mask, modmask ); + + /* cleanup */ + ACL_PRIV_CLR( *mask, ~ACL_PRIV_MASK ); + + } else { + /* assign privs */ + *mask = modmask; + } + + Debug( LDAP_DEBUG_ACL, + "<= acl_mask: [%d] mask: %s\n", + i, accessmask2str(*mask, accessmaskbuf, 1), 0 ); + + if( b->a_type == ACL_CONTINUE ) { + continue; + + } else if ( b->a_type == ACL_BREAK ) { + return ACL_BREAK; + + } else { + return ACL_STOP; + } + } + + /* implicit "by * none" clause */ + ACL_INIT(*mask); + + Debug( LDAP_DEBUG_ACL, + "<= acl_mask: no more <who> clauses, returning %s (stop)\n", + accessmask2str(*mask, accessmaskbuf, 1), 0, 0 ); + return ACL_STOP; +} + +/* + * acl_check_modlist - check access control on the given entry to see if + * it allows the given modifications by the user associated with op. + * returns 1 if mods allowed ok + * 0 mods not allowed + */ + +int +acl_check_modlist( + Operation *op, + Entry *e, + Modifications *mlist ) +{ + struct berval *bv; + AccessControlState state = ACL_STATE_INIT; + Backend *be; + int be_null = 0; + int ret = 1; /* default is access allowed */ + + be = op->o_bd; + if ( be == NULL ) { + be = LDAP_STAILQ_FIRST(&backendDB); + be_null = 1; + op->o_bd = be; + } + assert( be != NULL ); + + /* If ADD attribute checking is not enabled, just allow it */ + if ( op->o_tag == LDAP_REQ_ADD && !SLAP_DBACL_ADD( be )) + return 1; + + /* short circuit root database access */ + if ( be_isroot( op ) ) { + Debug( LDAP_DEBUG_ACL, + "<= acl_access_allowed: granted to database root\n", + 0, 0, 0 ); + goto done; + } + + /* use backend default access if no backend acls */ + if( op->o_bd != NULL && op->o_bd->be_acl == NULL && frontendDB->be_acl == NULL ) { + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: backend default %s access %s to \"%s\"\n", + access2str( ACL_WRITE ), + op->o_bd->be_dfltaccess >= ACL_WRITE + ? "granted" : "denied", + op->o_dn.bv_val ); + ret = (op->o_bd->be_dfltaccess >= ACL_WRITE); + goto done; + } + + for ( ; mlist != NULL; mlist = mlist->sml_next ) { + /* + * Internal mods are ignored by ACL_WRITE checking + */ + if ( mlist->sml_flags & SLAP_MOD_INTERNAL ) { + Debug( LDAP_DEBUG_ACL, "acl: internal mod %s:" + " modify access granted\n", + mlist->sml_desc->ad_cname.bv_val, 0, 0 ); + continue; + } + + /* + * no-user-modification operational attributes are ignored + * by ACL_WRITE checking as any found here are not provided + * by the user + */ + if ( is_at_no_user_mod( mlist->sml_desc->ad_type ) + && ! ( mlist->sml_flags & SLAP_MOD_MANAGING ) ) + { + Debug( LDAP_DEBUG_ACL, "acl: no-user-mod %s:" + " modify access granted\n", + mlist->sml_desc->ad_cname.bv_val, 0, 0 ); + continue; + } + + switch ( mlist->sml_op ) { + case LDAP_MOD_REPLACE: + case LDAP_MOD_INCREMENT: + /* + * We must check both permission to delete the whole + * attribute and permission to add the specific attributes. + * This prevents abuse from selfwriters. + */ + if ( ! access_allowed( op, e, + mlist->sml_desc, NULL, + ( mlist->sml_flags & SLAP_MOD_MANAGING ) ? ACL_MANAGE : ACL_WDEL, + &state ) ) + { + ret = 0; + goto done; + } + + if ( mlist->sml_values == NULL ) break; + + /* fall thru to check value to add */ + + case LDAP_MOD_ADD: + case SLAP_MOD_ADD_IF_NOT_PRESENT: + assert( mlist->sml_values != NULL ); + + if ( mlist->sml_op == SLAP_MOD_ADD_IF_NOT_PRESENT + && attr_find( e->e_attrs, mlist->sml_desc ) ) + { + break; + } + + for ( bv = mlist->sml_nvalues + ? mlist->sml_nvalues : mlist->sml_values; + bv->bv_val != NULL; bv++ ) + { + if ( ! access_allowed( op, e, + mlist->sml_desc, bv, + ( mlist->sml_flags & SLAP_MOD_MANAGING ) ? ACL_MANAGE : ACL_WADD, + &state ) ) + { + ret = 0; + goto done; + } + } + break; + + case LDAP_MOD_DELETE: + case SLAP_MOD_SOFTDEL: + if ( mlist->sml_values == NULL ) { + if ( ! access_allowed( op, e, + mlist->sml_desc, NULL, + ( mlist->sml_flags & SLAP_MOD_MANAGING ) ? ACL_MANAGE : ACL_WDEL, + &state ) ) + { + ret = 0; + goto done; + } + break; + } + for ( bv = mlist->sml_nvalues + ? mlist->sml_nvalues : mlist->sml_values; + bv->bv_val != NULL; bv++ ) + { + if ( ! access_allowed( op, e, + mlist->sml_desc, bv, + ( mlist->sml_flags & SLAP_MOD_MANAGING ) ? ACL_MANAGE : ACL_WDEL, + &state ) ) + { + ret = 0; + goto done; + } + } + break; + + case SLAP_MOD_SOFTADD: + /* allow adding attribute via modrdn thru */ + break; + + default: + assert( 0 ); + /* not reached */ + ret = 0; + break; + } + } + +done: + if (be_null) op->o_bd = NULL; + return( ret ); +} + +int +acl_get_part( + struct berval *list, + int ix, + char sep, + struct berval *bv ) +{ + int len; + char *p; + + if ( bv ) { + BER_BVZERO( bv ); + } + len = list->bv_len; + p = list->bv_val; + while ( len >= 0 && --ix >= 0 ) { + while ( --len >= 0 && *p++ != sep ) + ; + } + while ( len >= 0 && *p == ' ' ) { + len--; + p++; + } + if ( len < 0 ) { + return -1; + } + + if ( !bv ) { + return 0; + } + + bv->bv_val = p; + while ( --len >= 0 && *p != sep ) { + bv->bv_len++; + p++; + } + while ( bv->bv_len > 0 && *--p == ' ' ) { + bv->bv_len--; + } + + return bv->bv_len; +} + +typedef struct acl_set_gather_t { + SetCookie *cookie; + BerVarray bvals; +} acl_set_gather_t; + +static int +acl_set_cb_gather( Operation *op, SlapReply *rs ) +{ + acl_set_gather_t *p = (acl_set_gather_t *)op->o_callback->sc_private; + + if ( rs->sr_type == REP_SEARCH ) { + BerValue bvals[ 2 ]; + BerVarray bvalsp = NULL; + int j; + + for ( j = 0; !BER_BVISNULL( &rs->sr_attrs[ j ].an_name ); j++ ) { + AttributeDescription *desc = rs->sr_attrs[ j ].an_desc; + + if ( desc == NULL ) { + continue; + } + + if ( desc == slap_schema.si_ad_entryDN ) { + bvalsp = bvals; + bvals[ 0 ] = rs->sr_entry->e_nname; + BER_BVZERO( &bvals[ 1 ] ); + + } else { + Attribute *a; + + a = attr_find( rs->sr_entry->e_attrs, desc ); + if ( a != NULL ) { + bvalsp = a->a_nvals; + } + } + + if ( bvalsp ) { + p->bvals = slap_set_join( p->cookie, p->bvals, + ( '|' | SLAP_SET_RREF ), bvalsp ); + } + } + + } else { + switch ( rs->sr_type ) { + case REP_SEARCHREF: + case REP_INTERMEDIATE: + /* ignore */ + break; + + default: + assert( rs->sr_type == REP_RESULT ); + break; + } + } + + return 0; +} + +BerVarray +acl_set_gather( SetCookie *cookie, struct berval *name, AttributeDescription *desc ) +{ + AclSetCookie *cp = (AclSetCookie *)cookie; + int rc = 0; + LDAPURLDesc *ludp = NULL; + Operation op2 = { 0 }; + SlapReply rs = {REP_RESULT}; + AttributeName anlist[ 2 ], *anlistp = NULL; + int nattrs = 0; + slap_callback cb = { NULL, acl_set_cb_gather, NULL, NULL }; + acl_set_gather_t p = { 0 }; + + /* this routine needs to return the bervals instead of + * plain strings, since syntax is not known. It should + * also return the syntax or some "comparison cookie". + */ + if ( strncasecmp( name->bv_val, "ldap:///", STRLENOF( "ldap:///" ) ) != 0 ) { + return acl_set_gather2( cookie, name, desc ); + } + + rc = ldap_url_parse( name->bv_val, &ludp ); + if ( rc != LDAP_URL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "%s acl_set_gather: unable to parse URL=\"%s\"\n", + cp->asc_op->o_log_prefix, name->bv_val, 0 ); + + rc = LDAP_PROTOCOL_ERROR; + goto url_done; + } + + if ( ( ludp->lud_host && ludp->lud_host[0] ) || ludp->lud_exts ) + { + /* host part must be empty */ + /* extensions parts must be empty */ + Debug( LDAP_DEBUG_TRACE, + "%s acl_set_gather: host/exts must be absent in URL=\"%s\"\n", + cp->asc_op->o_log_prefix, name->bv_val, 0 ); + + rc = LDAP_PROTOCOL_ERROR; + goto url_done; + } + + /* Grab the searchbase and see if an appropriate database can be found */ + ber_str2bv( ludp->lud_dn, 0, 0, &op2.o_req_dn ); + rc = dnNormalize( 0, NULL, NULL, &op2.o_req_dn, + &op2.o_req_ndn, cp->asc_op->o_tmpmemctx ); + BER_BVZERO( &op2.o_req_dn ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "%s acl_set_gather: DN=\"%s\" normalize failed\n", + cp->asc_op->o_log_prefix, ludp->lud_dn, 0 ); + + goto url_done; + } + + op2.o_bd = select_backend( &op2.o_req_ndn, 1 ); + if ( ( op2.o_bd == NULL ) || ( op2.o_bd->be_search == NULL ) ) { + Debug( LDAP_DEBUG_TRACE, + "%s acl_set_gather: no database could be selected for DN=\"%s\"\n", + cp->asc_op->o_log_prefix, op2.o_req_ndn.bv_val, 0 ); + + rc = LDAP_NO_SUCH_OBJECT; + goto url_done; + } + + /* Grab the filter */ + if ( ludp->lud_filter ) { + ber_str2bv_x( ludp->lud_filter, 0, 0, &op2.ors_filterstr, + cp->asc_op->o_tmpmemctx ); + op2.ors_filter = str2filter_x( cp->asc_op, op2.ors_filterstr.bv_val ); + if ( op2.ors_filter == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "%s acl_set_gather: unable to parse filter=\"%s\"\n", + cp->asc_op->o_log_prefix, op2.ors_filterstr.bv_val, 0 ); + + rc = LDAP_PROTOCOL_ERROR; + goto url_done; + } + + } else { + op2.ors_filterstr = *slap_filterstr_objectClass_pres; + op2.ors_filter = (Filter *)slap_filter_objectClass_pres; + } + + + /* Grab the scope */ + op2.ors_scope = ludp->lud_scope; + + /* Grap the attributes */ + if ( ludp->lud_attrs ) { + int i; + + for ( ; ludp->lud_attrs[ nattrs ]; nattrs++ ) + ; + + anlistp = slap_sl_calloc( sizeof( AttributeName ), nattrs + 2, + cp->asc_op->o_tmpmemctx ); + + for ( i = 0, nattrs = 0; ludp->lud_attrs[ i ]; i++ ) { + struct berval name; + AttributeDescription *desc = NULL; + const char *text = NULL; + + ber_str2bv( ludp->lud_attrs[ i ], 0, 0, &name ); + rc = slap_bv2ad( &name, &desc, &text ); + if ( rc == LDAP_SUCCESS ) { + anlistp[ nattrs ].an_name = name; + anlistp[ nattrs ].an_desc = desc; + nattrs++; + } + } + + } else { + anlistp = anlist; + } + + anlistp[ nattrs ].an_name = desc->ad_cname; + anlistp[ nattrs ].an_desc = desc; + + BER_BVZERO( &anlistp[ nattrs + 1 ].an_name ); + + p.cookie = cookie; + + op2.o_hdr = cp->asc_op->o_hdr; + op2.o_tag = LDAP_REQ_SEARCH; + op2.o_ndn = op2.o_bd->be_rootndn; + op2.o_callback = &cb; + slap_op_time( &op2.o_time, &op2.o_tincr ); + op2.o_do_not_cache = 1; + op2.o_is_auth_check = 0; + ber_dupbv_x( &op2.o_req_dn, &op2.o_req_ndn, cp->asc_op->o_tmpmemctx ); + op2.ors_slimit = SLAP_NO_LIMIT; + op2.ors_tlimit = SLAP_NO_LIMIT; + op2.ors_attrs = anlistp; + op2.ors_attrsonly = 0; + op2.o_private = cp->asc_op->o_private; + op2.o_extra = cp->asc_op->o_extra; + + cb.sc_private = &p; + + rc = op2.o_bd->be_search( &op2, &rs ); + if ( rc != 0 ) { + goto url_done; + } + +url_done:; + if ( op2.ors_filter && op2.ors_filter != slap_filter_objectClass_pres ) { + filter_free_x( cp->asc_op, op2.ors_filter, 1 ); + } + if ( !BER_BVISNULL( &op2.o_req_ndn ) ) { + slap_sl_free( op2.o_req_ndn.bv_val, cp->asc_op->o_tmpmemctx ); + } + if ( !BER_BVISNULL( &op2.o_req_dn ) ) { + slap_sl_free( op2.o_req_dn.bv_val, cp->asc_op->o_tmpmemctx ); + } + if ( ludp ) { + ldap_free_urldesc( ludp ); + } + if ( anlistp && anlistp != anlist ) { + slap_sl_free( anlistp, cp->asc_op->o_tmpmemctx ); + } + + return p.bvals; +} + +BerVarray +acl_set_gather2( SetCookie *cookie, struct berval *name, AttributeDescription *desc ) +{ + AclSetCookie *cp = (AclSetCookie *)cookie; + BerVarray bvals = NULL; + struct berval ndn; + int rc = 0; + + /* this routine needs to return the bervals instead of + * plain strings, since syntax is not known. It should + * also return the syntax or some "comparison cookie". + */ + rc = dnNormalize( 0, NULL, NULL, name, &ndn, cp->asc_op->o_tmpmemctx ); + if ( rc == LDAP_SUCCESS ) { + if ( desc == slap_schema.si_ad_entryDN ) { + bvals = (BerVarray)slap_sl_malloc( sizeof( BerValue ) * 2, + cp->asc_op->o_tmpmemctx ); + bvals[ 0 ] = ndn; + BER_BVZERO( &bvals[ 1 ] ); + BER_BVZERO( &ndn ); + + } else { + backend_attribute( cp->asc_op, + cp->asc_e, &ndn, desc, &bvals, ACL_NONE ); + } + + if ( !BER_BVISNULL( &ndn ) ) { + slap_sl_free( ndn.bv_val, cp->asc_op->o_tmpmemctx ); + } + } + + return bvals; +} + +int +acl_match_set ( + struct berval *subj, + Operation *op, + Entry *e, + struct berval *default_set_attribute ) +{ + struct berval set = BER_BVNULL; + int rc = 0; + AclSetCookie cookie; + + if ( default_set_attribute == NULL ) { + set = *subj; + + } else { + struct berval subjdn, ndn = BER_BVNULL; + struct berval setat; + BerVarray bvals = NULL; + const char *text; + AttributeDescription *desc = NULL; + + /* format of string is "entry/setAttrName" */ + if ( acl_get_part( subj, 0, '/', &subjdn ) < 0 ) { + return 0; + } + + if ( acl_get_part( subj, 1, '/', &setat ) < 0 ) { + setat = *default_set_attribute; + } + + /* + * NOTE: dnNormalize honors the ber_len field + * as the length of the dn to be normalized + */ + if ( slap_bv2ad( &setat, &desc, &text ) == LDAP_SUCCESS ) { + if ( dnNormalize( 0, NULL, NULL, &subjdn, &ndn, op->o_tmpmemctx ) == LDAP_SUCCESS ) + { + backend_attribute( op, e, &ndn, desc, &bvals, ACL_NONE ); + if ( bvals != NULL && !BER_BVISNULL( &bvals[0] ) ) { + int i; + + set = bvals[0]; + BER_BVZERO( &bvals[0] ); + for ( i = 1; !BER_BVISNULL( &bvals[i] ); i++ ) + /* count */ ; + bvals[0].bv_val = bvals[i-1].bv_val; + BER_BVZERO( &bvals[i-1] ); + } + ber_bvarray_free_x( bvals, op->o_tmpmemctx ); + slap_sl_free( ndn.bv_val, op->o_tmpmemctx ); + } + } + } + + if ( !BER_BVISNULL( &set ) ) { + cookie.asc_op = op; + cookie.asc_e = e; + rc = ( slap_set_filter( + acl_set_gather, + (SetCookie *)&cookie, &set, + &op->o_ndn, &e->e_nname, NULL ) > 0 ); + if ( set.bv_val != subj->bv_val ) { + slap_sl_free( set.bv_val, op->o_tmpmemctx ); + } + } + + return(rc); +} + +#ifdef SLAP_DYNACL + +/* + * dynamic ACL infrastructure + */ +static slap_dynacl_t *da_list = NULL; + +int +slap_dynacl_register( slap_dynacl_t *da ) +{ + slap_dynacl_t *tmp; + + for ( tmp = da_list; tmp; tmp = tmp->da_next ) { + if ( strcasecmp( da->da_name, tmp->da_name ) == 0 ) { + break; + } + } + + if ( tmp != NULL ) { + return -1; + } + + if ( da->da_mask == NULL ) { + return -1; + } + + da->da_private = NULL; + da->da_next = da_list; + da_list = da; + + return 0; +} + +static slap_dynacl_t * +slap_dynacl_next( slap_dynacl_t *da ) +{ + if ( da ) { + return da->da_next; + } + return da_list; +} + +slap_dynacl_t * +slap_dynacl_get( const char *name ) +{ + slap_dynacl_t *da; + + for ( da = slap_dynacl_next( NULL ); da; da = slap_dynacl_next( da ) ) { + if ( strcasecmp( da->da_name, name ) == 0 ) { + break; + } + } + + return da; +} +#endif /* SLAP_DYNACL */ + +/* + * statically built-in dynamic ACL initialization + */ +static int (*acl_init_func[])( void ) = { +#ifdef SLAP_DYNACL + /* TODO: remove when ACI will only be dynamic */ +#if SLAPD_ACI_ENABLED == SLAPD_MOD_STATIC + dynacl_aci_init, +#endif /* SLAPD_ACI_ENABLED */ +#endif /* SLAP_DYNACL */ + + NULL +}; + +int +acl_init( void ) +{ + int i, rc; + + for ( i = 0; acl_init_func[ i ] != NULL; i++ ) { + rc = (*(acl_init_func[ i ]))(); + if ( rc != 0 ) { + return rc; + } + } + + return 0; +} + +int +acl_string_expand( + struct berval *bv, + struct berval *pat, + struct berval *dn_matches, + struct berval *val_matches, + AclRegexMatches *matches) +{ + ber_len_t size; + char *sp; + char *dp; + int flag; + enum { DN_FLAG, VAL_FLAG } tflag; + + size = 0; + bv->bv_val[0] = '\0'; + bv->bv_len--; /* leave space for lone $ */ + + flag = 0; + tflag = DN_FLAG; + for ( dp = bv->bv_val, sp = pat->bv_val; size < bv->bv_len && + sp < pat->bv_val + pat->bv_len ; sp++ ) + { + /* did we previously see a $ */ + if ( flag ) { + if ( flag == 1 && *sp == '$' ) { + *dp++ = '$'; + size++; + flag = 0; + tflag = DN_FLAG; + + } else if ( flag == 2 && *sp == 'v' /*'}'*/) { + tflag = VAL_FLAG; + + } else if ( flag == 2 && *sp == 'd' /*'}'*/) { + tflag = DN_FLAG; + + } else if ( flag == 1 && *sp == '{' /*'}'*/) { + flag = 2; + + } else if ( *sp >= '0' && *sp <= '9' ) { + int nm; + regmatch_t *m; + char *data; + int n; + int i; + int l; + + n = *sp - '0'; + + if ( flag == 2 ) { + for ( sp++; *sp != '\0' && *sp != /*'{'*/ '}'; sp++ ) { + if ( *sp >= '0' && *sp <= '9' ) { + n = 10*n + ( *sp - '0' ); + } + } + + if ( *sp != /*'{'*/ '}' ) { + /* FIXME: error */ + return 1; + } + } + + switch (tflag) { + case DN_FLAG: + nm = matches->dn_count; + m = matches->dn_data; + data = dn_matches ? dn_matches->bv_val : NULL; + break; + case VAL_FLAG: + nm = matches->val_count; + m = matches->val_data; + data = val_matches ? val_matches->bv_val : NULL; + break; + default: + assert( 0 ); + } + if ( n >= nm ) { + /* FIXME: error */ + return 1; + } + if ( data == NULL ) { + /* FIXME: error */ + return 1; + } + + *dp = '\0'; + i = m[n].rm_so; + l = m[n].rm_eo; + + for ( ; size < bv->bv_len && i < l; size++, i++ ) { + *dp++ = data[i]; + } + *dp = '\0'; + + flag = 0; + tflag = DN_FLAG; + } + } else { + if (*sp == '$') { + flag = 1; + } else { + *dp++ = *sp; + size++; + } + } + } + + if ( flag ) { + /* must have ended with a single $ */ + *dp++ = '$'; + size++; + } + + *dp = '\0'; + bv->bv_len = size; + + Debug( LDAP_DEBUG_ACL, "=> acl_string_expand: pattern: %.*s\n", (int)pat->bv_len, pat->bv_val, 0 ); + Debug( LDAP_DEBUG_ACL, "=> acl_string_expand: expanded: %s\n", bv->bv_val, 0, 0 ); + + return 0; +} + +static int +regex_matches( + struct berval *pat, /* pattern to expand and match against */ + char *str, /* string to match against pattern */ + struct berval *dn_matches, /* buffer with $N expansion variables from DN */ + struct berval *val_matches, /* buffer with $N expansion variables from val */ + AclRegexMatches *matches /* offsets in buffer for $N expansion variables */ +) +{ + regex_t re; + char newbuf[ACL_BUF_SIZE]; + struct berval bv; + int rc; + + bv.bv_len = sizeof( newbuf ) - 1; + bv.bv_val = newbuf; + + if (str == NULL) { + str = ""; + }; + + if ( acl_string_expand( &bv, pat, dn_matches, val_matches, matches )) { + Debug( LDAP_DEBUG_TRACE, + "expand( \"%s\", \"%s\") failed\n", + pat->bv_val, str, 0 ); + return( 0 ); + } + rc = regcomp( &re, newbuf, REG_EXTENDED|REG_ICASE ); + if ( rc ) { + char error[ACL_BUF_SIZE]; + regerror( rc, &re, error, sizeof( error ) ); + + Debug( LDAP_DEBUG_TRACE, + "compile( \"%s\", \"%s\") failed %s\n", + pat->bv_val, str, error ); + return( 0 ); + } + + rc = regexec( &re, str, 0, NULL, 0 ); + regfree( &re ); + + Debug( LDAP_DEBUG_TRACE, + "=> regex_matches: string: %s\n", str, 0, 0 ); + Debug( LDAP_DEBUG_TRACE, + "=> regex_matches: rc: %d %s\n", + rc, !rc ? "matches" : "no matches", 0 ); + return( !rc ); +} + diff --git a/servers/slapd/aclparse.c b/servers/slapd/aclparse.c new file mode 100644 index 0000000..abb6ae0 --- /dev/null +++ b/servers/slapd/aclparse.c @@ -0,0 +1,2869 @@ +/* aclparse.c - routines to parse and check acl's */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/regex.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/unistd.h> + +#include "slap.h" +#include "lber_pvt.h" +#include "lutil.h" + +static const char style_base[] = "base"; +const char *style_strings[] = { + "regex", + "expand", + "exact", + "one", + "subtree", + "children", + "level", + "attrof", + "anonymous", + "users", + "self", + "ip", + "ipv6", + "path", + NULL +}; + +#define ACLBUF_CHUNKSIZE 8192 +static struct berval aclbuf; + +static void split(char *line, int splitchar, char **left, char **right); +static void access_append(Access **l, Access *a); +static void access_free( Access *a ); +static int acl_usage(void); + +static void acl_regex_normalized_dn(const char *src, struct berval *pat); + +#ifdef LDAP_DEBUG +static void print_acl(Backend *be, AccessControl *a); +#endif + +static int check_scope( BackendDB *be, AccessControl *a ); + +#ifdef SLAP_DYNACL +static int +slap_dynacl_config( + const char *fname, + int lineno, + Access *b, + const char *name, + const char *opts, + slap_style_t sty, + const char *right ) +{ + slap_dynacl_t *da, *tmp; + int rc = 0; + + for ( da = b->a_dynacl; da; da = da->da_next ) { + if ( strcasecmp( da->da_name, name ) == 0 ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: dynacl \"%s\" already specified.\n", + fname, lineno, name ); + return acl_usage(); + } + } + + da = slap_dynacl_get( name ); + if ( da == NULL ) { + return -1; + } + + tmp = ch_malloc( sizeof( slap_dynacl_t ) ); + *tmp = *da; + + if ( tmp->da_parse ) { + rc = ( *tmp->da_parse )( fname, lineno, opts, sty, right, &tmp->da_private ); + if ( rc ) { + ch_free( tmp ); + return rc; + } + } + + tmp->da_next = b->a_dynacl; + b->a_dynacl = tmp; + + return 0; +} +#endif /* SLAP_DYNACL */ + +static void +regtest(const char *fname, int lineno, char *pat) { + int e; + regex_t re; + + char buf[ SLAP_TEXT_BUFLEN ]; + unsigned size; + + char *sp; + char *dp; + int flag; + + sp = pat; + dp = buf; + size = 0; + buf[0] = '\0'; + + for (size = 0, flag = 0; (size < sizeof(buf)) && *sp; sp++) { + if (flag) { + if (*sp == '$'|| (*sp >= '0' && *sp <= '9')) { + *dp++ = *sp; + size++; + } + flag = 0; + + } else { + if (*sp == '$') { + flag = 1; + } else { + *dp++ = *sp; + size++; + } + } + } + + *dp = '\0'; + if ( size >= (sizeof(buf) - 1) ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: regular expression \"%s\" too large\n", + fname, lineno, pat ); + (void)acl_usage(); + exit( EXIT_FAILURE ); + } + + if ((e = regcomp(&re, buf, REG_EXTENDED|REG_ICASE))) { + char error[ SLAP_TEXT_BUFLEN ]; + + regerror(e, &re, error, sizeof(error)); + + snprintf( buf, sizeof( buf ), + "regular expression \"%s\" bad because of %s", + pat, error ); + Debug( LDAP_DEBUG_ANY, + "%s: line %d: %s\n", + fname, lineno, buf ); + acl_usage(); + exit( EXIT_FAILURE ); + } + regfree(&re); +} + +/* + * Experimental + * + * Check if the pattern of an ACL, if any, matches the scope + * of the backend it is defined within. + */ +#define ACL_SCOPE_UNKNOWN (-2) +#define ACL_SCOPE_ERR (-1) +#define ACL_SCOPE_OK (0) +#define ACL_SCOPE_PARTIAL (1) +#define ACL_SCOPE_WARN (2) + +static int +check_scope( BackendDB *be, AccessControl *a ) +{ + ber_len_t patlen; + struct berval dn; + + dn = be->be_nsuffix[0]; + + if ( BER_BVISEMPTY( &dn ) ) { + return ACL_SCOPE_OK; + } + + if ( !BER_BVISEMPTY( &a->acl_dn_pat ) || + a->acl_dn_style != ACL_STYLE_REGEX ) + { + slap_style_t style = a->acl_dn_style; + + if ( style == ACL_STYLE_REGEX ) { + char dnbuf[SLAP_LDAPDN_MAXLEN + 2]; + char rebuf[SLAP_LDAPDN_MAXLEN + 1]; + ber_len_t rebuflen; + regex_t re; + int rc; + + /* add trailing '$' to database suffix to form + * a simple trial regex pattern "<suffix>$" */ + AC_MEMCPY( dnbuf, be->be_nsuffix[0].bv_val, + be->be_nsuffix[0].bv_len ); + dnbuf[be->be_nsuffix[0].bv_len] = '$'; + dnbuf[be->be_nsuffix[0].bv_len + 1] = '\0'; + + if ( regcomp( &re, dnbuf, REG_EXTENDED|REG_ICASE ) ) { + return ACL_SCOPE_WARN; + } + + /* remove trailing ')$', if any, from original + * regex pattern */ + rebuflen = a->acl_dn_pat.bv_len; + AC_MEMCPY( rebuf, a->acl_dn_pat.bv_val, rebuflen + 1 ); + if ( rebuf[rebuflen - 1] == '$' ) { + rebuf[--rebuflen] = '\0'; + } + while ( rebuflen > be->be_nsuffix[0].bv_len && rebuf[rebuflen - 1] == ')' ) { + rebuf[--rebuflen] = '\0'; + } + if ( rebuflen == be->be_nsuffix[0].bv_len ) { + rc = ACL_SCOPE_WARN; + goto regex_done; + } + + /* not a clear indication of scoping error, though */ + rc = regexec( &re, rebuf, 0, NULL, 0 ) + ? ACL_SCOPE_WARN : ACL_SCOPE_OK; + +regex_done:; + regfree( &re ); + return rc; + } + + patlen = a->acl_dn_pat.bv_len; + /* If backend suffix is longer than pattern, + * it is a potential mismatch (in the sense + * that a superior naming context could + * match */ + if ( dn.bv_len > patlen ) { + /* base is blatantly wrong */ + if ( style == ACL_STYLE_BASE ) return ACL_SCOPE_ERR; + + /* a style of one can be wrong if there is + * more than one level between the suffix + * and the pattern */ + if ( style == ACL_STYLE_ONE ) { + ber_len_t rdnlen = 0; + int sep = 0; + + if ( patlen > 0 ) { + if ( !DN_SEPARATOR( dn.bv_val[dn.bv_len - patlen - 1] )) { + return ACL_SCOPE_ERR; + } + sep = 1; + } + + rdnlen = dn_rdnlen( NULL, &dn ); + if ( rdnlen != dn.bv_len - patlen - sep ) + return ACL_SCOPE_ERR; + } + + /* if the trailing part doesn't match, + * then it's an error */ + if ( strcmp( a->acl_dn_pat.bv_val, + &dn.bv_val[dn.bv_len - patlen] ) != 0 ) + { + return ACL_SCOPE_ERR; + } + + return ACL_SCOPE_PARTIAL; + } + + switch ( style ) { + case ACL_STYLE_BASE: + case ACL_STYLE_ONE: + case ACL_STYLE_CHILDREN: + case ACL_STYLE_SUBTREE: + break; + + default: + assert( 0 ); + break; + } + + if ( dn.bv_len < patlen && + !DN_SEPARATOR( a->acl_dn_pat.bv_val[patlen - dn.bv_len - 1] )) + { + return ACL_SCOPE_ERR; + } + + if ( strcmp( &a->acl_dn_pat.bv_val[patlen - dn.bv_len], dn.bv_val ) + != 0 ) + { + return ACL_SCOPE_ERR; + } + + return ACL_SCOPE_OK; + } + + return ACL_SCOPE_UNKNOWN; +} + +int +parse_acl( + Backend *be, + const char *fname, + int lineno, + int argc, + char **argv, + int pos ) +{ + int i; + char *left, *right, *style; + struct berval bv; + AccessControl *a = NULL; + Access *b = NULL; + int rc; + const char *text; + + for ( i = 1; i < argc; i++ ) { + /* to clause - select which entries are protected */ + if ( strcasecmp( argv[i], "to" ) == 0 ) { + if ( a != NULL ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "only one to clause allowed in access line\n", + fname, lineno, 0 ); + goto fail; + } + a = (AccessControl *) ch_calloc( 1, sizeof(AccessControl) ); + a->acl_attrval_style = ACL_STYLE_NONE; + for ( ++i; i < argc; i++ ) { + if ( strcasecmp( argv[i], "by" ) == 0 ) { + i--; + break; + } + + if ( strcasecmp( argv[i], "*" ) == 0 ) { + if ( !BER_BVISEMPTY( &a->acl_dn_pat ) || + a->acl_dn_style != ACL_STYLE_REGEX ) + { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: dn pattern" + " already specified in to clause.\n", + fname, lineno, 0 ); + goto fail; + } + + ber_str2bv( "*", STRLENOF( "*" ), 1, &a->acl_dn_pat ); + continue; + } + + split( argv[i], '=', &left, &right ); + split( left, '.', &left, &style ); + + if ( right == NULL ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "missing \"=\" in \"%s\" in to clause\n", + fname, lineno, left ); + goto fail; + } + + if ( strcasecmp( left, "dn" ) == 0 ) { + if ( !BER_BVISEMPTY( &a->acl_dn_pat ) || + a->acl_dn_style != ACL_STYLE_REGEX ) + { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: dn pattern" + " already specified in to clause.\n", + fname, lineno, 0 ); + goto fail; + } + + if ( style == NULL || *style == '\0' || + strcasecmp( style, "baseObject" ) == 0 || + strcasecmp( style, "base" ) == 0 || + strcasecmp( style, "exact" ) == 0 ) + { + a->acl_dn_style = ACL_STYLE_BASE; + ber_str2bv( right, 0, 1, &a->acl_dn_pat ); + + } else if ( strcasecmp( style, "oneLevel" ) == 0 || + strcasecmp( style, "one" ) == 0 ) + { + a->acl_dn_style = ACL_STYLE_ONE; + ber_str2bv( right, 0, 1, &a->acl_dn_pat ); + + } else if ( strcasecmp( style, "subtree" ) == 0 || + strcasecmp( style, "sub" ) == 0 ) + { + if( *right == '\0' ) { + ber_str2bv( "*", STRLENOF( "*" ), 1, &a->acl_dn_pat ); + + } else { + a->acl_dn_style = ACL_STYLE_SUBTREE; + ber_str2bv( right, 0, 1, &a->acl_dn_pat ); + } + + } else if ( strcasecmp( style, "children" ) == 0 ) { + a->acl_dn_style = ACL_STYLE_CHILDREN; + ber_str2bv( right, 0, 1, &a->acl_dn_pat ); + + } else if ( strcasecmp( style, "regex" ) == 0 ) { + a->acl_dn_style = ACL_STYLE_REGEX; + + if ( *right == '\0' ) { + /* empty regex should match empty DN */ + a->acl_dn_style = ACL_STYLE_BASE; + ber_str2bv( right, 0, 1, &a->acl_dn_pat ); + + } else if ( strcmp(right, "*") == 0 + || strcmp(right, ".*") == 0 + || strcmp(right, ".*$") == 0 + || strcmp(right, "^.*") == 0 + || strcmp(right, "^.*$") == 0 + || strcmp(right, ".*$$") == 0 + || strcmp(right, "^.*$$") == 0 ) + { + ber_str2bv( "*", STRLENOF("*"), 1, &a->acl_dn_pat ); + + } else { + acl_regex_normalized_dn( right, &a->acl_dn_pat ); + } + + } else { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "unknown dn style \"%s\" in to clause\n", + fname, lineno, style ); + goto fail; + } + + continue; + } + + if ( strcasecmp( left, "filter" ) == 0 ) { + if ( (a->acl_filter = str2filter( right )) == NULL ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: bad filter \"%s\" in to clause\n", + fname, lineno, right ); + goto fail; + } + + } else if ( strcasecmp( left, "attr" ) == 0 /* TOLERATED */ + || strcasecmp( left, "attrs" ) == 0 ) /* DOCUMENTED */ + { + if ( strcasecmp( left, "attr" ) == 0 ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: \"attr\" " + "is deprecated (and undocumented); " + "use \"attrs\" instead.\n", + fname, lineno, 0 ); + } + + a->acl_attrs = str2anlist( a->acl_attrs, + right, "," ); + if ( a->acl_attrs == NULL ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: unknown attr \"%s\" in to clause\n", + fname, lineno, right ); + goto fail; + } + + } else if ( strncasecmp( left, "val", 3 ) == 0 ) { + struct berval bv; + char *mr; + + if ( !BER_BVISEMPTY( &a->acl_attrval ) ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: attr val already specified in to clause.\n", + fname, lineno, 0 ); + goto fail; + } + if ( a->acl_attrs == NULL || !BER_BVISEMPTY( &a->acl_attrs[1].an_name ) ) + { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: attr val requires a single attribute.\n", + fname, lineno, 0 ); + goto fail; + } + + ber_str2bv( right, 0, 0, &bv ); + a->acl_attrval_style = ACL_STYLE_BASE; + + mr = strchr( left, '/' ); + if ( mr != NULL ) { + mr[ 0 ] = '\0'; + mr++; + + a->acl_attrval_mr = mr_find( mr ); + if ( a->acl_attrval_mr == NULL ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "invalid matching rule \"%s\".\n", + fname, lineno, mr ); + goto fail; + } + + if( !mr_usable_with_at( a->acl_attrval_mr, a->acl_attrs[ 0 ].an_desc->ad_type ) ) + { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "matching rule \"%s\" use " + "with attr \"%s\" not appropriate.", + mr, a->acl_attrs[ 0 ].an_name.bv_val ); + + + Debug( LDAP_DEBUG_ANY, "%s: line %d: %s\n", + fname, lineno, buf ); + goto fail; + } + } + + if ( style != NULL ) { + if ( strcasecmp( style, "regex" ) == 0 ) { + int e = regcomp( &a->acl_attrval_re, bv.bv_val, + REG_EXTENDED | REG_ICASE ); + if ( e ) { + char err[SLAP_TEXT_BUFLEN], + buf[ SLAP_TEXT_BUFLEN ]; + + regerror( e, &a->acl_attrval_re, err, sizeof( err ) ); + + snprintf( buf, sizeof( buf ), + "regular expression \"%s\" bad because of %s", + right, err ); + + Debug( LDAP_DEBUG_ANY, "%s: line %d: %s\n", + fname, lineno, buf ); + goto fail; + } + a->acl_attrval_style = ACL_STYLE_REGEX; + + } else { + /* FIXME: if the attribute has DN syntax, we might + * allow one, subtree and children styles as well */ + if ( !strcasecmp( style, "base" ) || + !strcasecmp( style, "exact" ) ) { + a->acl_attrval_style = ACL_STYLE_BASE; + + } else if ( a->acl_attrs[0].an_desc->ad_type-> + sat_syntax == slap_schema.si_syn_distinguishedName ) + { + if ( !strcasecmp( style, "baseObject" ) || + !strcasecmp( style, "base" ) ) + { + a->acl_attrval_style = ACL_STYLE_BASE; + } else if ( !strcasecmp( style, "onelevel" ) || + !strcasecmp( style, "one" ) ) + { + a->acl_attrval_style = ACL_STYLE_ONE; + } else if ( !strcasecmp( style, "subtree" ) || + !strcasecmp( style, "sub" ) ) + { + a->acl_attrval_style = ACL_STYLE_SUBTREE; + } else if ( !strcasecmp( style, "children" ) ) { + a->acl_attrval_style = ACL_STYLE_CHILDREN; + } else { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "unknown val.<style> \"%s\" for attributeType \"%s\" " + "with DN syntax.", + style, + a->acl_attrs[0].an_desc->ad_cname.bv_val ); + + Debug( LDAP_DEBUG_CONFIG | LDAP_DEBUG_ACL, + "%s: line %d: %s\n", + fname, lineno, buf ); + goto fail; + } + + rc = dnNormalize( 0, NULL, NULL, &bv, &a->acl_attrval, NULL ); + if ( rc != LDAP_SUCCESS ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "unable to normalize DN \"%s\" " + "for attributeType \"%s\" (%d).", + bv.bv_val, + a->acl_attrs[0].an_desc->ad_cname.bv_val, + rc ); + Debug( LDAP_DEBUG_ANY, + "%s: line %d: %s\n", + fname, lineno, buf ); + goto fail; + } + + } else { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "unknown val.<style> \"%s\" for attributeType \"%s\".", + style, a->acl_attrs[0].an_desc->ad_cname.bv_val ); + Debug( LDAP_DEBUG_CONFIG | LDAP_DEBUG_ACL, + "%s: line %d: %s\n", + fname, lineno, buf ); + goto fail; + } + } + } + + /* Check for appropriate matching rule */ + if ( a->acl_attrval_style == ACL_STYLE_REGEX ) { + ber_dupbv( &a->acl_attrval, &bv ); + + } else if ( BER_BVISNULL( &a->acl_attrval ) ) { + int rc; + const char *text; + + if ( a->acl_attrval_mr == NULL ) { + a->acl_attrval_mr = a->acl_attrs[ 0 ].an_desc->ad_type->sat_equality; + } + + if ( a->acl_attrval_mr == NULL ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "attr \"%s\" does not have an EQUALITY matching rule.\n", + fname, lineno, a->acl_attrs[ 0 ].an_name.bv_val ); + goto fail; + } + + rc = asserted_value_validate_normalize( + a->acl_attrs[ 0 ].an_desc, + a->acl_attrval_mr, + SLAP_MR_EQUALITY|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX, + &bv, + &a->acl_attrval, + &text, + NULL ); + if ( rc != LDAP_SUCCESS ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), "%s: line %d: " + " attr \"%s\" normalization failed (%d: %s)", + fname, lineno, + a->acl_attrs[ 0 ].an_name.bv_val, rc, text ); + Debug( LDAP_DEBUG_ANY, "%s: line %d: %s.\n", + fname, lineno, buf ); + goto fail; + } + } + + } else { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: expecting <what> got \"%s\"\n", + fname, lineno, left ); + goto fail; + } + } + + if ( !BER_BVISNULL( &a->acl_dn_pat ) && + ber_bvccmp( &a->acl_dn_pat, '*' ) ) + { + free( a->acl_dn_pat.bv_val ); + BER_BVZERO( &a->acl_dn_pat ); + a->acl_dn_style = ACL_STYLE_REGEX; + } + + if ( !BER_BVISEMPTY( &a->acl_dn_pat ) || + a->acl_dn_style != ACL_STYLE_REGEX ) + { + if ( a->acl_dn_style != ACL_STYLE_REGEX ) { + struct berval bv; + rc = dnNormalize( 0, NULL, NULL, &a->acl_dn_pat, &bv, NULL); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: bad DN \"%s\" in to DN clause\n", + fname, lineno, a->acl_dn_pat.bv_val ); + goto fail; + } + free( a->acl_dn_pat.bv_val ); + a->acl_dn_pat = bv; + + } else { + int e = regcomp( &a->acl_dn_re, a->acl_dn_pat.bv_val, + REG_EXTENDED | REG_ICASE ); + if ( e ) { + char err[ SLAP_TEXT_BUFLEN ], + buf[ SLAP_TEXT_BUFLEN ]; + + regerror( e, &a->acl_dn_re, err, sizeof( err ) ); + snprintf( buf, sizeof( buf ), + "regular expression \"%s\" bad because of %s", + right, err ); + Debug( LDAP_DEBUG_ANY, "%s: line %d: %s\n", + fname, lineno, buf ); + goto fail; + } + } + } + + /* by clause - select who has what access to entries */ + } else if ( strcasecmp( argv[i], "by" ) == 0 ) { + if ( a == NULL ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "to clause required before by clause in access line\n", + fname, lineno, 0 ); + goto fail; + } + + /* + * by clause consists of <who> and <access> + */ + + if ( ++i == argc ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: premature EOL: expecting <who>\n", + fname, lineno, 0 ); + goto fail; + } + + b = (Access *) ch_calloc( 1, sizeof(Access) ); + + ACL_INVALIDATE( b->a_access_mask ); + + /* get <who> */ + for ( ; i < argc; i++ ) { + slap_style_t sty = ACL_STYLE_REGEX; + char *style_modifier = NULL; + char *style_level = NULL; + int level = 0; + int expand = 0; + slap_dn_access *bdn = &b->a_dn; + int is_realdn = 0; + + split( argv[i], '=', &left, &right ); + split( left, '.', &left, &style ); + if ( style ) { + split( style, ',', &style, &style_modifier ); + + if ( strncasecmp( style, "level", STRLENOF( "level" ) ) == 0 ) { + split( style, '{', &style, &style_level ); + if ( style_level != NULL ) { + char *p = strchr( style_level, '}' ); + if ( p == NULL ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: premature eol: " + "expecting closing '}' in \"level{n}\"\n", + fname, lineno, 0 ); + goto fail; + } else if ( p == style_level ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: empty level " + "in \"level{n}\"\n", + fname, lineno, 0 ); + goto fail; + } + p[0] = '\0'; + } + } + } + + if ( style == NULL || *style == '\0' || + strcasecmp( style, "exact" ) == 0 || + strcasecmp( style, "baseObject" ) == 0 || + strcasecmp( style, "base" ) == 0 ) + { + sty = ACL_STYLE_BASE; + + } else if ( strcasecmp( style, "onelevel" ) == 0 || + strcasecmp( style, "one" ) == 0 ) + { + sty = ACL_STYLE_ONE; + + } else if ( strcasecmp( style, "subtree" ) == 0 || + strcasecmp( style, "sub" ) == 0 ) + { + sty = ACL_STYLE_SUBTREE; + + } else if ( strcasecmp( style, "children" ) == 0 ) { + sty = ACL_STYLE_CHILDREN; + + } else if ( strcasecmp( style, "level" ) == 0 ) + { + if ( lutil_atoi( &level, style_level ) != 0 ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: unable to parse level " + "in \"level{n}\"\n", + fname, lineno, 0 ); + goto fail; + } + + sty = ACL_STYLE_LEVEL; + + } else if ( strcasecmp( style, "regex" ) == 0 ) { + sty = ACL_STYLE_REGEX; + + } else if ( strcasecmp( style, "expand" ) == 0 ) { + sty = ACL_STYLE_EXPAND; + + } else if ( strcasecmp( style, "ip" ) == 0 ) { + sty = ACL_STYLE_IP; + + } else if ( strcasecmp( style, "ipv6" ) == 0 ) { +#ifndef LDAP_PF_INET6 + Debug( LDAP_DEBUG_ANY, + "%s: line %d: IPv6 not supported\n", + fname, lineno, 0 ); +#endif /* ! LDAP_PF_INET6 */ + sty = ACL_STYLE_IPV6; + + } else if ( strcasecmp( style, "path" ) == 0 ) { + sty = ACL_STYLE_PATH; +#ifndef LDAP_PF_LOCAL + Debug( LDAP_DEBUG_CONFIG | LDAP_DEBUG_ACL, + "%s: line %d: " + "\"path\" style modifier is useless without local.\n", + fname, lineno, 0 ); + goto fail; +#endif /* LDAP_PF_LOCAL */ + + } else { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: unknown style \"%s\" in by clause\n", + fname, lineno, style ); + goto fail; + } + + if ( style_modifier && + strcasecmp( style_modifier, "expand" ) == 0 ) + { + switch ( sty ) { + case ACL_STYLE_REGEX: + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "\"regex\" style implies \"expand\" modifier.\n", + fname, lineno, 0 ); + goto fail; + break; + + case ACL_STYLE_EXPAND: + break; + + default: + /* we'll see later if it's pertinent */ + expand = 1; + break; + } + } + + if ( strncasecmp( left, "real", STRLENOF( "real" ) ) == 0 ) { + is_realdn = 1; + bdn = &b->a_realdn; + left += STRLENOF( "real" ); + } + + if ( strcasecmp( left, "*" ) == 0 ) { + if ( is_realdn ) { + goto fail; + } + + ber_str2bv( "*", STRLENOF( "*" ), 1, &bv ); + sty = ACL_STYLE_REGEX; + + } else if ( strcasecmp( left, "anonymous" ) == 0 ) { + ber_str2bv("anonymous", STRLENOF( "anonymous" ), 1, &bv); + sty = ACL_STYLE_ANONYMOUS; + + } else if ( strcasecmp( left, "users" ) == 0 ) { + ber_str2bv("users", STRLENOF( "users" ), 1, &bv); + sty = ACL_STYLE_USERS; + + } else if ( strcasecmp( left, "self" ) == 0 ) { + ber_str2bv("self", STRLENOF( "self" ), 1, &bv); + sty = ACL_STYLE_SELF; + + } else if ( strcasecmp( left, "dn" ) == 0 ) { + if ( sty == ACL_STYLE_REGEX ) { + bdn->a_style = ACL_STYLE_REGEX; + if ( right == NULL ) { + /* no '=' */ + ber_str2bv("users", + STRLENOF( "users" ), + 1, &bv); + bdn->a_style = ACL_STYLE_USERS; + + } else if (*right == '\0' ) { + /* dn="" */ + ber_str2bv("anonymous", + STRLENOF( "anonymous" ), + 1, &bv); + bdn->a_style = ACL_STYLE_ANONYMOUS; + + } else if ( strcmp( right, "*" ) == 0 ) { + /* dn=* */ + /* any or users? users for now */ + ber_str2bv("users", + STRLENOF( "users" ), + 1, &bv); + bdn->a_style = ACL_STYLE_USERS; + + } else if ( strcmp( right, ".+" ) == 0 + || strcmp( right, "^.+" ) == 0 + || strcmp( right, ".+$" ) == 0 + || strcmp( right, "^.+$" ) == 0 + || strcmp( right, ".+$$" ) == 0 + || strcmp( right, "^.+$$" ) == 0 ) + { + ber_str2bv("users", + STRLENOF( "users" ), + 1, &bv); + bdn->a_style = ACL_STYLE_USERS; + + } else if ( strcmp( right, ".*" ) == 0 + || strcmp( right, "^.*" ) == 0 + || strcmp( right, ".*$" ) == 0 + || strcmp( right, "^.*$" ) == 0 + || strcmp( right, ".*$$" ) == 0 + || strcmp( right, "^.*$$" ) == 0 ) + { + ber_str2bv("*", + STRLENOF( "*" ), + 1, &bv); + + } else { + acl_regex_normalized_dn( right, &bv ); + if ( !ber_bvccmp( &bv, '*' ) ) { + regtest( fname, lineno, bv.bv_val ); + } + } + + } else if ( right == NULL || *right == '\0' ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "missing \"=\" in (or value after) \"%s\" " + "in by clause\n", + fname, lineno, left ); + goto fail; + + } else { + ber_str2bv( right, 0, 1, &bv ); + } + + } else { + BER_BVZERO( &bv ); + } + + if ( !BER_BVISNULL( &bv ) ) { + if ( !BER_BVISEMPTY( &bdn->a_pat ) ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: dn pattern already specified.\n", + fname, lineno, 0 ); + goto fail; + } + + if ( sty != ACL_STYLE_REGEX && + sty != ACL_STYLE_ANONYMOUS && + sty != ACL_STYLE_USERS && + sty != ACL_STYLE_SELF && + expand == 0 ) + { + rc = dnNormalize(0, NULL, NULL, + &bv, &bdn->a_pat, NULL); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: bad DN \"%s\" in by DN clause\n", + fname, lineno, bv.bv_val ); + goto fail; + } + free( bv.bv_val ); + if ( sty == ACL_STYLE_BASE + && be != NULL + && !BER_BVISNULL( &be->be_rootndn ) + && dn_match( &bdn->a_pat, &be->be_rootndn ) ) + { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: rootdn is always granted " + "unlimited privileges.\n", + fname, lineno, 0 ); + } + + } else { + bdn->a_pat = bv; + } + bdn->a_style = sty; + if ( expand ) { + char *exp; + int gotit = 0; + + for ( exp = strchr( bdn->a_pat.bv_val, '$' ); + exp && (ber_len_t)(exp - bdn->a_pat.bv_val) + < bdn->a_pat.bv_len; + exp = strchr( exp, '$' ) ) + { + if ( ( isdigit( (unsigned char) exp[ 1 ] ) || + exp[ 1 ] == '{' ) ) { + gotit = 1; + break; + } + } + + if ( gotit == 1 ) { + bdn->a_expand = expand; + + } else { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "\"expand\" used with no expansions in \"pattern\".\n", + fname, lineno, 0 ); + goto fail; + } + } + if ( sty == ACL_STYLE_SELF ) { + bdn->a_self_level = level; + + } else { + if ( level < 0 ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: bad negative level \"%d\" " + "in by DN clause\n", + fname, lineno, level ); + goto fail; + } else if ( level == 1 ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: \"onelevel\" should be used " + "instead of \"level{1}\" in by DN clause\n", + fname, lineno, 0 ); + } else if ( level == 0 && sty == ACL_STYLE_LEVEL ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: \"base\" should be used " + "instead of \"level{0}\" in by DN clause\n", + fname, lineno, 0 ); + } + + bdn->a_level = level; + } + continue; + } + + if ( strcasecmp( left, "dnattr" ) == 0 ) { + if ( right == NULL || right[0] == '\0' ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "missing \"=\" in (or value after) \"%s\" " + "in by clause\n", + fname, lineno, left ); + goto fail; + } + + if( bdn->a_at != NULL ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: dnattr already specified.\n", + fname, lineno, 0 ); + goto fail; + } + + rc = slap_str2ad( right, &bdn->a_at, &text ); + + if( rc != LDAP_SUCCESS ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "dnattr \"%s\": %s", + right, text ); + Debug( LDAP_DEBUG_ANY, + "%s: line %d: %s\n", + fname, lineno, buf ); + goto fail; + } + + + if( !is_at_syntax( bdn->a_at->ad_type, + SLAPD_DN_SYNTAX ) && + !is_at_syntax( bdn->a_at->ad_type, + SLAPD_NAMEUID_SYNTAX )) + { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "dnattr \"%s\": " + "inappropriate syntax: %s\n", + right, + bdn->a_at->ad_type->sat_syntax_oid ); + Debug( LDAP_DEBUG_ANY, + "%s: line %d: %s\n", + fname, lineno, buf ); + goto fail; + } + + if( bdn->a_at->ad_type->sat_equality == NULL ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: dnattr \"%s\": " + "inappropriate matching (no EQUALITY)\n", + fname, lineno, right ); + goto fail; + } + + continue; + } + + if ( strncasecmp( left, "group", STRLENOF( "group" ) ) == 0 ) { + char *name = NULL; + char *value = NULL; + char *attr_name = SLAPD_GROUP_ATTR; + + switch ( sty ) { + case ACL_STYLE_REGEX: + /* legacy, tolerated */ + Debug( LDAP_DEBUG_CONFIG | LDAP_DEBUG_ACL, + "%s: line %d: " + "deprecated group style \"regex\"; " + "use \"expand\" instead.\n", + fname, lineno, 0 ); + sty = ACL_STYLE_EXPAND; + break; + + case ACL_STYLE_BASE: + /* legal, traditional */ + case ACL_STYLE_EXPAND: + /* legal, substring expansion; supersedes regex */ + break; + + default: + /* unknown */ + Debug( LDAP_DEBUG_ANY, + "%s: line %d: " + "inappropriate style \"%s\" in by clause.\n", + fname, lineno, style ); + goto fail; + } + + if ( right == NULL || right[0] == '\0' ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: " + "missing \"=\" in (or value after) \"%s\" " + "in by clause.\n", + fname, lineno, left ); + goto fail; + } + + if ( !BER_BVISEMPTY( &b->a_group_pat ) ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: group pattern already specified.\n", + fname, lineno, 0 ); + goto fail; + } + + /* format of string is + "group/objectClassValue/groupAttrName" */ + if ( ( value = strchr(left, '/') ) != NULL ) { + *value++ = '\0'; + if ( *value && ( name = strchr( value, '/' ) ) != NULL ) { + *name++ = '\0'; + } + } + + b->a_group_style = sty; + if ( sty == ACL_STYLE_EXPAND ) { + acl_regex_normalized_dn( right, &bv ); + if ( !ber_bvccmp( &bv, '*' ) ) { + regtest( fname, lineno, bv.bv_val ); + } + b->a_group_pat = bv; + + } else { + ber_str2bv( right, 0, 0, &bv ); + rc = dnNormalize( 0, NULL, NULL, &bv, + &b->a_group_pat, NULL ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: bad DN \"%s\".\n", + fname, lineno, right ); + goto fail; + } + } + + if ( value && *value ) { + b->a_group_oc = oc_find( value ); + *--value = '/'; + + if ( b->a_group_oc == NULL ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: group objectclass " + "\"%s\" unknown.\n", + fname, lineno, value ); + goto fail; + } + + } else { + b->a_group_oc = oc_find( SLAPD_GROUP_CLASS ); + + if( b->a_group_oc == NULL ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: group default objectclass " + "\"%s\" unknown.\n", + fname, lineno, SLAPD_GROUP_CLASS ); + goto fail; + } + } + + if ( is_object_subclass( slap_schema.si_oc_referral, + b->a_group_oc ) ) + { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: group objectclass \"%s\" " + "is subclass of referral.\n", + fname, lineno, value ); + goto fail; + } + + if ( is_object_subclass( slap_schema.si_oc_alias, + b->a_group_oc ) ) + { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: group objectclass \"%s\" " + "is subclass of alias.\n", + fname, lineno, value ); + goto fail; + } + + if ( name && *name ) { + attr_name = name; + *--name = '/'; + + } + + rc = slap_str2ad( attr_name, &b->a_group_at, &text ); + if ( rc != LDAP_SUCCESS ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "group \"%s\": %s.", + right, text ); + Debug( LDAP_DEBUG_ANY, + "%s: line %d: %s\n", + fname, lineno, buf ); + goto fail; + } + + if ( !is_at_syntax( b->a_group_at->ad_type, + SLAPD_DN_SYNTAX ) /* e.g. "member" */ + && !is_at_syntax( b->a_group_at->ad_type, + SLAPD_NAMEUID_SYNTAX ) /* e.g. memberUID */ + && !is_at_subtype( b->a_group_at->ad_type, + slap_schema.si_ad_labeledURI->ad_type ) /* e.g. memberURL */ ) + { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "group \"%s\" attr \"%s\": inappropriate syntax: %s; " + "must be " SLAPD_DN_SYNTAX " (DN), " + SLAPD_NAMEUID_SYNTAX " (NameUID) " + "or a subtype of labeledURI.", + right, + attr_name, + at_syntax( b->a_group_at->ad_type ) ); + Debug( LDAP_DEBUG_ANY, + "%s: line %d: %s\n", + fname, lineno, buf ); + goto fail; + } + + + { + int rc; + ObjectClass *ocs[2]; + + ocs[0] = b->a_group_oc; + ocs[1] = NULL; + + rc = oc_check_allowed( b->a_group_at->ad_type, + ocs, NULL ); + + if( rc != 0 ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "group: \"%s\" not allowed by \"%s\".", + b->a_group_at->ad_cname.bv_val, + b->a_group_oc->soc_oid ); + Debug( LDAP_DEBUG_ANY, "%s: line %d: %s\n", + fname, lineno, buf ); + goto fail; + } + } + continue; + } + + if ( strcasecmp( left, "peername" ) == 0 ) { + switch ( sty ) { + case ACL_STYLE_REGEX: + case ACL_STYLE_BASE: + /* legal, traditional */ + case ACL_STYLE_EXPAND: + /* cheap replacement to regex for simple expansion */ + case ACL_STYLE_IP: + case ACL_STYLE_IPV6: + case ACL_STYLE_PATH: + /* legal, peername specific */ + break; + + default: + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "inappropriate style \"%s\" in by clause.\n", + fname, lineno, style ); + goto fail; + } + + if ( right == NULL || right[0] == '\0' ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "missing \"=\" in (or value after) \"%s\" " + "in by clause.\n", + fname, lineno, left ); + goto fail; + } + + if ( !BER_BVISEMPTY( &b->a_peername_pat ) ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "peername pattern already specified.\n", + fname, lineno, 0 ); + goto fail; + } + + b->a_peername_style = sty; + if ( sty == ACL_STYLE_REGEX ) { + acl_regex_normalized_dn( right, &bv ); + if ( !ber_bvccmp( &bv, '*' ) ) { + regtest( fname, lineno, bv.bv_val ); + } + b->a_peername_pat = bv; + + } else { + ber_str2bv( right, 0, 1, &b->a_peername_pat ); + + if ( sty == ACL_STYLE_IP ) { + char *addr = NULL, + *mask = NULL, + *port = NULL; + + split( right, '{', &addr, &port ); + split( addr, '%', &addr, &mask ); + + b->a_peername_addr = inet_addr( addr ); + if ( b->a_peername_addr == (unsigned long)(-1) ) { + /* illegal address */ + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "illegal peername address \"%s\".\n", + fname, lineno, addr ); + goto fail; + } + + b->a_peername_mask = (unsigned long)(-1); + if ( mask != NULL ) { + b->a_peername_mask = inet_addr( mask ); + if ( b->a_peername_mask == + (unsigned long)(-1) ) + { + /* illegal mask */ + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "illegal peername address mask " + "\"%s\".\n", + fname, lineno, mask ); + goto fail; + } + } + + b->a_peername_port = -1; + if ( port ) { + char *end = NULL; + + b->a_peername_port = strtol( port, &end, 10 ); + if ( end == port || end[0] != '}' ) { + /* illegal port */ + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "illegal peername port specification " + "\"{%s}\".\n", + fname, lineno, port ); + goto fail; + } + } + +#ifdef LDAP_PF_INET6 + } else if ( sty == ACL_STYLE_IPV6 ) { + char *addr = NULL, + *mask = NULL, + *port = NULL; + + split( right, '{', &addr, &port ); + split( addr, '%', &addr, &mask ); + + if ( inet_pton( AF_INET6, addr, &b->a_peername_addr6 ) != 1 ) { + /* illegal address */ + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "illegal peername address \"%s\".\n", + fname, lineno, addr ); + goto fail; + } + + if ( mask == NULL ) { + mask = "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"; + } + + if ( inet_pton( AF_INET6, mask, &b->a_peername_mask6 ) != 1 ) { + /* illegal mask */ + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "illegal peername address mask " + "\"%s\".\n", + fname, lineno, mask ); + goto fail; + } + + b->a_peername_port = -1; + if ( port ) { + char *end = NULL; + + b->a_peername_port = strtol( port, &end, 10 ); + if ( end == port || end[0] != '}' ) { + /* illegal port */ + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "illegal peername port specification " + "\"{%s}\".\n", + fname, lineno, port ); + goto fail; + } + } +#endif /* LDAP_PF_INET6 */ + } + } + continue; + } + + if ( strcasecmp( left, "sockname" ) == 0 ) { + switch ( sty ) { + case ACL_STYLE_REGEX: + case ACL_STYLE_BASE: + /* legal, traditional */ + case ACL_STYLE_EXPAND: + /* cheap replacement to regex for simple expansion */ + break; + + default: + /* unknown */ + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "inappropriate style \"%s\" in by clause\n", + fname, lineno, style ); + goto fail; + } + + if ( right == NULL || right[0] == '\0' ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "missing \"=\" in (or value after) \"%s\" " + "in by clause\n", + fname, lineno, left ); + goto fail; + } + + if ( !BER_BVISNULL( &b->a_sockname_pat ) ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "sockname pattern already specified.\n", + fname, lineno, 0 ); + goto fail; + } + + b->a_sockname_style = sty; + if ( sty == ACL_STYLE_REGEX ) { + acl_regex_normalized_dn( right, &bv ); + if ( !ber_bvccmp( &bv, '*' ) ) { + regtest( fname, lineno, bv.bv_val ); + } + b->a_sockname_pat = bv; + + } else { + ber_str2bv( right, 0, 1, &b->a_sockname_pat ); + } + continue; + } + + if ( strcasecmp( left, "domain" ) == 0 ) { + switch ( sty ) { + case ACL_STYLE_REGEX: + case ACL_STYLE_BASE: + case ACL_STYLE_SUBTREE: + /* legal, traditional */ + break; + + case ACL_STYLE_EXPAND: + /* tolerated: means exact,expand */ + if ( expand ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: " + "\"expand\" modifier " + "with \"expand\" style.\n", + fname, lineno, 0 ); + } + sty = ACL_STYLE_BASE; + expand = 1; + break; + + default: + /* unknown */ + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "inappropriate style \"%s\" in by clause.\n", + fname, lineno, style ); + goto fail; + } + + if ( right == NULL || right[0] == '\0' ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "missing \"=\" in (or value after) \"%s\" " + "in by clause.\n", + fname, lineno, left ); + goto fail; + } + + if ( !BER_BVISEMPTY( &b->a_domain_pat ) ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: domain pattern already specified.\n", + fname, lineno, 0 ); + goto fail; + } + + b->a_domain_style = sty; + b->a_domain_expand = expand; + if ( sty == ACL_STYLE_REGEX ) { + acl_regex_normalized_dn( right, &bv ); + if ( !ber_bvccmp( &bv, '*' ) ) { + regtest( fname, lineno, bv.bv_val ); + } + b->a_domain_pat = bv; + + } else { + ber_str2bv( right, 0, 1, &b->a_domain_pat ); + } + continue; + } + + if ( strcasecmp( left, "sockurl" ) == 0 ) { + switch ( sty ) { + case ACL_STYLE_REGEX: + case ACL_STYLE_BASE: + /* legal, traditional */ + case ACL_STYLE_EXPAND: + /* cheap replacement to regex for simple expansion */ + break; + + default: + /* unknown */ + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "inappropriate style \"%s\" in by clause.\n", + fname, lineno, style ); + goto fail; + } + + if ( right == NULL || right[0] == '\0' ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "missing \"=\" in (or value after) \"%s\" " + "in by clause.\n", + fname, lineno, left ); + goto fail; + } + + if ( !BER_BVISEMPTY( &b->a_sockurl_pat ) ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: sockurl pattern already specified.\n", + fname, lineno, 0 ); + goto fail; + } + + b->a_sockurl_style = sty; + if ( sty == ACL_STYLE_REGEX ) { + acl_regex_normalized_dn( right, &bv ); + if ( !ber_bvccmp( &bv, '*' ) ) { + regtest( fname, lineno, bv.bv_val ); + } + b->a_sockurl_pat = bv; + + } else { + ber_str2bv( right, 0, 1, &b->a_sockurl_pat ); + } + continue; + } + + if ( strcasecmp( left, "set" ) == 0 ) { + switch ( sty ) { + /* deprecated */ + case ACL_STYLE_REGEX: + Debug( LDAP_DEBUG_CONFIG | LDAP_DEBUG_ACL, + "%s: line %d: " + "deprecated set style " + "\"regex\" in <by> clause; " + "use \"expand\" instead.\n", + fname, lineno, 0 ); + sty = ACL_STYLE_EXPAND; + /* FALLTHRU */ + + case ACL_STYLE_BASE: + case ACL_STYLE_EXPAND: + break; + + default: + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "inappropriate style \"%s\" in by clause.\n", + fname, lineno, style ); + goto fail; + } + + if ( !BER_BVISEMPTY( &b->a_set_pat ) ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: set attribute already specified.\n", + fname, lineno, 0 ); + goto fail; + } + + if ( right == NULL || *right == '\0' ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: no set is defined.\n", + fname, lineno, 0 ); + goto fail; + } + + b->a_set_style = sty; + ber_str2bv( right, 0, 1, &b->a_set_pat ); + + continue; + } + +#ifdef SLAP_DYNACL + { + char *name = NULL, + *opts = NULL; + +#if 1 /* tolerate legacy "aci" <who> */ + if ( strcasecmp( left, "aci" ) == 0 ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "undocumented deprecated \"aci\" directive " + "is superseded by \"dynacl/aci\".\n", + fname, lineno, 0 ); + name = "aci"; + + } else +#endif /* tolerate legacy "aci" <who> */ + if ( strncasecmp( left, "dynacl/", STRLENOF( "dynacl/" ) ) == 0 ) { + name = &left[ STRLENOF( "dynacl/" ) ]; + opts = strchr( name, '/' ); + if ( opts ) { + opts[ 0 ] = '\0'; + opts++; + } + } + + if ( name ) { + if ( slap_dynacl_config( fname, lineno, b, name, opts, sty, right ) ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "unable to configure dynacl \"%s\".\n", + fname, lineno, name ); + goto fail; + } + + continue; + } + } +#endif /* SLAP_DYNACL */ + + if ( strcasecmp( left, "ssf" ) == 0 ) { + if ( sty != ACL_STYLE_REGEX && sty != ACL_STYLE_BASE ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "inappropriate style \"%s\" in by clause.\n", + fname, lineno, style ); + goto fail; + } + + if ( b->a_authz.sai_ssf ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: ssf attribute already specified.\n", + fname, lineno, 0 ); + goto fail; + } + + if ( right == NULL || *right == '\0' ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: no ssf is defined.\n", + fname, lineno, 0 ); + goto fail; + } + + if ( lutil_atou( &b->a_authz.sai_ssf, right ) != 0 ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: unable to parse ssf value (%s).\n", + fname, lineno, right ); + goto fail; + } + + if ( !b->a_authz.sai_ssf ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: invalid ssf value (%s).\n", + fname, lineno, right ); + goto fail; + } + continue; + } + + if ( strcasecmp( left, "transport_ssf" ) == 0 ) { + if ( sty != ACL_STYLE_REGEX && sty != ACL_STYLE_BASE ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "inappropriate style \"%s\" in by clause.\n", + fname, lineno, style ); + goto fail; + } + + if ( b->a_authz.sai_transport_ssf ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "transport_ssf attribute already specified.\n", + fname, lineno, 0 ); + goto fail; + } + + if ( right == NULL || *right == '\0' ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: no transport_ssf is defined.\n", + fname, lineno, 0 ); + goto fail; + } + + if ( lutil_atou( &b->a_authz.sai_transport_ssf, right ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "unable to parse transport_ssf value (%s).\n", + fname, lineno, right ); + goto fail; + } + + if ( !b->a_authz.sai_transport_ssf ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: invalid transport_ssf value (%s).\n", + fname, lineno, right ); + goto fail; + } + continue; + } + + if ( strcasecmp( left, "tls_ssf" ) == 0 ) { + if ( sty != ACL_STYLE_REGEX && sty != ACL_STYLE_BASE ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "inappropriate style \"%s\" in by clause.\n", + fname, lineno, style ); + goto fail; + } + + if ( b->a_authz.sai_tls_ssf ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "tls_ssf attribute already specified.\n", + fname, lineno, 0 ); + goto fail; + } + + if ( right == NULL || *right == '\0' ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: no tls_ssf is defined\n", + fname, lineno, 0 ); + goto fail; + } + + if ( lutil_atou( &b->a_authz.sai_tls_ssf, right ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "unable to parse tls_ssf value (%s).\n", + fname, lineno, right ); + goto fail; + } + + if ( !b->a_authz.sai_tls_ssf ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: invalid tls_ssf value (%s).\n", + fname, lineno, right ); + goto fail; + } + continue; + } + + if ( strcasecmp( left, "sasl_ssf" ) == 0 ) { + if ( sty != ACL_STYLE_REGEX && sty != ACL_STYLE_BASE ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "inappropriate style \"%s\" in by clause.\n", + fname, lineno, style ); + goto fail; + } + + if ( b->a_authz.sai_sasl_ssf ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "sasl_ssf attribute already specified.\n", + fname, lineno, 0 ); + goto fail; + } + + if ( right == NULL || *right == '\0' ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: no sasl_ssf is defined.\n", + fname, lineno, 0 ); + goto fail; + } + + if ( lutil_atou( &b->a_authz.sai_sasl_ssf, right ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "unable to parse sasl_ssf value (%s).\n", + fname, lineno, right ); + goto fail; + } + + if ( !b->a_authz.sai_sasl_ssf ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: invalid sasl_ssf value (%s).\n", + fname, lineno, right ); + goto fail; + } + continue; + } + + if ( right != NULL ) { + /* unsplit */ + right[-1] = '='; + } + break; + } + + if ( i == argc || ( strcasecmp( left, "stop" ) == 0 ) ) { + /* out of arguments or plain stop */ + + ACL_PRIV_ASSIGN( b->a_access_mask, ACL_PRIV_ADDITIVE ); + ACL_PRIV_SET( b->a_access_mask, ACL_PRIV_NONE); + b->a_type = ACL_STOP; + + access_append( &a->acl_access, b ); + continue; + } + + if ( strcasecmp( left, "continue" ) == 0 ) { + /* plain continue */ + + ACL_PRIV_ASSIGN( b->a_access_mask, ACL_PRIV_ADDITIVE ); + ACL_PRIV_SET( b->a_access_mask, ACL_PRIV_NONE); + b->a_type = ACL_CONTINUE; + + access_append( &a->acl_access, b ); + continue; + } + + if ( strcasecmp( left, "break" ) == 0 ) { + /* plain continue */ + + ACL_PRIV_ASSIGN(b->a_access_mask, ACL_PRIV_ADDITIVE); + ACL_PRIV_SET( b->a_access_mask, ACL_PRIV_NONE); + b->a_type = ACL_BREAK; + + access_append( &a->acl_access, b ); + continue; + } + + if ( strcasecmp( left, "by" ) == 0 ) { + /* we've gone too far */ + --i; + ACL_PRIV_ASSIGN( b->a_access_mask, ACL_PRIV_ADDITIVE ); + ACL_PRIV_SET( b->a_access_mask, ACL_PRIV_NONE); + b->a_type = ACL_STOP; + + access_append( &a->acl_access, b ); + continue; + } + + /* get <access> */ + { + char *lleft = left; + + if ( strncasecmp( left, "self", STRLENOF( "self" ) ) == 0 ) { + b->a_dn_self = 1; + lleft = &left[ STRLENOF( "self" ) ]; + + } else if ( strncasecmp( left, "realself", STRLENOF( "realself" ) ) == 0 ) { + b->a_realdn_self = 1; + lleft = &left[ STRLENOF( "realself" ) ]; + } + + ACL_PRIV_ASSIGN( b->a_access_mask, str2accessmask( lleft ) ); + } + + if ( ACL_IS_INVALID( b->a_access_mask ) ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: expecting <access> got \"%s\".\n", + fname, lineno, left ); + goto fail; + } + + b->a_type = ACL_STOP; + + if ( ++i == argc ) { + /* out of arguments or plain stop */ + access_append( &a->acl_access, b ); + continue; + } + + if ( strcasecmp( argv[i], "continue" ) == 0 ) { + /* plain continue */ + b->a_type = ACL_CONTINUE; + + } else if ( strcasecmp( argv[i], "break" ) == 0 ) { + /* plain continue */ + b->a_type = ACL_BREAK; + + } else if ( strcasecmp( argv[i], "stop" ) != 0 ) { + /* gone to far */ + i--; + } + + access_append( &a->acl_access, b ); + b = NULL; + + } else { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: expecting \"to\" " + "or \"by\" got \"%s\"\n", + fname, lineno, argv[i] ); + goto fail; + } + } + + /* if we have no real access clause, complain and do nothing */ + if ( a == NULL ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "warning: no access clause(s) specified in access line.\n", + fname, lineno, 0 ); + goto fail; + + } else { +#ifdef LDAP_DEBUG + if ( slap_debug & LDAP_DEBUG_ACL ) { + print_acl( be, a ); + } +#endif + + if ( a->acl_access == NULL ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "warning: no by clause(s) specified in access line.\n", + fname, lineno, 0 ); + goto fail; + } + + if ( be != NULL ) { + if ( be->be_nsuffix == NULL ) { + Debug( LDAP_DEBUG_ACL, "%s: line %d: warning: " + "scope checking needs suffix before ACLs.\n", + fname, lineno, 0 ); + /* go ahead, since checking is not authoritative */ + } else if ( !BER_BVISNULL( &be->be_nsuffix[ 1 ] ) ) { + Debug( LDAP_DEBUG_ACL, "%s: line %d: warning: " + "scope checking only applies to single-valued " + "suffix databases\n", + fname, lineno, 0 ); + /* go ahead, since checking is not authoritative */ + } else { + switch ( check_scope( be, a ) ) { + case ACL_SCOPE_UNKNOWN: + Debug( LDAP_DEBUG_ACL, "%s: line %d: warning: " + "cannot assess the validity of the ACL scope within " + "backend naming context\n", + fname, lineno, 0 ); + break; + + case ACL_SCOPE_WARN: + Debug( LDAP_DEBUG_ACL, "%s: line %d: warning: " + "ACL could be out of scope within backend naming context\n", + fname, lineno, 0 ); + break; + + case ACL_SCOPE_PARTIAL: + Debug( LDAP_DEBUG_ACL, "%s: line %d: warning: " + "ACL appears to be partially out of scope within " + "backend naming context\n", + fname, lineno, 0 ); + break; + + case ACL_SCOPE_ERR: + Debug( LDAP_DEBUG_ACL, "%s: line %d: warning: " + "ACL appears to be out of scope within " + "backend naming context\n", + fname, lineno, 0 ); + break; + + default: + break; + } + } + acl_append( &be->be_acl, a, pos ); + + } else { + acl_append( &frontendDB->be_acl, a, pos ); + } + } + + return 0; + +fail: + if ( b ) access_free( b ); + if ( a ) acl_free( a ); + return acl_usage(); +} + +char * +accessmask2str( slap_mask_t mask, char *buf, int debug ) +{ + int none = 1; + char *ptr = buf; + + assert( buf != NULL ); + + if ( ACL_IS_INVALID( mask ) ) { + return "invalid"; + } + + buf[0] = '\0'; + + if ( ACL_IS_LEVEL( mask ) ) { + if ( ACL_LVL_IS_NONE(mask) ) { + ptr = lutil_strcopy( ptr, "none" ); + + } else if ( ACL_LVL_IS_DISCLOSE(mask) ) { + ptr = lutil_strcopy( ptr, "disclose" ); + + } else if ( ACL_LVL_IS_AUTH(mask) ) { + ptr = lutil_strcopy( ptr, "auth" ); + + } else if ( ACL_LVL_IS_COMPARE(mask) ) { + ptr = lutil_strcopy( ptr, "compare" ); + + } else if ( ACL_LVL_IS_SEARCH(mask) ) { + ptr = lutil_strcopy( ptr, "search" ); + + } else if ( ACL_LVL_IS_READ(mask) ) { + ptr = lutil_strcopy( ptr, "read" ); + + } else if ( ACL_LVL_IS_WRITE(mask) ) { + ptr = lutil_strcopy( ptr, "write" ); + + } else if ( ACL_LVL_IS_WADD(mask) ) { + ptr = lutil_strcopy( ptr, "add" ); + + } else if ( ACL_LVL_IS_WDEL(mask) ) { + ptr = lutil_strcopy( ptr, "delete" ); + + } else if ( ACL_LVL_IS_MANAGE(mask) ) { + ptr = lutil_strcopy( ptr, "manage" ); + + } else { + ptr = lutil_strcopy( ptr, "unknown" ); + } + + if ( !debug ) { + *ptr = '\0'; + return buf; + } + *ptr++ = '('; + } + + if( ACL_IS_ADDITIVE( mask ) ) { + *ptr++ = '+'; + + } else if( ACL_IS_SUBTRACTIVE( mask ) ) { + *ptr++ = '-'; + + } else { + *ptr++ = '='; + } + + if ( ACL_PRIV_ISSET(mask, ACL_PRIV_MANAGE) ) { + none = 0; + *ptr++ = 'm'; + } + + if ( ACL_PRIV_ISSET(mask, ACL_PRIV_WRITE) ) { + none = 0; + *ptr++ = 'w'; + + } else if ( ACL_PRIV_ISSET(mask, ACL_PRIV_WADD) ) { + none = 0; + *ptr++ = 'a'; + + } else if ( ACL_PRIV_ISSET(mask, ACL_PRIV_WDEL) ) { + none = 0; + *ptr++ = 'z'; + } + + if ( ACL_PRIV_ISSET(mask, ACL_PRIV_READ) ) { + none = 0; + *ptr++ = 'r'; + } + + if ( ACL_PRIV_ISSET(mask, ACL_PRIV_SEARCH) ) { + none = 0; + *ptr++ = 's'; + } + + if ( ACL_PRIV_ISSET(mask, ACL_PRIV_COMPARE) ) { + none = 0; + *ptr++ = 'c'; + } + + if ( ACL_PRIV_ISSET(mask, ACL_PRIV_AUTH) ) { + none = 0; + *ptr++ = 'x'; + } + + if ( ACL_PRIV_ISSET(mask, ACL_PRIV_DISCLOSE) ) { + none = 0; + *ptr++ = 'd'; + } + + if ( none && ACL_PRIV_ISSET(mask, ACL_PRIV_NONE) ) { + none = 0; + *ptr++ = '0'; + } + + if ( none ) { + ptr = buf; + } + + if ( ACL_IS_LEVEL( mask ) ) { + *ptr++ = ')'; + } + + *ptr = '\0'; + + return buf; +} + +slap_mask_t +str2accessmask( const char *str ) +{ + slap_mask_t mask; + + if( !ASCII_ALPHA(str[0]) ) { + int i; + + if ( str[0] == '=' ) { + ACL_INIT(mask); + + } else if( str[0] == '+' ) { + ACL_PRIV_ASSIGN(mask, ACL_PRIV_ADDITIVE); + + } else if( str[0] == '-' ) { + ACL_PRIV_ASSIGN(mask, ACL_PRIV_SUBSTRACTIVE); + + } else { + ACL_INVALIDATE(mask); + return mask; + } + + for( i=1; str[i] != '\0'; i++ ) { + if( TOLOWER((unsigned char) str[i]) == 'm' ) { + ACL_PRIV_SET(mask, ACL_PRIV_MANAGE); + + } else if( TOLOWER((unsigned char) str[i]) == 'w' ) { + ACL_PRIV_SET(mask, ACL_PRIV_WRITE); + + } else if( TOLOWER((unsigned char) str[i]) == 'a' ) { + ACL_PRIV_SET(mask, ACL_PRIV_WADD); + + } else if( TOLOWER((unsigned char) str[i]) == 'z' ) { + ACL_PRIV_SET(mask, ACL_PRIV_WDEL); + + } else if( TOLOWER((unsigned char) str[i]) == 'r' ) { + ACL_PRIV_SET(mask, ACL_PRIV_READ); + + } else if( TOLOWER((unsigned char) str[i]) == 's' ) { + ACL_PRIV_SET(mask, ACL_PRIV_SEARCH); + + } else if( TOLOWER((unsigned char) str[i]) == 'c' ) { + ACL_PRIV_SET(mask, ACL_PRIV_COMPARE); + + } else if( TOLOWER((unsigned char) str[i]) == 'x' ) { + ACL_PRIV_SET(mask, ACL_PRIV_AUTH); + + } else if( TOLOWER((unsigned char) str[i]) == 'd' ) { + ACL_PRIV_SET(mask, ACL_PRIV_DISCLOSE); + + } else if( str[i] == '0' ) { + ACL_PRIV_SET(mask, ACL_PRIV_NONE); + + } else { + ACL_INVALIDATE(mask); + return mask; + } + } + + return mask; + } + + if ( strcasecmp( str, "none" ) == 0 ) { + ACL_LVL_ASSIGN_NONE(mask); + + } else if ( strcasecmp( str, "disclose" ) == 0 ) { + ACL_LVL_ASSIGN_DISCLOSE(mask); + + } else if ( strcasecmp( str, "auth" ) == 0 ) { + ACL_LVL_ASSIGN_AUTH(mask); + + } else if ( strcasecmp( str, "compare" ) == 0 ) { + ACL_LVL_ASSIGN_COMPARE(mask); + + } else if ( strcasecmp( str, "search" ) == 0 ) { + ACL_LVL_ASSIGN_SEARCH(mask); + + } else if ( strcasecmp( str, "read" ) == 0 ) { + ACL_LVL_ASSIGN_READ(mask); + + } else if ( strcasecmp( str, "add" ) == 0 ) { + ACL_LVL_ASSIGN_WADD(mask); + + } else if ( strcasecmp( str, "delete" ) == 0 ) { + ACL_LVL_ASSIGN_WDEL(mask); + + } else if ( strcasecmp( str, "write" ) == 0 ) { + ACL_LVL_ASSIGN_WRITE(mask); + + } else if ( strcasecmp( str, "manage" ) == 0 ) { + ACL_LVL_ASSIGN_MANAGE(mask); + + } else { + ACL_INVALIDATE( mask ); + } + + return mask; +} + +static int +acl_usage( void ) +{ + char *access = + "<access clause> ::= access to <what> " + "[ by <who> [ <access> ] [ <control> ] ]+ \n"; + char *what = + "<what> ::= * | dn[.<dnstyle>=<DN>] [filter=<filter>] [attrs=<attrspec>]\n" + "<attrspec> ::= <attrname> [val[/<matchingRule>][.<attrstyle>]=<value>] | <attrlist>\n" + "<attrlist> ::= <attr> [ , <attrlist> ]\n" + "<attr> ::= <attrname> | @<objectClass> | !<objectClass> | entry | children\n"; + + char *who = + "<who> ::= [ * | anonymous | users | self | dn[.<dnstyle>]=<DN> ]\n" + "\t[ realanonymous | realusers | realself | realdn[.<dnstyle>]=<DN> ]\n" + "\t[dnattr=<attrname>]\n" + "\t[realdnattr=<attrname>]\n" + "\t[group[/<objectclass>[/<attrname>]][.<style>]=<group>]\n" + "\t[peername[.<peernamestyle>]=<peer>] [sockname[.<style>]=<name>]\n" + "\t[domain[.<domainstyle>]=<domain>] [sockurl[.<style>]=<url>]\n" +#ifdef SLAP_DYNACL + "\t[dynacl/<name>[/<options>][.<dynstyle>][=<pattern>]]\n" +#endif /* SLAP_DYNACL */ + "\t[ssf=<n>] [transport_ssf=<n>] [tls_ssf=<n>] [sasl_ssf=<n>]\n" + "<style> ::= exact | regex | base(Object)\n" + "<dnstyle> ::= base(Object) | one(level) | sub(tree) | children | " + "exact | regex\n" + "<attrstyle> ::= exact | regex | base(Object) | one(level) | " + "sub(tree) | children\n" + "<peernamestyle> ::= exact | regex | ip | ipv6 | path\n" + "<domainstyle> ::= exact | regex | base(Object) | sub(tree)\n" + "<access> ::= [[real]self]{<level>|<priv>}\n" + "<level> ::= none|disclose|auth|compare|search|read|{write|add|delete}|manage\n" + "<priv> ::= {=|+|-}{0|d|x|c|s|r|{w|a|z}|m}+\n" + "<control> ::= [ stop | continue | break ]\n" +#ifdef SLAP_DYNACL +#ifdef SLAPD_ACI_ENABLED + "dynacl:\n" + "\t<name>=ACI\t<pattern>=<attrname>\n" +#endif /* SLAPD_ACI_ENABLED */ +#endif /* ! SLAP_DYNACL */ + ""; + + Debug( LDAP_DEBUG_ANY, "%s%s%s\n", access, what, who ); + + return 1; +} + +/* + * Set pattern to a "normalized" DN from src. + * At present it simply eats the (optional) space after + * a RDN separator (,) + * Eventually will evolve in a more complete normalization + */ +static void +acl_regex_normalized_dn( + const char *src, + struct berval *pattern ) +{ + char *str, *p; + ber_len_t len; + + str = ch_strdup( src ); + len = strlen( src ); + + for ( p = str; p && p[0]; p++ ) { + /* escape */ + if ( p[0] == '\\' && p[1] ) { + /* + * if escaping a hex pair we should + * increment p twice; however, in that + * case the second hex number does + * no harm + */ + p++; + } + + if ( p[0] == ',' && p[1] == ' ' ) { + char *q; + + /* + * too much space should be an error if we are pedantic + */ + for ( q = &p[2]; q[0] == ' '; q++ ) { + /* DO NOTHING */ ; + } + AC_MEMCPY( p+1, q, len-(q-str)+1); + } + } + pattern->bv_val = str; + pattern->bv_len = p - str; + + return; +} + +static void +split( + char *line, + int splitchar, + char **left, + char **right ) +{ + *left = line; + if ( (*right = strchr( line, splitchar )) != NULL ) { + *((*right)++) = '\0'; + } +} + +static void +access_append( Access **l, Access *a ) +{ + for ( ; *l != NULL; l = &(*l)->a_next ) { + ; /* Empty */ + } + + *l = a; +} + +void +acl_append( AccessControl **l, AccessControl *a, int pos ) +{ + int i; + + for (i=0 ; i != pos && *l != NULL; l = &(*l)->acl_next, i++ ) { + ; /* Empty */ + } + if ( *l && a ) + a->acl_next = *l; + *l = a; +} + +static void +access_free( Access *a ) +{ + if ( !BER_BVISNULL( &a->a_dn_pat ) ) { + free( a->a_dn_pat.bv_val ); + } + if ( !BER_BVISNULL( &a->a_realdn_pat ) ) { + free( a->a_realdn_pat.bv_val ); + } + if ( !BER_BVISNULL( &a->a_peername_pat ) ) { + free( a->a_peername_pat.bv_val ); + } + if ( !BER_BVISNULL( &a->a_sockname_pat ) ) { + free( a->a_sockname_pat.bv_val ); + } + if ( !BER_BVISNULL( &a->a_domain_pat ) ) { + free( a->a_domain_pat.bv_val ); + } + if ( !BER_BVISNULL( &a->a_sockurl_pat ) ) { + free( a->a_sockurl_pat.bv_val ); + } + if ( !BER_BVISNULL( &a->a_set_pat ) ) { + free( a->a_set_pat.bv_val ); + } + if ( !BER_BVISNULL( &a->a_group_pat ) ) { + free( a->a_group_pat.bv_val ); + } +#ifdef SLAP_DYNACL + if ( a->a_dynacl != NULL ) { + slap_dynacl_t *da; + for ( da = a->a_dynacl; da; ) { + slap_dynacl_t *tmp = da; + + da = da->da_next; + + if ( tmp->da_destroy ) { + tmp->da_destroy( tmp->da_private ); + } + + ch_free( tmp ); + } + } +#endif /* SLAP_DYNACL */ + free( a ); +} + +void +acl_free( AccessControl *a ) +{ + Access *n; + AttributeName *an; + + if ( a->acl_filter ) { + filter_free( a->acl_filter ); + } + if ( !BER_BVISNULL( &a->acl_dn_pat ) ) { + if ( a->acl_dn_style == ACL_STYLE_REGEX ) { + regfree( &a->acl_dn_re ); + } + free ( a->acl_dn_pat.bv_val ); + } + if ( a->acl_attrs ) { + for ( an = a->acl_attrs; !BER_BVISNULL( &an->an_name ); an++ ) { + free( an->an_name.bv_val ); + } + free( a->acl_attrs ); + + if ( a->acl_attrval_style == ACL_STYLE_REGEX ) { + regfree( &a->acl_attrval_re ); + } + + if ( !BER_BVISNULL( &a->acl_attrval ) ) { + ber_memfree( a->acl_attrval.bv_val ); + } + } + for ( ; a->acl_access; a->acl_access = n ) { + n = a->acl_access->a_next; + access_free( a->acl_access ); + } + free( a ); +} + +void +acl_destroy( AccessControl *a ) +{ + AccessControl *n; + + for ( ; a; a = n ) { + n = a->acl_next; + acl_free( a ); + } + + if ( !BER_BVISNULL( &aclbuf ) ) { + ch_free( aclbuf.bv_val ); + BER_BVZERO( &aclbuf ); + } +} + +char * +access2str( slap_access_t access ) +{ + if ( access == ACL_NONE ) { + return "none"; + + } else if ( access == ACL_DISCLOSE ) { + return "disclose"; + + } else if ( access == ACL_AUTH ) { + return "auth"; + + } else if ( access == ACL_COMPARE ) { + return "compare"; + + } else if ( access == ACL_SEARCH ) { + return "search"; + + } else if ( access == ACL_READ ) { + return "read"; + + } else if ( access == ACL_WRITE ) { + return "write"; + + } else if ( access == ACL_WADD ) { + return "add"; + + } else if ( access == ACL_WDEL ) { + return "delete"; + + } else if ( access == ACL_MANAGE ) { + return "manage"; + + } + + return "unknown"; +} + +slap_access_t +str2access( const char *str ) +{ + if ( strcasecmp( str, "none" ) == 0 ) { + return ACL_NONE; + + } else if ( strcasecmp( str, "disclose" ) == 0 ) { + return ACL_DISCLOSE; + + } else if ( strcasecmp( str, "auth" ) == 0 ) { + return ACL_AUTH; + + } else if ( strcasecmp( str, "compare" ) == 0 ) { + return ACL_COMPARE; + + } else if ( strcasecmp( str, "search" ) == 0 ) { + return ACL_SEARCH; + + } else if ( strcasecmp( str, "read" ) == 0 ) { + return ACL_READ; + + } else if ( strcasecmp( str, "write" ) == 0 ) { + return ACL_WRITE; + + } else if ( strcasecmp( str, "add" ) == 0 ) { + return ACL_WADD; + + } else if ( strcasecmp( str, "delete" ) == 0 ) { + return ACL_WDEL; + + } else if ( strcasecmp( str, "manage" ) == 0 ) { + return ACL_MANAGE; + } + + return( ACL_INVALID_ACCESS ); +} + +static char * +safe_strncopy( char *ptr, const char *src, size_t n, struct berval *buf ) +{ + while ( ptr + n >= buf->bv_val + buf->bv_len ) { + char *tmp = ch_realloc( buf->bv_val, 2*buf->bv_len ); + if ( tmp == NULL ) { + return NULL; + } + ptr = tmp + (ptr - buf->bv_val); + buf->bv_val = tmp; + buf->bv_len *= 2; + } + + return lutil_strncopy( ptr, src, n ); +} + +static char * +safe_strcopy( char *ptr, const char *s, struct berval *buf ) +{ + size_t n = strlen( s ); + + return safe_strncopy( ptr, s, n, buf ); +} + +static char * +safe_strbvcopy( char *ptr, const struct berval *bv, struct berval *buf ) +{ + return safe_strncopy( ptr, bv->bv_val, bv->bv_len, buf ); +} + +#define acl_safe_strcopy( ptr, s ) safe_strcopy( (ptr), (s), &aclbuf ) +#define acl_safe_strncopy( ptr, s, n ) safe_strncopy( (ptr), (s), (n), &aclbuf ) +#define acl_safe_strbvcopy( ptr, bv ) safe_strbvcopy( (ptr), (bv), &aclbuf ) + +static char * +dnaccess2text( slap_dn_access *bdn, char *ptr, int is_realdn ) +{ + *ptr++ = ' '; + + if ( is_realdn ) { + ptr = acl_safe_strcopy( ptr, "real" ); + } + + if ( ber_bvccmp( &bdn->a_pat, '*' ) || + bdn->a_style == ACL_STYLE_ANONYMOUS || + bdn->a_style == ACL_STYLE_USERS || + bdn->a_style == ACL_STYLE_SELF ) + { + if ( is_realdn ) { + assert( ! ber_bvccmp( &bdn->a_pat, '*' ) ); + } + + ptr = acl_safe_strbvcopy( ptr, &bdn->a_pat ); + if ( bdn->a_style == ACL_STYLE_SELF && bdn->a_self_level != 0 ) { + char buf[SLAP_TEXT_BUFLEN]; + int n = snprintf( buf, sizeof(buf), ".level{%d}", bdn->a_self_level ); + if ( n > 0 ) { + ptr = acl_safe_strncopy( ptr, buf, n ); + } /* else ? */ + } + + } else { + ptr = acl_safe_strcopy( ptr, "dn." ); + if ( bdn->a_style == ACL_STYLE_BASE ) + ptr = acl_safe_strcopy( ptr, style_base ); + else + ptr = acl_safe_strcopy( ptr, style_strings[bdn->a_style] ); + if ( bdn->a_style == ACL_STYLE_LEVEL ) { + char buf[SLAP_TEXT_BUFLEN]; + int n = snprintf( buf, sizeof(buf), "{%d}", bdn->a_level ); + if ( n > 0 ) { + ptr = acl_safe_strncopy( ptr, buf, n ); + } /* else ? */ + } + if ( bdn->a_expand ) { + ptr = acl_safe_strcopy( ptr, ",expand" ); + } + ptr = acl_safe_strcopy( ptr, "=\"" ); + ptr = acl_safe_strbvcopy( ptr, &bdn->a_pat ); + ptr = acl_safe_strcopy( ptr, "\"" ); + } + return ptr; +} + +static char * +access2text( Access *b, char *ptr ) +{ + char maskbuf[ACCESSMASK_MAXLEN]; + + ptr = acl_safe_strcopy( ptr, "\tby" ); + + if ( !BER_BVISEMPTY( &b->a_dn_pat ) ) { + ptr = dnaccess2text( &b->a_dn, ptr, 0 ); + } + if ( b->a_dn_at ) { + ptr = acl_safe_strcopy( ptr, " dnattr=" ); + ptr = acl_safe_strbvcopy( ptr, &b->a_dn_at->ad_cname ); + } + + if ( !BER_BVISEMPTY( &b->a_realdn_pat ) ) { + ptr = dnaccess2text( &b->a_realdn, ptr, 1 ); + } + if ( b->a_realdn_at ) { + ptr = acl_safe_strcopy( ptr, " realdnattr=" ); + ptr = acl_safe_strbvcopy( ptr, &b->a_realdn_at->ad_cname ); + } + + if ( !BER_BVISEMPTY( &b->a_group_pat ) ) { + ptr = acl_safe_strcopy( ptr, " group/" ); + ptr = acl_safe_strcopy( ptr, b->a_group_oc ? + b->a_group_oc->soc_cname.bv_val : SLAPD_GROUP_CLASS ); + ptr = acl_safe_strcopy( ptr, "/" ); + ptr = acl_safe_strcopy( ptr, b->a_group_at ? + b->a_group_at->ad_cname.bv_val : SLAPD_GROUP_ATTR ); + ptr = acl_safe_strcopy( ptr, "." ); + ptr = acl_safe_strcopy( ptr, style_strings[b->a_group_style] ); + ptr = acl_safe_strcopy( ptr, "=\"" ); + ptr = acl_safe_strbvcopy( ptr, &b->a_group_pat ); + ptr = acl_safe_strcopy( ptr, "\"" ); + } + + if ( !BER_BVISEMPTY( &b->a_peername_pat ) ) { + ptr = acl_safe_strcopy( ptr, " peername" ); + ptr = acl_safe_strcopy( ptr, "." ); + ptr = acl_safe_strcopy( ptr, style_strings[b->a_peername_style] ); + ptr = acl_safe_strcopy( ptr, "=\"" ); + ptr = acl_safe_strbvcopy( ptr, &b->a_peername_pat ); + ptr = acl_safe_strcopy( ptr, "\"" ); + } + + if ( !BER_BVISEMPTY( &b->a_sockname_pat ) ) { + ptr = acl_safe_strcopy( ptr, " sockname" ); + ptr = acl_safe_strcopy( ptr, "." ); + ptr = acl_safe_strcopy( ptr, style_strings[b->a_sockname_style] ); + ptr = acl_safe_strcopy( ptr, "=\"" ); + ptr = acl_safe_strbvcopy( ptr, &b->a_sockname_pat ); + ptr = acl_safe_strcopy( ptr, "\"" ); + } + + if ( !BER_BVISEMPTY( &b->a_domain_pat ) ) { + ptr = acl_safe_strcopy( ptr, " domain" ); + ptr = acl_safe_strcopy( ptr, "." ); + ptr = acl_safe_strcopy( ptr, style_strings[b->a_domain_style] ); + if ( b->a_domain_expand ) { + ptr = acl_safe_strcopy( ptr, ",expand" ); + } + ptr = acl_safe_strcopy( ptr, "=" ); + ptr = acl_safe_strbvcopy( ptr, &b->a_domain_pat ); + } + + if ( !BER_BVISEMPTY( &b->a_sockurl_pat ) ) { + ptr = acl_safe_strcopy( ptr, " sockurl" ); + ptr = acl_safe_strcopy( ptr, "." ); + ptr = acl_safe_strcopy( ptr, style_strings[b->a_sockurl_style] ); + ptr = acl_safe_strcopy( ptr, "=\"" ); + ptr = acl_safe_strbvcopy( ptr, &b->a_sockurl_pat ); + ptr = acl_safe_strcopy( ptr, "\"" ); + } + + if ( !BER_BVISEMPTY( &b->a_set_pat ) ) { + ptr = acl_safe_strcopy( ptr, " set" ); + ptr = acl_safe_strcopy( ptr, "." ); + ptr = acl_safe_strcopy( ptr, style_strings[b->a_set_style] ); + ptr = acl_safe_strcopy( ptr, "=\"" ); + ptr = acl_safe_strbvcopy( ptr, &b->a_set_pat ); + ptr = acl_safe_strcopy( ptr, "\"" ); + } + +#ifdef SLAP_DYNACL + if ( b->a_dynacl ) { + slap_dynacl_t *da; + + for ( da = b->a_dynacl; da; da = da->da_next ) { + if ( da->da_unparse ) { + struct berval bv = BER_BVNULL; + (void)( *da->da_unparse )( da->da_private, &bv ); + assert( !BER_BVISNULL( &bv ) ); + ptr = acl_safe_strbvcopy( ptr, &bv ); + ch_free( bv.bv_val ); + } + } + } +#endif /* SLAP_DYNACL */ + + /* Security Strength Factors */ + if ( b->a_authz.sai_ssf ) { + char buf[SLAP_TEXT_BUFLEN]; + int n = snprintf( buf, sizeof(buf), " ssf=%u", + b->a_authz.sai_ssf ); + ptr = acl_safe_strncopy( ptr, buf, n ); + } + if ( b->a_authz.sai_transport_ssf ) { + char buf[SLAP_TEXT_BUFLEN]; + int n = snprintf( buf, sizeof(buf), " transport_ssf=%u", + b->a_authz.sai_transport_ssf ); + ptr = acl_safe_strncopy( ptr, buf, n ); + } + if ( b->a_authz.sai_tls_ssf ) { + char buf[SLAP_TEXT_BUFLEN]; + int n = snprintf( buf, sizeof(buf), " tls_ssf=%u", + b->a_authz.sai_tls_ssf ); + ptr = acl_safe_strncopy( ptr, buf, n ); + } + if ( b->a_authz.sai_sasl_ssf ) { + char buf[SLAP_TEXT_BUFLEN]; + int n = snprintf( buf, sizeof(buf), " sasl_ssf=%u", + b->a_authz.sai_sasl_ssf ); + ptr = acl_safe_strncopy( ptr, buf, n ); + } + + ptr = acl_safe_strcopy( ptr, " " ); + if ( b->a_dn_self ) { + ptr = acl_safe_strcopy( ptr, "self" ); + } else if ( b->a_realdn_self ) { + ptr = acl_safe_strcopy( ptr, "realself" ); + } + ptr = acl_safe_strcopy( ptr, accessmask2str( b->a_access_mask, maskbuf, 0 )); + if ( !maskbuf[0] ) ptr--; + + if( b->a_type == ACL_BREAK ) { + ptr = acl_safe_strcopy( ptr, " break" ); + + } else if( b->a_type == ACL_CONTINUE ) { + ptr = acl_safe_strcopy( ptr, " continue" ); + + } else if( b->a_type != ACL_STOP ) { + ptr = acl_safe_strcopy( ptr, " unknown-control" ); + } else { + if ( !maskbuf[0] ) ptr = acl_safe_strcopy( ptr, " stop" ); + } + ptr = acl_safe_strcopy( ptr, "\n" ); + + return ptr; +} + +void +acl_unparse( AccessControl *a, struct berval *bv ) +{ + Access *b; + char *ptr; + int to = 0; + + if ( BER_BVISNULL( &aclbuf ) ) { + aclbuf.bv_val = ch_malloc( ACLBUF_CHUNKSIZE ); + aclbuf.bv_len = ACLBUF_CHUNKSIZE; + } + + bv->bv_len = 0; + + ptr = aclbuf.bv_val; + + ptr = acl_safe_strcopy( ptr, "to" ); + if ( !BER_BVISNULL( &a->acl_dn_pat ) ) { + to++; + ptr = acl_safe_strcopy( ptr, " dn." ); + if ( a->acl_dn_style == ACL_STYLE_BASE ) + ptr = acl_safe_strcopy( ptr, style_base ); + else + ptr = acl_safe_strcopy( ptr, style_strings[a->acl_dn_style] ); + ptr = acl_safe_strcopy( ptr, "=\"" ); + ptr = acl_safe_strbvcopy( ptr, &a->acl_dn_pat ); + ptr = acl_safe_strcopy( ptr, "\"\n" ); + } + + if ( a->acl_filter != NULL ) { + struct berval fbv = BER_BVNULL; + + to++; + filter2bv( a->acl_filter, &fbv ); + ptr = acl_safe_strcopy( ptr, " filter=\"" ); + ptr = acl_safe_strbvcopy( ptr, &fbv ); + ptr = acl_safe_strcopy( ptr, "\"\n" ); + ch_free( fbv.bv_val ); + } + + if ( a->acl_attrs != NULL ) { + int first = 1; + AttributeName *an; + to++; + + ptr = acl_safe_strcopy( ptr, " attrs=" ); + for ( an = a->acl_attrs; an && !BER_BVISNULL( &an->an_name ); an++ ) { + if ( ! first ) ptr = acl_safe_strcopy( ptr, ","); + if (an->an_oc) { + ptr = acl_safe_strcopy( ptr, ( an->an_flags & SLAP_AN_OCEXCLUDE ) ? "!" : "@" ); + ptr = acl_safe_strbvcopy( ptr, &an->an_oc->soc_cname ); + + } else { + ptr = acl_safe_strbvcopy( ptr, &an->an_name ); + } + first = 0; + } + ptr = acl_safe_strcopy( ptr, "\n" ); + } + + if ( !BER_BVISNULL( &a->acl_attrval ) ) { + to++; + ptr = acl_safe_strcopy( ptr, " val." ); + if ( a->acl_attrval_style == ACL_STYLE_BASE && + a->acl_attrs[0].an_desc->ad_type->sat_syntax == + slap_schema.si_syn_distinguishedName ) + ptr = acl_safe_strcopy( ptr, style_base ); + else + ptr = acl_safe_strcopy( ptr, style_strings[a->acl_attrval_style] ); + ptr = acl_safe_strcopy( ptr, "=\"" ); + ptr = acl_safe_strbvcopy( ptr, &a->acl_attrval ); + ptr = acl_safe_strcopy( ptr, "\"\n" ); + } + + if ( !to ) { + ptr = acl_safe_strcopy( ptr, " *\n" ); + } + + for ( b = a->acl_access; b != NULL; b = b->a_next ) { + ptr = access2text( b, ptr ); + } + *ptr = '\0'; + bv->bv_val = aclbuf.bv_val; + bv->bv_len = ptr - bv->bv_val; +} + +#ifdef LDAP_DEBUG +static void +print_acl( Backend *be, AccessControl *a ) +{ + struct berval bv; + + acl_unparse( a, &bv ); + fprintf( stderr, "%s ACL: access %s\n", + be == NULL ? "Global" : "Backend", bv.bv_val ); +} +#endif /* LDAP_DEBUG */ diff --git a/servers/slapd/ad.c b/servers/slapd/ad.c new file mode 100644 index 0000000..1f1cb90 --- /dev/null +++ b/servers/slapd/ad.c @@ -0,0 +1,1312 @@ +/* ad.c - routines for dealing with attribute descriptions */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "slap.h" +#include "lutil.h" + +static struct berval bv_no_attrs = BER_BVC( LDAP_NO_ATTRS ); +static struct berval bv_all_user_attrs = BER_BVC( "*" ); +static struct berval bv_all_operational_attrs = BER_BVC( "+" ); + +static AttributeName anlist_no_attrs[] = { + { BER_BVC( LDAP_NO_ATTRS ), NULL, 0, NULL }, + { BER_BVNULL, NULL, 0, NULL } +}; + +static AttributeName anlist_all_user_attributes[] = { + { BER_BVC( LDAP_ALL_USER_ATTRIBUTES ), NULL, 0, NULL }, + { BER_BVNULL, NULL, 0, NULL } +}; + +static AttributeName anlist_all_operational_attributes[] = { + { BER_BVC( LDAP_ALL_OPERATIONAL_ATTRIBUTES ), NULL, 0, NULL }, + { BER_BVNULL, NULL, 0, NULL } +}; + +static AttributeName anlist_all_attributes[] = { + { BER_BVC( LDAP_ALL_USER_ATTRIBUTES ), NULL, 0, NULL }, + { BER_BVC( LDAP_ALL_OPERATIONAL_ATTRIBUTES ), NULL, 0, NULL }, + { BER_BVNULL, NULL, 0, NULL } +}; + +AttributeName *slap_anlist_no_attrs = anlist_no_attrs; +AttributeName *slap_anlist_all_user_attributes = anlist_all_user_attributes; +AttributeName *slap_anlist_all_operational_attributes = anlist_all_operational_attributes; +AttributeName *slap_anlist_all_attributes = anlist_all_attributes; + +struct berval * slap_bv_no_attrs = &bv_no_attrs; +struct berval * slap_bv_all_user_attrs = &bv_all_user_attrs; +struct berval * slap_bv_all_operational_attrs = &bv_all_operational_attrs; + +typedef struct Attr_option { + struct berval name; /* option name or prefix */ + int prefix; /* NAME is a tag and range prefix */ +} Attr_option; + +static Attr_option lang_option = { BER_BVC("lang-"), 1 }; + +/* Options sorted by name, and number of options */ +static Attr_option *options = &lang_option; +static int option_count = 1; + +static int msad_range_hack = 0; + +static int ad_count; + +static Attr_option *ad_find_option_definition( const char *opt, int optlen ); + +int ad_keystring( + struct berval *bv ) +{ + ber_len_t i; + + if( !AD_LEADCHAR( bv->bv_val[0] ) ) { + return 1; + } + + for( i=1; i<bv->bv_len; i++ ) { + if( !AD_CHAR( bv->bv_val[i] )) { + if ( msad_range_hack && bv->bv_val[i] == '=' ) + continue; + return 1; + } + } + return 0; +} + +void ad_destroy( AttributeDescription *ad ) +{ + AttributeDescription *n; + + for (; ad != NULL; ad = n) { + n = ad->ad_next; + ldap_memfree( ad ); + } +} + +/* Is there an AttributeDescription for this type that uses these tags? */ +AttributeDescription * ad_find_tags( + AttributeType *type, + struct berval *tags ) +{ + AttributeDescription *ad; + + ldap_pvt_thread_mutex_lock( &type->sat_ad_mutex ); + for (ad = type->sat_ad; ad; ad=ad->ad_next) + { + if (ad->ad_tags.bv_len == tags->bv_len && + !strcasecmp(ad->ad_tags.bv_val, tags->bv_val)) + break; + } + ldap_pvt_thread_mutex_unlock( &type->sat_ad_mutex ); + return ad; +} + +int slap_str2ad( + const char *str, + AttributeDescription **ad, + const char **text ) +{ + struct berval bv; + bv.bv_val = (char *) str; + bv.bv_len = strlen( str ); + + return slap_bv2ad( &bv, ad, text ); +} + +static char *strchrlen( + const char *beg, + const char *end, + const char ch, + int *len ) +{ + const char *p; + + for( p=beg; p < end && *p; p++ ) { + if( *p == ch ) { + *len = p - beg; + return (char *) p; + } + } + + *len = p - beg; + return NULL; +} + +int slap_bv2ad( + struct berval *bv, + AttributeDescription **ad, + const char **text ) +{ + int rtn = LDAP_UNDEFINED_TYPE; + AttributeDescription desc, *d2; + char *name, *options, *optn; + char *opt, *next; + int ntags; + int tagslen; + + /* hardcoded limits for speed */ +#define MAX_TAGGING_OPTIONS 128 + struct berval tags[MAX_TAGGING_OPTIONS+1]; +#define MAX_TAGS_LEN 1024 + char tagbuf[MAX_TAGS_LEN]; + + assert( ad != NULL ); + assert( *ad == NULL ); /* temporary */ + + if( bv == NULL || BER_BVISNULL( bv ) || BER_BVISEMPTY( bv ) ) { + *text = "empty AttributeDescription"; + return rtn; + } + + /* make sure description is IA5 */ + if( ad_keystring( bv ) ) { + *text = "AttributeDescription contains inappropriate characters"; + return rtn; + } + + /* find valid base attribute type; parse in place */ + desc.ad_cname = *bv; + desc.ad_flags = 0; + BER_BVZERO( &desc.ad_tags ); + name = bv->bv_val; + options = ber_bvchr( bv, ';' ); + if ( options != NULL && (unsigned) ( options - name ) < bv->bv_len ) { + /* don't go past the end of the berval! */ + desc.ad_cname.bv_len = options - name; + } else { + options = NULL; + } + desc.ad_type = at_bvfind( &desc.ad_cname ); + if( desc.ad_type == NULL ) { + *text = "attribute type undefined"; + return rtn; + } + + if( is_at_operational( desc.ad_type ) && options != NULL ) { + *text = "operational attribute with options undefined"; + return rtn; + } + + /* + * parse options in place + */ + ntags = 0; + tagslen = 0; + optn = bv->bv_val + bv->bv_len; + + for( opt=options; opt != NULL; opt=next ) { + int optlen; + opt++; + next = strchrlen( opt, optn, ';', &optlen ); + + if( optlen == 0 ) { + *text = "zero length option is invalid"; + return rtn; + + } else if ( optlen == STRLENOF("binary") && + strncasecmp( opt, "binary", STRLENOF("binary") ) == 0 ) + { + /* binary option */ + if( slap_ad_is_binary( &desc ) ) { + *text = "option \"binary\" specified multiple times"; + return rtn; + } + + if( !slap_syntax_is_binary( desc.ad_type->sat_syntax )) { + /* not stored in binary, disallow option */ + *text = "option \"binary\" not supported with type"; + return rtn; + } + + desc.ad_flags |= SLAP_DESC_BINARY; + continue; + + } else if ( ad_find_option_definition( opt, optlen ) ) { + int i; + + if( opt[optlen-1] == '-' || + ( opt[optlen-1] == '=' && msad_range_hack )) { + desc.ad_flags |= SLAP_DESC_TAG_RANGE; + } + + if( ntags >= MAX_TAGGING_OPTIONS ) { + *text = "too many tagging options"; + return rtn; + } + + /* + * tags should be presented in sorted order, + * so run the array in reverse. + */ + for( i=ntags-1; i>=0; i-- ) { + int rc; + + rc = strncasecmp( opt, tags[i].bv_val, + (unsigned) optlen < tags[i].bv_len + ? (unsigned) optlen : tags[i].bv_len ); + + if( rc == 0 && (unsigned)optlen == tags[i].bv_len ) { + /* duplicate (ignore) */ + ntags--; + goto done; + + } else if ( rc > 0 || + ( rc == 0 && (unsigned)optlen > tags[i].bv_len )) + { + AC_MEMCPY( &tags[i+2], &tags[i+1], + (ntags-i-1)*sizeof(struct berval) ); + tags[i+1].bv_val = opt; + tags[i+1].bv_len = optlen; + goto done; + } + } + + if( ntags ) { + AC_MEMCPY( &tags[1], &tags[0], + ntags*sizeof(struct berval) ); + } + tags[0].bv_val = opt; + tags[0].bv_len = optlen; + +done:; + tagslen += optlen + 1; + ntags++; + + } else { + *text = "unrecognized option"; + return rtn; + } + } + + if( ntags > 0 ) { + int i; + + if( tagslen > MAX_TAGS_LEN ) { + *text = "tagging options too long"; + return rtn; + } + + desc.ad_tags.bv_val = tagbuf; + tagslen = 0; + + for( i=0; i<ntags; i++ ) { + AC_MEMCPY( &desc.ad_tags.bv_val[tagslen], + tags[i].bv_val, tags[i].bv_len ); + + tagslen += tags[i].bv_len; + desc.ad_tags.bv_val[tagslen++] = ';'; + } + + desc.ad_tags.bv_val[--tagslen] = '\0'; + desc.ad_tags.bv_len = tagslen; + } + + /* see if a matching description is already cached */ + for (d2 = desc.ad_type->sat_ad; d2; d2=d2->ad_next) { + if( d2->ad_flags != desc.ad_flags ) { + continue; + } + if( d2->ad_tags.bv_len != desc.ad_tags.bv_len ) { + continue; + } + if( d2->ad_tags.bv_len == 0 ) { + break; + } + if( strncasecmp( d2->ad_tags.bv_val, desc.ad_tags.bv_val, + desc.ad_tags.bv_len ) == 0 ) + { + break; + } + } + + /* Not found, add new one */ + while (d2 == NULL) { + size_t dlen = 0; + ldap_pvt_thread_mutex_lock( &desc.ad_type->sat_ad_mutex ); + /* check again now that we've locked */ + for (d2 = desc.ad_type->sat_ad; d2; d2=d2->ad_next) { + if (d2->ad_flags != desc.ad_flags) + continue; + if (d2->ad_tags.bv_len != desc.ad_tags.bv_len) + continue; + if (d2->ad_tags.bv_len == 0) + break; + if (strncasecmp(d2->ad_tags.bv_val, desc.ad_tags.bv_val, + desc.ad_tags.bv_len) == 0) + break; + } + if (d2) { + ldap_pvt_thread_mutex_unlock( &desc.ad_type->sat_ad_mutex ); + break; + } + + /* Allocate a single contiguous block. If there are no + * options, we just need space for the AttrDesc structure. + * Otherwise, we need to tack on the full name length + + * options length, + maybe tagging options length again. + */ + if (desc.ad_tags.bv_len || desc.ad_flags != SLAP_DESC_NONE) { + dlen = desc.ad_type->sat_cname.bv_len + 1; + if (desc.ad_tags.bv_len) { + dlen += 1 + desc.ad_tags.bv_len; + } + if ( slap_ad_is_binary( &desc ) ) { + dlen += 1 + STRLENOF(";binary") + desc.ad_tags.bv_len; + } + } + + d2 = ch_malloc(sizeof(AttributeDescription) + dlen); + d2->ad_next = NULL; + d2->ad_type = desc.ad_type; + d2->ad_flags = desc.ad_flags; + d2->ad_cname.bv_len = desc.ad_type->sat_cname.bv_len; + d2->ad_tags.bv_len = desc.ad_tags.bv_len; + ldap_pvt_thread_mutex_lock( &ad_index_mutex ); + d2->ad_index = ++ad_count; + ldap_pvt_thread_mutex_unlock( &ad_index_mutex ); + + if (dlen == 0) { + d2->ad_cname.bv_val = d2->ad_type->sat_cname.bv_val; + d2->ad_tags.bv_val = NULL; + } else { + char *cp, *op, *lp; + int j; + d2->ad_cname.bv_val = (char *)(d2+1); + strcpy(d2->ad_cname.bv_val, d2->ad_type->sat_cname.bv_val); + cp = d2->ad_cname.bv_val + d2->ad_cname.bv_len; + if( slap_ad_is_binary( &desc ) ) { + op = cp; + lp = NULL; + if( desc.ad_tags.bv_len ) { + lp = desc.ad_tags.bv_val; + while( strncasecmp(lp, "binary", STRLENOF("binary")) < 0 + && (lp = strchr( lp, ';' )) != NULL ) + ++lp; + if( lp != desc.ad_tags.bv_val ) { + *cp++ = ';'; + j = (lp + ? (unsigned) (lp - desc.ad_tags.bv_val - 1) + : strlen( desc.ad_tags.bv_val )); + cp = lutil_strncopy(cp, desc.ad_tags.bv_val, j); + } + } + cp = lutil_strcopy(cp, ";binary"); + if( lp != NULL ) { + *cp++ = ';'; + cp = lutil_strcopy(cp, lp); + } + d2->ad_cname.bv_len = cp - d2->ad_cname.bv_val; + if( desc.ad_tags.bv_len ) + ldap_pvt_str2lower(op); + j = 1; + } else { + j = 0; + } + if( desc.ad_tags.bv_len ) { + lp = d2->ad_cname.bv_val + d2->ad_cname.bv_len + j; + if ( j == 0 ) + *lp++ = ';'; + d2->ad_tags.bv_val = lp; + strcpy(lp, desc.ad_tags.bv_val); + ldap_pvt_str2lower(lp); + if( j == 0 ) + d2->ad_cname.bv_len += 1 + desc.ad_tags.bv_len; + } + } + /* Add new desc to list. We always want the bare Desc with + * no options to stay at the head of the list, assuming + * that one will be used most frequently. + */ + if (desc.ad_type->sat_ad == NULL || dlen == 0) { + d2->ad_next = desc.ad_type->sat_ad; + desc.ad_type->sat_ad = d2; + } else { + d2->ad_next = desc.ad_type->sat_ad->ad_next; + desc.ad_type->sat_ad->ad_next = d2; + } + ldap_pvt_thread_mutex_unlock( &desc.ad_type->sat_ad_mutex ); + } + + if( *ad == NULL ) { + *ad = d2; + } else { + **ad = *d2; + } + + return LDAP_SUCCESS; +} + +static int is_ad_subtags( + struct berval *subtagsbv, + struct berval *suptagsbv ) +{ + const char *suptags, *supp, *supdelimp, *supn; + const char *subtags, *subp, *subdelimp, *subn; + int suplen, sublen; + + subtags =subtagsbv->bv_val; + suptags =suptagsbv->bv_val; + subn = subtags + subtagsbv->bv_len; + supn = suptags + suptagsbv->bv_len; + + for( supp=suptags ; supp; supp=supdelimp ) { + supdelimp = strchrlen( supp, supn, ';', &suplen ); + if( supdelimp ) supdelimp++; + + for( subp=subtags ; subp; subp=subdelimp ) { + subdelimp = strchrlen( subp, subn, ';', &sublen ); + if( subdelimp ) subdelimp++; + + if ( suplen > sublen + ? ( suplen-1 == sublen && supp[suplen-1] == '-' + && strncmp( supp, subp, sublen ) == 0 ) + : ( ( suplen == sublen || supp[suplen-1] == '-' ) + && strncmp( supp, subp, suplen ) == 0 ) ) + { + goto match; + } + } + + return 0; +match:; + } + return 1; +} + +int is_ad_subtype( + AttributeDescription *sub, + AttributeDescription *super +) +{ + AttributeType *a; + int lr; + + for ( a = sub->ad_type; a; a=a->sat_sup ) { + if ( a == super->ad_type ) break; + } + if( !a ) { + return 0; + } + + /* ensure sub does support all flags of super */ + lr = sub->ad_tags.bv_len ? SLAP_DESC_TAG_RANGE : 0; + if(( super->ad_flags & ( sub->ad_flags | lr )) != super->ad_flags ) { + return 0; + } + + /* check for tagging options */ + if ( super->ad_tags.bv_len == 0 ) + return 1; + if ( sub->ad_tags.bv_len == 0 ) + return 0; + + return is_ad_subtags( &sub->ad_tags, &super->ad_tags ); +} + +int ad_inlist( + AttributeDescription *desc, + AttributeName *attrs ) +{ + if (! attrs ) return 0; + + for( ; attrs->an_name.bv_val; attrs++ ) { + AttributeType *a; + ObjectClass *oc; + + if ( attrs->an_desc ) { + int lr; + + if ( desc == attrs->an_desc ) { + return 1; + } + + /* + * EXTENSION: if requested description is preceeded by + * a '-' character, do not match on subtypes. + */ + if ( attrs->an_name.bv_val[0] == '-' ) { + continue; + } + + /* Is this a subtype of the requested attr? */ + for (a = desc->ad_type; a; a=a->sat_sup) { + if ( a == attrs->an_desc->ad_type ) + break; + } + if ( !a ) { + continue; + } + /* Does desc support all the requested flags? */ + lr = desc->ad_tags.bv_len ? SLAP_DESC_TAG_RANGE : 0; + if(( attrs->an_desc->ad_flags & (desc->ad_flags | lr)) + != attrs->an_desc->ad_flags ) { + continue; + } + /* Do the descs have compatible tags? */ + if ( attrs->an_desc->ad_tags.bv_len == 0 ) { + return 1; + } + if ( desc->ad_tags.bv_len == 0) { + continue; + } + if ( is_ad_subtags( &desc->ad_tags, + &attrs->an_desc->ad_tags ) ) { + return 1; + } + continue; + } + + if ( ber_bvccmp( &attrs->an_name, '*' ) ) { + if ( !is_at_operational( desc->ad_type ) ) { + return 1; + } + continue; + } + + if ( ber_bvccmp( &attrs->an_name, '+' ) ) { + if ( is_at_operational( desc->ad_type ) ) { + return 1; + } + continue; + } + + /* + * EXTENSION: see if requested description is @objectClass + * if so, return attributes which the class requires/allows + * else if requested description is !objectClass, return + * attributes which the class does not require/allow + */ + if ( !( attrs->an_flags & SLAP_AN_OCINITED )) { + if( attrs->an_name.bv_val ) { + switch( attrs->an_name.bv_val[0] ) { + case '@': /* @objectClass */ + case '+': /* +objectClass (deprecated) */ + case '!': { /* exclude */ + struct berval ocname; + ocname.bv_len = attrs->an_name.bv_len - 1; + ocname.bv_val = &attrs->an_name.bv_val[1]; + oc = oc_bvfind( &ocname ); + if ( oc && attrs->an_name.bv_val[0] == '!' ) { + attrs->an_flags |= SLAP_AN_OCEXCLUDE; + } else { + attrs->an_flags &= ~SLAP_AN_OCEXCLUDE; + } + } break; + + default: /* old (deprecated) way */ + oc = oc_bvfind( &attrs->an_name ); + } + attrs->an_oc = oc; + } + attrs->an_flags |= SLAP_AN_OCINITED; + } + oc = attrs->an_oc; + if( oc != NULL ) { + if ( attrs->an_flags & SLAP_AN_OCEXCLUDE ) { + if ( oc == slap_schema.si_oc_extensibleObject ) { + /* extensibleObject allows the return of anything */ + return 0; + } + + if( oc->soc_required ) { + /* allow return of required attributes */ + int i; + + for ( i = 0; oc->soc_required[i] != NULL; i++ ) { + for (a = desc->ad_type; a; a=a->sat_sup) { + if ( a == oc->soc_required[i] ) { + return 0; + } + } + } + } + + if( oc->soc_allowed ) { + /* allow return of allowed attributes */ + int i; + for ( i = 0; oc->soc_allowed[i] != NULL; i++ ) { + for (a = desc->ad_type; a; a=a->sat_sup) { + if ( a == oc->soc_allowed[i] ) { + return 0; + } + } + } + } + + return 1; + } + + if ( oc == slap_schema.si_oc_extensibleObject ) { + /* extensibleObject allows the return of anything */ + return 1; + } + + if( oc->soc_required ) { + /* allow return of required attributes */ + int i; + + for ( i = 0; oc->soc_required[i] != NULL; i++ ) { + for (a = desc->ad_type; a; a=a->sat_sup) { + if ( a == oc->soc_required[i] ) { + return 1; + } + } + } + } + + if( oc->soc_allowed ) { + /* allow return of allowed attributes */ + int i; + for ( i = 0; oc->soc_allowed[i] != NULL; i++ ) { + for (a = desc->ad_type; a; a=a->sat_sup) { + if ( a == oc->soc_allowed[i] ) { + return 1; + } + } + } + } + + } else { + const char *text; + + /* give it a chance of being retrieved by a proxy... */ + (void)slap_bv2undef_ad( &attrs->an_name, + &attrs->an_desc, &text, + SLAP_AD_PROXIED|SLAP_AD_NOINSERT ); + } + } + + return 0; +} + + +int slap_str2undef_ad( + const char *str, + AttributeDescription **ad, + const char **text, + unsigned flags ) +{ + struct berval bv; + bv.bv_val = (char *) str; + bv.bv_len = strlen( str ); + + return slap_bv2undef_ad( &bv, ad, text, flags ); +} + +int slap_bv2undef_ad( + struct berval *bv, + AttributeDescription **ad, + const char **text, + unsigned flags ) +{ + AttributeDescription *desc; + AttributeType *at; + + assert( ad != NULL ); + + if( bv == NULL || bv->bv_len == 0 ) { + *text = "empty AttributeDescription"; + return LDAP_UNDEFINED_TYPE; + } + + /* make sure description is IA5 */ + if( ad_keystring( bv ) ) { + *text = "AttributeDescription contains inappropriate characters"; + return LDAP_UNDEFINED_TYPE; + } + + /* use the appropriate type */ + if ( flags & SLAP_AD_PROXIED ) { + at = slap_schema.si_at_proxied; + + } else { + at = slap_schema.si_at_undefined; + } + + for( desc = at->sat_ad; desc; desc=desc->ad_next ) { + if( desc->ad_cname.bv_len == bv->bv_len && + !strcasecmp( desc->ad_cname.bv_val, bv->bv_val ) ) + { + break; + } + } + + if( !desc ) { + if ( flags & SLAP_AD_NOINSERT ) { + *text = NULL; + return LDAP_UNDEFINED_TYPE; + } + + desc = ch_malloc(sizeof(AttributeDescription) + 1 + + bv->bv_len); + + desc->ad_flags = SLAP_DESC_NONE; + BER_BVZERO( &desc->ad_tags ); + + desc->ad_cname.bv_len = bv->bv_len; + desc->ad_cname.bv_val = (char *)(desc+1); + strncpy(desc->ad_cname.bv_val, bv->bv_val, bv->bv_len); + desc->ad_cname.bv_val[bv->bv_len] = '\0'; + + /* canonical to upper case */ + ldap_pvt_str2upper( desc->ad_cname.bv_val ); + + /* shouldn't we protect this for concurrency? */ + desc->ad_type = at; + desc->ad_index = 0; + ldap_pvt_thread_mutex_lock( &ad_undef_mutex ); + desc->ad_next = desc->ad_type->sat_ad; + desc->ad_type->sat_ad = desc; + ldap_pvt_thread_mutex_unlock( &ad_undef_mutex ); + + Debug( LDAP_DEBUG_ANY, + "%s attributeDescription \"%s\" inserted.\n", + ( flags & SLAP_AD_PROXIED ) ? "PROXIED" : "UNKNOWN", + desc->ad_cname.bv_val, 0 ); + } + + if( !*ad ) { + *ad = desc; + } else { + **ad = *desc; + } + + return LDAP_SUCCESS; +} + +AttributeDescription * +slap_bv2tmp_ad( + struct berval *bv, + void *memctx ) +{ + AttributeDescription *ad = + slap_sl_mfuncs.bmf_malloc( sizeof(AttributeDescription) + + bv->bv_len + 1, memctx ); + + ad->ad_cname.bv_val = (char *)(ad+1); + strncpy( ad->ad_cname.bv_val, bv->bv_val, bv->bv_len+1 ); + ad->ad_cname.bv_len = bv->bv_len; + ad->ad_flags = SLAP_DESC_TEMPORARY; + ad->ad_type = slap_schema.si_at_undefined; + + return ad; +} + +static int +undef_promote( + AttributeType *at, + char *name, + AttributeType *nat ) +{ + AttributeDescription **u_ad, **n_ad; + + /* Get to last ad on the new type */ + for ( n_ad = &nat->sat_ad; *n_ad; n_ad = &(*n_ad)->ad_next ) ; + + for ( u_ad = &at->sat_ad; *u_ad; ) { + struct berval bv; + + ber_str2bv( name, 0, 0, &bv ); + + /* remove iff undef == name or undef == name;tag */ + if ( (*u_ad)->ad_cname.bv_len >= bv.bv_len + && strncasecmp( (*u_ad)->ad_cname.bv_val, bv.bv_val, bv.bv_len ) == 0 + && ( (*u_ad)->ad_cname.bv_val[ bv.bv_len ] == '\0' + || (*u_ad)->ad_cname.bv_val[ bv.bv_len ] == ';' ) ) + { + AttributeDescription *tmp = *u_ad; + + *u_ad = (*u_ad)->ad_next; + + tmp->ad_type = nat; + tmp->ad_next = NULL; + /* ad_cname was contiguous, no leak here */ + tmp->ad_cname = nat->sat_cname; + ldap_pvt_thread_mutex_lock( &ad_index_mutex ); + tmp->ad_index = ++ad_count; + ldap_pvt_thread_mutex_unlock( &ad_index_mutex ); + *n_ad = tmp; + n_ad = &tmp->ad_next; + } else { + u_ad = &(*u_ad)->ad_next; + } + } + + return 0; +} + +int +slap_ad_undef_promote( + char *name, + AttributeType *at ) +{ + int rc; + + ldap_pvt_thread_mutex_lock( &ad_undef_mutex ); + + rc = undef_promote( slap_schema.si_at_undefined, name, at ); + if ( rc == 0 ) { + rc = undef_promote( slap_schema.si_at_proxied, name, at ); + } + + ldap_pvt_thread_mutex_unlock( &ad_undef_mutex ); + + return rc; +} + +int +an_find( + AttributeName *a, + struct berval *s +) +{ + if( a == NULL ) return 0; + + for ( ; a->an_name.bv_val; a++ ) { + if ( a->an_name.bv_len != s->bv_len) continue; + if ( strcasecmp( s->bv_val, a->an_name.bv_val ) == 0 ) { + return( 1 ); + } + } + + return( 0 ); +} + +/* + * Convert a delimited string into a list of AttributeNames; add + * on to an existing list if it was given. If the string is not + * a valid attribute name, if a '-' is prepended it is skipped + * and the remaining name is tried again; if a '@' (or '+') is + * prepended, an objectclass name is searched instead; if a '!' + * is prepended, the objectclass name is negated. + * + * NOTE: currently, if a valid attribute name is not found, the + * same string is also checked as valid objectclass name; however, + * this behavior is deprecated. + */ +AttributeName * +str2anlist( AttributeName *an, char *in, const char *brkstr ) +{ + char *str; + char *s; + char *lasts; + int i, j; + const char *text; + AttributeName *anew; + + /* find last element in list */ + i = 0; + if ( an != NULL ) { + for ( i = 0; !BER_BVISNULL( &an[ i ].an_name ) ; i++) + ; + } + + /* protect the input string from strtok */ + str = ch_strdup( in ); + + /* Count words in string */ + j = 1; + for ( s = str; *s; s++ ) { + if ( strchr( brkstr, *s ) != NULL ) { + j++; + } + } + + an = ch_realloc( an, ( i + j + 1 ) * sizeof( AttributeName ) ); + anew = an + i; + for ( s = ldap_pvt_strtok( str, brkstr, &lasts ); + s != NULL; + s = ldap_pvt_strtok( NULL, brkstr, &lasts ) ) + { + /* put a stop mark */ + BER_BVZERO( &anew[1].an_name ); + + anew->an_desc = NULL; + anew->an_oc = NULL; + anew->an_flags = 0; + ber_str2bv(s, 0, 1, &anew->an_name); + slap_bv2ad(&anew->an_name, &anew->an_desc, &text); + if ( !anew->an_desc ) { + switch( anew->an_name.bv_val[0] ) { + case '-': { + struct berval adname; + adname.bv_len = anew->an_name.bv_len - 1; + adname.bv_val = &anew->an_name.bv_val[1]; + slap_bv2ad(&adname, &anew->an_desc, &text); + if ( !anew->an_desc ) { + goto reterr; + } + } break; + + case '@': + case '+': /* (deprecated) */ + case '!': { + struct berval ocname; + ocname.bv_len = anew->an_name.bv_len - 1; + ocname.bv_val = &anew->an_name.bv_val[1]; + anew->an_oc = oc_bvfind( &ocname ); + if ( !anew->an_oc ) { + goto reterr; + } + + if ( anew->an_name.bv_val[0] == '!' ) { + anew->an_flags |= SLAP_AN_OCEXCLUDE; + } + } break; + + default: + /* old (deprecated) way */ + anew->an_oc = oc_bvfind( &anew->an_name ); + if ( !anew->an_oc ) { + goto reterr; + } + } + } + anew->an_flags |= SLAP_AN_OCINITED; + anew++; + } + + BER_BVZERO( &anew->an_name ); + free( str ); + return( an ); + +reterr: + anlist_free( an, 1, NULL ); + + /* + * overwrites input string + * on error! + */ + strcpy( in, s ); + free( str ); + return NULL; +} + +void +anlist_free( AttributeName *an, int freename, void *ctx ) +{ + if ( an == NULL ) { + return; + } + + if ( freename ) { + int i; + + for ( i = 0; an[i].an_name.bv_val; i++ ) { + ber_memfree_x( an[i].an_name.bv_val, ctx ); + } + } + + ber_memfree_x( an, ctx ); +} + +char **anlist2charray_x( AttributeName *an, int dup, void *ctx ) +{ + char **attrs; + int i; + + if ( an != NULL ) { + for ( i = 0; !BER_BVISNULL( &an[i].an_name ); i++ ) + ; + attrs = (char **) slap_sl_malloc( (i + 1) * sizeof(char *), ctx ); + for ( i = 0; !BER_BVISNULL( &an[i].an_name ); i++ ) { + if ( dup ) + attrs[i] = ch_strdup( an[i].an_name.bv_val ); + else + attrs[i] = an[i].an_name.bv_val; + } + attrs[i] = NULL; + } else { + attrs = NULL; + } + + return attrs; +} + +char **anlist2charray( AttributeName *an, int dup ) +{ + return anlist2charray_x( an, dup, NULL ); +} + +char** +anlist2attrs( AttributeName * anlist ) +{ + int i, j, k = 0; + int n; + char **attrs; + ObjectClass *oc; + + if ( anlist == NULL ) + return NULL; + + for ( i = 0; anlist[i].an_name.bv_val; i++ ) { + if ( ( oc = anlist[i].an_oc ) ) { + for ( j = 0; oc->soc_required && oc->soc_required[j]; j++ ) ; + k += j; + for ( j = 0; oc->soc_allowed && oc->soc_allowed[j]; j++ ) ; + k += j; + } + } + + if ( i == 0 ) + return NULL; + + attrs = anlist2charray( anlist, 1 ); + + n = i; + + if ( k ) + attrs = (char **) ch_realloc( attrs, (i + k + 1) * sizeof( char * )); + + for ( i = 0; anlist[i].an_name.bv_val; i++ ) { + if ( ( oc = anlist[i].an_oc ) ) { + for ( j = 0; oc->soc_required && oc->soc_required[j]; j++ ) { + attrs[n++] = ch_strdup( + oc->soc_required[j]->sat_cname.bv_val ); + } + for ( j = 0; oc->soc_allowed && oc->soc_allowed[j]; j++ ) { + attrs[n++] = ch_strdup( + oc->soc_allowed[j]->sat_cname.bv_val ); + } + } + } + + if ( attrs ) + attrs[n] = NULL; + + i = 0; + while ( attrs && attrs[i] ) { + if ( *attrs[i] == '@' ) { + ch_free( attrs[i] ); + for ( j = i; attrs[j]; j++ ) { + attrs[j] = attrs[j+1]; + } + } else { + i++; + } + } + + for ( i = 0; attrs && attrs[i]; i++ ) { + j = i + 1; + while ( attrs && attrs[j] ) { + if ( !strcmp( attrs[i], attrs[j] )) { + ch_free( attrs[j] ); + for ( k = j; attrs && attrs[k]; k++ ) { + attrs[k] = attrs[k+1]; + } + } else { + j++; + } + } + } + + if ( i != n ) + attrs = (char **) ch_realloc( attrs, (i+1) * sizeof( char * )); + + return attrs; +} + +#define LBUFSIZ 80 +AttributeName* +file2anlist( AttributeName *an, const char *fname, const char *brkstr ) +{ + FILE *fp; + char *line = NULL; + char *lcur = NULL; + char *c; + size_t lmax = LBUFSIZ; + + fp = fopen( fname, "r" ); + if ( fp == NULL ) { + Debug( LDAP_DEBUG_ANY, + "get_attrs_from_file: failed to open attribute list file " + "\"%s\": %s\n", fname, strerror(errno), 0 ); + return NULL; + } + + lcur = line = (char *) ch_malloc( lmax ); + if ( !line ) { + Debug( LDAP_DEBUG_ANY, + "get_attrs_from_file: could not allocate memory\n", + 0, 0, 0 ); + fclose(fp); + return NULL; + } + + while ( fgets( lcur, LBUFSIZ, fp ) != NULL ) { + if ( ( c = strchr( lcur, '\n' ) ) ) { + if ( c == line ) { + *c = '\0'; + } else if ( *(c-1) == '\r' ) { + *(c-1) = '\0'; + } else { + *c = '\0'; + } + } else { + lmax += LBUFSIZ; + line = (char *) ch_realloc( line, lmax ); + if ( !line ) { + Debug( LDAP_DEBUG_ANY, + "get_attrs_from_file: could not allocate memory\n", + 0, 0, 0 ); + fclose(fp); + return NULL; + } + lcur = line + strlen( line ); + continue; + } + an = str2anlist( an, line, brkstr ); + if ( an == NULL ) + break; + lcur = line; + } + ch_free( line ); + fclose(fp); + return an; +} +#undef LBUFSIZ + +/* Define an attribute option. */ +int +ad_define_option( const char *name, const char *fname, int lineno ) +{ + int i; + unsigned int optlen; + + if ( options == &lang_option ) { + options = NULL; + option_count = 0; + } + if ( name == NULL ) + return 0; + + optlen = 0; + do { + if ( !DESC_CHAR( name[optlen] ) ) { + /* allow trailing '=', same as '-' */ + if ( name[optlen] == '=' && !name[optlen+1] ) { + msad_range_hack = 1; + continue; + } + Debug( LDAP_DEBUG_ANY, + "%s: line %d: illegal option name \"%s\"\n", + fname, lineno, name ); + return 1; + } + } while ( name[++optlen] ); + + options = ch_realloc( options, + (option_count+1) * sizeof(Attr_option) ); + + if ( strcasecmp( name, "binary" ) == 0 + || ad_find_option_definition( name, optlen ) ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: option \"%s\" is already defined\n", + fname, lineno, name ); + return 1; + } + + for ( i = option_count; i; --i ) { + if ( strcasecmp( name, options[i-1].name.bv_val ) >= 0 ) + break; + options[i] = options[i-1]; + } + + options[i].name.bv_val = ch_strdup( name ); + options[i].name.bv_len = optlen; + options[i].prefix = (name[optlen-1] == '-') || + (name[optlen-1] == '='); + + if ( i != option_count && + options[i].prefix && + optlen < options[i+1].name.bv_len && + strncasecmp( name, options[i+1].name.bv_val, optlen ) == 0 ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: option \"%s\" overrides previous option\n", + fname, lineno, name ); + return 1; + } + + option_count++; + return 0; +} + +void +ad_unparse_options( BerVarray *res ) +{ + int i; + for ( i = 0; i < option_count; i++ ) { + value_add_one( res, &options[i].name ); + } +} + +/* Find the definition of the option name or prefix matching the arguments */ +static Attr_option * +ad_find_option_definition( const char *opt, int optlen ) +{ + int top = 0, bot = option_count; + while ( top < bot ) { + int mid = (top + bot) / 2; + int mlen = options[mid].name.bv_len; + char *mname = options[mid].name.bv_val; + int j; + if ( optlen < mlen ) { + j = strncasecmp( opt, mname, optlen ) - 1; + } else { + j = strncasecmp( opt, mname, mlen ); + if ( j==0 && (optlen==mlen || options[mid].prefix) ) + return &options[mid]; + } + if ( j < 0 ) + bot = mid; + else + top = mid + 1; + } + return NULL; +} + +MatchingRule *ad_mr( + AttributeDescription *ad, + unsigned usage ) +{ + switch( usage & SLAP_MR_TYPE_MASK ) { + case SLAP_MR_NONE: + case SLAP_MR_EQUALITY: + return ad->ad_type->sat_equality; + break; + case SLAP_MR_ORDERING: + return ad->ad_type->sat_ordering; + break; + case SLAP_MR_SUBSTR: + return ad->ad_type->sat_substr; + break; + case SLAP_MR_EXT: + default: + assert( 0 /* ad_mr: bad usage */); + } + return NULL; +} diff --git a/servers/slapd/add.c b/servers/slapd/add.c new file mode 100644 index 0000000..302d394 --- /dev/null +++ b/servers/slapd/add.c @@ -0,0 +1,686 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/time.h> +#include <ac/socket.h> + +#include "lutil.h" +#include "slap.h" + +int +do_add( Operation *op, SlapReply *rs ) +{ + BerElement *ber = op->o_ber; + char *last; + struct berval dn = BER_BVNULL; + ber_len_t len; + ber_tag_t tag; + Modifications *modlist = NULL; + Modifications **modtail = &modlist; + Modifications tmp; + char textbuf[ SLAP_TEXT_BUFLEN ]; + size_t textlen = sizeof( textbuf ); + int rc = 0; + int freevals = 1; + OpExtraDB oex; + + Debug( LDAP_DEBUG_TRACE, "%s do_add\n", + op->o_log_prefix, 0, 0 ); + + /* + * Parse the add request. It looks like this: + * + * AddRequest := [APPLICATION 14] SEQUENCE { + * name DistinguishedName, + * attrs SEQUENCE OF SEQUENCE { + * type AttributeType, + * values SET OF AttributeValue + * } + * } + */ + + /* get the name */ + if ( ber_scanf( ber, "{m", /*}*/ &dn ) == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, "%s do_add: ber_scanf failed\n", + op->o_log_prefix, 0, 0 ); + send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" ); + return SLAPD_DISCONNECT; + } + + Debug( LDAP_DEBUG_ARGS, "%s do_add: dn (%s)\n", + op->o_log_prefix, dn.bv_val, 0 ); + + /* get the attrs */ + for ( tag = ber_first_element( ber, &len, &last ); tag != LBER_DEFAULT; + tag = ber_next_element( ber, &len, last ) ) + { + Modifications *mod; + ber_tag_t rtag; + + tmp.sml_nvalues = NULL; + + rtag = ber_scanf( ber, "{m{W}}", &tmp.sml_type, &tmp.sml_values ); + + if ( rtag == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, "%s do_add: decoding error\n", + op->o_log_prefix, 0, 0 ); + send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" ); + rs->sr_err = SLAPD_DISCONNECT; + goto done; + } + + if ( tmp.sml_values == NULL ) { + Debug( LDAP_DEBUG_ANY, "%s do_add: no values for type %s\n", + op->o_log_prefix, tmp.sml_type.bv_val, 0 ); + send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR, + "no values for attribute type" ); + goto done; + } + + mod = (Modifications *) ch_malloc( sizeof(Modifications) ); + mod->sml_op = LDAP_MOD_ADD; + mod->sml_flags = 0; + mod->sml_next = NULL; + mod->sml_desc = NULL; + mod->sml_type = tmp.sml_type; + mod->sml_values = tmp.sml_values; + mod->sml_nvalues = NULL; + + *modtail = mod; + modtail = &mod->sml_next; + } + + if ( ber_scanf( ber, /*{*/ "}") == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, "%s do_add: ber_scanf failed\n", + op->o_log_prefix, 0, 0 ); + send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" ); + rs->sr_err = SLAPD_DISCONNECT; + goto done; + } + + if ( get_ctrls( op, rs, 1 ) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s do_add: get_ctrls failed\n", + op->o_log_prefix, 0, 0 ); + goto done; + } + + rs->sr_err = dnPrettyNormal( NULL, &dn, &op->o_req_dn, &op->o_req_ndn, + op->o_tmpmemctx ); + + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s do_add: invalid dn (%s)\n", + op->o_log_prefix, dn.bv_val, 0 ); + send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid DN" ); + goto done; + } + + op->ora_e = entry_alloc(); + ber_dupbv( &op->ora_e->e_name, &op->o_req_dn ); + ber_dupbv( &op->ora_e->e_nname, &op->o_req_ndn ); + + Statslog( LDAP_DEBUG_STATS, "%s ADD dn=\"%s\"\n", + op->o_log_prefix, op->o_req_dn.bv_val, 0, 0, 0 ); + + if ( modlist == NULL ) { + send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR, + "no attributes provided" ); + goto done; + } + + if ( dn_match( &op->ora_e->e_nname, &slap_empty_bv ) ) { + /* protocolError may be a more appropriate error */ + send_ldap_error( op, rs, LDAP_ALREADY_EXISTS, + "root DSE already exists" ); + goto done; + + } else if ( dn_match( &op->ora_e->e_nname, &frontendDB->be_schemandn ) ) { + send_ldap_error( op, rs, LDAP_ALREADY_EXISTS, + "subschema subentry already exists" ); + goto done; + } + + rs->sr_err = slap_mods_check( op, modlist, &rs->sr_text, + textbuf, textlen, NULL ); + + if ( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto done; + } + + /* temporary; remove if not invoking backend function */ + op->ora_modlist = modlist; + + /* call this so global overlays/SLAPI have access to ora_e */ + rs->sr_err = slap_mods2entry( op->ora_modlist, &op->ora_e, + 1, 0, &rs->sr_text, textbuf, textlen ); + if ( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto done; + } + + freevals = 0; + + oex.oe.oe_key = (void *)do_add; + oex.oe_db = NULL; + LDAP_SLIST_INSERT_HEAD(&op->o_extra, &oex.oe, oe_next); + + op->o_bd = frontendDB; + rc = frontendDB->be_add( op, rs ); + LDAP_SLIST_REMOVE(&op->o_extra, &oex.oe, OpExtra, oe_next); + +#ifdef LDAP_X_TXN + if ( rc == LDAP_X_TXN_SPECIFY_OKAY ) { + /* skip cleanup */ + return rc; + } else +#endif + if ( rc == 0 ) { + if ( op->ora_e != NULL && oex.oe_db != NULL ) { + BackendDB *bd = op->o_bd; + + op->o_bd = oex.oe_db; + + be_entry_release_w( op, op->ora_e ); + + op->ora_e = NULL; + op->o_bd = bd; + } + } + +done:; + if ( modlist != NULL ) { + /* in case of error, free the values as well */ + slap_mods_free( modlist, freevals ); + } + + if ( op->ora_e != NULL ) { + entry_free( op->ora_e ); + } + op->o_tmpfree( op->o_req_dn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx ); + + return rc; +} + +int +fe_op_add( Operation *op, SlapReply *rs ) +{ + Modifications **modtail = &op->ora_modlist; + int rc = 0; + BackendDB *op_be, *bd = op->o_bd; + char textbuf[ SLAP_TEXT_BUFLEN ]; + size_t textlen = sizeof( textbuf ); + + /* + * We could be serving multiple database backends. Select the + * appropriate one, or send a referral to our "referral server" + * if we don't hold it. + */ + op->o_bd = select_backend( &op->ora_e->e_nname, 1 ); + if ( op->o_bd == NULL ) { + op->o_bd = bd; + rs->sr_ref = referral_rewrite( default_referral, + NULL, &op->ora_e->e_name, LDAP_SCOPE_DEFAULT ); + if ( !rs->sr_ref ) rs->sr_ref = default_referral; + if ( rs->sr_ref ) { + rs->sr_err = LDAP_REFERRAL; + send_ldap_result( op, rs ); + + if ( rs->sr_ref != default_referral ) { + ber_bvarray_free( rs->sr_ref ); + } + } else { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "no global superior knowledge" ); + } + goto done; + } + + /* If we've got a glued backend, check the real backend */ + op_be = op->o_bd; + if ( SLAP_GLUE_INSTANCE( op->o_bd )) { + op->o_bd = select_backend( &op->ora_e->e_nname, 0 ); + } + + /* check restrictions */ + if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto done; + } + + /* check for referrals */ + if( backend_check_referrals( op, rs ) != LDAP_SUCCESS ) { + goto done; + } + + rs->sr_err = slap_mods_obsolete_check( op, op->ora_modlist, + &rs->sr_text, textbuf, textlen ); + + if ( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto done; + } + + /* + * do the add if 1 && (2 || 3) + * 1) there is an add function implemented in this backend; + * 2) this backend is the provider for what it holds; + * 3) it's a replica and the dn supplied is the updatedn. + */ + if ( op->o_bd->be_add ) { + /* do the update here */ + int repl_user = be_isupdate( op ); + if ( !SLAP_SINGLE_SHADOW(op->o_bd) || repl_user ) { + int update = !BER_BVISEMPTY( &op->o_bd->be_update_ndn ); + + op->o_bd = op_be; + + if ( !update ) { + rs->sr_err = slap_mods_no_user_mod_check( op, op->ora_modlist, + &rs->sr_text, textbuf, textlen ); + + if ( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto done; + } + } + + if ( !repl_user ) { + /* go to the last mod */ + for ( modtail = &op->ora_modlist; + *modtail != NULL; + modtail = &(*modtail)->sml_next ) + { + assert( (*modtail)->sml_op == LDAP_MOD_ADD ); + assert( (*modtail)->sml_desc != NULL ); + } + + + /* check for unmodifiable attributes */ + rs->sr_err = slap_mods_no_repl_user_mod_check( op, + op->ora_modlist, &rs->sr_text, textbuf, textlen ); + if ( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto done; + } + } + + rc = op->o_bd->be_add( op, rs ); + if ( rc == LDAP_SUCCESS ) { + OpExtra *oex; + /* NOTE: be_entry_release_w() is + * called by do_add(), so that global + * overlays on the way back can + * at least read the entry */ + LDAP_SLIST_FOREACH(oex, &op->o_extra, oe_next) { + if ( oex->oe_key == (void *)do_add ) { + ((OpExtraDB *)oex)->oe_db = op->o_bd; + break; + } + } + } + + } else { + BerVarray defref = NULL; + + defref = op->o_bd->be_update_refs + ? op->o_bd->be_update_refs : default_referral; + + if ( defref != NULL ) { + rs->sr_ref = referral_rewrite( defref, + NULL, &op->ora_e->e_name, LDAP_SCOPE_DEFAULT ); + if ( rs->sr_ref == NULL ) rs->sr_ref = defref; + rs->sr_err = LDAP_REFERRAL; + if (!rs->sr_ref) rs->sr_ref = default_referral; + send_ldap_result( op, rs ); + + if ( rs->sr_ref != default_referral ) { + ber_bvarray_free( rs->sr_ref ); + } + } else { + send_ldap_error( op, rs, + LDAP_UNWILLING_TO_PERFORM, + "shadow context; no update referral" ); + } + } + } else { + Debug( LDAP_DEBUG_ARGS, "do_add: no backend support\n", 0, 0, 0 ); + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "operation not supported within namingContext" ); + } + +done:; + op->o_bd = bd; + return rc; +} + +int +slap_mods2entry( + Modifications *mods, + Entry **e, + int initial, + int dup, + const char **text, + char *textbuf, size_t textlen ) +{ + Attribute **tail; + int i; + + if ( initial ) { + assert( (*e)->e_attrs == NULL ); + } + + for ( tail = &(*e)->e_attrs; *tail != NULL; tail = &(*tail)->a_next ) + ; + + *text = textbuf; + + for( ; mods != NULL; mods = mods->sml_next ) { + Attribute *attr; + + assert( mods->sml_desc != NULL ); + + attr = attr_find( (*e)->e_attrs, mods->sml_desc ); + + if( attr != NULL ) { +#define SLURPD_FRIENDLY +#ifdef SLURPD_FRIENDLY + int j; + + if ( !initial ) { + /* + * This check allows overlays to override operational + * attributes by setting them directly in the entry. + * We assume slap_mods_no_user_mod_check() was called + * with the user modifications. + */ + *text = NULL; + return LDAP_SUCCESS; + } + + i = attr->a_numvals; + j = mods->sml_numvals; + attr->a_numvals += j; + j++; /* NULL */ + + attr->a_vals = ch_realloc( attr->a_vals, + sizeof( struct berval ) * (i+j) ); + + /* checked for duplicates in slap_mods_check */ + + if ( dup ) { + for ( j = 0; mods->sml_values[j].bv_val; j++ ) { + ber_dupbv( &attr->a_vals[i+j], &mods->sml_values[j] ); + } + BER_BVZERO( &attr->a_vals[i+j] ); + j++; + } else { + AC_MEMCPY( &attr->a_vals[i], mods->sml_values, + sizeof( struct berval ) * j ); + } + + if( mods->sml_nvalues ) { + attr->a_nvals = ch_realloc( attr->a_nvals, + sizeof( struct berval ) * (i+j) ); + if ( dup ) { + for ( j = 0; mods->sml_nvalues[j].bv_val; j++ ) { + ber_dupbv( &attr->a_nvals[i+j], &mods->sml_nvalues[j] ); + } + BER_BVZERO( &attr->a_nvals[i+j] ); + } else { + AC_MEMCPY( &attr->a_nvals[i], mods->sml_nvalues, + sizeof( struct berval ) * j ); + } + } else { + attr->a_nvals = attr->a_vals; + } + + continue; +#else + snprintf( textbuf, textlen, + "attribute '%s' provided more than once", + mods->sml_desc->ad_cname.bv_val ); + *text = textbuf; + return LDAP_TYPE_OR_VALUE_EXISTS; +#endif + } + + attr = attr_alloc( mods->sml_desc ); + + /* move values to attr structure */ + i = mods->sml_numvals; + attr->a_numvals = mods->sml_numvals; + if ( dup ) { + attr->a_vals = (BerVarray) ch_calloc( i+1, sizeof( BerValue )); + for ( i = 0; mods->sml_values[i].bv_val; i++ ) { + ber_dupbv( &attr->a_vals[i], &mods->sml_values[i] ); + } + BER_BVZERO( &attr->a_vals[i] ); + } else { + attr->a_vals = mods->sml_values; + } + + if ( mods->sml_nvalues ) { + if ( dup ) { + i = mods->sml_numvals; + attr->a_nvals = (BerVarray) ch_calloc( i+1, sizeof( BerValue )); + for ( i = 0; mods->sml_nvalues[i].bv_val; i++ ) { + ber_dupbv( &attr->a_nvals[i], &mods->sml_nvalues[i] ); + } + BER_BVZERO( &attr->a_nvals[i] ); + } else { + attr->a_nvals = mods->sml_nvalues; + } + } else { + attr->a_nvals = attr->a_vals; + } + + *tail = attr; + tail = &attr->a_next; + } + + *text = NULL; + + return LDAP_SUCCESS; +} + +int +slap_entry2mods( + Entry *e, + Modifications **mods, + const char **text, + char *textbuf, size_t textlen ) +{ + Modifications *modhead = NULL; + Modifications *mod; + Modifications **modtail = &modhead; + Attribute *a_new; + AttributeDescription *a_new_desc; + int i, count; + + a_new = e->e_attrs; + + while ( a_new != NULL ) { + a_new_desc = a_new->a_desc; + mod = (Modifications *) ch_malloc( sizeof( Modifications )); + + mod->sml_op = LDAP_MOD_REPLACE; + mod->sml_flags = 0; + + mod->sml_type = a_new_desc->ad_cname; + + count = a_new->a_numvals; + mod->sml_numvals = a_new->a_numvals; + + mod->sml_values = (struct berval*) ch_malloc( + (count+1) * sizeof( struct berval) ); + + /* see slap_mods_check() comments... + * if a_vals == a_nvals, there is no normalizer. + * in this case, mod->sml_nvalues must be left NULL. + */ + if ( a_new->a_vals != a_new->a_nvals ) { + mod->sml_nvalues = (struct berval*) ch_malloc( + (count+1) * sizeof( struct berval) ); + } else { + mod->sml_nvalues = NULL; + } + + for ( i = 0; i < count; i++ ) { + ber_dupbv(mod->sml_values+i, a_new->a_vals+i); + if ( mod->sml_nvalues ) { + ber_dupbv( mod->sml_nvalues+i, a_new->a_nvals+i ); + } + } + + mod->sml_values[count].bv_val = NULL; + mod->sml_values[count].bv_len = 0; + + if ( mod->sml_nvalues ) { + mod->sml_nvalues[count].bv_val = NULL; + mod->sml_nvalues[count].bv_len = 0; + } + + mod->sml_desc = a_new_desc; + mod->sml_next =NULL; + *modtail = mod; + modtail = &mod->sml_next; + a_new = a_new->a_next; + } + + *mods = modhead; + + return LDAP_SUCCESS; +} + +int slap_add_opattrs( + Operation *op, + const char **text, + char *textbuf, + size_t textlen, + int manage_ctxcsn ) +{ + struct berval name, timestamp, csn = BER_BVNULL; + struct berval nname, tmp; + char timebuf[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + char csnbuf[ LDAP_PVT_CSNSTR_BUFSIZE ]; + Attribute *a; + + if ( SLAP_LASTMOD( op->o_bd ) ) { + char *ptr; + int gotcsn = 0; + + timestamp.bv_val = timebuf; + a = attr_find( op->ora_e->e_attrs, slap_schema.si_ad_entryCSN ); + if ( a ) { + gotcsn = 1; + csn = a->a_vals[0]; + } + if ( BER_BVISEMPTY( &op->o_csn )) { + if ( !gotcsn ) { + csn.bv_val = csnbuf; + csn.bv_len = sizeof(csnbuf); + slap_get_csn( op, &csn, manage_ctxcsn ); + } else { + if ( manage_ctxcsn ) + slap_queue_csn( op, &csn ); + } + } else { + csn = op->o_csn; + } + ptr = ber_bvchr( &csn, '#' ); + if ( ptr ) { + timestamp.bv_len = STRLENOF("YYYYMMDDHHMMSSZ"); + AC_MEMCPY( timebuf, csn.bv_val, timestamp.bv_len ); + timebuf[timestamp.bv_len-1] = 'Z'; + timebuf[timestamp.bv_len] = '\0'; + } else { + time_t now = slap_get_time(); + + timestamp.bv_len = sizeof(timebuf); + + slap_timestamp( &now, ×tamp ); + } + + if ( BER_BVISEMPTY( &op->o_dn ) ) { + BER_BVSTR( &name, SLAPD_ANONYMOUS ); + nname = name; + } else { + name = op->o_dn; + nname = op->o_ndn; + } + + a = attr_find( op->ora_e->e_attrs, + slap_schema.si_ad_entryUUID ); + if ( !a ) { + char uuidbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE ]; + + tmp.bv_len = lutil_uuidstr( uuidbuf, sizeof( uuidbuf ) ); + tmp.bv_val = uuidbuf; + + attr_merge_normalize_one( op->ora_e, + slap_schema.si_ad_entryUUID, &tmp, op->o_tmpmemctx ); + } + + a = attr_find( op->ora_e->e_attrs, + slap_schema.si_ad_creatorsName ); + if ( !a ) { + attr_merge_one( op->ora_e, + slap_schema.si_ad_creatorsName, &name, &nname ); + } + + a = attr_find( op->ora_e->e_attrs, + slap_schema.si_ad_createTimestamp ); + if ( !a ) { + attr_merge_one( op->ora_e, + slap_schema.si_ad_createTimestamp, ×tamp, NULL ); + } + + if ( !gotcsn ) { + attr_merge_one( op->ora_e, + slap_schema.si_ad_entryCSN, &csn, NULL ); + } + + a = attr_find( op->ora_e->e_attrs, + slap_schema.si_ad_modifiersName ); + if ( !a ) { + attr_merge_one( op->ora_e, + slap_schema.si_ad_modifiersName, &name, &nname ); + } + + a = attr_find( op->ora_e->e_attrs, + slap_schema.si_ad_modifyTimestamp ); + if ( !a ) { + attr_merge_one( op->ora_e, + slap_schema.si_ad_modifyTimestamp, ×tamp, NULL ); + } + } + + return LDAP_SUCCESS; +} diff --git a/servers/slapd/alock.c b/servers/slapd/alock.c new file mode 100644 index 0000000..26406d5 --- /dev/null +++ b/servers/slapd/alock.c @@ -0,0 +1,718 @@ +/* alock.c - access lock library */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2021 The OpenLDAP Foundation. + * Portions Copyright 2004-2005 Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Emily Backes at Symas + * Corporation for inclusion in OpenLDAP Software. + */ + +#include "portable.h" + +#if SLAPD_BDB || SLAPD_HDB + +#include <lber.h> +#include "alock.h" +#include "lutil.h" + +#include <ac/stdlib.h> +#include <ac/string.h> +#include <ac/unistd.h> +#include <ac/errno.h> +#include <ac/assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#ifdef HAVE_SYS_FILE_H +#include <sys/file.h> +#endif +#include <fcntl.h> + +#ifdef _WIN32 +#include <windows.h> +#include <stdio.h> +#include <io.h> +#endif + + +static int +alock_grab_lock ( int fd, int slot ) +{ + int res; + +#if defined( HAVE_LOCKF ) + res = lseek (fd, (off_t) (ALOCK_SLOT_SIZE * slot), SEEK_SET); + if (res == -1) return -1; + res = lockf (fd, F_LOCK, (off_t) ALOCK_SLOT_SIZE); +#elif defined( HAVE_FCNTL ) + struct flock lock_info; + (void) memset ((void *) &lock_info, 0, sizeof (struct flock)); + + lock_info.l_type = F_WRLCK; + lock_info.l_whence = SEEK_SET; + lock_info.l_start = (off_t) (ALOCK_SLOT_SIZE * slot); + lock_info.l_len = (off_t) ALOCK_SLOT_SIZE; + + res = fcntl (fd, F_SETLKW, &lock_info); +#elif defined( _WIN32 ) + OVERLAPPED ov; + HANDLE hh = _get_osfhandle ( fd ); + ov.hEvent = 0; + ov.Offset = ALOCK_SLOT_SIZE*slot; + ov.OffsetHigh = 0; + res = LockFileEx( hh, LOCKFILE_EXCLUSIVE_LOCK, 0, + ALOCK_SLOT_SIZE, 0, &ov ) ? 0 : -1; +#else +# error alock needs lockf, fcntl, or LockFile[Ex] +#endif + if (res == -1) { + assert (errno != EDEADLK); + return -1; + } + return 0; +} + +static int +alock_release_lock ( int fd, int slot ) +{ + int res; + +#if defined( HAVE_LOCKF ) + res = lseek (fd, (off_t) (ALOCK_SLOT_SIZE * slot), SEEK_SET); + if (res == -1) return -1; + res = lockf (fd, F_ULOCK, (off_t) ALOCK_SLOT_SIZE); + if (res == -1) return -1; +#elif defined ( HAVE_FCNTL ) + struct flock lock_info; + (void) memset ((void *) &lock_info, 0, sizeof (struct flock)); + + lock_info.l_type = F_UNLCK; + lock_info.l_whence = SEEK_SET; + lock_info.l_start = (off_t) (ALOCK_SLOT_SIZE * slot); + lock_info.l_len = (off_t) ALOCK_SLOT_SIZE; + + res = fcntl (fd, F_SETLKW, &lock_info); + if (res == -1) return -1; +#elif defined( _WIN32 ) + HANDLE hh = _get_osfhandle ( fd ); + if ( !UnlockFile ( hh, ALOCK_SLOT_SIZE*slot, 0, + ALOCK_SLOT_SIZE, 0 )) + return -1; +#else +# error alock needs lockf, fcntl, or LockFile[Ex] +#endif + + return 0; +} + +static int +alock_share_lock ( int fd, int slot ) +{ + int res; + +#if defined( HAVE_LOCKF ) + res = 0; /* lockf has no shared locks */ +#elif defined ( HAVE_FCNTL ) + struct flock lock_info; + (void) memset ((void *) &lock_info, 0, sizeof (struct flock)); + + /* The shared lock replaces the existing lock */ + lock_info.l_type = F_RDLCK; + lock_info.l_whence = SEEK_SET; + lock_info.l_start = (off_t) (ALOCK_SLOT_SIZE * slot); + lock_info.l_len = (off_t) ALOCK_SLOT_SIZE; + + res = fcntl (fd, F_SETLK, &lock_info); + if (res == -1) return -1; +#elif defined( _WIN32 ) + OVERLAPPED ov; + HANDLE hh = _get_osfhandle ( fd ); + + /* Windows locks are mandatory, not advisory. + * We must downgrade the lock to allow future + * callers to read the slot data. + * + * First acquire a shared lock. Unlock will + * release the existing exclusive lock. + */ + ov.hEvent = 0; + ov.Offset = ALOCK_SLOT_SIZE*slot; + ov.OffsetHigh = 0; + LockFileEx (hh, 0, 0, ALOCK_SLOT_SIZE, 0, &ov); + UnlockFile (hh, ALOCK_SLOT_SIZE*slot, 0, ALOCK_SLOT_SIZE, 0); +#else +# error alock needs lockf, fcntl, or LockFile[Ex] +#endif + + return 0; +} + +static int +alock_test_lock ( int fd, int slot ) +{ + int res; + +#if defined( HAVE_LOCKF ) + res = lseek (fd, (off_t) (ALOCK_SLOT_SIZE * slot), SEEK_SET); + if (res == -1) return -1; + + res = lockf (fd, F_TEST, (off_t) ALOCK_SLOT_SIZE); + if (res == -1) { + if (errno == EACCES || errno == EAGAIN) { + return ALOCK_LOCKED; + } else { + return -1; + } + } +#elif defined( HAVE_FCNTL ) + struct flock lock_info; + (void) memset ((void *) &lock_info, 0, sizeof (struct flock)); + + lock_info.l_type = F_WRLCK; + lock_info.l_whence = SEEK_SET; + lock_info.l_start = (off_t) (ALOCK_SLOT_SIZE * slot); + lock_info.l_len = (off_t) ALOCK_SLOT_SIZE; + + res = fcntl (fd, F_GETLK, &lock_info); + if (res == -1) return -1; + + if (lock_info.l_type != F_UNLCK) return ALOCK_LOCKED; +#elif defined( _WIN32 ) + OVERLAPPED ov; + HANDLE hh = _get_osfhandle ( fd ); + ov.hEvent = 0; + ov.Offset = ALOCK_SLOT_SIZE*slot; + ov.OffsetHigh = 0; + if( !LockFileEx( hh, + LOCKFILE_EXCLUSIVE_LOCK|LOCKFILE_FAIL_IMMEDIATELY, 0, + ALOCK_SLOT_SIZE, 0, &ov )) { + int err = GetLastError(); + if ( err == ERROR_LOCK_VIOLATION ) + return ALOCK_LOCKED; + else + return -1; + } +#else +# error alock needs lockf, fcntl, or LockFile +#endif + + return 0; +} + +/* Read a 64bit LE value */ +static unsigned long int +alock_read_iattr ( unsigned char * bufptr ) +{ + unsigned long int val = 0; + int count; + + assert (bufptr != NULL); + + bufptr += sizeof (unsigned long int); + for (count=0; count <= (int) sizeof (unsigned long int); ++count) { + val <<= 8; + val += (unsigned long int) *bufptr--; + } + + return val; +} + +/* Write a 64bit LE value */ +static void +alock_write_iattr ( unsigned char * bufptr, + unsigned long int val ) +{ + int count; + + assert (bufptr != NULL); + + for (count=0; count < 8; ++count) { + *bufptr++ = (unsigned char) (val & 0xff); + val >>= 8; + } +} + +static int +alock_read_slot ( alock_info_t * info, + alock_slot_t * slot_data ) +{ + unsigned char slotbuf [ALOCK_SLOT_SIZE]; + int res, size, size_total, err; + + assert (info != NULL); + assert (slot_data != NULL); + assert (info->al_slot > 0); + + res = lseek (info->al_fd, + (off_t) (ALOCK_SLOT_SIZE * info->al_slot), + SEEK_SET); + if (res == -1) return -1; + + size_total = 0; + while (size_total < ALOCK_SLOT_SIZE) { + size = read (info->al_fd, + slotbuf + size_total, + ALOCK_SLOT_SIZE - size_total); + if (size == 0) return -1; + if (size < 0) { + err = errno; + if (err != EINTR && err != EAGAIN) return -1; + } else { + size_total += size; + } + } + + if (alock_read_iattr (slotbuf) != ALOCK_MAGIC) { + return -1; + } + slot_data->al_lock = alock_read_iattr (slotbuf+8); + slot_data->al_stamp = alock_read_iattr (slotbuf+16); + slot_data->al_pid = alock_read_iattr (slotbuf+24); + + if (slot_data->al_appname) ber_memfree (slot_data->al_appname); + slot_data->al_appname = ber_memcalloc (1, ALOCK_MAX_APPNAME); + if (slot_data->al_appname == NULL) { + return -1; + } + strncpy (slot_data->al_appname, (char *)slotbuf+32, ALOCK_MAX_APPNAME-1); + (slot_data->al_appname) [ALOCK_MAX_APPNAME-1] = '\0'; + + return 0; +} + +static int +alock_write_slot ( alock_info_t * info, + alock_slot_t * slot_data ) +{ + unsigned char slotbuf [ALOCK_SLOT_SIZE]; + int res, size, size_total, err; + + assert (info != NULL); + assert (slot_data != NULL); + assert (info->al_slot > 0); + + (void) memset ((void *) slotbuf, 0, ALOCK_SLOT_SIZE); + + alock_write_iattr (slotbuf, ALOCK_MAGIC); + assert (alock_read_iattr (slotbuf) == ALOCK_MAGIC); + alock_write_iattr (slotbuf+8, slot_data->al_lock); + alock_write_iattr (slotbuf+16, slot_data->al_stamp); + alock_write_iattr (slotbuf+24, slot_data->al_pid); + + if (slot_data->al_appname) + strncpy ((char *)slotbuf+32, slot_data->al_appname, ALOCK_MAX_APPNAME-1); + slotbuf[ALOCK_SLOT_SIZE-1] = '\0'; + + res = lseek (info->al_fd, + (off_t) (ALOCK_SLOT_SIZE * info->al_slot), + SEEK_SET); + if (res == -1) return -1; + + size_total = 0; + while (size_total < ALOCK_SLOT_SIZE) { + size = write (info->al_fd, + slotbuf + size_total, + ALOCK_SLOT_SIZE - size_total); + if (size == 0) return -1; + if (size < 0) { + err = errno; + if (err != EINTR && err != EAGAIN) return -1; + } else { + size_total += size; + } + } + + return 0; +} + +static int +alock_query_slot ( alock_info_t * info ) +{ + int res, nosave; + alock_slot_t slot_data; + + assert (info != NULL); + assert (info->al_slot > 0); + + (void) memset ((void *) &slot_data, 0, sizeof (alock_slot_t)); + alock_read_slot (info, &slot_data); + + if (slot_data.al_appname != NULL) ber_memfree (slot_data.al_appname); + slot_data.al_appname = NULL; + + nosave = slot_data.al_lock & ALOCK_NOSAVE; + + if ((slot_data.al_lock & ALOCK_SMASK) == ALOCK_UNLOCKED) + return slot_data.al_lock; + + res = alock_test_lock (info->al_fd, info->al_slot); + if (res < 0) return -1; + if (res > 0) { + if ((slot_data.al_lock & ALOCK_SMASK) == ALOCK_UNIQUE) { + return slot_data.al_lock; + } else { + return ALOCK_LOCKED | nosave; + } + } + + return ALOCK_DIRTY | nosave; +} + +int +alock_open ( alock_info_t * info, + const char * appname, + const char * envdir, + int locktype ) +{ + struct stat statbuf; + alock_info_t scan_info; + alock_slot_t slot_data; + char * filename; + int res, max_slot; + int dirty_count, live_count, nosave; + char *ptr; + + assert (info != NULL); + assert (appname != NULL); + assert (envdir != NULL); + assert ((locktype & ALOCK_SMASK) >= 1 && (locktype & ALOCK_SMASK) <= 2); + + slot_data.al_lock = locktype; + slot_data.al_stamp = time(NULL); + slot_data.al_pid = getpid(); + slot_data.al_appname = ber_memcalloc (1, ALOCK_MAX_APPNAME); + if (slot_data.al_appname == NULL) { + return ALOCK_UNSTABLE; + } + strncpy (slot_data.al_appname, appname, ALOCK_MAX_APPNAME-1); + slot_data.al_appname [ALOCK_MAX_APPNAME-1] = '\0'; + + filename = ber_memcalloc (1, strlen (envdir) + strlen ("/alock") + 1); + if (filename == NULL ) { + ber_memfree (slot_data.al_appname); + return ALOCK_UNSTABLE; + } + ptr = lutil_strcopy(filename, envdir); + lutil_strcopy(ptr, "/alock"); +#ifdef _WIN32 + { HANDLE handle = CreateFile (filename, GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, NULL); + info->al_fd = _open_osfhandle (handle, 0); + } +#else + info->al_fd = open (filename, O_CREAT|O_RDWR, 0666); +#endif + ber_memfree (filename); + if (info->al_fd < 0) { + ber_memfree (slot_data.al_appname); + return ALOCK_UNSTABLE; + } + info->al_slot = 0; + + res = alock_grab_lock (info->al_fd, 0); + if (res == -1) { + close (info->al_fd); + ber_memfree (slot_data.al_appname); + return ALOCK_UNSTABLE; + } + + res = fstat (info->al_fd, &statbuf); + if (res == -1) { + close (info->al_fd); + ber_memfree (slot_data.al_appname); + return ALOCK_UNSTABLE; + } + + max_slot = (statbuf.st_size + ALOCK_SLOT_SIZE - 1) / ALOCK_SLOT_SIZE; + dirty_count = 0; + live_count = 0; + nosave = 0; + scan_info.al_fd = info->al_fd; + for (scan_info.al_slot = 1; + scan_info.al_slot < max_slot; + ++ scan_info.al_slot) { + if (scan_info.al_slot != info->al_slot) { + res = alock_query_slot (&scan_info); + + if (res & ALOCK_NOSAVE) { + nosave = ALOCK_NOSAVE; + res ^= ALOCK_NOSAVE; + } + if (res == ALOCK_UNLOCKED + && info->al_slot == 0) { + info->al_slot = scan_info.al_slot; + + } else if (res == ALOCK_LOCKED) { + ++live_count; + + } else if (res == ALOCK_UNIQUE + && (( locktype & ALOCK_SMASK ) == ALOCK_UNIQUE + || nosave )) { + close (info->al_fd); + ber_memfree (slot_data.al_appname); + return ALOCK_BUSY; + + } else if (res == ALOCK_DIRTY) { + ++dirty_count; + + } else if (res == -1) { + close (info->al_fd); + ber_memfree (slot_data.al_appname); + return ALOCK_UNSTABLE; + + } + } + } + + if (dirty_count && live_count) { + close (info->al_fd); + ber_memfree (slot_data.al_appname); + return ALOCK_UNSTABLE; + } + + if (info->al_slot == 0) info->al_slot = max_slot + 1; + res = alock_grab_lock (info->al_fd, + info->al_slot); + if (res == -1) { + close (info->al_fd); + ber_memfree (slot_data.al_appname); + return ALOCK_UNSTABLE; + } + res = alock_write_slot (info, &slot_data); + ber_memfree (slot_data.al_appname); + if (res == -1) { + close (info->al_fd); + return ALOCK_UNSTABLE; + } + alock_share_lock (info->al_fd, info->al_slot); + + res = alock_release_lock (info->al_fd, 0); + if (res == -1) { + close (info->al_fd); + return ALOCK_UNSTABLE; + } + + if (dirty_count) return ALOCK_RECOVER | nosave; + return ALOCK_CLEAN | nosave; +} + +int +alock_scan ( alock_info_t * info ) +{ + struct stat statbuf; + alock_info_t scan_info; + int res, max_slot; + int dirty_count, live_count, nosave; + + assert (info != NULL); + + scan_info.al_fd = info->al_fd; + + res = alock_grab_lock (info->al_fd, 0); + if (res == -1) { + close (info->al_fd); + return ALOCK_UNSTABLE; + } + + res = fstat (info->al_fd, &statbuf); + if (res == -1) { + close (info->al_fd); + return ALOCK_UNSTABLE; + } + + max_slot = (statbuf.st_size + ALOCK_SLOT_SIZE - 1) / ALOCK_SLOT_SIZE; + dirty_count = 0; + live_count = 0; + nosave = 0; + for (scan_info.al_slot = 1; + scan_info.al_slot < max_slot; + ++ scan_info.al_slot) { + if (scan_info.al_slot != info->al_slot) { + res = alock_query_slot (&scan_info); + + if (res & ALOCK_NOSAVE) { + nosave = ALOCK_NOSAVE; + res ^= ALOCK_NOSAVE; + } + + if (res == ALOCK_LOCKED) { + ++live_count; + + } else if (res == ALOCK_DIRTY) { + ++dirty_count; + + } else if (res == -1) { + close (info->al_fd); + return ALOCK_UNSTABLE; + + } + } + } + + res = alock_release_lock (info->al_fd, 0); + if (res == -1) { + close (info->al_fd); + return ALOCK_UNSTABLE; + } + + if (dirty_count) { + if (live_count) { + close (info->al_fd); + return ALOCK_UNSTABLE; + } else { + return ALOCK_RECOVER | nosave; + } + } + + return ALOCK_CLEAN | nosave; +} + +int +alock_close ( alock_info_t * info, int nosave ) +{ + alock_slot_t slot_data; + int res; + + if ( !info->al_slot ) + return ALOCK_CLEAN; + + (void) memset ((void *) &slot_data, 0, sizeof(alock_slot_t)); + + res = alock_grab_lock (info->al_fd, 0); + if (res == -1) { +fail: + /* Windows doesn't clean up locks immediately when a process exits. + * Make sure we release our locks, to prevent stale locks from + * hanging around. + */ + alock_release_lock (info->al_fd, 0); + close (info->al_fd); + return ALOCK_UNSTABLE; + } + + /* mark our slot as clean */ + res = alock_read_slot (info, &slot_data); + if (res == -1) { + if (slot_data.al_appname != NULL) + ber_memfree (slot_data.al_appname); + goto fail; + } + slot_data.al_lock = ALOCK_UNLOCKED; + if ( nosave ) + slot_data.al_lock |= ALOCK_NOSAVE; + /* since we have slot 0 locked, we don't need our slot lock */ + res = alock_release_lock (info->al_fd, info->al_slot); + if (res == -1) { + goto fail; + } + res = alock_write_slot (info, &slot_data); + if (res == -1) { + if (slot_data.al_appname != NULL) + ber_memfree (slot_data.al_appname); + goto fail; + } + if (slot_data.al_appname != NULL) { + ber_memfree (slot_data.al_appname); + slot_data.al_appname = NULL; + } + + res = alock_release_lock (info->al_fd, 0); + if (res == -1) { + close (info->al_fd); + return ALOCK_UNSTABLE; + } + + res = close (info->al_fd); + if (res == -1) return ALOCK_UNSTABLE; + + return ALOCK_CLEAN; +} + +int +alock_recover ( alock_info_t * info ) +{ + struct stat statbuf; + alock_slot_t slot_data; + alock_info_t scan_info; + int res, max_slot; + + assert (info != NULL); + + scan_info.al_fd = info->al_fd; + + (void) memset ((void *) &slot_data, 0, sizeof(alock_slot_t)); + + res = alock_grab_lock (info->al_fd, 0); + if (res == -1) { + goto fail; + } + + res = fstat (info->al_fd, &statbuf); + if (res == -1) { + goto fail; + } + + max_slot = (statbuf.st_size + ALOCK_SLOT_SIZE - 1) / ALOCK_SLOT_SIZE; + for (scan_info.al_slot = 1; + scan_info.al_slot < max_slot; + ++ scan_info.al_slot) { + if (scan_info.al_slot != info->al_slot) { + res = alock_query_slot (&scan_info) & ~ALOCK_NOSAVE; + + if (res == ALOCK_LOCKED + || res == ALOCK_UNIQUE) { + /* recovery attempt on an active db? */ + goto fail; + + } else if (res == ALOCK_DIRTY) { + /* mark it clean */ + res = alock_read_slot (&scan_info, &slot_data); + if (res == -1) { + goto fail; + } + slot_data.al_lock = ALOCK_UNLOCKED; + res = alock_write_slot (&scan_info, &slot_data); + if (res == -1) { + if (slot_data.al_appname != NULL) + ber_memfree (slot_data.al_appname); + goto fail; + } + if (slot_data.al_appname != NULL) { + ber_memfree (slot_data.al_appname); + slot_data.al_appname = NULL; + } + + } else if (res == -1) { + goto fail; + + } + } + } + + res = alock_release_lock (info->al_fd, 0); + if (res == -1) { + close (info->al_fd); + return ALOCK_UNSTABLE; + } + + return ALOCK_CLEAN; + +fail: + alock_release_lock (info->al_fd, 0); + close (info->al_fd); + return ALOCK_UNSTABLE; +} + +#endif /* SLAPD_BDB || SLAPD_HDB */ diff --git a/servers/slapd/alock.h b/servers/slapd/alock.h new file mode 100644 index 0000000..e121bc6 --- /dev/null +++ b/servers/slapd/alock.h @@ -0,0 +1,74 @@ +/* alock.h - access lock header */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2021 The OpenLDAP Foundation. + * Portions Copyright 2004-2005 Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Emily Backes at Symas + * Corporation for inclusion in OpenLDAP Software. + */ + +#ifndef _ALOCK_H_ +#define _ALOCK_H_ + +#include "portable.h" +#include <ac/time.h> +#include <ac/unistd.h> + +/* environment states (all the slots together) */ +#define ALOCK_CLEAN (0) +#define ALOCK_RECOVER (1) +#define ALOCK_BUSY (2) +#define ALOCK_UNSTABLE (3) + +/* lock user types and states */ +#define ALOCK_UNLOCKED (0) +#define ALOCK_LOCKED (1) +#define ALOCK_UNIQUE (2) +#define ALOCK_DIRTY (3) + +#define ALOCK_SMASK 3 + +/* lock/state where recovery is not available */ +#define ALOCK_NOSAVE 4 + +/* constants */ +#define ALOCK_SLOT_SIZE (1024) +#define ALOCK_SLOT_IATTRS (4) +#define ALOCK_MAX_APPNAME (ALOCK_SLOT_SIZE - 8 * ALOCK_SLOT_IATTRS) +#define ALOCK_MAGIC (0x12345678) + +LDAP_BEGIN_DECL + +typedef struct alock_info { + int al_fd; + int al_slot; +} alock_info_t; + +typedef struct alock_slot { + unsigned int al_lock; + time_t al_stamp; + pid_t al_pid; + char * al_appname; +} alock_slot_t; + +LDAP_SLAPD_F (int) alock_open LDAP_P(( alock_info_t * info, const char * appname, + const char * envdir, int locktype )); +LDAP_SLAPD_F (int) alock_scan LDAP_P(( alock_info_t * info )); +LDAP_SLAPD_F (int) alock_close LDAP_P(( alock_info_t * info, int nosave )); +LDAP_SLAPD_F (int) alock_recover LDAP_P(( alock_info_t * info )); + +LDAP_END_DECL + +#endif diff --git a/servers/slapd/at.c b/servers/slapd/at.c new file mode 100644 index 0000000..583f690 --- /dev/null +++ b/servers/slapd/at.c @@ -0,0 +1,1108 @@ +/* at.c - routines for dealing with attribute types */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "slap.h" + + +const char * +at_syntax( + AttributeType *at ) +{ + for ( ; at != NULL; at = at->sat_sup ) { + if ( at->sat_syntax_oid ) { + return at->sat_syntax_oid; + } + } + + assert( 0 ); + + return NULL; +} + +int +is_at_syntax( + AttributeType *at, + const char *oid ) +{ + const char *syn_oid = at_syntax( at ); + + if ( syn_oid ) { + return strcmp( syn_oid, oid ) == 0; + } + + return 0; +} + +int is_at_subtype( + AttributeType *sub, + AttributeType *sup ) +{ + for( ; sub != NULL; sub = sub->sat_sup ) { + if( sub == sup ) return 1; + } + + return 0; +} + +struct aindexrec { + struct berval air_name; + AttributeType *air_at; +}; + +static Avlnode *attr_index = NULL; +static Avlnode *attr_cache = NULL; +static LDAP_STAILQ_HEAD(ATList, AttributeType) attr_list + = LDAP_STAILQ_HEAD_INITIALIZER(attr_list); + +/* Last hardcoded attribute registered */ +AttributeType *at_sys_tail; + +int at_oc_cache; + +static int +attr_index_cmp( + const void *v_air1, + const void *v_air2 ) +{ + const struct aindexrec *air1 = v_air1; + const struct aindexrec *air2 = v_air2; + int i = air1->air_name.bv_len - air2->air_name.bv_len; + if (i) return i; + return (strcasecmp( air1->air_name.bv_val, air2->air_name.bv_val )); +} + +static int +attr_index_name_cmp( + const void *v_type, + const void *v_air ) +{ + const struct berval *type = v_type; + const struct aindexrec *air = v_air; + int i = type->bv_len - air->air_name.bv_len; + if (i) return i; + return (strncasecmp( type->bv_val, air->air_name.bv_val, type->bv_len )); +} + +AttributeType * +at_find( const char *name ) +{ + struct berval bv; + + bv.bv_val = (char *)name; + bv.bv_len = strlen( name ); + + return at_bvfind( &bv ); +} + +AttributeType * +at_bvfind( struct berval *name ) +{ + struct aindexrec *air; + + if ( attr_cache ) { + air = avl_find( attr_cache, name, attr_index_name_cmp ); + if ( air ) return air->air_at; + } + + air = avl_find( attr_index, name, attr_index_name_cmp ); + + if ( air ) { + if ( air->air_at->sat_flags & SLAP_AT_DELETED ) { + air = NULL; + } else if (( slapMode & SLAP_TOOL_MODE ) && at_oc_cache ) { + avl_insert( &attr_cache, (caddr_t) air, + attr_index_cmp, avl_dup_error ); + } + } + + return air != NULL ? air->air_at : NULL; +} + +int +at_append_to_list( + AttributeType *sat, + AttributeType ***listp ) +{ + AttributeType **list; + AttributeType **list1; + int size; + + list = *listp; + if ( !list ) { + size = 2; + list = ch_calloc(size, sizeof(AttributeType *)); + if ( !list ) { + return -1; + } + } else { + size = 0; + list1 = *listp; + while ( *list1 ) { + size++; + list1++; + } + size += 2; + list1 = ch_realloc(list, size*sizeof(AttributeType *)); + if ( !list1 ) { + return -1; + } + list = list1; + } + list[size-2] = sat; + list[size-1] = NULL; + *listp = list; + return 0; +} + +int +at_delete_from_list( + int pos, + AttributeType ***listp ) +{ + AttributeType **list; + AttributeType **list1; + int i; + int j; + + if ( pos < 0 ) { + return -2; + } + list = *listp; + for ( i=0; list[i]; i++ ) + ; + if ( pos >= i ) { + return -2; + } + for ( i=pos, j=pos+1; list[j]; i++, j++ ) { + list[i] = list[j]; + } + list[i] = NULL; + /* Tell the runtime this can be shrinked */ + list1 = ch_realloc(list, (i+1)*sizeof(AttributeType **)); + if ( !list1 ) { + return -1; + } + *listp = list1; + return 0; +} + +int +at_find_in_list( + AttributeType *sat, + AttributeType **list ) +{ + int i; + + if ( !list ) { + return -1; + } + for ( i=0; list[i]; i++ ) { + if ( sat == list[i] ) { + return i; + } + } + return -1; +} + +static void +at_delete_names( AttributeType *at ) +{ + char **names = at->sat_names; + + if (!names) return; + + while (*names) { + struct aindexrec tmpair, *air; + + ber_str2bv( *names, 0, 0, &tmpair.air_name ); + tmpair.air_at = at; + air = (struct aindexrec *)avl_delete( &attr_index, + (caddr_t)&tmpair, attr_index_cmp ); + assert( air != NULL ); + ldap_memfree( air ); + names++; + } +} + +/* Mark the attribute as deleted, remove from list, and remove all its + * names from the AVL tree. Leave the OID in the tree. + */ +void +at_delete( AttributeType *at ) +{ + at->sat_flags |= SLAP_AT_DELETED; + + LDAP_STAILQ_REMOVE(&attr_list, at, AttributeType, sat_next); + + at_delete_names( at ); +} + +static void +at_clean( AttributeType *a ) +{ + if ( a->sat_equality ) { + MatchingRule *mr; + + mr = mr_find( a->sat_equality->smr_oid ); + assert( mr != NULL ); + if ( mr != a->sat_equality ) { + ch_free( a->sat_equality ); + a->sat_equality = NULL; + } + } + + assert( a->sat_syntax != NULL ); + if ( a->sat_syntax != NULL ) { + Syntax *syn; + + syn = syn_find( a->sat_syntax->ssyn_oid ); + assert( syn != NULL ); + if ( syn != a->sat_syntax ) { + ch_free( a->sat_syntax ); + a->sat_syntax = NULL; + } + } + + if ( a->sat_oidmacro ) { + ldap_memfree( a->sat_oidmacro ); + a->sat_oidmacro = NULL; + } + if ( a->sat_soidmacro ) { + ldap_memfree( a->sat_soidmacro ); + a->sat_soidmacro = NULL; + } + if ( a->sat_subtypes ) { + ldap_memfree( a->sat_subtypes ); + a->sat_subtypes = NULL; + } +} + +static void +at_destroy_one( void *v ) +{ + struct aindexrec *air = v; + AttributeType *a = air->air_at; + + at_clean( a ); + ad_destroy(a->sat_ad); + ldap_pvt_thread_mutex_destroy(&a->sat_ad_mutex); + ldap_attributetype_free((LDAPAttributeType *)a); + ldap_memfree(air); +} + +void +at_destroy( void ) +{ + AttributeType *a; + + while( !LDAP_STAILQ_EMPTY(&attr_list) ) { + a = LDAP_STAILQ_FIRST(&attr_list); + LDAP_STAILQ_REMOVE_HEAD(&attr_list, sat_next); + + at_delete_names( a ); + } + + avl_free(attr_index, at_destroy_one); + + if ( slap_schema.si_at_undefined ) { + ad_destroy(slap_schema.si_at_undefined->sat_ad); + } + + if ( slap_schema.si_at_proxied ) { + ad_destroy(slap_schema.si_at_proxied->sat_ad); + } +} + +int +at_start( AttributeType **at ) +{ + assert( at != NULL ); + + *at = LDAP_STAILQ_FIRST(&attr_list); + + return (*at != NULL); +} + +int +at_next( AttributeType **at ) +{ + assert( at != NULL ); + +#if 0 /* pedantic check: don't use this */ + { + AttributeType *tmp = NULL; + + LDAP_STAILQ_FOREACH(tmp,&attr_list,sat_next) { + if ( tmp == *at ) { + break; + } + } + + assert( tmp != NULL ); + } +#endif + + if ( *at == NULL ) { + return 0; + } + + *at = LDAP_STAILQ_NEXT(*at,sat_next); + + return (*at != NULL); +} + +/* + * check whether the two attributeTypes actually __are__ identical, + * or rather inconsistent + */ +static int +at_check_dup( + AttributeType *sat, + AttributeType *new_sat ) +{ + if ( new_sat->sat_oid != NULL ) { + if ( sat->sat_oid == NULL ) { + return SLAP_SCHERR_ATTR_INCONSISTENT; + } + + if ( strcmp( sat->sat_oid, new_sat->sat_oid ) != 0 ) { + return SLAP_SCHERR_ATTR_INCONSISTENT; + } + + } else { + if ( sat->sat_oid != NULL ) { + return SLAP_SCHERR_ATTR_INCONSISTENT; + } + } + + if ( new_sat->sat_names ) { + int i; + + if ( sat->sat_names == NULL ) { + return SLAP_SCHERR_ATTR_INCONSISTENT; + } + + for ( i = 0; new_sat->sat_names[ i ]; i++ ) { + if ( sat->sat_names[ i ] == NULL ) { + return SLAP_SCHERR_ATTR_INCONSISTENT; + } + + if ( strcasecmp( sat->sat_names[ i ], + new_sat->sat_names[ i ] ) != 0 ) + { + return SLAP_SCHERR_ATTR_INCONSISTENT; + } + } + } else { + if ( sat->sat_names != NULL ) { + return SLAP_SCHERR_ATTR_INCONSISTENT; + } + } + + return SLAP_SCHERR_ATTR_DUP; +} + +static struct aindexrec *air_old; + +static int +at_dup_error( void *left, void *right ) +{ + air_old = left; + return -1; +} + +static int +at_insert( + AttributeType **rat, + AttributeType *prev, + const char **err ) +{ + struct aindexrec *air; + char **names = NULL; + AttributeType *sat = *rat; + + if ( sat->sat_oid ) { + air = (struct aindexrec *) + ch_calloc( 1, sizeof(struct aindexrec) ); + ber_str2bv( sat->sat_oid, 0, 0, &air->air_name ); + air->air_at = sat; + air_old = NULL; + + if ( avl_insert( &attr_index, (caddr_t) air, + attr_index_cmp, at_dup_error ) ) + { + AttributeType *old_sat; + int rc; + + *err = sat->sat_oid; + + assert( air_old != NULL ); + old_sat = air_old->air_at; + + /* replacing a deleted definition? */ + if ( old_sat->sat_flags & SLAP_AT_DELETED ) { + AttributeType tmp; + AttributeDescription *ad; + + /* Keep old oid, free new oid; + * Keep old ads, free new ads; + * Keep old ad_mutex, free new ad_mutex; + * Keep new everything else, free old + */ + tmp = *old_sat; + *old_sat = *sat; + old_sat->sat_oid = tmp.sat_oid; + tmp.sat_oid = sat->sat_oid; + old_sat->sat_ad = tmp.sat_ad; + tmp.sat_ad = sat->sat_ad; + old_sat->sat_ad_mutex = tmp.sat_ad_mutex; + tmp.sat_ad_mutex = sat->sat_ad_mutex; + *sat = tmp; + + /* Check for basic ad pointing at old cname */ + for ( ad = old_sat->sat_ad; ad; ad=ad->ad_next ) { + if ( ad->ad_cname.bv_val == sat->sat_cname.bv_val ) { + ad->ad_cname = old_sat->sat_cname; + break; + } + } + + at_clean( sat ); + at_destroy_one( air ); + + air = air_old; + sat = old_sat; + *rat = sat; + } else { + ldap_memfree( air ); + + rc = at_check_dup( old_sat, sat ); + + return rc; + } + } + /* FIX: temporal consistency check */ + at_bvfind( &air->air_name ); + } + + names = sat->sat_names; + if ( names ) { + while ( *names ) { + air = (struct aindexrec *) + ch_calloc( 1, sizeof(struct aindexrec) ); + ber_str2bv( *names, 0, 0, &air->air_name ); + air->air_at = sat; + if ( avl_insert( &attr_index, (caddr_t) air, + attr_index_cmp, avl_dup_error ) ) + { + AttributeType *old_sat; + int rc; + + *err = *names; + + old_sat = at_bvfind( &air->air_name ); + assert( old_sat != NULL ); + rc = at_check_dup( old_sat, sat ); + + ldap_memfree(air); + + while ( names > sat->sat_names ) { + struct aindexrec tmpair; + + names--; + ber_str2bv( *names, 0, 0, &tmpair.air_name ); + tmpair.air_at = sat; + air = (struct aindexrec *)avl_delete( &attr_index, + (caddr_t)&tmpair, attr_index_cmp ); + assert( air != NULL ); + ldap_memfree( air ); + } + + if ( sat->sat_oid ) { + struct aindexrec tmpair; + + ber_str2bv( sat->sat_oid, 0, 0, &tmpair.air_name ); + tmpair.air_at = sat; + air = (struct aindexrec *)avl_delete( &attr_index, + (caddr_t)&tmpair, attr_index_cmp ); + assert( air != NULL ); + ldap_memfree( air ); + } + + return rc; + } + /* FIX: temporal consistency check */ + at_bvfind(&air->air_name); + names++; + } + } + + if ( sat->sat_oid ) { + slap_ad_undef_promote( sat->sat_oid, sat ); + } + + names = sat->sat_names; + if ( names ) { + while ( *names ) { + slap_ad_undef_promote( *names, sat ); + names++; + } + } + + if ( sat->sat_flags & SLAP_AT_HARDCODE ) { + prev = at_sys_tail; + at_sys_tail = sat; + } + if ( prev ) { + LDAP_STAILQ_INSERT_AFTER( &attr_list, prev, sat, sat_next ); + } else { + LDAP_STAILQ_INSERT_TAIL( &attr_list, sat, sat_next ); + } + + return 0; +} + +int +at_add( + LDAPAttributeType *at, + int user, + AttributeType **rsat, + AttributeType *prev, + const char **err ) +{ + AttributeType *sat = NULL; + MatchingRule *mr = NULL; + Syntax *syn = NULL; + int i; + int code = LDAP_SUCCESS; + char *cname = NULL; + char *oidm = NULL; + char *soidm = NULL; + + if ( !at->at_oid ) { + *err = ""; + return SLAP_SCHERR_ATTR_INCOMPLETE; + } + + if ( !OID_LEADCHAR( at->at_oid[0] )) { + char *oid; + + /* Expand OID macros */ + oid = oidm_find( at->at_oid ); + if ( !oid ) { + *err = at->at_oid; + return SLAP_SCHERR_OIDM; + } + if ( oid != at->at_oid ) { + oidm = at->at_oid; + at->at_oid = oid; + } + } + + if ( at->at_syntax_oid && !OID_LEADCHAR( at->at_syntax_oid[0] )) { + char *oid; + + /* Expand OID macros */ + oid = oidm_find( at->at_syntax_oid ); + if ( !oid ) { + *err = at->at_syntax_oid; + code = SLAP_SCHERR_OIDM; + goto error_return; + } + if ( oid != at->at_syntax_oid ) { + soidm = at->at_syntax_oid; + at->at_syntax_oid = oid; + } + } + + if ( at->at_names && at->at_names[0] ) { + int i; + + for( i=0; at->at_names[i]; i++ ) { + if( !slap_valid_descr( at->at_names[i] ) ) { + *err = at->at_names[i]; + code = SLAP_SCHERR_BAD_DESCR; + goto error_return; + } + } + + cname = at->at_names[0]; + + } else { + cname = at->at_oid; + + } + + *err = cname; + + if ( !at->at_usage && at->at_no_user_mod ) { + /* user attribute must be modifable */ + code = SLAP_SCHERR_ATTR_BAD_USAGE; + goto error_return; + } + + if ( at->at_collective ) { + if( at->at_usage ) { + /* collective attributes cannot be operational */ + code = SLAP_SCHERR_ATTR_BAD_USAGE; + goto error_return; + } + + if( at->at_single_value ) { + /* collective attributes cannot be single-valued */ + code = SLAP_SCHERR_ATTR_BAD_USAGE; + goto error_return; + } + } + + sat = (AttributeType *) ch_calloc( 1, sizeof(AttributeType) ); + AC_MEMCPY( &sat->sat_atype, at, sizeof(LDAPAttributeType)); + + sat->sat_cname.bv_val = cname; + sat->sat_cname.bv_len = strlen( cname ); + sat->sat_oidmacro = oidm; + sat->sat_soidmacro = soidm; + ldap_pvt_thread_mutex_init(&sat->sat_ad_mutex); + + if ( at->at_sup_oid ) { + AttributeType *supsat = at_find(at->at_sup_oid); + + if ( supsat == NULL ) { + *err = at->at_sup_oid; + code = SLAP_SCHERR_ATTR_NOT_FOUND; + goto error_return; + } + + sat->sat_sup = supsat; + + if ( at_append_to_list(sat, &supsat->sat_subtypes) ) { + code = SLAP_SCHERR_OUTOFMEM; + goto error_return; + } + + if ( sat->sat_usage != supsat->sat_usage ) { + /* subtypes must have same usage as their SUP */ + code = SLAP_SCHERR_ATTR_BAD_USAGE; + goto error_return; + } + + if ( supsat->sat_obsolete && !sat->sat_obsolete ) { + /* subtypes must be obsolete if super is */ + code = SLAP_SCHERR_ATTR_BAD_SUP; + goto error_return; + } + + if ( sat->sat_flags & SLAP_AT_FINAL ) { + /* cannot subtype a "final" attribute type */ + code = SLAP_SCHERR_ATTR_BAD_SUP; + goto error_return; + } + } + + /* + * Inherit definitions from superiors. We only check the + * direct superior since that one has already inherited from + * its own superiorss + */ + if ( sat->sat_sup ) { + Syntax *syn = syn_find(sat->sat_sup->sat_syntax->ssyn_oid); + if ( syn != sat->sat_sup->sat_syntax ) { + sat->sat_syntax = ch_malloc( sizeof( Syntax )); + *sat->sat_syntax = *sat->sat_sup->sat_syntax; + } else { + sat->sat_syntax = sat->sat_sup->sat_syntax; + } + if ( sat->sat_sup->sat_equality ) { + MatchingRule *mr = mr_find( sat->sat_sup->sat_equality->smr_oid ); + if ( mr != sat->sat_sup->sat_equality ) { + sat->sat_equality = ch_malloc( sizeof( MatchingRule )); + *sat->sat_equality = *sat->sat_sup->sat_equality; + } else { + sat->sat_equality = sat->sat_sup->sat_equality; + } + } + sat->sat_approx = sat->sat_sup->sat_approx; + sat->sat_ordering = sat->sat_sup->sat_ordering; + sat->sat_substr = sat->sat_sup->sat_substr; + } + + /* + * check for X-ORDERED attributes + */ + if ( sat->sat_extensions ) { + for (i=0; sat->sat_extensions[i]; i++) { + if (!strcasecmp( sat->sat_extensions[i]->lsei_name, + "X-ORDERED" ) && sat->sat_extensions[i]->lsei_values ) { + if ( !strcasecmp( sat->sat_extensions[i]->lsei_values[0], + "VALUES" )) { + sat->sat_flags |= SLAP_AT_ORDERED_VAL; + break; + } else if ( !strcasecmp( sat->sat_extensions[i]->lsei_values[0], + "SIBLINGS" )) { + sat->sat_flags |= SLAP_AT_ORDERED_SIB; + break; + } + } + } + } + + if ( !user ) + sat->sat_flags |= SLAP_AT_HARDCODE; + + if ( at->at_syntax_oid ) { + syn = syn_find(sat->sat_syntax_oid); + if ( syn == NULL ) { + *err = sat->sat_syntax_oid; + code = SLAP_SCHERR_SYN_NOT_FOUND; + goto error_return; + } + + if ( sat->sat_syntax != NULL && sat->sat_syntax != syn ) { + /* BEWARE: no loop detection! */ + if ( syn_is_sup( sat->sat_syntax, syn ) ) { + code = SLAP_SCHERR_ATTR_BAD_SUP; + goto error_return; + } + } + + sat->sat_syntax = syn; + + } else if ( sat->sat_syntax == NULL ) { + code = SLAP_SCHERR_ATTR_INCOMPLETE; + goto error_return; + } + + if ( sat->sat_equality_oid ) { + mr = mr_find(sat->sat_equality_oid); + + if( mr == NULL ) { + *err = sat->sat_equality_oid; + code = SLAP_SCHERR_MR_NOT_FOUND; + goto error_return; + } + + if(( mr->smr_usage & SLAP_MR_EQUALITY ) != SLAP_MR_EQUALITY ) { + *err = sat->sat_equality_oid; + code = SLAP_SCHERR_ATTR_BAD_MR; + goto error_return; + } + + if( sat->sat_syntax != mr->smr_syntax ) { + if( mr->smr_compat_syntaxes == NULL ) { + *err = sat->sat_equality_oid; + code = SLAP_SCHERR_ATTR_BAD_MR; + goto error_return; + } + + for(i=0; mr->smr_compat_syntaxes[i]; i++) { + if( sat->sat_syntax == mr->smr_compat_syntaxes[i] ) { + i = -1; + break; + } + } + + if( i >= 0 ) { + *err = sat->sat_equality_oid; + code = SLAP_SCHERR_ATTR_BAD_MR; + goto error_return; + } + } + + sat->sat_equality = mr; + sat->sat_approx = mr->smr_associated; + } + + if ( sat->sat_ordering_oid ) { + if( !sat->sat_equality ) { + *err = sat->sat_ordering_oid; + code = SLAP_SCHERR_ATTR_BAD_MR; + goto error_return; + } + + mr = mr_find(sat->sat_ordering_oid); + + if( mr == NULL ) { + *err = sat->sat_ordering_oid; + code = SLAP_SCHERR_MR_NOT_FOUND; + goto error_return; + } + + if(( mr->smr_usage & SLAP_MR_ORDERING ) != SLAP_MR_ORDERING ) { + *err = sat->sat_ordering_oid; + code = SLAP_SCHERR_ATTR_BAD_MR; + goto error_return; + } + + if( sat->sat_syntax != mr->smr_syntax ) { + if( mr->smr_compat_syntaxes == NULL ) { + *err = sat->sat_ordering_oid; + code = SLAP_SCHERR_ATTR_BAD_MR; + goto error_return; + } + + for(i=0; mr->smr_compat_syntaxes[i]; i++) { + if( sat->sat_syntax == mr->smr_compat_syntaxes[i] ) { + i = -1; + break; + } + } + + if( i >= 0 ) { + *err = sat->sat_ordering_oid; + code = SLAP_SCHERR_ATTR_BAD_MR; + goto error_return; + } + } + + sat->sat_ordering = mr; + } + + if ( sat->sat_substr_oid ) { + if( !sat->sat_equality ) { + *err = sat->sat_substr_oid; + code = SLAP_SCHERR_ATTR_BAD_MR; + goto error_return; + } + + mr = mr_find(sat->sat_substr_oid); + + if( mr == NULL ) { + *err = sat->sat_substr_oid; + code = SLAP_SCHERR_MR_NOT_FOUND; + goto error_return; + } + + if(( mr->smr_usage & SLAP_MR_SUBSTR ) != SLAP_MR_SUBSTR ) { + *err = sat->sat_substr_oid; + code = SLAP_SCHERR_ATTR_BAD_MR; + goto error_return; + } + + /* due to funky LDAP builtin substring rules, + * we check against the equality rule assertion + * syntax and compat syntaxes instead of those + * associated with the substrings rule. + */ + if( sat->sat_syntax != sat->sat_equality->smr_syntax ) { + if( sat->sat_equality->smr_compat_syntaxes == NULL ) { + *err = sat->sat_substr_oid; + code = SLAP_SCHERR_ATTR_BAD_MR; + goto error_return; + } + + for(i=0; sat->sat_equality->smr_compat_syntaxes[i]; i++) { + if( sat->sat_syntax == + sat->sat_equality->smr_compat_syntaxes[i] ) + { + i = -1; + break; + } + } + + if( i >= 0 ) { + *err = sat->sat_substr_oid; + code = SLAP_SCHERR_ATTR_BAD_MR; + goto error_return; + } + } + + sat->sat_substr = mr; + } + + code = at_insert( &sat, prev, err ); + if ( code != 0 ) { +error_return:; + if ( sat ) { + ldap_pvt_thread_mutex_destroy( &sat->sat_ad_mutex ); + ch_free( sat ); + } + + if ( oidm ) { + SLAP_FREE( at->at_oid ); + at->at_oid = oidm; + } + + if ( soidm ) { + SLAP_FREE( at->at_syntax_oid ); + at->at_syntax_oid = soidm; + } + + } else if ( rsat ) { + *rsat = sat; + } + + return code; +} + +#ifdef LDAP_DEBUG +#ifdef SLAPD_UNUSED +static int +at_index_printnode( void *v_air, void *ignore ) +{ + struct aindexrec *air = v_air; + printf("%s = %s\n", + air->air_name.bv_val, + ldap_attributetype2str(&air->air_at->sat_atype) ); + return( 0 ); +} + +static void +at_index_print( void ) +{ + printf("Printing attribute type index:\n"); + (void) avl_apply( attr_index, at_index_printnode, 0, -1, AVL_INORDER ); +} +#endif +#endif + +void +at_unparse( BerVarray *res, AttributeType *start, AttributeType *end, int sys ) +{ + AttributeType *at; + int i, num; + struct berval bv, *bva = NULL, idx; + char ibuf[32]; + + if ( !start ) + start = LDAP_STAILQ_FIRST( &attr_list ); + + /* count the result size */ + i = 0; + for ( at=start; at; at=LDAP_STAILQ_NEXT(at, sat_next)) { + if ( sys && !(at->sat_flags & SLAP_AT_HARDCODE)) break; + i++; + if ( at == end ) break; + } + if (!i) return; + + num = i; + bva = ch_malloc( (num+1) * sizeof(struct berval) ); + BER_BVZERO( bva ); + idx.bv_val = ibuf; + if ( sys ) { + idx.bv_len = 0; + ibuf[0] = '\0'; + } + i = 0; + for ( at=start; at; at=LDAP_STAILQ_NEXT(at, sat_next)) { + LDAPAttributeType lat, *latp; + if ( sys && !(at->sat_flags & SLAP_AT_HARDCODE)) break; + if ( at->sat_oidmacro || at->sat_soidmacro ) { + lat = at->sat_atype; + if ( at->sat_oidmacro ) + lat.at_oid = at->sat_oidmacro; + if ( at->sat_soidmacro ) + lat.at_syntax_oid = at->sat_soidmacro; + latp = ⪫ + } else { + latp = &at->sat_atype; + } + if ( ldap_attributetype2bv( latp, &bv ) == NULL ) { + ber_bvarray_free( bva ); + } + if ( !sys ) { + idx.bv_len = sprintf(idx.bv_val, "{%d}", i); + } + bva[i].bv_len = idx.bv_len + bv.bv_len; + bva[i].bv_val = ch_malloc( bva[i].bv_len + 1 ); + strcpy( bva[i].bv_val, ibuf ); + strcpy( bva[i].bv_val + idx.bv_len, bv.bv_val ); + i++; + bva[i].bv_val = NULL; + ldap_memfree( bv.bv_val ); + if ( at == end ) break; + } + *res = bva; +} + +int +at_schema_info( Entry *e ) +{ + AttributeDescription *ad_attributeTypes = slap_schema.si_ad_attributeTypes; + AttributeType *at; + struct berval val; + struct berval nval; + + LDAP_STAILQ_FOREACH(at,&attr_list,sat_next) { + if( at->sat_flags & SLAP_AT_HIDE ) continue; + + if ( ldap_attributetype2bv( &at->sat_atype, &val ) == NULL ) { + return -1; + } + + ber_str2bv( at->sat_oid, 0, 0, &nval ); + + if( attr_merge_one( e, ad_attributeTypes, &val, &nval ) ) + { + return -1; + } + ldap_memfree( val.bv_val ); + } + return 0; +} + +int +register_at( const char *def, AttributeDescription **rad, int dupok ) +{ + LDAPAttributeType *at; + int code, freeit = 0; + const char *err; + AttributeDescription *ad = NULL; + + at = ldap_str2attributetype( def, &code, &err, LDAP_SCHEMA_ALLOW_ALL ); + if ( !at ) { + Debug( LDAP_DEBUG_ANY, + "register_at: AttributeType \"%s\": %s, %s\n", + def, ldap_scherr2str(code), err ); + return code; + } + + code = at_add( at, 0, NULL, NULL, &err ); + if ( code ) { + if ( code == SLAP_SCHERR_ATTR_DUP && dupok ) { + freeit = 1; + + } else { + Debug( LDAP_DEBUG_ANY, + "register_at: AttributeType \"%s\": %s, %s\n", + def, scherr2str(code), err ); + ldap_attributetype_free( at ); + return code; + } + } + code = slap_str2ad( at->at_names[0], &ad, &err ); + if ( freeit || code ) { + ldap_attributetype_free( at ); + } else { + ldap_memfree( at ); + } + if ( code ) { + Debug( LDAP_DEBUG_ANY, "register_at: AttributeType \"%s\": %s\n", + def, err, 0 ); + } + if ( rad ) *rad = ad; + return code; +} diff --git a/servers/slapd/attr.c b/servers/slapd/attr.c new file mode 100644 index 0000000..1b577a6 --- /dev/null +++ b/servers/slapd/attr.c @@ -0,0 +1,722 @@ +/* attr.c - routines for dealing with attributes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#include <ac/ctype.h> +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "slap.h" + +/* + * Allocate in chunks, minimum of 1000 at a time. + */ +#define CHUNK_SIZE 1000 +typedef struct slap_list { + struct slap_list *next; +} slap_list; +static slap_list *attr_chunks; +static Attribute *attr_list; +static ldap_pvt_thread_mutex_t attr_mutex; + +int +attr_prealloc( int num ) +{ + Attribute *a; + slap_list *s; + + if (!num) return 0; + + s = ch_calloc( 1, sizeof(slap_list) + num * sizeof(Attribute)); + s->next = attr_chunks; + attr_chunks = s; + + a = (Attribute *)(s+1); + for ( ;num>1; num--) { + a->a_next = a+1; + a++; + } + a->a_next = attr_list; + attr_list = (Attribute *)(s+1); + + return 0; +} + +Attribute * +attr_alloc( AttributeDescription *ad ) +{ + Attribute *a; + + ldap_pvt_thread_mutex_lock( &attr_mutex ); + if ( !attr_list ) + attr_prealloc( CHUNK_SIZE ); + a = attr_list; + attr_list = a->a_next; + a->a_next = NULL; + ldap_pvt_thread_mutex_unlock( &attr_mutex ); + + a->a_desc = ad; + if ( ad && ( ad->ad_type->sat_flags & SLAP_AT_SORTED_VAL )) + a->a_flags |= SLAP_ATTR_SORTED_VALS; + + return a; +} + +/* Return a list of num attrs */ +Attribute * +attrs_alloc( int num ) +{ + Attribute *head = NULL; + Attribute **a; + + ldap_pvt_thread_mutex_lock( &attr_mutex ); + for ( a = &attr_list; *a && num > 0; a = &(*a)->a_next ) { + if ( !head ) + head = *a; + num--; + } + attr_list = *a; + if ( num > 0 ) { + attr_prealloc( num > CHUNK_SIZE ? num : CHUNK_SIZE ); + *a = attr_list; + for ( ; *a && num > 0; a = &(*a)->a_next ) { + if ( !head ) + head = *a; + num--; + } + attr_list = *a; + } + *a = NULL; + ldap_pvt_thread_mutex_unlock( &attr_mutex ); + + return head; +} + + +void +attr_clean( Attribute *a ) +{ + if ( a->a_nvals && a->a_nvals != a->a_vals && + !( a->a_flags & SLAP_ATTR_DONT_FREE_VALS )) { + if ( a->a_flags & SLAP_ATTR_DONT_FREE_DATA ) { + free( a->a_nvals ); + } else { + ber_bvarray_free( a->a_nvals ); + } + } + /* a_vals may be equal to slap_dummy_bv, a static empty berval; + * this is used as a placeholder for attributes that do not carry + * values, e.g. when proxying search entries with the "attrsonly" + * bit set. */ + if ( a->a_vals != &slap_dummy_bv && + !( a->a_flags & SLAP_ATTR_DONT_FREE_VALS )) { + if ( a->a_flags & SLAP_ATTR_DONT_FREE_DATA ) { + free( a->a_vals ); + } else { + ber_bvarray_free( a->a_vals ); + } + } + a->a_desc = NULL; + a->a_vals = NULL; + a->a_nvals = NULL; +#ifdef LDAP_COMP_MATCH + a->a_comp_data = NULL; +#endif + a->a_flags = 0; + a->a_numvals = 0; +} + +void +attr_free( Attribute *a ) +{ + attr_clean( a ); + ldap_pvt_thread_mutex_lock( &attr_mutex ); + a->a_next = attr_list; + attr_list = a; + ldap_pvt_thread_mutex_unlock( &attr_mutex ); +} + +#ifdef LDAP_COMP_MATCH +void +comp_tree_free( Attribute *a ) +{ + Attribute *next; + + for( ; a != NULL ; a = next ) { + next = a->a_next; + if ( component_destructor && a->a_comp_data ) { + if ( a->a_comp_data->cd_mem_op ) + component_destructor( a->a_comp_data->cd_mem_op ); + free ( a->a_comp_data ); + } + } +} +#endif + +void +attrs_free( Attribute *a ) +{ + if ( a ) { + Attribute *b = (Attribute *)0xBAD, *tail, *next; + + /* save tail */ + tail = a; + do { + next = a->a_next; + attr_clean( a ); + a->a_next = b; + b = a; + a = next; + } while ( next ); + + ldap_pvt_thread_mutex_lock( &attr_mutex ); + /* replace NULL with current attr list and let attr list + * start from last attribute returned to list */ + tail->a_next = attr_list; + attr_list = b; + ldap_pvt_thread_mutex_unlock( &attr_mutex ); + } +} + +static void +attr_dup2( Attribute *tmp, Attribute *a ) +{ + tmp->a_flags = a->a_flags & SLAP_ATTR_PERSISTENT_FLAGS; + if ( a->a_vals != NULL ) { + unsigned i, j; + + tmp->a_numvals = a->a_numvals; + tmp->a_vals = ch_malloc( (tmp->a_numvals + 1) * sizeof(struct berval) ); + for ( i = 0; i < tmp->a_numvals; i++ ) { + ber_dupbv( &tmp->a_vals[i], &a->a_vals[i] ); + if ( BER_BVISNULL( &tmp->a_vals[i] ) ) break; + /* FIXME: error? */ + } + BER_BVZERO( &tmp->a_vals[i] ); + + /* a_nvals must be non null; it may be equal to a_vals */ + assert( a->a_nvals != NULL ); + + if ( a->a_nvals != a->a_vals ) { + + tmp->a_nvals = ch_malloc( (tmp->a_numvals + 1) * sizeof(struct berval) ); + j = 0; + if ( i ) { + for ( ; !BER_BVISNULL( &a->a_nvals[j] ); j++ ) { + assert( j < i ); + ber_dupbv( &tmp->a_nvals[j], &a->a_nvals[j] ); + if ( BER_BVISNULL( &tmp->a_nvals[j] ) ) break; + /* FIXME: error? */ + } + assert( j == i ); + } + BER_BVZERO( &tmp->a_nvals[j] ); + + } else { + tmp->a_nvals = tmp->a_vals; + } + } +} + +Attribute * +attr_dup( Attribute *a ) +{ + Attribute *tmp; + + if ( a == NULL) return NULL; + + tmp = attr_alloc( a->a_desc ); + attr_dup2( tmp, a ); + return tmp; +} + +Attribute * +attrs_dup( Attribute *a ) +{ + int i; + Attribute *tmp, *anew; + + if( a == NULL ) return NULL; + + /* count them */ + for( tmp=a,i=0; tmp; tmp=tmp->a_next ) { + i++; + } + + anew = attrs_alloc( i ); + + for( tmp=anew; a; a=a->a_next ) { + tmp->a_desc = a->a_desc; + attr_dup2( tmp, a ); + tmp=tmp->a_next; + } + + return anew; +} + +int +attr_valfind( + Attribute *a, + unsigned flags, + struct berval *val, + unsigned *slot, + void *ctx ) +{ + struct berval nval = BER_BVNULL, *cval; + MatchingRule *mr; + const char *text; + int match = -1, rc; + unsigned i, n; + + if ( flags & SLAP_MR_ORDERING ) + mr = a->a_desc->ad_type->sat_ordering; + else + mr = a->a_desc->ad_type->sat_equality; + + if( !SLAP_IS_MR_ASSERTED_VALUE_NORMALIZED_MATCH( flags ) && + mr->smr_normalize ) + { + rc = (mr->smr_normalize)( + flags & (SLAP_MR_TYPE_MASK|SLAP_MR_SUBTYPE_MASK|SLAP_MR_VALUE_OF_SYNTAX), + a->a_desc->ad_type->sat_syntax, + mr, val, &nval, ctx ); + + if( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + cval = &nval; + } else { + cval = val; + } + + n = a->a_numvals; + if ( (a->a_flags & SLAP_ATTR_SORTED_VALS) && n ) { + /* Binary search */ + unsigned base = 0; + + do { + unsigned pivot = n >> 1; + i = base + pivot; + rc = value_match( &match, a->a_desc, mr, flags, + &a->a_nvals[i], cval, &text ); + if ( rc == LDAP_SUCCESS && match == 0 ) + break; + if ( match < 0 ) { + base = i+1; + n -= pivot+1; + } else { + n = pivot; + } + } while ( n ); + if ( match < 0 ) + i++; + } else { + /* Linear search */ + for ( i = 0; i < n; i++ ) { + const char *text; + + rc = ordered_value_match( &match, a->a_desc, mr, flags, + &a->a_nvals[i], cval, &text ); + if ( rc == LDAP_SUCCESS && match == 0 ) + break; + } + } + if ( match ) + rc = LDAP_NO_SUCH_ATTRIBUTE; + if ( slot ) + *slot = i; + if ( nval.bv_val ) + slap_sl_free( nval.bv_val, ctx ); + + return rc; +} + +int +attr_valadd( + Attribute *a, + BerVarray vals, + BerVarray nvals, + int nn ) +{ + int i; + BerVarray v2; + + v2 = (BerVarray) SLAP_REALLOC( (char *) a->a_vals, + (a->a_numvals + nn + 1) * sizeof(struct berval) ); + if( v2 == NULL ) { + Debug(LDAP_DEBUG_TRACE, + "attr_valadd: SLAP_REALLOC failed.\n", 0, 0, 0 ); + return LBER_ERROR_MEMORY; + } + a->a_vals = v2; + if ( nvals ) { + v2 = (BerVarray) SLAP_REALLOC( (char *) a->a_nvals, + (a->a_numvals + nn + 1) * sizeof(struct berval) ); + if( v2 == NULL ) { + Debug(LDAP_DEBUG_TRACE, + "attr_valadd: SLAP_REALLOC failed.\n", 0, 0, 0 ); + return LBER_ERROR_MEMORY; + } + a->a_nvals = v2; + } else { + a->a_nvals = a->a_vals; + } + + /* If sorted and old vals exist, must insert */ + if (( a->a_flags & SLAP_ATTR_SORTED_VALS ) && a->a_numvals ) { + unsigned slot; + int j, rc; + v2 = nvals ? nvals : vals; + for ( i = 0; i < nn; i++ ) { + rc = attr_valfind( a, SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH | SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH, + &v2[i], &slot, NULL ); + if ( rc != LDAP_NO_SUCH_ATTRIBUTE ) { + /* should never happen */ + if ( rc == LDAP_SUCCESS ) + rc = LDAP_TYPE_OR_VALUE_EXISTS; + return rc; + } + for ( j = a->a_numvals; j >= (int)slot; j-- ) { + a->a_vals[j+1] = a->a_vals[j]; + if ( nvals ) + a->a_nvals[j+1] = a->a_nvals[j]; + } + ber_dupbv( &a->a_nvals[slot], &v2[i] ); + if ( nvals ) + ber_dupbv( &a->a_vals[slot], &vals[i] ); + a->a_numvals++; + } + BER_BVZERO( &a->a_vals[a->a_numvals] ); + if ( a->a_vals != a->a_nvals ) + BER_BVZERO( &a->a_nvals[a->a_numvals] ); + } else { + v2 = &a->a_vals[a->a_numvals]; + for ( i = 0 ; i < nn; i++ ) { + ber_dupbv( &v2[i], &vals[i] ); + if ( BER_BVISNULL( &v2[i] ) ) break; + } + BER_BVZERO( &v2[i] ); + + if ( nvals ) { + v2 = &a->a_nvals[a->a_numvals]; + for ( i = 0 ; i < nn; i++ ) { + ber_dupbv( &v2[i], &nvals[i] ); + if ( BER_BVISNULL( &v2[i] ) ) break; + } + BER_BVZERO( &v2[i] ); + } + a->a_numvals += i; + } + return 0; +} + +/* + * attr_merge - merge the given type and value with the list of + * attributes in attrs. + * + * nvals must be NULL if the attribute has no normalizer. + * In this case, a->a_nvals will be set equal to a->a_vals. + * + * returns 0 everything went ok + * -1 trouble + */ + +int +attr_merge( + Entry *e, + AttributeDescription *desc, + BerVarray vals, + BerVarray nvals ) +{ + int i = 0; + + Attribute **a; + + for ( a = &e->e_attrs; *a != NULL; a = &(*a)->a_next ) { + if ( (*a)->a_desc == desc ) { + break; + } + } + + if ( *a == NULL ) { + *a = attr_alloc( desc ); + } else { + /* + * FIXME: if the attribute already exists, the presence + * of nvals and the value of (*a)->a_nvals must be consistent + */ + assert( ( nvals == NULL && (*a)->a_nvals == (*a)->a_vals ) + || ( nvals != NULL && ( + ( (*a)->a_vals == NULL && (*a)->a_nvals == NULL ) + || ( (*a)->a_nvals != (*a)->a_vals ) ) ) ); + } + + if ( vals != NULL ) { + for ( ; !BER_BVISNULL( &vals[i] ); i++ ) ; + } + return attr_valadd( *a, vals, nvals, i ); +} + +/* + * if a normalization function is defined for the equality matchingRule + * of desc, the value is normalized and stored in nval; otherwise nval + * is NULL + */ +int +attr_normalize( + AttributeDescription *desc, + BerVarray vals, + BerVarray *nvalsp, + void *memctx ) +{ + int rc = LDAP_SUCCESS; + BerVarray nvals = NULL; + + *nvalsp = NULL; + + if ( desc->ad_type->sat_equality && + desc->ad_type->sat_equality->smr_normalize ) + { + int i; + + for ( i = 0; !BER_BVISNULL( &vals[i] ); i++ ); + + nvals = slap_sl_calloc( sizeof(struct berval), i + 1, memctx ); + for ( i = 0; !BER_BVISNULL( &vals[i] ); i++ ) { + rc = desc->ad_type->sat_equality->smr_normalize( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + desc->ad_type->sat_syntax, + desc->ad_type->sat_equality, + &vals[i], &nvals[i], memctx ); + + if ( rc != LDAP_SUCCESS ) { + BER_BVZERO( &nvals[i + 1] ); + break; + } + } + BER_BVZERO( &nvals[i] ); + *nvalsp = nvals; + } + + if ( rc != LDAP_SUCCESS && nvals != NULL ) { + ber_bvarray_free_x( nvals, memctx ); + } + + return rc; +} + +int +attr_merge_normalize( + Entry *e, + AttributeDescription *desc, + BerVarray vals, + void *memctx ) +{ + BerVarray nvals = NULL; + int rc; + + rc = attr_normalize( desc, vals, &nvals, memctx ); + if ( rc == LDAP_SUCCESS ) { + rc = attr_merge( e, desc, vals, nvals ); + if ( nvals != NULL ) { + ber_bvarray_free_x( nvals, memctx ); + } + } + + return rc; +} + +int +attr_merge_one( + Entry *e, + AttributeDescription *desc, + struct berval *val, + struct berval *nval ) +{ + Attribute **a; + + for ( a = &e->e_attrs; *a != NULL; a = &(*a)->a_next ) { + if ( (*a)->a_desc == desc ) { + break; + } + } + + if ( *a == NULL ) { + *a = attr_alloc( desc ); + } + + return attr_valadd( *a, val, nval, 1 ); +} + +/* + * if a normalization function is defined for the equality matchingRule + * of desc, the value is normalized and stored in nval; otherwise nval + * is NULL + */ +int +attr_normalize_one( + AttributeDescription *desc, + struct berval *val, + struct berval *nval, + void *memctx ) +{ + int rc = LDAP_SUCCESS; + + BER_BVZERO( nval ); + + if ( desc->ad_type->sat_equality && + desc->ad_type->sat_equality->smr_normalize ) + { + rc = desc->ad_type->sat_equality->smr_normalize( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + desc->ad_type->sat_syntax, + desc->ad_type->sat_equality, + val, nval, memctx ); + + if ( rc != LDAP_SUCCESS ) { + return rc; + } + } + + return rc; +} + +int +attr_merge_normalize_one( + Entry *e, + AttributeDescription *desc, + struct berval *val, + void *memctx ) +{ + struct berval nval = BER_BVNULL; + struct berval *nvalp = NULL; + int rc; + + rc = attr_normalize_one( desc, val, &nval, memctx ); + if ( rc == LDAP_SUCCESS && !BER_BVISNULL( &nval ) ) { + nvalp = &nval; + } + + rc = attr_merge_one( e, desc, val, nvalp ); + if ( nvalp != NULL ) { + slap_sl_free( nval.bv_val, memctx ); + } + return rc; +} + +/* + * attrs_find - find attribute(s) by AttributeDescription + * returns next attribute which is subtype of provided description. + */ + +Attribute * +attrs_find( + Attribute *a, + AttributeDescription *desc ) +{ + for ( ; a != NULL; a = a->a_next ) { + if ( is_ad_subtype( a->a_desc, desc ) ) { + return( a ); + } + } + + return( NULL ); +} + +/* + * attr_find - find attribute by type + */ + +Attribute * +attr_find( + Attribute *a, + AttributeDescription *desc ) +{ + for ( ; a != NULL; a = a->a_next ) { + if ( a->a_desc == desc ) { + return( a ); + } + } + + return( NULL ); +} + +/* + * attr_delete - delete the attribute type in list pointed to by attrs + * return 0 deleted ok + * 1 not found in list a + * -1 something bad happened + */ + +int +attr_delete( + Attribute **attrs, + AttributeDescription *desc ) +{ + Attribute **a; + + for ( a = attrs; *a != NULL; a = &(*a)->a_next ) { + if ( (*a)->a_desc == desc ) { + Attribute *save = *a; + *a = (*a)->a_next; + attr_free( save ); + + return LDAP_SUCCESS; + } + } + + return LDAP_NO_SUCH_ATTRIBUTE; +} + +int +attr_init( void ) +{ + ldap_pvt_thread_mutex_init( &attr_mutex ); + return 0; +} + +int +attr_destroy( void ) +{ + slap_list *a; + + for ( a=attr_chunks; a; a=attr_chunks ) { + attr_chunks = a->next; + free( a ); + } + ldap_pvt_thread_mutex_destroy( &attr_mutex ); + return 0; +} diff --git a/servers/slapd/ava.c b/servers/slapd/ava.c new file mode 100644 index 0000000..9504051 --- /dev/null +++ b/servers/slapd/ava.c @@ -0,0 +1,133 @@ +/* ava.c - routines for dealing with attribute value assertions */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" + +#ifdef LDAP_COMP_MATCH +#include "component.h" +#endif + +void +ava_free( + Operation *op, + AttributeAssertion *ava, + int freeit ) +{ +#ifdef LDAP_COMP_MATCH + if ( ava->aa_cf && ava->aa_cf->cf_ca->ca_comp_data.cd_mem_op ) + nibble_mem_free ( ava->aa_cf->cf_ca->ca_comp_data.cd_mem_op ); +#endif + op->o_tmpfree( ava->aa_value.bv_val, op->o_tmpmemctx ); + if ( ava->aa_desc->ad_flags & SLAP_DESC_TEMPORARY ) + op->o_tmpfree( ava->aa_desc, op->o_tmpmemctx ); + if ( freeit ) op->o_tmpfree( (char *) ava, op->o_tmpmemctx ); +} + +int +get_ava( + Operation *op, + BerElement *ber, + Filter *f, + unsigned usage, + const char **text ) +{ + int rc; + ber_tag_t rtag; + struct berval type, value; + AttributeAssertion *aa; +#ifdef LDAP_COMP_MATCH + AttributeAliasing* a_alias = NULL; +#endif + + rtag = ber_scanf( ber, "{mm}", &type, &value ); + + if( rtag == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, " get_ava ber_scanf\n", 0, 0, 0 ); + *text = "Error decoding attribute value assertion"; + return SLAPD_DISCONNECT; + } + + aa = op->o_tmpalloc( sizeof( AttributeAssertion ), op->o_tmpmemctx ); + aa->aa_desc = NULL; + aa->aa_value.bv_val = NULL; +#ifdef LDAP_COMP_MATCH + aa->aa_cf = NULL; +#endif + + rc = slap_bv2ad( &type, &aa->aa_desc, text ); + + if( rc != LDAP_SUCCESS ) { + f->f_choice |= SLAPD_FILTER_UNDEFINED; + *text = NULL; + rc = slap_bv2undef_ad( &type, &aa->aa_desc, text, + SLAP_AD_PROXIED|SLAP_AD_NOINSERT ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_FILTER, + "get_ava: unknown attributeType %s\n", type.bv_val, 0, 0 ); + aa->aa_desc = slap_bv2tmp_ad( &type, op->o_tmpmemctx ); + ber_dupbv_x( &aa->aa_value, &value, op->o_tmpmemctx ); + f->f_ava = aa; + return LDAP_SUCCESS; + } + } + + rc = asserted_value_validate_normalize( + aa->aa_desc, ad_mr(aa->aa_desc, usage), + usage, &value, &aa->aa_value, text, op->o_tmpmemctx ); + + if( rc != LDAP_SUCCESS ) { + f->f_choice |= SLAPD_FILTER_UNDEFINED; + Debug( LDAP_DEBUG_FILTER, + "get_ava: illegal value for attributeType %s\n", type.bv_val, 0, 0 ); + ber_dupbv_x( &aa->aa_value, &value, op->o_tmpmemctx ); + *text = NULL; + rc = LDAP_SUCCESS; + } + +#ifdef LDAP_COMP_MATCH + if( is_aliased_attribute ) { + a_alias = is_aliased_attribute ( aa->aa_desc ); + if ( a_alias ) { + rc = get_aliased_filter_aa ( op, aa, a_alias, text ); + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_FILTER, + "get_ava: Invalid Attribute Aliasing\n", 0, 0, 0 ); + return rc; + } + } + } +#endif + f->f_ava = aa; + return LDAP_SUCCESS; +} diff --git a/servers/slapd/back-bdb/Makefile.in b/servers/slapd/back-bdb/Makefile.in new file mode 100644 index 0000000..a9d5ba6 --- /dev/null +++ b/servers/slapd/back-bdb/Makefile.in @@ -0,0 +1,53 @@ +# Makefile.in for back-bdb +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +SRCS = init.c tools.c config.c \ + add.c bind.c compare.c delete.c modify.c modrdn.c search.c \ + extended.c referral.c operational.c \ + attr.c index.c key.c dbcache.c filterindex.c \ + dn2entry.c dn2id.c error.c id2entry.c idl.c \ + nextid.c cache.c trans.c monitor.c + +OBJS = init.lo tools.lo config.lo \ + add.lo bind.lo compare.lo delete.lo modify.lo modrdn.lo search.lo \ + extended.lo referral.lo operational.lo \ + attr.lo index.lo key.lo dbcache.lo filterindex.lo \ + dn2entry.lo dn2id.lo error.lo id2entry.lo idl.lo \ + nextid.lo cache.lo trans.lo monitor.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-bdb" +BUILD_MOD = @BUILD_BDB@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_BDB@_DEFS) +MOD_LIBS = $(BDB_LIBS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_R_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBBASE = back_bdb + +XINCPATH = -I.. -I$(srcdir)/.. +XDEFS = $(MODULES_CPPFLAGS) + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ + diff --git a/servers/slapd/back-bdb/add.c b/servers/slapd/back-bdb/add.c new file mode 100644 index 0000000..9a232af --- /dev/null +++ b/servers/slapd/back-bdb/add.c @@ -0,0 +1,547 @@ +/* add.c - ldap BerkeleyDB back-end add routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-bdb.h" + +int +bdb_add(Operation *op, SlapReply *rs ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + struct berval pdn; + Entry *p = NULL, *oe = op->ora_e; + EntryInfo *ei; + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof textbuf; + AttributeDescription *children = slap_schema.si_ad_children; + AttributeDescription *entry = slap_schema.si_ad_entry; + DB_TXN *ltid = NULL, *lt2; + ID eid = NOID; + struct bdb_op_info opinfo = {{{ 0 }}}; + int subentry; + DB_LOCK lock; + + int num_retries = 0; + int success; + + LDAPControl **postread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + +#ifdef LDAP_X_TXN + int settle = 0; +#endif + + Debug(LDAP_DEBUG_ARGS, "==> " LDAP_XSTRING(bdb_add) ": %s\n", + op->ora_e->e_name.bv_val, 0, 0); + +#ifdef LDAP_X_TXN + if( op->o_txnSpec ) { + /* acquire connection lock */ + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + if( op->o_conn->c_txn == CONN_TXN_INACTIVE ) { + rs->sr_text = "invalid transaction identifier"; + rs->sr_err = LDAP_X_TXN_ID_INVALID; + goto txnReturn; + } else if( op->o_conn->c_txn == CONN_TXN_SETTLE ) { + settle=1; + goto txnReturn; + } + + if( op->o_conn->c_txn_backend == NULL ) { + op->o_conn->c_txn_backend = op->o_bd; + + } else if( op->o_conn->c_txn_backend != op->o_bd ) { + rs->sr_text = "transaction cannot span multiple database contexts"; + rs->sr_err = LDAP_AFFECTS_MULTIPLE_DSAS; + goto txnReturn; + } + + /* insert operation into transaction */ + + rs->sr_text = "transaction specified"; + rs->sr_err = LDAP_X_TXN_SPECIFY_OKAY; + +txnReturn: + /* release connection lock */ + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + + if( !settle ) { + send_ldap_result( op, rs ); + return rs->sr_err; + } + } +#endif + + ctrls[num_ctrls] = 0; + + /* check entry's schema */ + rs->sr_err = entry_schema_check( op, op->ora_e, NULL, + get_relax(op), 1, NULL, &rs->sr_text, textbuf, textlen ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": entry failed schema check: " + "%s (%d)\n", rs->sr_text, rs->sr_err, 0 ); + goto return_results; + } + + /* add opattrs to shadow as well, only missing attrs will actually + * be added; helps compatibility with older OL versions */ + rs->sr_err = slap_add_opattrs( op, &rs->sr_text, textbuf, textlen, 1 ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": entry failed op attrs add: " + "%s (%d)\n", rs->sr_text, rs->sr_err, 0 ); + goto return_results; + } + + if ( get_assert( op ) && + ( test_filter( op, op->ora_e, get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto return_results; + } + + subentry = is_entry_subentry( op->ora_e ); + + if( 0 ) { +retry: /* transaction retry */ + if( p ) { + /* free parent and reader lock */ + if ( p != (Entry *)&slap_entry_root ) { + bdb_unlocked_cache_return_entry_r( bdb, p ); + } + p = NULL; + } + rs->sr_err = TXN_ABORT( ltid ); + ltid = NULL; + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + opinfo.boi_oe.oe_key = NULL; + op->o_do_not_cache = opinfo.boi_acl_cache; + if( rs->sr_err != 0 ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto return_results; + } + bdb_trans_backoff( ++num_retries ); + } + + /* begin transaction */ + rs->sr_err = TXN_BEGIN( bdb->bi_dbenv, NULL, <id, + bdb->bi_db_opflags ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": txn_begin failed: %s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_add) ": txn1 id: %x\n", + ltid->id(ltid), 0, 0 ); + + opinfo.boi_oe.oe_key = bdb; + opinfo.boi_txn = ltid; + opinfo.boi_err = 0; + opinfo.boi_acl_cache = op->o_do_not_cache; + LDAP_SLIST_INSERT_HEAD( &op->o_extra, &opinfo.boi_oe, oe_next ); + + /* + * Get the parent dn and see if the corresponding entry exists. + */ + if ( be_issuffix( op->o_bd, &op->ora_e->e_nname ) ) { + pdn = slap_empty_bv; + } else { + dnParent( &op->ora_e->e_nname, &pdn ); + } + + /* get entry or parent */ + rs->sr_err = bdb_dn2entry( op, ltid, &op->ora_e->e_nname, &ei, + 1, &lock ); + switch( rs->sr_err ) { + case 0: + rs->sr_err = LDAP_ALREADY_EXISTS; + goto return_results; + case DB_NOTFOUND: + break; + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + p = ei->bei_e; + if ( !p ) + p = (Entry *)&slap_entry_root; + + if ( !bvmatch( &pdn, &p->e_nname ) ) { + rs->sr_matched = ber_strdup_x( p->e_name.bv_val, + op->o_tmpmemctx ); + rs->sr_ref = is_entry_referral( p ) + ? get_entry_referrals( op, p ) + : NULL; + if ( p != (Entry *)&slap_entry_root ) + bdb_unlocked_cache_return_entry_r( bdb, p ); + p = NULL; + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": parent " + "does not exist\n", 0, 0, 0 ); + + rs->sr_err = LDAP_REFERRAL; + rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED; + goto return_results; + } + + rs->sr_err = access_allowed( op, p, + children, NULL, ACL_WADD, NULL ); + + if ( ! rs->sr_err ) { + switch( opinfo.boi_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + if ( p != (Entry *)&slap_entry_root ) + bdb_unlocked_cache_return_entry_r( bdb, p ); + p = NULL; + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": no write access to parent\n", + 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to parent"; + goto return_results;; + } + + if ( p != (Entry *)&slap_entry_root ) { + if ( is_entry_subentry( p ) ) { + bdb_unlocked_cache_return_entry_r( bdb, p ); + p = NULL; + /* parent is a subentry, don't allow add */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": parent is subentry\n", + 0, 0, 0 ); + rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; + rs->sr_text = "parent is a subentry"; + goto return_results;; + } + + if ( is_entry_alias( p ) ) { + bdb_unlocked_cache_return_entry_r( bdb, p ); + p = NULL; + /* parent is an alias, don't allow add */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": parent is alias\n", + 0, 0, 0 ); + rs->sr_err = LDAP_ALIAS_PROBLEM; + rs->sr_text = "parent is an alias"; + goto return_results;; + } + + if ( is_entry_referral( p ) ) { + /* parent is a referral, don't allow add */ + rs->sr_matched = ber_strdup_x( p->e_name.bv_val, + op->o_tmpmemctx ); + rs->sr_ref = get_entry_referrals( op, p ); + bdb_unlocked_cache_return_entry_r( bdb, p ); + p = NULL; + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": parent is referral\n", + 0, 0, 0 ); + + rs->sr_err = LDAP_REFERRAL; + rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED; + goto return_results; + } + + } + + if ( subentry ) { + /* FIXME: */ + /* parent must be an administrative point of the required kind */ + } + + /* free parent and reader lock */ + if ( p != (Entry *)&slap_entry_root ) { + if ( p->e_nname.bv_len ) { + struct berval ppdn; + + /* ITS#5326: use parent's DN if differs from provided one */ + dnParent( &op->ora_e->e_name, &ppdn ); + if ( !dn_match( &p->e_name, &ppdn ) ) { + struct berval rdn; + struct berval newdn; + + dnRdn( &op->ora_e->e_name, &rdn ); + + build_new_dn( &newdn, &p->e_name, &rdn, NULL ); + if ( op->ora_e->e_name.bv_val != op->o_req_dn.bv_val ) + ber_memfree( op->ora_e->e_name.bv_val ); + op->ora_e->e_name = newdn; + + /* FIXME: should check whether + * dnNormalize(newdn) == e->e_nname ... */ + } + } + + bdb_unlocked_cache_return_entry_r( bdb, p ); + } + p = NULL; + + rs->sr_err = access_allowed( op, op->ora_e, + entry, NULL, ACL_WADD, NULL ); + + if ( ! rs->sr_err ) { + switch( opinfo.boi_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": no write access to entry\n", + 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to entry"; + goto return_results;; + } + + /* + * Check ACL for attribute write access + */ + if (!acl_check_modlist(op, oe, op->ora_modlist)) { + switch( opinfo.boi_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": no write access to attribute\n", + 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to attribute"; + goto return_results;; + } + + if ( eid == NOID ) { + rs->sr_err = bdb_next_id( op->o_bd, &eid ); + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": next_id failed (%d)\n", + rs->sr_err, 0, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + op->ora_e->e_id = eid; + } + + /* nested transaction */ + rs->sr_err = TXN_BEGIN( bdb->bi_dbenv, ltid, <2, + bdb->bi_db_opflags ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": txn_begin(2) failed: " + "%s (%d)\n", db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_add) ": txn2 id: %x\n", + lt2->id(lt2), 0, 0 ); + + /* dn2id index */ + rs->sr_err = bdb_dn2id_add( op, lt2, ei, op->ora_e ); + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": dn2id_add failed: %s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case DB_KEYEXIST: + rs->sr_err = LDAP_ALREADY_EXISTS; + break; + default: + rs->sr_err = LDAP_OTHER; + } + goto return_results; + } + + /* attribute indexes */ + rs->sr_err = bdb_index_entry_add( op, lt2, op->ora_e ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": index_entry_add failed\n", + 0, 0, 0 ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + default: + rs->sr_err = LDAP_OTHER; + } + rs->sr_text = "index generation failed"; + goto return_results; + } + + /* id2entry index */ + rs->sr_err = bdb_id2entry_add( op->o_bd, lt2, op->ora_e ); + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": id2entry_add failed\n", + 0, 0, 0 ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + default: + rs->sr_err = LDAP_OTHER; + } + rs->sr_text = "entry store failed"; + goto return_results; + } + + if ( TXN_COMMIT( lt2, 0 ) != 0 ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "txn_commit(2) failed"; + goto return_results; + } + + /* post-read */ + if( op->o_postread ) { + if( postread_ctrl == NULL ) { + postread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if ( slap_read_controls( op, rs, op->ora_e, + &slap_post_read_bv, postread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_add) ": post-read " + "failed!\n", 0, 0, 0 ); + if ( op->o_postread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + if ( op->o_noop ) { + if (( rs->sr_err=TXN_ABORT( ltid )) != 0 ) { + rs->sr_text = "txn_abort (no-op) failed"; + } else { + rs->sr_err = LDAP_X_NO_OPERATION; + ltid = NULL; + goto return_results; + } + + } else { + struct berval nrdn; + + /* pick the RDN if not suffix; otherwise pick the entire DN */ + if (pdn.bv_len) { + nrdn.bv_val = op->ora_e->e_nname.bv_val; + nrdn.bv_len = pdn.bv_val - op->ora_e->e_nname.bv_val - 1; + } else { + nrdn = op->ora_e->e_nname; + } + + bdb_cache_add( bdb, ei, op->ora_e, &nrdn, ltid, &lock ); + + if(( rs->sr_err=TXN_COMMIT( ltid, 0 )) != 0 ) { + rs->sr_text = "txn_commit failed"; + } else { + rs->sr_err = LDAP_SUCCESS; + } + } + + ltid = NULL; + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + opinfo.boi_oe.oe_key = NULL; + + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": %s : %s (%d)\n", + rs->sr_text, db_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + goto return_results; + } + + Debug(LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": added%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + op->ora_e->e_id, op->ora_e->e_dn ); + + rs->sr_text = NULL; + if( num_ctrls ) rs->sr_ctrls = ctrls; + +return_results: + success = rs->sr_err; + send_ldap_result( op, rs ); + + if( ltid != NULL ) { + TXN_ABORT( ltid ); + } + if ( opinfo.boi_oe.oe_key ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + } + + if( success == LDAP_SUCCESS ) { + /* We own the entry now, and it can be purged at will + * Check to make sure it's the same entry we entered with. + * Possibly a callback may have mucked with it, although + * in general callbacks should treat the entry as read-only. + */ + bdb_cache_deref( oe->e_private ); + if ( op->ora_e == oe ) + op->ora_e = NULL; + + if ( bdb->bi_txn_cp_kbyte ) { + TXN_CHECKPOINT( bdb->bi_dbenv, + bdb->bi_txn_cp_kbyte, bdb->bi_txn_cp_min, 0 ); + } + } + + slap_graduate_commit_csn( op ); + + if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) { + slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *postread_ctrl, op->o_tmpmemctx ); + } + return rs->sr_err; +} diff --git a/servers/slapd/back-bdb/attr.c b/servers/slapd/back-bdb/attr.c new file mode 100644 index 0000000..4a95fe7 --- /dev/null +++ b/servers/slapd/back-bdb/attr.c @@ -0,0 +1,441 @@ +/* attr.c - backend routines for dealing with attributes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-bdb.h" +#include "config.h" +#include "lutil.h" + +/* Find the ad, return -1 if not found, + * set point for insertion if ins is non-NULL + */ +int +bdb_attr_slot( struct bdb_info *bdb, AttributeDescription *ad, int *ins ) +{ + unsigned base = 0, cursor = 0; + unsigned n = bdb->bi_nattrs; + int val = 0; + + while ( 0 < n ) { + unsigned pivot = n >> 1; + cursor = base + pivot; + + val = SLAP_PTRCMP( ad, bdb->bi_attrs[cursor]->ai_desc ); + if ( val < 0 ) { + n = pivot; + } else if ( val > 0 ) { + base = cursor + 1; + n -= pivot + 1; + } else { + return cursor; + } + } + if ( ins ) { + if ( val > 0 ) + ++cursor; + *ins = cursor; + } + return -1; +} + +static int +ainfo_insert( struct bdb_info *bdb, AttrInfo *a ) +{ + int x; + int i = bdb_attr_slot( bdb, a->ai_desc, &x ); + + /* Is it a dup? */ + if ( i >= 0 ) + return -1; + + bdb->bi_attrs = ch_realloc( bdb->bi_attrs, ( bdb->bi_nattrs+1 ) * + sizeof( AttrInfo * )); + if ( x < bdb->bi_nattrs ) + AC_MEMCPY( &bdb->bi_attrs[x+1], &bdb->bi_attrs[x], + ( bdb->bi_nattrs - x ) * sizeof( AttrInfo *)); + bdb->bi_attrs[x] = a; + bdb->bi_nattrs++; + return 0; +} + +AttrInfo * +bdb_attr_mask( + struct bdb_info *bdb, + AttributeDescription *desc ) +{ + int i = bdb_attr_slot( bdb, desc, NULL ); + return i < 0 ? NULL : bdb->bi_attrs[i]; +} + +int +bdb_attr_index_config( + struct bdb_info *bdb, + const char *fname, + int lineno, + int argc, + char **argv, + struct config_reply_s *c_reply) +{ + int rc = 0; + int i; + slap_mask_t mask; + char **attrs; + char **indexes = NULL; + + attrs = ldap_str2charray( argv[0], "," ); + + if( attrs == NULL ) { + fprintf( stderr, "%s: line %d: " + "no attributes specified: %s\n", + fname, lineno, argv[0] ); + return LDAP_PARAM_ERROR; + } + + if ( argc > 1 ) { + indexes = ldap_str2charray( argv[1], "," ); + + if( indexes == NULL ) { + fprintf( stderr, "%s: line %d: " + "no indexes specified: %s\n", + fname, lineno, argv[1] ); + rc = LDAP_PARAM_ERROR; + goto done; + } + } + + if( indexes == NULL ) { + mask = bdb->bi_defaultmask; + + } else { + mask = 0; + + for ( i = 0; indexes[i] != NULL; i++ ) { + slap_mask_t index; + rc = slap_str2index( indexes[i], &index ); + + if( rc != LDAP_SUCCESS ) { + if ( c_reply ) + { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "index type \"%s\" undefined", indexes[i] ); + + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + rc = LDAP_PARAM_ERROR; + goto done; + } + + mask |= index; + } + } + + if( !mask ) { + if ( c_reply ) + { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "no indexes selected" ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + rc = LDAP_PARAM_ERROR; + goto done; + } + + for ( i = 0; attrs[i] != NULL; i++ ) { + AttrInfo *a; + AttributeDescription *ad; + const char *text; +#ifdef LDAP_COMP_MATCH + ComponentReference* cr = NULL; + AttrInfo *a_cr = NULL; +#endif + + if( strcasecmp( attrs[i], "default" ) == 0 ) { + bdb->bi_defaultmask |= mask; + continue; + } + +#ifdef LDAP_COMP_MATCH + if ( is_component_reference( attrs[i] ) ) { + rc = extract_component_reference( attrs[i], &cr ); + if ( rc != LDAP_SUCCESS ) { + if ( c_reply ) + { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "index component reference\"%s\" undefined", + attrs[i] ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + goto done; + } + cr->cr_indexmask = mask; + /* + * After extracting a component reference + * only the name of a attribute will be remaining + */ + } else { + cr = NULL; + } +#endif + ad = NULL; + rc = slap_str2ad( attrs[i], &ad, &text ); + + if( rc != LDAP_SUCCESS ) { + if ( c_reply ) + { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "index attribute \"%s\" undefined", + attrs[i] ); + + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + goto done; + } + + if( ad == slap_schema.si_ad_entryDN || slap_ad_is_binary( ad ) ) { + if (c_reply) { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "index of attribute \"%s\" disallowed", attrs[i] ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + rc = LDAP_UNWILLING_TO_PERFORM; + goto done; + } + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_APPROX ) && !( + ad->ad_type->sat_approx + && ad->ad_type->sat_approx->smr_indexer + && ad->ad_type->sat_approx->smr_filter ) ) + { + if (c_reply) { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "approx index of attribute \"%s\" disallowed", attrs[i] ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + rc = LDAP_INAPPROPRIATE_MATCHING; + goto done; + } + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_EQUALITY ) && !( + ad->ad_type->sat_equality + && ad->ad_type->sat_equality->smr_indexer + && ad->ad_type->sat_equality->smr_filter ) ) + { + if (c_reply) { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "equality index of attribute \"%s\" disallowed", attrs[i] ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + rc = LDAP_INAPPROPRIATE_MATCHING; + goto done; + } + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_SUBSTR ) && !( + ad->ad_type->sat_substr + && ad->ad_type->sat_substr->smr_indexer + && ad->ad_type->sat_substr->smr_filter ) ) + { + if (c_reply) { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "substr index of attribute \"%s\" disallowed", attrs[i] ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + rc = LDAP_INAPPROPRIATE_MATCHING; + goto done; + } + + Debug( LDAP_DEBUG_CONFIG, "index %s 0x%04lx\n", + ad->ad_cname.bv_val, mask, 0 ); + + a = (AttrInfo *) ch_malloc( sizeof(AttrInfo) ); + +#ifdef LDAP_COMP_MATCH + a->ai_cr = NULL; +#endif + a->ai_desc = ad; + + if ( bdb->bi_flags & BDB_IS_OPEN ) { + a->ai_indexmask = 0; + a->ai_newmask = mask; + } else { + a->ai_indexmask = mask; + a->ai_newmask = 0; + } + +#ifdef LDAP_COMP_MATCH + if ( cr ) { + a_cr = bdb_attr_mask( bdb, ad ); + if ( a_cr ) { + /* + * AttrInfo is already in AVL + * just add the extracted component reference + * in the AttrInfo + */ + rc = insert_component_reference( cr, &a_cr->ai_cr ); + if ( rc != LDAP_SUCCESS) { + fprintf( stderr, " error during inserting component reference in %s ", attrs[i]); + rc = LDAP_PARAM_ERROR; + goto done; + } + continue; + } else { + rc = insert_component_reference( cr, &a->ai_cr ); + if ( rc != LDAP_SUCCESS) { + fprintf( stderr, " error during inserting component reference in %s ", attrs[i]); + rc = LDAP_PARAM_ERROR; + goto done; + } + } + } +#endif + rc = ainfo_insert( bdb, a ); + if( rc ) { + if ( bdb->bi_flags & BDB_IS_OPEN ) { + AttrInfo *b = bdb_attr_mask( bdb, ad ); + /* If there is already an index defined for this attribute + * it must be replaced. Otherwise we end up with multiple + * olcIndex values for the same attribute */ + if ( b->ai_indexmask & BDB_INDEX_DELETING ) { + /* If we were editing this attr, reset it */ + b->ai_indexmask &= ~BDB_INDEX_DELETING; + /* If this is leftover from a previous add, commit it */ + if ( b->ai_newmask ) + b->ai_indexmask = b->ai_newmask; + b->ai_newmask = a->ai_newmask; + ch_free( a ); + rc = 0; + continue; + } + } + if (c_reply) { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "duplicate index definition for attr \"%s\"", + attrs[i] ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + + rc = LDAP_PARAM_ERROR; + goto done; + } + } + +done: + ldap_charray_free( attrs ); + if ( indexes != NULL ) ldap_charray_free( indexes ); + + return rc; +} + +static int +bdb_attr_index_unparser( void *v1, void *v2 ) +{ + AttrInfo *ai = v1; + BerVarray *bva = v2; + struct berval bv; + char *ptr; + + slap_index2bvlen( ai->ai_indexmask, &bv ); + if ( bv.bv_len ) { + bv.bv_len += ai->ai_desc->ad_cname.bv_len + 1; + ptr = ch_malloc( bv.bv_len+1 ); + bv.bv_val = lutil_strcopy( ptr, ai->ai_desc->ad_cname.bv_val ); + *bv.bv_val++ = ' '; + slap_index2bv( ai->ai_indexmask, &bv ); + bv.bv_val = ptr; + ber_bvarray_add( bva, &bv ); + } + return 0; +} + +static AttributeDescription addef = { NULL, NULL, BER_BVC("default") }; +static AttrInfo aidef = { &addef }; + +void +bdb_attr_index_unparse( struct bdb_info *bdb, BerVarray *bva ) +{ + int i; + + if ( bdb->bi_defaultmask ) { + aidef.ai_indexmask = bdb->bi_defaultmask; + bdb_attr_index_unparser( &aidef, bva ); + } + for ( i=0; i<bdb->bi_nattrs; i++ ) + bdb_attr_index_unparser( bdb->bi_attrs[i], bva ); +} + +void +bdb_attr_info_free( AttrInfo *ai ) +{ +#ifdef LDAP_COMP_MATCH + free( ai->ai_cr ); +#endif + free( ai ); +} + +void +bdb_attr_index_destroy( struct bdb_info *bdb ) +{ + int i; + + for ( i=0; i<bdb->bi_nattrs; i++ ) + bdb_attr_info_free( bdb->bi_attrs[i] ); + + free( bdb->bi_attrs ); +} + +void bdb_attr_index_free( struct bdb_info *bdb, AttributeDescription *ad ) +{ + int i; + + i = bdb_attr_slot( bdb, ad, NULL ); + if ( i >= 0 ) { + bdb_attr_info_free( bdb->bi_attrs[i] ); + bdb->bi_nattrs--; + for (; i<bdb->bi_nattrs; i++) + bdb->bi_attrs[i] = bdb->bi_attrs[i+1]; + } +} + +void bdb_attr_flush( struct bdb_info *bdb ) +{ + int i; + + for ( i=0; i<bdb->bi_nattrs; i++ ) { + if ( bdb->bi_attrs[i]->ai_indexmask & BDB_INDEX_DELETING ) { + int j; + bdb_attr_info_free( bdb->bi_attrs[i] ); + bdb->bi_nattrs--; + for (j=i; j<bdb->bi_nattrs; j++) + bdb->bi_attrs[j] = bdb->bi_attrs[j+1]; + i--; + } + } +} diff --git a/servers/slapd/back-bdb/back-bdb.h b/servers/slapd/back-bdb/back-bdb.h new file mode 100644 index 0000000..ea68ebb --- /dev/null +++ b/servers/slapd/back-bdb/back-bdb.h @@ -0,0 +1,377 @@ +/* back-bdb.h - bdb back-end header file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#ifndef _BACK_BDB_H_ +#define _BACK_BDB_H_ + +#include <portable.h> +#include "slap.h" +#include <db.h> +#include "alock.h" + +LDAP_BEGIN_DECL + +#define DB_VERSION_FULL ((DB_VERSION_MAJOR << 24) | (DB_VERSION_MINOR << 16) | DB_VERSION_PATCH) + +#define DN_BASE_PREFIX SLAP_INDEX_EQUALITY_PREFIX +#define DN_ONE_PREFIX '%' +#define DN_SUBTREE_PREFIX '@' + +#define DBTzero(t) (memset((t), 0, sizeof(DBT))) +#define DBT2bv(t,bv) ((bv)->bv_val = (t)->data, \ + (bv)->bv_len = (t)->size) +#define bv2DBT(bv,t) ((t)->data = (bv)->bv_val, \ + (t)->size = (bv)->bv_len ) + +#define BDB_TXN_RETRIES 16 + +#define BDB_MAX_ADD_LOOP 30 + +#define BDB_SUFFIX ".bdb" +#define BDB_ID2ENTRY 0 +#define BDB_DN2ID 1 +#define BDB_NDB 2 + +/* The bdb on-disk entry format is pretty space-inefficient. Average + * sized user entries are 3-4K each. You need at least two entries to + * fit into a single database page, more is better. 64K is BDB's + * upper bound. Smaller pages are better for concurrency. + */ +#ifndef BDB_ID2ENTRY_PAGESIZE +#define BDB_ID2ENTRY_PAGESIZE 16384 +#endif + +#define DEFAULT_CACHE_SIZE 1000 + +/* The default search IDL stack cache depth */ +#define DEFAULT_SEARCH_STACK_DEPTH 16 + +/* The minimum we can function with */ +#define MINIMUM_SEARCH_STACK_DEPTH 8 + +typedef struct bdb_idl_cache_entry_s { + struct berval kstr; + ID *idl; + DB *db; + int idl_flags; + struct bdb_idl_cache_entry_s* idl_lru_prev; + struct bdb_idl_cache_entry_s* idl_lru_next; +} bdb_idl_cache_entry_t; + +/* BDB backend specific entry info */ +typedef struct bdb_entry_info { + struct bdb_entry_info *bei_parent; + ID bei_id; + + /* we use the bei_id as a lockobj, but we need to make the size != 4 + * to avoid conflicting with BDB's internal locks. So add a byte here + * that is always zero. + */ + short bei_lockpad; + + short bei_state; +#define CACHE_ENTRY_DELETED 1 +#define CACHE_ENTRY_NO_KIDS 2 +#define CACHE_ENTRY_NOT_LINKED 4 +#define CACHE_ENTRY_NO_GRANDKIDS 8 +#define CACHE_ENTRY_LOADING 0x10 +#define CACHE_ENTRY_WALKING 0x20 +#define CACHE_ENTRY_ONELEVEL 0x40 +#define CACHE_ENTRY_REFERENCED 0x80 +#define CACHE_ENTRY_NOT_CACHED 0x100 + int bei_finders; + + /* + * remaining fields require backend cache lock to access + */ + struct berval bei_nrdn; +#ifdef BDB_HIER + struct berval bei_rdn; + int bei_modrdns; /* track renames */ + int bei_ckids; /* number of kids cached */ + int bei_dkids; /* number of kids on-disk, plus 1 */ +#endif + Entry *bei_e; + Avlnode *bei_kids; +#ifdef SLAP_ZONE_ALLOC + struct bdb_info *bei_bdb; + int bei_zseq; +#endif + ldap_pvt_thread_mutex_t bei_kids_mutex; + + struct bdb_entry_info *bei_lrunext; /* for cache lru list */ + struct bdb_entry_info *bei_lruprev; +} EntryInfo; +#undef BEI +#define BEI(e) ((EntryInfo *) ((e)->e_private)) + +/* for the in-core cache of entries */ +typedef struct bdb_cache { + EntryInfo *c_eifree; /* free list */ + Avlnode *c_idtree; + EntryInfo *c_lruhead; /* lru - add accessed entries here */ + EntryInfo *c_lrutail; /* lru - rem lru entries from here */ + EntryInfo c_dntree; + ID c_maxsize; + ID c_cursize; + ID c_minfree; + ID c_eimax; + ID c_eiused; /* EntryInfo's in use */ + ID c_leaves; /* EntryInfo leaf nodes */ + int c_purging; + DB_TXN *c_txn; /* used by lru cleaner */ + ldap_pvt_thread_rdwr_t c_rwlock; + ldap_pvt_thread_mutex_t c_lru_mutex; + ldap_pvt_thread_mutex_t c_count_mutex; + ldap_pvt_thread_mutex_t c_eifree_mutex; +#ifdef SLAP_ZONE_ALLOC + void *c_zctx; +#endif +} Cache; + +#define CACHE_READ_LOCK 0 +#define CACHE_WRITE_LOCK 1 + +#define BDB_INDICES 128 + +struct bdb_db_info { + struct berval bdi_name; + DB *bdi_db; +}; + +struct bdb_db_pgsize { + struct bdb_db_pgsize *bdp_next; + struct berval bdp_name; + int bdp_size; +}; + +#ifdef LDAP_DEVEL +#define BDB_MONITOR_IDX +#endif /* LDAP_DEVEL */ + +typedef struct bdb_monitor_t { + void *bdm_cb; + struct berval bdm_ndn; +} bdb_monitor_t; + +/* From ldap_rq.h */ +struct re_s; + +struct bdb_info { + DB_ENV *bi_dbenv; + + /* DB_ENV parameters */ + /* The DB_ENV can be tuned via DB_CONFIG */ + char *bi_dbenv_home; + u_int32_t bi_dbenv_xflags; /* extra flags */ + int bi_dbenv_mode; + + int bi_ndatabases; + int bi_db_opflags; /* db-specific flags */ + struct bdb_db_info **bi_databases; + ldap_pvt_thread_mutex_t bi_database_mutex; + struct bdb_db_pgsize *bi_pagesizes; + + slap_mask_t bi_defaultmask; + Cache bi_cache; + struct bdb_attrinfo **bi_attrs; + int bi_nattrs; + void *bi_search_stack; + int bi_search_stack_depth; + int bi_linear_index; + + int bi_txn_cp; + u_int32_t bi_txn_cp_min; + u_int32_t bi_txn_cp_kbyte; + struct re_s *bi_txn_cp_task; + struct re_s *bi_index_task; + + u_int32_t bi_lock_detect; + long bi_shm_key; + + ID bi_lastid; + ldap_pvt_thread_mutex_t bi_lastid_mutex; + ID bi_idl_cache_max_size; + ID bi_idl_cache_size; + Avlnode *bi_idl_tree; + bdb_idl_cache_entry_t *bi_idl_lru_head; + bdb_idl_cache_entry_t *bi_idl_lru_tail; + ldap_pvt_thread_rdwr_t bi_idl_tree_rwlock; + ldap_pvt_thread_mutex_t bi_idl_tree_lrulock; + alock_info_t bi_alock_info; + char *bi_db_config_path; + BerVarray bi_db_config; + char *bi_db_crypt_file; + struct berval bi_db_crypt_key; + bdb_monitor_t bi_monitor; + +#ifdef BDB_MONITOR_IDX + ldap_pvt_thread_mutex_t bi_idx_mutex; + Avlnode *bi_idx; +#endif /* BDB_MONITOR_IDX */ + + int bi_flags; +#define BDB_IS_OPEN 0x01 +#define BDB_HAS_CONFIG 0x02 +#define BDB_UPD_CONFIG 0x04 +#define BDB_DEL_INDEX 0x08 +#define BDB_RE_OPEN 0x10 +#define BDB_CHKSUM 0x20 +#ifdef BDB_HIER + int bi_modrdns; /* number of modrdns completed */ + ldap_pvt_thread_mutex_t bi_modrdns_mutex; +#endif +}; + +#define bi_id2entry bi_databases[BDB_ID2ENTRY] +#define bi_dn2id bi_databases[BDB_DN2ID] + + +struct bdb_lock_info { + struct bdb_lock_info *bli_next; + DB_LOCK bli_lock; + ID bli_id; + int bli_flag; +}; +#define BLI_DONTFREE 1 + +struct bdb_op_info { + OpExtra boi_oe; + DB_TXN* boi_txn; + struct bdb_lock_info *boi_locks; /* used when no txn */ + u_int32_t boi_err; + char boi_acl_cache; + char boi_flag; +}; +#define BOI_DONTFREE 1 + +#define DB_OPEN(db, file, name, type, flags, mode) \ + ((db)->open)(db, file, name, type, flags, mode) + +#if DB_VERSION_MAJOR < 4 +#define LOCK_DETECT(env,f,t,a) lock_detect(env, f, t, a) +#define LOCK_GET(env,i,f,o,m,l) lock_get(env, i, f, o, m, l) +#define LOCK_PUT(env,l) lock_put(env, l) +#define TXN_CHECKPOINT(env,k,m,f) txn_checkpoint(env, k, m, f) +#define TXN_BEGIN(env,p,t,f) txn_begin((env), p, t, f) +#define TXN_PREPARE(txn,gid) txn_prepare((txn), (gid)) +#define TXN_COMMIT(txn,f) txn_commit((txn), (f)) +#define TXN_ABORT(txn) txn_abort((txn)) +#define TXN_ID(txn) txn_id(txn) +#define XLOCK_ID(env, locker) lock_id(env, locker) +#define XLOCK_ID_FREE(env, locker) lock_id_free(env, locker) +#else +#define LOCK_DETECT(env,f,t,a) (env)->lock_detect(env, f, t, a) +#define LOCK_GET(env,i,f,o,m,l) (env)->lock_get(env, i, f, o, m, l) +#define LOCK_PUT(env,l) (env)->lock_put(env, l) +#define TXN_CHECKPOINT(env,k,m,f) (env)->txn_checkpoint(env, k, m, f) +#define TXN_BEGIN(env,p,t,f) (env)->txn_begin((env), p, t, f) +#define TXN_PREPARE(txn,g) (txn)->prepare((txn), (g)) +#define TXN_COMMIT(txn,f) (txn)->commit((txn), (f)) +#define TXN_ABORT(txn) (txn)->abort((txn)) +#define TXN_ID(txn) (txn)->id(txn) +#define XLOCK_ID(env, locker) (env)->lock_id(env, locker) +#define XLOCK_ID_FREE(env, locker) (env)->lock_id_free(env, locker) + +/* BDB 4.1.17 adds txn arg to db->open */ +#if DB_VERSION_FULL >= 0x04010011 +#undef DB_OPEN +#define DB_OPEN(db, file, name, type, flags, mode) \ + ((db)->open)(db, NULL, file, name, type, flags, mode) +#endif + +/* #undef BDB_LOG_DEBUG */ + +#ifdef BDB_LOG_DEBUG + +/* env->log_printf appeared in 4.4 */ +#if DB_VERSION_FULL >= 0x04040000 +#define BDB_LOG_PRINTF(env,txn,fmt,...) (env)->log_printf((env),(txn),(fmt),__VA_ARGS__) +#else +extern int __db_logmsg(const DB_ENV *env, DB_TXN *txn, const char *op, u_int32_t flags, + const char *fmt,...); +#define BDB_LOG_PRINTF(env,txn,fmt,...) __db_logmsg((env),(txn),"DIAGNOSTIC",0,(fmt),__VA_ARGS__) +#endif + +/* !BDB_LOG_DEBUG */ +#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || \ + (defined(__GNUC__) && __GNUC__ >= 3 && !defined(__STRICT_ANSI__)) +#define BDB_LOG_PRINTF(a,b,c,...) +#else +#define BDB_LOG_PRINTF (void) /* will evaluate and discard the arguments */ + +#endif /* BDB_LOG_DEBUG */ + +#endif + +#ifndef DB_BUFFER_SMALL +#define DB_BUFFER_SMALL ENOMEM +#endif + +#define BDB_CSN_COMMIT 0 +#define BDB_CSN_ABORT 1 +#define BDB_CSN_RETRY 2 + +/* Copy an ID "src" to pointer "dst" in big-endian byte order */ +#define BDB_ID2DISK( src, dst ) \ + do { int i0; ID tmp; unsigned char *_p; \ + tmp = (src); _p = (unsigned char *)(dst); \ + for ( i0=sizeof(ID)-1; i0>=0; i0-- ) { \ + _p[i0] = tmp & 0xff; tmp >>= 8; \ + } \ + } while(0) + +/* Copy a pointer "src" to a pointer "dst" from big-endian to native order */ +#define BDB_DISK2ID( src, dst ) \ + do { unsigned i0; ID tmp = 0; unsigned char *_p; \ + _p = (unsigned char *)(src); \ + for ( i0=0; i0<sizeof(ID); i0++ ) { \ + tmp <<= 8; tmp |= *_p++; \ + } *(dst) = tmp; \ + } while (0) + +LDAP_END_DECL + +/* for the cache of attribute information (which are indexed, etc.) */ +typedef struct bdb_attrinfo { + AttributeDescription *ai_desc; /* attribute description cn;lang-en */ + slap_mask_t ai_indexmask; /* how the attr is indexed */ + slap_mask_t ai_newmask; /* new settings to replace old mask */ +#ifdef LDAP_COMP_MATCH + ComponentReference* ai_cr; /*component indexing*/ +#endif +} AttrInfo; + +/* These flags must not clash with SLAP_INDEX flags or ops in slap.h! */ +#define BDB_INDEX_DELETING 0x8000U /* index is being modified */ +#define BDB_INDEX_UPDATE_OP 0x03 /* performing an index update */ + +/* For slapindex to record which attrs in an entry belong to which + * index database + */ +typedef struct AttrList { + struct AttrList *next; + Attribute *attr; +} AttrList; + +typedef struct IndexRec { + AttrInfo *ai; + AttrList *attrs; +} IndexRec; + +#include "proto-bdb.h" + +#endif /* _BACK_BDB_H_ */ diff --git a/servers/slapd/back-bdb/bind.c b/servers/slapd/back-bdb/bind.c new file mode 100644 index 0000000..1daf537 --- /dev/null +++ b/servers/slapd/back-bdb/bind.c @@ -0,0 +1,166 @@ +/* bind.c - bdb backend bind routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/unistd.h> + +#include "back-bdb.h" + +int +bdb_bind( Operation *op, SlapReply *rs ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + Entry *e; + Attribute *a; + EntryInfo *ei; + + AttributeDescription *password = slap_schema.si_ad_userPassword; + + DB_TXN *rtxn; + DB_LOCK lock; + + Debug( LDAP_DEBUG_ARGS, + "==> " LDAP_XSTRING(bdb_bind) ": dn: %s\n", + op->o_req_dn.bv_val, 0, 0); + + /* allow noauth binds */ + switch ( be_rootdn_bind( op, NULL ) ) { + case LDAP_SUCCESS: + /* frontend will send result */ + return rs->sr_err = LDAP_SUCCESS; + + default: + /* give the database a chance */ + /* NOTE: this behavior departs from that of other backends, + * since the others, in case of password checking failure + * do not give the database a chance. If an entry with + * rootdn's name does not exist in the database the result + * will be the same. See ITS#4962 for discussion. */ + break; + } + + rs->sr_err = bdb_reader_get(op, bdb->bi_dbenv, &rtxn); + switch(rs->sr_err) { + case 0: + break; + default: + rs->sr_text = "internal error"; + send_ldap_result( op, rs ); + return rs->sr_err; + } + +dn2entry_retry: + /* get entry with reader lock */ + rs->sr_err = bdb_dn2entry( op, rtxn, &op->o_req_ndn, &ei, 1, + &lock ); + + switch(rs->sr_err) { + case DB_NOTFOUND: + case 0: + break; + case LDAP_BUSY: + send_ldap_error( op, rs, LDAP_BUSY, "ldap_server_busy" ); + return LDAP_BUSY; + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto dn2entry_retry; + default: + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return rs->sr_err; + } + + e = ei->bei_e; + if ( rs->sr_err == DB_NOTFOUND ) { + if( e != NULL ) { + bdb_cache_return_entry_r( bdb, e, &lock ); + e = NULL; + } + + rs->sr_err = LDAP_INVALID_CREDENTIALS; + send_ldap_result( op, rs ); + + return rs->sr_err; + } + + ber_dupbv( &op->oq_bind.rb_edn, &e->e_name ); + + /* check for deleted */ + if ( is_entry_subentry( e ) ) { + /* entry is an subentry, don't allow bind */ + Debug( LDAP_DEBUG_TRACE, "entry is subentry\n", 0, + 0, 0 ); + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + if ( is_entry_alias( e ) ) { + /* entry is an alias, don't allow bind */ + Debug( LDAP_DEBUG_TRACE, "entry is alias\n", 0, 0, 0 ); + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + if ( is_entry_referral( e ) ) { + Debug( LDAP_DEBUG_TRACE, "entry is referral\n", 0, + 0, 0 ); + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + switch ( op->oq_bind.rb_method ) { + case LDAP_AUTH_SIMPLE: + a = attr_find( e->e_attrs, password ); + if ( a == NULL ) { + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + if ( slap_passwd_check( op, e, a, &op->oq_bind.rb_cred, + &rs->sr_text ) != 0 ) + { + /* failure; stop front end from sending result */ + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + rs->sr_err = 0; + break; + + default: + assert( 0 ); /* should not be reachable */ + rs->sr_err = LDAP_STRONG_AUTH_NOT_SUPPORTED; + rs->sr_text = "authentication method not supported"; + } + +done: + /* free entry and reader lock */ + if( e != NULL ) { + bdb_cache_return_entry_r( bdb, e, &lock ); + } + + if ( rs->sr_err ) { + send_ldap_result( op, rs ); + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + } + /* front end will send result on success (rs->sr_err==0) */ + return rs->sr_err; +} diff --git a/servers/slapd/back-bdb/cache.c b/servers/slapd/back-bdb/cache.c new file mode 100644 index 0000000..83aaa62 --- /dev/null +++ b/servers/slapd/back-bdb/cache.c @@ -0,0 +1,1692 @@ +/* cache.c - routines to maintain an in-core cache of entries */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/errno.h> +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" + +#include "back-bdb.h" + +#include "ldap_rq.h" + +#ifdef BDB_HIER +#define bdb_cache_lru_purge hdb_cache_lru_purge +#endif +static void bdb_cache_lru_purge( struct bdb_info *bdb ); + +static int bdb_cache_delete_internal(Cache *cache, EntryInfo *e, int decr); +#ifdef LDAP_DEBUG +#define SLAPD_UNUSED +#ifdef SLAPD_UNUSED +static void bdb_lru_print(Cache *cache); +static void bdb_idtree_print(Cache *cache); +#endif +#endif + +/* For concurrency experiments only! */ +#if 0 +#define ldap_pvt_thread_rdwr_wlock(a) 0 +#define ldap_pvt_thread_rdwr_wunlock(a) 0 +#define ldap_pvt_thread_rdwr_rlock(a) 0 +#define ldap_pvt_thread_rdwr_runlock(a) 0 +#endif + +#if 0 +#define ldap_pvt_thread_mutex_trylock(a) 0 +#endif + +static EntryInfo * +bdb_cache_entryinfo_new( Cache *cache ) +{ + EntryInfo *ei = NULL; + + if ( cache->c_eifree ) { + ldap_pvt_thread_mutex_lock( &cache->c_eifree_mutex ); + if ( cache->c_eifree ) { + ei = cache->c_eifree; + cache->c_eifree = ei->bei_lrunext; + ei->bei_finders = 0; + ei->bei_lrunext = NULL; + } + ldap_pvt_thread_mutex_unlock( &cache->c_eifree_mutex ); + } + if ( !ei ) { + ei = ch_calloc(1, sizeof(EntryInfo)); + ldap_pvt_thread_mutex_init( &ei->bei_kids_mutex ); + } + + ei->bei_state = CACHE_ENTRY_REFERENCED; + + return ei; +} + +static void +bdb_cache_entryinfo_free( Cache *cache, EntryInfo *ei ) +{ + free( ei->bei_nrdn.bv_val ); + BER_BVZERO( &ei->bei_nrdn ); +#ifdef BDB_HIER + free( ei->bei_rdn.bv_val ); + BER_BVZERO( &ei->bei_rdn ); + ei->bei_modrdns = 0; + ei->bei_ckids = 0; + ei->bei_dkids = 0; +#endif + ei->bei_parent = NULL; + ei->bei_kids = NULL; + ei->bei_lruprev = NULL; + +#if 0 + ldap_pvt_thread_mutex_lock( &cache->c_eifree_mutex ); + ei->bei_lrunext = cache->c_eifree; + cache->c_eifree = ei; + ldap_pvt_thread_mutex_unlock( &cache->c_eifree_mutex ); +#else + ldap_pvt_thread_mutex_destroy( &ei->bei_kids_mutex ); + ch_free( ei ); +#endif +} + +#define LRU_DEL( c, e ) do { \ + if ( e == e->bei_lruprev ) { \ + (c)->c_lruhead = (c)->c_lrutail = NULL; \ + } else { \ + if ( e == (c)->c_lruhead ) (c)->c_lruhead = e->bei_lruprev; \ + if ( e == (c)->c_lrutail ) (c)->c_lrutail = e->bei_lruprev; \ + e->bei_lrunext->bei_lruprev = e->bei_lruprev; \ + e->bei_lruprev->bei_lrunext = e->bei_lrunext; \ + } \ + e->bei_lruprev = NULL; \ +} while ( 0 ) + +/* Note - we now use a Second-Chance / Clock algorithm instead of + * Least-Recently-Used. This tremendously improves concurrency + * because we no longer need to manipulate the lists every time an + * entry is touched. We only need to lock the lists when adding + * or deleting an entry. It's now a circular doubly-linked list. + * We always append to the tail, but the head traverses the circle + * during a purge operation. + */ +static void +bdb_cache_lru_link( struct bdb_info *bdb, EntryInfo *ei ) +{ + + /* Already linked, ignore */ + if ( ei->bei_lruprev ) + return; + + /* Insert into circular LRU list */ + ldap_pvt_thread_mutex_lock( &bdb->bi_cache.c_lru_mutex ); + + ei->bei_lruprev = bdb->bi_cache.c_lrutail; + if ( bdb->bi_cache.c_lrutail ) { + ei->bei_lrunext = bdb->bi_cache.c_lrutail->bei_lrunext; + bdb->bi_cache.c_lrutail->bei_lrunext = ei; + if ( ei->bei_lrunext ) + ei->bei_lrunext->bei_lruprev = ei; + } else { + ei->bei_lrunext = ei->bei_lruprev = ei; + bdb->bi_cache.c_lruhead = ei; + } + bdb->bi_cache.c_lrutail = ei; + ldap_pvt_thread_mutex_unlock( &bdb->bi_cache.c_lru_mutex ); +} + +#ifdef NO_THREADS +#define NO_DB_LOCK +#endif + +/* #define NO_DB_LOCK 1 */ +/* Note: The BerkeleyDB locks are much slower than regular + * mutexes or rdwr locks. But the BDB implementation has the + * advantage of using a fixed size lock table, instead of + * allocating a lock object per entry in the DB. That's a + * key benefit for scaling. It also frees us from worrying + * about undetectable deadlocks between BDB activity and our + * own cache activity. It's still worth exploring faster + * alternatives though. + */ + +/* Atomically release and reacquire a lock */ +int +bdb_cache_entry_db_relock( + struct bdb_info *bdb, + DB_TXN *txn, + EntryInfo *ei, + int rw, + int tryOnly, + DB_LOCK *lock ) +{ +#ifdef NO_DB_LOCK + return 0; +#else + int rc; + DBT lockobj; + DB_LOCKREQ list[2]; + + if ( !lock ) return 0; + + DBTzero( &lockobj ); + lockobj.data = &ei->bei_id; + lockobj.size = sizeof(ei->bei_id) + 1; + + list[0].op = DB_LOCK_PUT; + list[0].lock = *lock; + list[1].op = DB_LOCK_GET; + list[1].lock = *lock; + list[1].mode = rw ? DB_LOCK_WRITE : DB_LOCK_READ; + list[1].obj = &lockobj; + rc = bdb->bi_dbenv->lock_vec(bdb->bi_dbenv, TXN_ID(txn), tryOnly ? DB_LOCK_NOWAIT : 0, + list, 2, NULL ); + + if (rc && !tryOnly) { + Debug( LDAP_DEBUG_TRACE, + "bdb_cache_entry_db_relock: entry %ld, rw %d, rc %d\n", + ei->bei_id, rw, rc ); + } else { + *lock = list[1].lock; + } + return rc; +#endif +} + +static int +bdb_cache_entry_db_lock( struct bdb_info *bdb, DB_TXN *txn, EntryInfo *ei, + int rw, int tryOnly, DB_LOCK *lock ) +{ +#ifdef NO_DB_LOCK + return 0; +#else + int rc; + DBT lockobj; + int db_rw; + + if ( !lock ) return 0; + + if (rw) + db_rw = DB_LOCK_WRITE; + else + db_rw = DB_LOCK_READ; + + DBTzero( &lockobj ); + lockobj.data = &ei->bei_id; + lockobj.size = sizeof(ei->bei_id) + 1; + + rc = LOCK_GET(bdb->bi_dbenv, TXN_ID(txn), tryOnly ? DB_LOCK_NOWAIT : 0, + &lockobj, db_rw, lock); + if (rc && !tryOnly) { + Debug( LDAP_DEBUG_TRACE, + "bdb_cache_entry_db_lock: entry %ld, rw %d, rc %d\n", + ei->bei_id, rw, rc ); + } + return rc; +#endif /* NO_DB_LOCK */ +} + +int +bdb_cache_entry_db_unlock ( struct bdb_info *bdb, DB_LOCK *lock ) +{ +#ifdef NO_DB_LOCK + return 0; +#else + int rc; + + if ( !lock || lock->mode == DB_LOCK_NG ) return 0; + + rc = LOCK_PUT ( bdb->bi_dbenv, lock ); + return rc; +#endif +} + +void +bdb_cache_return_entry_rw( struct bdb_info *bdb, Entry *e, + int rw, DB_LOCK *lock ) +{ + EntryInfo *ei; + int free = 0; + + ei = e->e_private; + if ( ei && ( ei->bei_state & CACHE_ENTRY_NOT_CACHED )) { + bdb_cache_entryinfo_lock( ei ); + if ( ei->bei_state & CACHE_ENTRY_NOT_CACHED ) { + /* Releasing the entry can only be done when + * we know that nobody else is using it, i.e we + * should have an entry_db writelock. But the + * flag is only set by the thread that loads the + * entry, and only if no other threads has found + * it while it was working. All other threads + * clear the flag, which mean that we should be + * the only thread using the entry if the flag + * is set here. + */ + ei->bei_e = NULL; + ei->bei_state ^= CACHE_ENTRY_NOT_CACHED; + free = 1; + } + bdb_cache_entryinfo_unlock( ei ); + } + bdb_cache_entry_db_unlock( bdb, lock ); + if ( free ) { + e->e_private = NULL; + bdb_entry_return( e ); + } +} + +static int +bdb_cache_entryinfo_destroy( EntryInfo *e ) +{ + ldap_pvt_thread_mutex_destroy( &e->bei_kids_mutex ); + free( e->bei_nrdn.bv_val ); +#ifdef BDB_HIER + free( e->bei_rdn.bv_val ); +#endif + free( e ); + return 0; +} + +/* Do a length-ordered sort on normalized RDNs */ +static int +bdb_rdn_cmp( const void *v_e1, const void *v_e2 ) +{ + const EntryInfo *e1 = v_e1, *e2 = v_e2; + int rc = e1->bei_nrdn.bv_len - e2->bei_nrdn.bv_len; + if (rc == 0) { + rc = strncmp( e1->bei_nrdn.bv_val, e2->bei_nrdn.bv_val, + e1->bei_nrdn.bv_len ); + } + return rc; +} + +static int +bdb_id_cmp( const void *v_e1, const void *v_e2 ) +{ + const EntryInfo *e1 = v_e1, *e2 = v_e2; + return e1->bei_id - e2->bei_id; +} + +static int +bdb_id_dup_err( void *v1, void *v2 ) +{ + EntryInfo *e2 = v2; + e2->bei_lrunext = v1; + return -1; +} + +/* Create an entryinfo in the cache. Caller must release the locks later. + */ +static int +bdb_entryinfo_add_internal( + struct bdb_info *bdb, + EntryInfo *ei, + EntryInfo **res ) +{ + EntryInfo *ei2 = NULL; + + *res = NULL; + + ei2 = bdb_cache_entryinfo_new( &bdb->bi_cache ); + + bdb_cache_entryinfo_lock( ei->bei_parent ); + ldap_pvt_thread_rdwr_wlock( &bdb->bi_cache.c_rwlock ); + + ei2->bei_id = ei->bei_id; + ei2->bei_parent = ei->bei_parent; +#ifdef BDB_HIER + ei2->bei_rdn = ei->bei_rdn; +#endif +#ifdef SLAP_ZONE_ALLOC + ei2->bei_bdb = bdb; +#endif + + /* Add to cache ID tree */ + if (avl_insert( &bdb->bi_cache.c_idtree, ei2, bdb_id_cmp, + bdb_id_dup_err )) { + EntryInfo *eix = ei2->bei_lrunext; + bdb_cache_entryinfo_free( &bdb->bi_cache, ei2 ); + ei2 = eix; +#ifdef BDB_HIER + /* It got freed above because its value was + * assigned to ei2. + */ + ei->bei_rdn.bv_val = NULL; +#endif + } else { + int rc; + + bdb->bi_cache.c_eiused++; + ber_dupbv( &ei2->bei_nrdn, &ei->bei_nrdn ); + + /* This is a new leaf node. But if parent had no kids, then it was + * a leaf and we would be decrementing that. So, only increment if + * the parent already has kids. + */ + if ( ei->bei_parent->bei_kids || !ei->bei_parent->bei_id ) + bdb->bi_cache.c_leaves++; + rc = avl_insert( &ei->bei_parent->bei_kids, ei2, bdb_rdn_cmp, + avl_dup_error ); +#ifdef BDB_HIER + /* it's possible for hdb_cache_find_parent to beat us to it */ + if ( !rc ) { + ei->bei_parent->bei_ckids++; + } +#endif + } + + *res = ei2; + return 0; +} + +/* Find the EntryInfo for the requested DN. If the DN cannot be found, return + * the info for its closest ancestor. *res should be NULL to process a + * complete DN starting from the tree root. Otherwise *res must be the + * immediate parent of the requested DN, and only the RDN will be searched. + * The EntryInfo is locked upon return and must be unlocked by the caller. + */ +int +bdb_cache_find_ndn( + Operation *op, + DB_TXN *txn, + struct berval *ndn, + EntryInfo **res ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + EntryInfo ei, *eip, *ei2; + int rc = 0; + char *ptr; + + /* this function is always called with normalized DN */ + if ( *res ) { + /* we're doing a onelevel search for an RDN */ + ei.bei_nrdn.bv_val = ndn->bv_val; + ei.bei_nrdn.bv_len = dn_rdnlen( op->o_bd, ndn ); + eip = *res; + } else { + /* we're searching a full DN from the root */ + ptr = ndn->bv_val + ndn->bv_len - op->o_bd->be_nsuffix[0].bv_len; + ei.bei_nrdn.bv_val = ptr; + ei.bei_nrdn.bv_len = op->o_bd->be_nsuffix[0].bv_len; + /* Skip to next rdn if suffix is empty */ + if ( ei.bei_nrdn.bv_len == 0 ) { + for (ptr = ei.bei_nrdn.bv_val - 2; ptr > ndn->bv_val + && !DN_SEPARATOR(*ptr); ptr--) /* empty */; + if ( ptr >= ndn->bv_val ) { + if (DN_SEPARATOR(*ptr)) ptr++; + ei.bei_nrdn.bv_len = ei.bei_nrdn.bv_val - ptr; + ei.bei_nrdn.bv_val = ptr; + } + } + eip = &bdb->bi_cache.c_dntree; + } + + for ( bdb_cache_entryinfo_lock( eip ); eip; ) { + eip->bei_state |= CACHE_ENTRY_REFERENCED; + ei.bei_parent = eip; + ei2 = (EntryInfo *)avl_find( eip->bei_kids, &ei, bdb_rdn_cmp ); + if ( !ei2 ) { + DBC *cursor; + int len = ei.bei_nrdn.bv_len; + + if ( BER_BVISEMPTY( ndn )) { + *res = eip; + return LDAP_SUCCESS; + } + + ei.bei_nrdn.bv_len = ndn->bv_len - + (ei.bei_nrdn.bv_val - ndn->bv_val); + eip->bei_finders++; + bdb_cache_entryinfo_unlock( eip ); + + BDB_LOG_PRINTF( bdb->bi_dbenv, NULL, "slapd Reading %s", + ei.bei_nrdn.bv_val ); + + cursor = NULL; + rc = bdb_dn2id( op, &ei.bei_nrdn, &ei, txn, &cursor ); + if (rc) { + bdb_cache_entryinfo_lock( eip ); + eip->bei_finders--; + if ( cursor ) cursor->c_close( cursor ); + *res = eip; + return rc; + } + + BDB_LOG_PRINTF( bdb->bi_dbenv, NULL, "slapd Read got %s(%d)", + ei.bei_nrdn.bv_val, ei.bei_id ); + + /* DN exists but needs to be added to cache */ + ei.bei_nrdn.bv_len = len; + rc = bdb_entryinfo_add_internal( bdb, &ei, &ei2 ); + /* add_internal left eip and c_rwlock locked */ + eip->bei_finders--; + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_cache.c_rwlock ); + if ( cursor ) cursor->c_close( cursor ); + if ( rc ) { + *res = eip; + return rc; + } + } + bdb_cache_entryinfo_lock( ei2 ); + if ( ei2->bei_state & CACHE_ENTRY_DELETED ) { + /* In the midst of deleting? Give it a chance to + * complete. + */ + bdb_cache_entryinfo_unlock( ei2 ); + bdb_cache_entryinfo_unlock( eip ); + ldap_pvt_thread_yield(); + bdb_cache_entryinfo_lock( eip ); + *res = eip; + return DB_NOTFOUND; + } + bdb_cache_entryinfo_unlock( eip ); + + eip = ei2; + + /* Advance to next lower RDN */ + for (ptr = ei.bei_nrdn.bv_val - 2; ptr > ndn->bv_val + && !DN_SEPARATOR(*ptr); ptr--) /* empty */; + if ( ptr >= ndn->bv_val ) { + if (DN_SEPARATOR(*ptr)) ptr++; + ei.bei_nrdn.bv_len = ei.bei_nrdn.bv_val - ptr - 1; + ei.bei_nrdn.bv_val = ptr; + } + if ( ptr < ndn->bv_val ) { + *res = eip; + break; + } + } + + return rc; +} + +#ifdef BDB_HIER +/* Walk up the tree from a child node, looking for an ID that's already + * been linked into the cache. + */ +int +hdb_cache_find_parent( + Operation *op, + DB_TXN *txn, + ID id, + EntryInfo **res ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + EntryInfo ei, eip, *ei2 = NULL, *ein = NULL, *eir = NULL; + int rc, add; + + ei.bei_id = id; + ei.bei_kids = NULL; + ei.bei_ckids = 0; + + for (;;) { + rc = hdb_dn2id_parent( op, txn, &ei, &eip.bei_id ); + if ( rc ) break; + + /* Save the previous node, if any */ + ei2 = ein; + + /* Create a new node for the current ID */ + ein = bdb_cache_entryinfo_new( &bdb->bi_cache ); + ein->bei_id = ei.bei_id; + ein->bei_kids = ei.bei_kids; + ein->bei_nrdn = ei.bei_nrdn; + ein->bei_rdn = ei.bei_rdn; + ein->bei_ckids = ei.bei_ckids; +#ifdef SLAP_ZONE_ALLOC + ein->bei_bdb = bdb; +#endif + ei.bei_ckids = 0; + add = 1; + + /* This node is not fully connected yet */ + ein->bei_state |= CACHE_ENTRY_NOT_LINKED; + + /* If this is the first time, save this node + * to be returned later. + */ + if ( eir == NULL ) { + eir = ein; + ein->bei_finders++; + } + +again: + /* Insert this node into the ID tree */ + ldap_pvt_thread_rdwr_wlock( &bdb->bi_cache.c_rwlock ); + if ( avl_insert( &bdb->bi_cache.c_idtree, (caddr_t)ein, + bdb_id_cmp, bdb_id_dup_err ) ) { + EntryInfo *eix = ein->bei_lrunext; + + if ( bdb_cache_entryinfo_trylock( eix )) { + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_cache.c_rwlock ); + ldap_pvt_thread_yield(); + goto again; + } + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_cache.c_rwlock ); + + /* Someone else created this node just before us. + * Free our new copy and use the existing one. + */ + bdb_cache_entryinfo_free( &bdb->bi_cache, ein ); + + /* if it was the node we were looking for, just return it */ + if ( eir == ein ) { + *res = eix; + rc = 0; + break; + } + + ein = ei2; + ei2 = eix; + add = 0; + + /* otherwise, link up what we have and return */ + goto gotparent; + } + + /* If there was a previous node, link it to this one */ + if ( ei2 ) ei2->bei_parent = ein; + + /* Look for this node's parent */ +par2: + if ( eip.bei_id ) { + ei2 = (EntryInfo *) avl_find( bdb->bi_cache.c_idtree, + (caddr_t) &eip, bdb_id_cmp ); + } else { + ei2 = &bdb->bi_cache.c_dntree; + } + if ( ei2 && bdb_cache_entryinfo_trylock( ei2 )) { + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_cache.c_rwlock ); + ldap_pvt_thread_yield(); + ldap_pvt_thread_rdwr_wlock( &bdb->bi_cache.c_rwlock ); + goto par2; + } + if ( add ) + bdb->bi_cache.c_eiused++; + if ( ei2 && ( ei2->bei_kids || !ei2->bei_id )) + bdb->bi_cache.c_leaves++; + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_cache.c_rwlock ); + +gotparent: + /* Got the parent, link in and we're done. */ + if ( ei2 ) { + bdb_cache_entryinfo_lock( eir ); + ein->bei_parent = ei2; + + if ( avl_insert( &ei2->bei_kids, (caddr_t)ein, bdb_rdn_cmp, + avl_dup_error) == 0 ) + ei2->bei_ckids++; + + /* Reset all the state info */ + for (ein = eir; ein != ei2; ein=ein->bei_parent) + ein->bei_state &= ~CACHE_ENTRY_NOT_LINKED; + + bdb_cache_entryinfo_unlock( ei2 ); + eir->bei_finders--; + + *res = eir; + break; + } + ei.bei_kids = NULL; + ei.bei_id = eip.bei_id; + ei.bei_ckids = 1; + avl_insert( &ei.bei_kids, (caddr_t)ein, bdb_rdn_cmp, + avl_dup_error ); + } + return rc; +} + +/* Used by hdb_dn2idl when loading the EntryInfo for all the children + * of a given node + */ +int hdb_cache_load( + struct bdb_info *bdb, + EntryInfo *ei, + EntryInfo **res ) +{ + EntryInfo *ei2; + int rc; + + /* See if we already have this one */ + bdb_cache_entryinfo_lock( ei->bei_parent ); + ei2 = (EntryInfo *)avl_find( ei->bei_parent->bei_kids, ei, bdb_rdn_cmp ); + bdb_cache_entryinfo_unlock( ei->bei_parent ); + + if ( !ei2 ) { + /* Not found, add it */ + struct berval bv; + + /* bei_rdn was not malloc'd before, do it now */ + ber_dupbv( &bv, &ei->bei_rdn ); + ei->bei_rdn = bv; + + rc = bdb_entryinfo_add_internal( bdb, ei, res ); + bdb_cache_entryinfo_unlock( ei->bei_parent ); + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_cache.c_rwlock ); + } else { + /* Found, return it */ + *res = ei2; + return 0; + } + return rc; +} +#endif + +/* This is best-effort only. If all entries in the cache are + * busy, they will all be kept. This is unlikely to happen + * unless the cache is very much smaller than the working set. + */ +static void +bdb_cache_lru_purge( struct bdb_info *bdb ) +{ + DB_LOCK lock, *lockp; + EntryInfo *elru, *elnext = NULL; + int islocked; + ID eicount, ecount; + ID count, efree, eifree = 0; +#ifdef LDAP_DEBUG + int iter; +#endif + + /* Wait for the mutex; we're the only one trying to purge. */ + ldap_pvt_thread_mutex_lock( &bdb->bi_cache.c_lru_mutex ); + + if ( bdb->bi_cache.c_cursize > bdb->bi_cache.c_maxsize ) { + efree = bdb->bi_cache.c_cursize - bdb->bi_cache.c_maxsize; + efree += bdb->bi_cache.c_minfree; + } else { + efree = 0; + } + + /* maximum number of EntryInfo leaves to cache. In slapcat + * we always free all leaf nodes. + */ + + if ( slapMode & SLAP_TOOL_READONLY ) { + eifree = bdb->bi_cache.c_leaves; + } else if ( bdb->bi_cache.c_eimax && + bdb->bi_cache.c_leaves > bdb->bi_cache.c_eimax ) { + eifree = bdb->bi_cache.c_minfree * 10; + if ( eifree >= bdb->bi_cache.c_leaves ) + eifree /= 2; + } + + if ( !efree && !eifree ) { + ldap_pvt_thread_mutex_unlock( &bdb->bi_cache.c_lru_mutex ); + bdb->bi_cache.c_purging = 0; + return; + } + + if ( bdb->bi_cache.c_txn ) { + lockp = &lock; + } else { + lockp = NULL; + } + + count = 0; + eicount = 0; + ecount = 0; +#ifdef LDAP_DEBUG + iter = 0; +#endif + + /* Look for an unused entry to remove */ + for ( elru = bdb->bi_cache.c_lruhead; elru; elru = elnext ) { + elnext = elru->bei_lrunext; + + if ( bdb_cache_entryinfo_trylock( elru )) + goto bottom; + + /* This flag implements the clock replacement behavior */ + if ( elru->bei_state & ( CACHE_ENTRY_REFERENCED )) { + elru->bei_state &= ~CACHE_ENTRY_REFERENCED; + bdb_cache_entryinfo_unlock( elru ); + goto bottom; + } + + /* If this node is in the process of linking into the cache, + * or this node is being deleted, skip it. + */ + if (( elru->bei_state & ( CACHE_ENTRY_NOT_LINKED | + CACHE_ENTRY_DELETED | CACHE_ENTRY_LOADING | + CACHE_ENTRY_ONELEVEL )) || + elru->bei_finders > 0 ) { + bdb_cache_entryinfo_unlock( elru ); + goto bottom; + } + + if ( bdb_cache_entryinfo_trylock( elru->bei_parent )) { + bdb_cache_entryinfo_unlock( elru ); + goto bottom; + } + + /* entryinfo is locked */ + islocked = 1; + + /* If we can successfully writelock it, then + * the object is idle. + */ + if ( bdb_cache_entry_db_lock( bdb, + bdb->bi_cache.c_txn, elru, 1, 1, lockp ) == 0 ) { + + /* Free entry for this node if it's present */ + if ( elru->bei_e ) { + ecount++; + + /* the cache may have gone over the limit while we + * weren't looking, so double check. + */ + if ( !efree && ecount > bdb->bi_cache.c_maxsize ) + efree = bdb->bi_cache.c_minfree; + + if ( count < efree ) { + elru->bei_e->e_private = NULL; +#ifdef SLAP_ZONE_ALLOC + bdb_entry_return( bdb, elru->bei_e, elru->bei_zseq ); +#else + bdb_entry_return( elru->bei_e ); +#endif + elru->bei_e = NULL; + count++; + } else { + /* Keep this node cached, skip to next */ + bdb_cache_entry_db_unlock( bdb, lockp ); + goto next; + } + } + bdb_cache_entry_db_unlock( bdb, lockp ); + + /* + * If it is a leaf node, and we're over the limit, free it. + */ + if ( elru->bei_kids ) { + /* Drop from list, we ignore it... */ + LRU_DEL( &bdb->bi_cache, elru ); + } else if ( eicount < eifree ) { + /* Too many leaf nodes, free this one */ + bdb_cache_delete_internal( &bdb->bi_cache, elru, 0 ); + bdb_cache_delete_cleanup( &bdb->bi_cache, elru ); + islocked = 0; + eicount++; + } /* Leave on list until we need to free it */ + } + +next: + if ( islocked ) { + bdb_cache_entryinfo_unlock( elru ); + bdb_cache_entryinfo_unlock( elru->bei_parent ); + } + + if ( count >= efree && eicount >= eifree ) + break; +bottom: + if ( elnext == bdb->bi_cache.c_lruhead ) + break; +#ifdef LDAP_DEBUG + iter++; +#endif + } + + if ( count || ecount > bdb->bi_cache.c_cursize ) { + ldap_pvt_thread_mutex_lock( &bdb->bi_cache.c_count_mutex ); + /* HACK: we seem to be losing track, fix up now */ + if ( ecount > bdb->bi_cache.c_cursize ) + bdb->bi_cache.c_cursize = ecount; + bdb->bi_cache.c_cursize -= count; + ldap_pvt_thread_mutex_unlock( &bdb->bi_cache.c_count_mutex ); + } + bdb->bi_cache.c_lruhead = elnext; + ldap_pvt_thread_mutex_unlock( &bdb->bi_cache.c_lru_mutex ); + bdb->bi_cache.c_purging = 0; +} + +/* + * cache_find_id - find an entry in the cache, given id. + * The entry is locked for Read upon return. Call with flag ID_LOCKED if + * the supplied *eip was already locked. + */ + +int +bdb_cache_find_id( + Operation *op, + DB_TXN *tid, + ID id, + EntryInfo **eip, + int flag, + DB_LOCK *lock ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + Entry *ep = NULL; + int rc = 0, load = 0; + EntryInfo ei = { 0 }; + + ei.bei_id = id; + +#ifdef SLAP_ZONE_ALLOC + slap_zh_rlock(bdb->bi_cache.c_zctx); +#endif + /* If we weren't given any info, see if we have it already cached */ + if ( !*eip ) { +again: ldap_pvt_thread_rdwr_rlock( &bdb->bi_cache.c_rwlock ); + *eip = (EntryInfo *) avl_find( bdb->bi_cache.c_idtree, + (caddr_t) &ei, bdb_id_cmp ); + if ( *eip ) { + /* If the lock attempt fails, the info is in use */ + if ( bdb_cache_entryinfo_trylock( *eip )) { + int del = (*eip)->bei_state & CACHE_ENTRY_DELETED; + ldap_pvt_thread_rdwr_runlock( &bdb->bi_cache.c_rwlock ); + /* If this node is being deleted, treat + * as if the delete has already finished + */ + if ( del ) { + return DB_NOTFOUND; + } + /* otherwise, wait for the info to free up */ + ldap_pvt_thread_yield(); + goto again; + } + /* If this info isn't hooked up to its parent yet, + * unlock and wait for it to be fully initialized + */ + if ( (*eip)->bei_state & CACHE_ENTRY_NOT_LINKED ) { + bdb_cache_entryinfo_unlock( *eip ); + ldap_pvt_thread_rdwr_runlock( &bdb->bi_cache.c_rwlock ); + ldap_pvt_thread_yield(); + goto again; + } + flag |= ID_LOCKED; + } + ldap_pvt_thread_rdwr_runlock( &bdb->bi_cache.c_rwlock ); + } + + /* See if the ID exists in the database; add it to the cache if so */ + if ( !*eip ) { +#ifndef BDB_HIER + rc = bdb_id2entry( op->o_bd, tid, id, &ep ); + if ( rc == 0 ) { + rc = bdb_cache_find_ndn( op, tid, + &ep->e_nname, eip ); + if ( *eip ) flag |= ID_LOCKED; + if ( rc ) { + ep->e_private = NULL; +#ifdef SLAP_ZONE_ALLOC + bdb_entry_return( bdb, ep, (*eip)->bei_zseq ); +#else + bdb_entry_return( ep ); +#endif + ep = NULL; + } + } +#else + rc = hdb_cache_find_parent(op, tid, id, eip ); + if ( rc == 0 ) flag |= ID_LOCKED; +#endif + } + + /* Ok, we found the info, do we have the entry? */ + if ( rc == 0 ) { + if ( !( flag & ID_LOCKED )) { + bdb_cache_entryinfo_lock( *eip ); + flag |= ID_LOCKED; + } + + if ( (*eip)->bei_state & CACHE_ENTRY_DELETED ) { + rc = DB_NOTFOUND; + } else { + (*eip)->bei_finders++; + (*eip)->bei_state |= CACHE_ENTRY_REFERENCED; + if ( flag & ID_NOENTRY ) { + bdb_cache_entryinfo_unlock( *eip ); + return 0; + } + /* Make sure only one thread tries to load the entry */ +load1: +#ifdef SLAP_ZONE_ALLOC + if ((*eip)->bei_e && !slap_zn_validate( + bdb->bi_cache.c_zctx, (*eip)->bei_e, (*eip)->bei_zseq)) { + (*eip)->bei_e = NULL; + (*eip)->bei_zseq = 0; + } +#endif + if ( !(*eip)->bei_e && !((*eip)->bei_state & CACHE_ENTRY_LOADING)) { + load = 1; + (*eip)->bei_state |= CACHE_ENTRY_LOADING; + flag |= ID_CHKPURGE; + } + + if ( !load ) { + /* Clear the uncached state if we are not + * loading it, i.e it is already cached or + * another thread is currently loading it. + */ + if ( (*eip)->bei_state & CACHE_ENTRY_NOT_CACHED ) { + (*eip)->bei_state ^= CACHE_ENTRY_NOT_CACHED; + flag |= ID_CHKPURGE; + } + } + + if ( flag & ID_LOCKED ) { + bdb_cache_entryinfo_unlock( *eip ); + flag ^= ID_LOCKED; + } + rc = bdb_cache_entry_db_lock( bdb, tid, *eip, load, 0, lock ); + if ( (*eip)->bei_state & CACHE_ENTRY_DELETED ) { + rc = DB_NOTFOUND; + bdb_cache_entry_db_unlock( bdb, lock ); + bdb_cache_entryinfo_lock( *eip ); + (*eip)->bei_finders--; + bdb_cache_entryinfo_unlock( *eip ); + } else if ( rc == 0 ) { + if ( load ) { + if ( !ep) { + rc = bdb_id2entry( op->o_bd, tid, id, &ep ); + } + if ( rc == 0 ) { + ep->e_private = *eip; +#ifdef BDB_HIER + while ( (*eip)->bei_state & CACHE_ENTRY_NOT_LINKED ) + ldap_pvt_thread_yield(); + bdb_fix_dn( ep, 0 ); +#endif + bdb_cache_entryinfo_lock( *eip ); + + (*eip)->bei_e = ep; +#ifdef SLAP_ZONE_ALLOC + (*eip)->bei_zseq = *((ber_len_t *)ep - 2); +#endif + ep = NULL; + if ( flag & ID_NOCACHE ) { + /* Set the cached state only if no other thread + * found the info while we were loading the entry. + */ + if ( (*eip)->bei_finders == 1 ) { + (*eip)->bei_state |= CACHE_ENTRY_NOT_CACHED; + flag ^= ID_CHKPURGE; + } + } + bdb_cache_entryinfo_unlock( *eip ); + bdb_cache_lru_link( bdb, *eip ); + } + if ( rc == 0 ) { + /* If we succeeded, downgrade back to a readlock. */ + rc = bdb_cache_entry_db_relock( bdb, tid, + *eip, 0, 0, lock ); + } else { + /* Otherwise, release the lock. */ + bdb_cache_entry_db_unlock( bdb, lock ); + } + } else if ( !(*eip)->bei_e ) { + /* Some other thread is trying to load the entry, + * wait for it to finish. + */ + bdb_cache_entry_db_unlock( bdb, lock ); + bdb_cache_entryinfo_lock( *eip ); + flag |= ID_LOCKED; + goto load1; +#ifdef BDB_HIER + } else { + /* Check for subtree renames + */ + rc = bdb_fix_dn( (*eip)->bei_e, 1 ); + if ( rc ) { + bdb_cache_entry_db_relock( bdb, + tid, *eip, 1, 0, lock ); + /* check again in case other modifier did it already */ + if ( bdb_fix_dn( (*eip)->bei_e, 1 ) ) + rc = bdb_fix_dn( (*eip)->bei_e, 2 ); + bdb_cache_entry_db_relock( bdb, + tid, *eip, 0, 0, lock ); + } +#endif + } + bdb_cache_entryinfo_lock( *eip ); + (*eip)->bei_finders--; + if ( load ) + (*eip)->bei_state ^= CACHE_ENTRY_LOADING; + bdb_cache_entryinfo_unlock( *eip ); + } + } + } + if ( flag & ID_LOCKED ) { + bdb_cache_entryinfo_unlock( *eip ); + } + if ( ep ) { + ep->e_private = NULL; +#ifdef SLAP_ZONE_ALLOC + bdb_entry_return( bdb, ep, (*eip)->bei_zseq ); +#else + bdb_entry_return( ep ); +#endif + } + if ( rc == 0 ) { + int purge = 0; + + if (( flag & ID_CHKPURGE ) || bdb->bi_cache.c_eimax ) { + ldap_pvt_thread_mutex_lock( &bdb->bi_cache.c_count_mutex ); + if ( flag & ID_CHKPURGE ) { + bdb->bi_cache.c_cursize++; + if ( !bdb->bi_cache.c_purging && bdb->bi_cache.c_cursize > bdb->bi_cache.c_maxsize ) { + purge = 1; + bdb->bi_cache.c_purging = 1; + } + } else if ( !bdb->bi_cache.c_purging && bdb->bi_cache.c_eimax && bdb->bi_cache.c_leaves > bdb->bi_cache.c_eimax ) { + purge = 1; + bdb->bi_cache.c_purging = 1; + } + ldap_pvt_thread_mutex_unlock( &bdb->bi_cache.c_count_mutex ); + } + if ( purge ) + bdb_cache_lru_purge( bdb ); + } + +#ifdef SLAP_ZONE_ALLOC + if (rc == 0 && (*eip)->bei_e) { + slap_zn_rlock(bdb->bi_cache.c_zctx, (*eip)->bei_e); + } + slap_zh_runlock(bdb->bi_cache.c_zctx); +#endif + return rc; +} + +int +bdb_cache_children( + Operation *op, + DB_TXN *txn, + Entry *e ) +{ + int rc; + + if ( BEI(e)->bei_kids ) { + return 0; + } + if ( BEI(e)->bei_state & CACHE_ENTRY_NO_KIDS ) { + return DB_NOTFOUND; + } + rc = bdb_dn2id_children( op, txn, e ); + if ( rc == DB_NOTFOUND ) { + BEI(e)->bei_state |= CACHE_ENTRY_NO_KIDS | CACHE_ENTRY_NO_GRANDKIDS; + } + return rc; +} + +/* Update the cache after a successful database Add. */ +int +bdb_cache_add( + struct bdb_info *bdb, + EntryInfo *eip, + Entry *e, + struct berval *nrdn, + DB_TXN *txn, + DB_LOCK *lock ) +{ + EntryInfo *new, ei; + int rc, purge = 0; +#ifdef BDB_HIER + struct berval rdn = e->e_name; +#endif + + ei.bei_id = e->e_id; + ei.bei_parent = eip; + ei.bei_nrdn = *nrdn; + ei.bei_lockpad = 0; + +#if 0 + /* Lock this entry so that bdb_add can run to completion. + * It can only fail if BDB has run out of lock resources. + */ + rc = bdb_cache_entry_db_lock( bdb, txn, &ei, 0, 0, lock ); + if ( rc ) { + bdb_cache_entryinfo_unlock( eip ); + return rc; + } +#endif + +#ifdef BDB_HIER + if ( nrdn->bv_len != e->e_nname.bv_len ) { + char *ptr = ber_bvchr( &rdn, ',' ); + assert( ptr != NULL ); + rdn.bv_len = ptr - rdn.bv_val; + } + ber_dupbv( &ei.bei_rdn, &rdn ); + if ( eip->bei_dkids ) eip->bei_dkids++; +#endif + + if (eip->bei_parent) { + bdb_cache_entryinfo_lock( eip->bei_parent ); + eip->bei_parent->bei_state &= ~CACHE_ENTRY_NO_GRANDKIDS; + bdb_cache_entryinfo_unlock( eip->bei_parent ); + } + + rc = bdb_entryinfo_add_internal( bdb, &ei, &new ); + /* bdb_csn_commit can cause this when adding the database root entry */ + if ( new->bei_e ) { + new->bei_e->e_private = NULL; +#ifdef SLAP_ZONE_ALLOC + bdb_entry_return( bdb, new->bei_e, new->bei_zseq ); +#else + bdb_entry_return( new->bei_e ); +#endif + } + new->bei_e = e; + e->e_private = new; + new->bei_state |= CACHE_ENTRY_NO_KIDS | CACHE_ENTRY_NO_GRANDKIDS; + eip->bei_state &= ~CACHE_ENTRY_NO_KIDS; + bdb_cache_entryinfo_unlock( eip ); + + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_cache.c_rwlock ); + ldap_pvt_thread_mutex_lock( &bdb->bi_cache.c_count_mutex ); + ++bdb->bi_cache.c_cursize; + if ( bdb->bi_cache.c_cursize > bdb->bi_cache.c_maxsize && + !bdb->bi_cache.c_purging ) { + purge = 1; + bdb->bi_cache.c_purging = 1; + } + ldap_pvt_thread_mutex_unlock( &bdb->bi_cache.c_count_mutex ); + + new->bei_finders = 1; + bdb_cache_lru_link( bdb, new ); + + if ( purge ) + bdb_cache_lru_purge( bdb ); + + return rc; +} + +void bdb_cache_deref( + EntryInfo *ei + ) +{ + bdb_cache_entryinfo_lock( ei ); + ei->bei_finders--; + bdb_cache_entryinfo_unlock( ei ); +} + +int +bdb_cache_modify( + struct bdb_info *bdb, + Entry *e, + Attribute *newAttrs, + DB_TXN *txn, + DB_LOCK *lock ) +{ + EntryInfo *ei = BEI(e); + int rc; + /* Get write lock on data */ + rc = bdb_cache_entry_db_relock( bdb, txn, ei, 1, 0, lock ); + + /* If we've done repeated mods on a cached entry, then e_attrs + * is no longer contiguous with the entry, and must be freed. + */ + if ( ! rc ) { + if ( (void *)e->e_attrs != (void *)(e+1) ) { + attrs_free( e->e_attrs ); + } + e->e_attrs = newAttrs; + } + return rc; +} + +/* + * Change the rdn in the entryinfo. Also move to a new parent if needed. + */ +int +bdb_cache_modrdn( + struct bdb_info *bdb, + Entry *e, + struct berval *nrdn, + Entry *new, + EntryInfo *ein, + DB_TXN *txn, + DB_LOCK *lock ) +{ + EntryInfo *ei = BEI(e), *pei; + int rc; +#ifdef BDB_HIER + struct berval rdn; +#endif + + /* Get write lock on data */ + rc = bdb_cache_entry_db_relock( bdb, txn, ei, 1, 0, lock ); + if ( rc ) return rc; + + /* If we've done repeated mods on a cached entry, then e_attrs + * is no longer contiguous with the entry, and must be freed. + */ + if ( (void *)e->e_attrs != (void *)(e+1) ) { + attrs_free( e->e_attrs ); + } + e->e_attrs = new->e_attrs; + if( e->e_nname.bv_val < e->e_bv.bv_val || + e->e_nname.bv_val > e->e_bv.bv_val + e->e_bv.bv_len ) + { + ch_free(e->e_name.bv_val); + ch_free(e->e_nname.bv_val); + } + e->e_name = new->e_name; + e->e_nname = new->e_nname; + + /* Lock the parent's kids AVL tree */ + pei = ei->bei_parent; + bdb_cache_entryinfo_lock( pei ); + avl_delete( &pei->bei_kids, (caddr_t) ei, bdb_rdn_cmp ); + free( ei->bei_nrdn.bv_val ); + ber_dupbv( &ei->bei_nrdn, nrdn ); + +#ifdef BDB_HIER + free( ei->bei_rdn.bv_val ); + + rdn = e->e_name; + if ( nrdn->bv_len != e->e_nname.bv_len ) { + char *ptr = ber_bvchr(&rdn, ','); + assert( ptr != NULL ); + rdn.bv_len = ptr - rdn.bv_val; + } + ber_dupbv( &ei->bei_rdn, &rdn ); + + /* If new parent, decrement kid counts */ + if ( ein ) { + pei->bei_ckids--; + if ( pei->bei_dkids ) { + pei->bei_dkids--; + if ( pei->bei_dkids < 2 ) + pei->bei_state |= CACHE_ENTRY_NO_KIDS | CACHE_ENTRY_NO_GRANDKIDS; + } + } +#endif + + if (!ein) { + ein = ei->bei_parent; + } else { + ei->bei_parent = ein; + bdb_cache_entryinfo_unlock( pei ); + bdb_cache_entryinfo_lock( ein ); + + /* new parent now has kids */ + if ( ein->bei_state & CACHE_ENTRY_NO_KIDS ) + ein->bei_state ^= CACHE_ENTRY_NO_KIDS; + /* grandparent has grandkids */ + if ( ein->bei_parent ) + ein->bei_parent->bei_state &= ~CACHE_ENTRY_NO_GRANDKIDS; +#ifdef BDB_HIER + /* parent might now have grandkids */ + if ( ein->bei_state & CACHE_ENTRY_NO_GRANDKIDS && + !(ei->bei_state & CACHE_ENTRY_NO_KIDS)) + ein->bei_state ^= CACHE_ENTRY_NO_GRANDKIDS; + + ein->bei_ckids++; + if ( ein->bei_dkids ) ein->bei_dkids++; +#endif + } + +#ifdef BDB_HIER + /* Record the generation number of this change */ + ldap_pvt_thread_mutex_lock( &bdb->bi_modrdns_mutex ); + bdb->bi_modrdns++; + ei->bei_modrdns = bdb->bi_modrdns; + ldap_pvt_thread_mutex_unlock( &bdb->bi_modrdns_mutex ); +#endif + + avl_insert( &ein->bei_kids, ei, bdb_rdn_cmp, avl_dup_error ); + bdb_cache_entryinfo_unlock( ein ); + return rc; +} +/* + * cache_delete - delete the entry e from the cache. + * + * returns: 0 e was deleted ok + * 1 e was not in the cache + * -1 something bad happened + */ +int +bdb_cache_delete( + struct bdb_info *bdb, + Entry *e, + DB_TXN *txn, + DB_LOCK *lock ) +{ + EntryInfo *ei = BEI(e); + int rc, busy = 0, counter = 0; + + assert( e->e_private != NULL ); + + /* Lock the entry's info */ + bdb_cache_entryinfo_lock( ei ); + + /* Set this early, warn off any queriers */ + ei->bei_state |= CACHE_ENTRY_DELETED; + + if (( ei->bei_state & ( CACHE_ENTRY_NOT_LINKED | + CACHE_ENTRY_LOADING | CACHE_ENTRY_ONELEVEL )) || + ei->bei_finders > 0 ) + busy = 1; + + bdb_cache_entryinfo_unlock( ei ); + + while ( busy && counter < 1000) { + ldap_pvt_thread_yield(); + busy = 0; + bdb_cache_entryinfo_lock( ei ); + if (( ei->bei_state & ( CACHE_ENTRY_NOT_LINKED | + CACHE_ENTRY_LOADING | CACHE_ENTRY_ONELEVEL )) || + ei->bei_finders > 0 ) + busy = 1; + bdb_cache_entryinfo_unlock( ei ); + counter ++; + } + if( busy ) { + bdb_cache_entryinfo_lock( ei ); + ei->bei_state ^= CACHE_ENTRY_DELETED; + bdb_cache_entryinfo_unlock( ei ); + return DB_LOCK_DEADLOCK; + } + + /* Get write lock on the data */ + rc = bdb_cache_entry_db_relock( bdb, txn, ei, 1, 0, lock ); + if ( rc ) { + bdb_cache_entryinfo_lock( ei ); + /* couldn't lock, undo and give up */ + ei->bei_state ^= CACHE_ENTRY_DELETED; + bdb_cache_entryinfo_unlock( ei ); + return rc; + } + + Debug( LDAP_DEBUG_TRACE, "====> bdb_cache_delete( %ld )\n", + e->e_id, 0, 0 ); + + /* set lru mutex */ + ldap_pvt_thread_mutex_lock( &bdb->bi_cache.c_lru_mutex ); + + bdb_cache_entryinfo_lock( ei->bei_parent ); + bdb_cache_entryinfo_lock( ei ); + rc = bdb_cache_delete_internal( &bdb->bi_cache, e->e_private, 1 ); + bdb_cache_entryinfo_unlock( ei ); + + /* free lru mutex */ + ldap_pvt_thread_mutex_unlock( &bdb->bi_cache.c_lru_mutex ); + + return( rc ); +} + +void +bdb_cache_delete_cleanup( + Cache *cache, + EntryInfo *ei ) +{ + /* Enter with ei locked */ + + /* already freed? */ + if ( !ei->bei_parent ) return; + + if ( ei->bei_e ) { + ei->bei_e->e_private = NULL; +#ifdef SLAP_ZONE_ALLOC + bdb_entry_return( ei->bei_bdb, ei->bei_e, ei->bei_zseq ); +#else + bdb_entry_return( ei->bei_e ); +#endif + ei->bei_e = NULL; + } + + bdb_cache_entryinfo_unlock( ei ); + bdb_cache_entryinfo_free( cache, ei ); +} + +static int +bdb_cache_delete_internal( + Cache *cache, + EntryInfo *e, + int decr ) +{ + int rc = 0; /* return code */ + int decr_leaf = 0; + + /* already freed? */ + if ( !e->bei_parent ) { + assert(0); + return -1; + } + +#ifdef BDB_HIER + e->bei_parent->bei_ckids--; + if ( decr && e->bei_parent->bei_dkids ) e->bei_parent->bei_dkids--; +#endif + /* dn tree */ + if ( avl_delete( &e->bei_parent->bei_kids, (caddr_t) e, bdb_rdn_cmp ) + == NULL ) + { + rc = -1; + assert(0); + } + if ( e->bei_parent->bei_kids ) + decr_leaf = 1; + + ldap_pvt_thread_rdwr_wlock( &cache->c_rwlock ); + /* id tree */ + if ( avl_delete( &cache->c_idtree, (caddr_t) e, bdb_id_cmp )) { + cache->c_eiused--; + if ( decr_leaf ) + cache->c_leaves--; + } else { + rc = -1; + assert(0); + } + ldap_pvt_thread_rdwr_wunlock( &cache->c_rwlock ); + bdb_cache_entryinfo_unlock( e->bei_parent ); + + if ( rc == 0 ){ + /* lru */ + LRU_DEL( cache, e ); + + if ( e->bei_e ) { + ldap_pvt_thread_mutex_lock( &cache->c_count_mutex ); + cache->c_cursize--; + ldap_pvt_thread_mutex_unlock( &cache->c_count_mutex ); + } + } + + return( rc ); +} + +static void +bdb_entryinfo_release( void *data ) +{ + EntryInfo *ei = (EntryInfo *)data; + if ( ei->bei_kids ) { + avl_free( ei->bei_kids, NULL ); + } + if ( ei->bei_e ) { + ei->bei_e->e_private = NULL; +#ifdef SLAP_ZONE_ALLOC + bdb_entry_return( ei->bei_bdb, ei->bei_e, ei->bei_zseq ); +#else + bdb_entry_return( ei->bei_e ); +#endif + } + bdb_cache_entryinfo_destroy( ei ); +} + +void +bdb_cache_release_all( Cache *cache ) +{ + /* set cache write lock */ + ldap_pvt_thread_rdwr_wlock( &cache->c_rwlock ); + /* set lru mutex */ + ldap_pvt_thread_mutex_lock( &cache->c_lru_mutex ); + + Debug( LDAP_DEBUG_TRACE, "====> bdb_cache_release_all\n", 0, 0, 0 ); + + avl_free( cache->c_dntree.bei_kids, NULL ); + avl_free( cache->c_idtree, bdb_entryinfo_release ); + for (;cache->c_eifree;cache->c_eifree = cache->c_lruhead) { + cache->c_lruhead = cache->c_eifree->bei_lrunext; + bdb_cache_entryinfo_destroy(cache->c_eifree); + } + cache->c_cursize = 0; + cache->c_eiused = 0; + cache->c_leaves = 0; + cache->c_idtree = NULL; + cache->c_lruhead = NULL; + cache->c_lrutail = NULL; + cache->c_dntree.bei_kids = NULL; + + /* free lru mutex */ + ldap_pvt_thread_mutex_unlock( &cache->c_lru_mutex ); + /* free cache write lock */ + ldap_pvt_thread_rdwr_wunlock( &cache->c_rwlock ); +} + +#ifdef LDAP_DEBUG +static void +bdb_lru_count( Cache *cache ) +{ + EntryInfo *e; + int ei = 0, ent = 0, nc = 0; + + for ( e = cache->c_lrutail; ; ) { + ei++; + if ( e->bei_e ) { + ent++; + if ( e->bei_state & CACHE_ENTRY_NOT_CACHED ) + nc++; + fprintf( stderr, "ei %d entry %p dn %s\n", ei, (void *) e->bei_e, e->bei_e->e_name.bv_val ); + } + e = e->bei_lrunext; + if ( e == cache->c_lrutail ) + break; + } + fprintf( stderr, "counted %d entryInfos and %d entries, %d notcached\n", + ei, ent, nc ); + ei = 0; + for ( e = cache->c_lrutail; ; ) { + ei++; + e = e->bei_lruprev; + if ( e == cache->c_lrutail ) + break; + } + fprintf( stderr, "counted %d entryInfos (on lruprev)\n", ei ); +} + +#ifdef SLAPD_UNUSED +static void +bdb_lru_print( Cache *cache ) +{ + EntryInfo *e; + + fprintf( stderr, "LRU circle head: %p\n", (void *) cache->c_lruhead ); + fprintf( stderr, "LRU circle (tail forward):\n" ); + for ( e = cache->c_lrutail; ; ) { + fprintf( stderr, "\t%p, %p id %ld rdn \"%s\"\n", + (void *) e, (void *) e->bei_e, e->bei_id, e->bei_nrdn.bv_val ); + e = e->bei_lrunext; + if ( e == cache->c_lrutail ) + break; + } + fprintf( stderr, "LRU circle (tail backward):\n" ); + for ( e = cache->c_lrutail; ; ) { + fprintf( stderr, "\t%p, %p id %ld rdn \"%s\"\n", + (void *) e, (void *) e->bei_e, e->bei_id, e->bei_nrdn.bv_val ); + e = e->bei_lruprev; + if ( e == cache->c_lrutail ) + break; + } +} + +static int +bdb_entryinfo_print(void *data, void *arg) +{ + EntryInfo *e = data; + fprintf( stderr, "\t%p, %p id %ld rdn \"%s\"\n", + (void *) e, (void *) e->bei_e, e->bei_id, e->bei_nrdn.bv_val ); + return 0; +} + +static void +bdb_idtree_print(Cache *cache) +{ + avl_apply( cache->c_idtree, bdb_entryinfo_print, NULL, -1, AVL_INORDER ); +} +#endif +#endif + +static void +bdb_reader_free( void *key, void *data ) +{ + /* DB_ENV *env = key; */ + DB_TXN *txn = data; + + if ( txn ) TXN_ABORT( txn ); +} + +/* free up any keys used by the main thread */ +void +bdb_reader_flush( DB_ENV *env ) +{ + void *data; + void *ctx = ldap_pvt_thread_pool_context(); + + if ( !ldap_pvt_thread_pool_getkey( ctx, env, &data, NULL ) ) { + ldap_pvt_thread_pool_setkey( ctx, env, NULL, 0, NULL, NULL ); + bdb_reader_free( env, data ); + } +} + +int +bdb_reader_get( Operation *op, DB_ENV *env, DB_TXN **txn ) +{ + int i, rc; + void *data; + void *ctx; + + if ( !env || !txn ) return -1; + + /* If no op was provided, try to find the ctx anyway... */ + if ( op ) { + ctx = op->o_threadctx; + } else { + ctx = ldap_pvt_thread_pool_context(); + } + + /* Shouldn't happen unless we're single-threaded */ + if ( !ctx ) { + *txn = NULL; + return 0; + } + + if ( ldap_pvt_thread_pool_getkey( ctx, env, &data, NULL ) ) { + for ( i=0, rc=1; rc != 0 && i<4; i++ ) { + rc = TXN_BEGIN( env, NULL, txn, DB_READ_COMMITTED ); + if (rc) ldap_pvt_thread_yield(); + } + if ( rc != 0) { + return rc; + } + data = *txn; + if ( ( rc = ldap_pvt_thread_pool_setkey( ctx, env, + data, bdb_reader_free, NULL, NULL ) ) ) { + TXN_ABORT( *txn ); + Debug( LDAP_DEBUG_ANY, "bdb_reader_get: err %s(%d)\n", + db_strerror(rc), rc, 0 ); + + return rc; + } + } else { + *txn = data; + } + return 0; +} diff --git a/servers/slapd/back-bdb/compare.c b/servers/slapd/back-bdb/compare.c new file mode 100644 index 0000000..adc4575 --- /dev/null +++ b/servers/slapd/back-bdb/compare.c @@ -0,0 +1,143 @@ +/* compare.c - bdb backend compare routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-bdb.h" + +int +bdb_compare( Operation *op, SlapReply *rs ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + Entry *e = NULL; + EntryInfo *ei; + int manageDSAit = get_manageDSAit( op ); + + DB_TXN *rtxn; + DB_LOCK lock; + + rs->sr_err = bdb_reader_get(op, bdb->bi_dbenv, &rtxn); + switch(rs->sr_err) { + case 0: + break; + default: + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return rs->sr_err; + } + +dn2entry_retry: + /* get entry */ + rs->sr_err = bdb_dn2entry( op, rtxn, &op->o_req_ndn, &ei, 1, + &lock ); + + switch( rs->sr_err ) { + case DB_NOTFOUND: + case 0: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto dn2entry_retry; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + e = ei->bei_e; + if ( rs->sr_err == DB_NOTFOUND ) { + if ( e != NULL ) { + /* return referral only if "disclose" is granted on the object */ + if ( ! access_allowed( op, e, slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + + } else { + rs->sr_matched = ch_strdup( e->e_dn ); + rs->sr_ref = is_entry_referral( e ) + ? get_entry_referrals( op, e ) + : NULL; + rs->sr_err = LDAP_REFERRAL; + } + + bdb_cache_return_entry_r( bdb, e, &lock ); + e = NULL; + + } else { + rs->sr_ref = referral_rewrite( default_referral, + NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + rs->sr_err = rs->sr_ref ? LDAP_REFERRAL : LDAP_NO_SUCH_OBJECT; + } + + send_ldap_result( op, rs ); + + ber_bvarray_free( rs->sr_ref ); + free( (char *)rs->sr_matched ); + rs->sr_ref = NULL; + rs->sr_matched = NULL; + + goto done; + } + + if (!manageDSAit && is_entry_referral( e ) ) { + /* return referral only if "disclose" is granted on the object */ + if ( !access_allowed( op, e, slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } else { + /* entry is a referral, don't allow compare */ + rs->sr_ref = get_entry_referrals( op, e ); + rs->sr_err = LDAP_REFERRAL; + rs->sr_matched = e->e_name.bv_val; + } + + Debug( LDAP_DEBUG_TRACE, "entry is referral\n", 0, 0, 0 ); + + send_ldap_result( op, rs ); + + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + rs->sr_matched = NULL; + goto done; + } + + rs->sr_err = slap_compare_entry( op, e, op->orc_ava ); + +return_results: + send_ldap_result( op, rs ); + + switch ( rs->sr_err ) { + case LDAP_COMPARE_FALSE: + case LDAP_COMPARE_TRUE: + rs->sr_err = LDAP_SUCCESS; + break; + } + +done: + /* free entry */ + if ( e != NULL ) { + bdb_cache_return_entry_r( bdb, e, &lock ); + } + + return rs->sr_err; +} diff --git a/servers/slapd/back-bdb/config.c b/servers/slapd/back-bdb/config.c new file mode 100644 index 0000000..5746db8 --- /dev/null +++ b/servers/slapd/back-bdb/config.c @@ -0,0 +1,951 @@ +/* config.c - bdb backend configuration file routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/errno.h> + +#include "back-bdb.h" + +#include "config.h" + +#include "lutil.h" +#include "ldap_rq.h" + +#ifdef DB_DIRTY_READ +# define SLAP_BDB_ALLOW_DIRTY_READ +#endif + +#define bdb_cf_gen BDB_SYMBOL(cf_gen) +#define bdb_cf_cleanup BDB_SYMBOL(cf_cleanup) +#define bdb_checkpoint BDB_SYMBOL(checkpoint) +#define bdb_online_index BDB_SYMBOL(online_index) + +static ConfigDriver bdb_cf_gen; + +enum { + BDB_CHKPT = 1, + BDB_CONFIG, + BDB_CRYPTFILE, + BDB_CRYPTKEY, + BDB_DIRECTORY, + BDB_NOSYNC, + BDB_DIRTYR, + BDB_INDEX, + BDB_LOCKD, + BDB_SSTACK, + BDB_MODE, + BDB_PGSIZE, + BDB_CHECKSUM +}; + +static ConfigTable bdbcfg[] = { + { "directory", "dir", 2, 2, 0, ARG_STRING|ARG_MAGIC|BDB_DIRECTORY, + bdb_cf_gen, "( OLcfgDbAt:0.1 NAME 'olcDbDirectory' " + "DESC 'Directory for database content' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "cachefree", "size", 2, 2, 0, ARG_ULONG|ARG_OFFSET, + (void *)offsetof(struct bdb_info, bi_cache.c_minfree), + "( OLcfgDbAt:1.11 NAME 'olcDbCacheFree' " + "DESC 'Number of extra entries to free when max is reached' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "cachesize", "size", 2, 2, 0, ARG_ULONG|ARG_OFFSET, + (void *)offsetof(struct bdb_info, bi_cache.c_maxsize), + "( OLcfgDbAt:1.1 NAME 'olcDbCacheSize' " + "DESC 'Entry cache size in entries' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "checkpoint", "kbyte> <min", 3, 3, 0, ARG_MAGIC|BDB_CHKPT, + bdb_cf_gen, "( OLcfgDbAt:1.2 NAME 'olcDbCheckpoint' " + "DESC 'Database checkpoint interval in kbytes and minutes' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )",NULL, NULL }, + { "checksum", NULL, 1, 2, 0, ARG_ON_OFF|ARG_MAGIC|BDB_CHECKSUM, + bdb_cf_gen, "( OLcfgDbAt:1.16 NAME 'olcDbChecksum' " + "DESC 'Enable database checksum validation' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "cryptfile", "file", 2, 2, 0, ARG_STRING|ARG_MAGIC|BDB_CRYPTFILE, + bdb_cf_gen, "( OLcfgDbAt:1.13 NAME 'olcDbCryptFile' " + "DESC 'Pathname of file containing the DB encryption key' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )",NULL, NULL }, + { "cryptkey", "key", 2, 2, 0, ARG_BERVAL|ARG_MAGIC|BDB_CRYPTKEY, + bdb_cf_gen, "( OLcfgDbAt:1.14 NAME 'olcDbCryptKey' " + "DESC 'DB encryption key' " + "SYNTAX OMsOctetString SINGLE-VALUE )",NULL, NULL }, + { "dbconfig", "DB_CONFIG setting", 1, 0, 0, ARG_MAGIC|BDB_CONFIG, + bdb_cf_gen, "( OLcfgDbAt:1.3 NAME 'olcDbConfig' " + "DESC 'BerkeleyDB DB_CONFIG configuration directives' " + "SYNTAX OMsIA5String X-ORDERED 'VALUES' )", NULL, NULL }, + { "dbnosync", NULL, 1, 2, 0, ARG_ON_OFF|ARG_MAGIC|BDB_NOSYNC, + bdb_cf_gen, "( OLcfgDbAt:1.4 NAME 'olcDbNoSync' " + "DESC 'Disable synchronous database writes' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "dbpagesize", "db> <size", 3, 3, 0, ARG_MAGIC|BDB_PGSIZE, + bdb_cf_gen, "( OLcfgDbAt:1.15 NAME 'olcDbPageSize' " + "DESC 'Page size of specified DB, in Kbytes' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "dirtyread", NULL, 1, 2, 0, +#ifdef SLAP_BDB_ALLOW_DIRTY_READ + ARG_ON_OFF|ARG_MAGIC|BDB_DIRTYR, bdb_cf_gen, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgDbAt:1.5 NAME 'olcDbDirtyRead' " + "DESC 'Allow reads of uncommitted data' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "dncachesize", "size", 2, 2, 0, ARG_ULONG|ARG_OFFSET, + (void *)offsetof(struct bdb_info, bi_cache.c_eimax), + "( OLcfgDbAt:1.12 NAME 'olcDbDNcacheSize' " + "DESC 'DN cache size' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "idlcachesize", "size", 2, 2, 0, ARG_ULONG|ARG_OFFSET, + (void *)offsetof(struct bdb_info, bi_idl_cache_max_size), + "( OLcfgDbAt:1.6 NAME 'olcDbIDLcacheSize' " + "DESC 'IDL cache size in IDLs' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "index", "attr> <[pres,eq,approx,sub]", 2, 3, 0, ARG_MAGIC|BDB_INDEX, + bdb_cf_gen, "( OLcfgDbAt:0.2 NAME 'olcDbIndex' " + "DESC 'Attribute index parameters' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "linearindex", NULL, 1, 2, 0, ARG_ON_OFF|ARG_OFFSET, + (void *)offsetof(struct bdb_info, bi_linear_index), + "( OLcfgDbAt:1.7 NAME 'olcDbLinearIndex' " + "DESC 'Index attributes one at a time' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "lockdetect", "policy", 2, 2, 0, ARG_MAGIC|BDB_LOCKD, + bdb_cf_gen, "( OLcfgDbAt:1.8 NAME 'olcDbLockDetect' " + "DESC 'Deadlock detection algorithm' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "mode", "mode", 2, 2, 0, ARG_MAGIC|BDB_MODE, + bdb_cf_gen, "( OLcfgDbAt:0.3 NAME 'olcDbMode' " + "DESC 'Unix permissions of database files' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "searchstack", "depth", 2, 2, 0, ARG_INT|ARG_MAGIC|BDB_SSTACK, + bdb_cf_gen, "( OLcfgDbAt:1.9 NAME 'olcDbSearchStack' " + "DESC 'Depth of search stack in IDLs' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "shm_key", "key", 2, 2, 0, ARG_LONG|ARG_OFFSET, + (void *)offsetof(struct bdb_info, bi_shm_key), + "( OLcfgDbAt:1.10 NAME 'olcDbShmKey' " + "DESC 'Key for shared memory region' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED, + NULL, NULL, NULL, NULL } +}; + +static ConfigOCs bdbocs[] = { + { +#ifdef BDB_HIER + "( OLcfgDbOc:1.2 " + "NAME 'olcHdbConfig' " + "DESC 'HDB backend configuration' " +#else + "( OLcfgDbOc:1.1 " + "NAME 'olcBdbConfig' " + "DESC 'BDB backend configuration' " +#endif + "SUP olcDatabaseConfig " + "MUST olcDbDirectory " + "MAY ( olcDbCacheSize $ olcDbCheckpoint $ olcDbChecksum $ " + "olcDbConfig $ olcDbCryptFile $ olcDbCryptKey $ " + "olcDbNoSync $ olcDbDirtyRead $ olcDbIDLcacheSize $ " + "olcDbIndex $ olcDbLinearIndex $ olcDbLockDetect $ " + "olcDbMode $ olcDbSearchStack $ olcDbShmKey $ " + "olcDbCacheFree $ olcDbDNcacheSize $ olcDbPageSize ) )", + Cft_Database, bdbcfg }, + { NULL, 0, NULL } +}; + +static slap_verbmasks bdb_lockd[] = { + { BER_BVC("default"), DB_LOCK_DEFAULT }, + { BER_BVC("oldest"), DB_LOCK_OLDEST }, + { BER_BVC("random"), DB_LOCK_RANDOM }, + { BER_BVC("youngest"), DB_LOCK_YOUNGEST }, + { BER_BVC("fewest"), DB_LOCK_MINLOCKS }, + { BER_BVNULL, 0 } +}; + +/* perform periodic checkpoints */ +static void * +bdb_checkpoint( void *ctx, void *arg ) +{ + struct re_s *rtask = arg; + struct bdb_info *bdb = rtask->arg; + + TXN_CHECKPOINT( bdb->bi_dbenv, bdb->bi_txn_cp_kbyte, + bdb->bi_txn_cp_min, 0 ); + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + ldap_pvt_runqueue_stoptask( &slapd_rq, rtask ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + return NULL; +} + +/* reindex entries on the fly */ +static void * +bdb_online_index( void *ctx, void *arg ) +{ + struct re_s *rtask = arg; + BackendDB *be = rtask->arg; + struct bdb_info *bdb = be->be_private; + + Connection conn = {0}; + OperationBuffer opbuf; + Operation *op; + + DBC *curs; + DBT key, data; + DB_TXN *txn; + DB_LOCK lock; + ID id, nid; + EntryInfo *ei; + int rc, getnext = 1; + int i; + + connection_fake_init( &conn, &opbuf, ctx ); + op = &opbuf.ob_op; + + op->o_bd = be; + + DBTzero( &key ); + DBTzero( &data ); + + id = 1; + key.data = &nid; + key.size = key.ulen = sizeof(ID); + key.flags = DB_DBT_USERMEM; + + data.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL; + data.dlen = data.ulen = 0; + + while ( 1 ) { + if ( slapd_shutdown ) + break; + + rc = TXN_BEGIN( bdb->bi_dbenv, NULL, &txn, bdb->bi_db_opflags ); + if ( rc ) + break; + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_online_index) ": txn id: %x\n", + txn->id(txn), 0, 0 ); + if ( getnext ) { + getnext = 0; + BDB_ID2DISK( id, &nid ); + rc = bdb->bi_id2entry->bdi_db->cursor( + bdb->bi_id2entry->bdi_db, txn, &curs, bdb->bi_db_opflags ); + if ( rc ) { + TXN_ABORT( txn ); + break; + } + rc = curs->c_get( curs, &key, &data, DB_SET_RANGE ); + curs->c_close( curs ); + if ( rc ) { + TXN_ABORT( txn ); + if ( rc == DB_NOTFOUND ) + rc = 0; + if ( rc == DB_LOCK_DEADLOCK ) { + ldap_pvt_thread_yield(); + continue; + } + break; + } + BDB_DISK2ID( &nid, &id ); + } + + ei = NULL; + rc = bdb_cache_find_id( op, txn, id, &ei, 0, &lock ); + if ( rc ) { + TXN_ABORT( txn ); + if ( rc == DB_LOCK_DEADLOCK ) { + ldap_pvt_thread_yield(); + continue; + } + if ( rc == DB_NOTFOUND ) { + id++; + getnext = 1; + continue; + } + break; + } + if ( ei->bei_e ) { + rc = bdb_index_entry( op, txn, BDB_INDEX_UPDATE_OP, ei->bei_e ); + if ( rc ) { + TXN_ABORT( txn ); + if ( rc == DB_LOCK_DEADLOCK ) { + ldap_pvt_thread_yield(); + continue; + } + break; + } + rc = TXN_COMMIT( txn, 0 ); + txn = NULL; + } + id++; + getnext = 1; + } + + for ( i = 0; i < bdb->bi_nattrs; i++ ) { + if ( bdb->bi_attrs[ i ]->ai_indexmask & BDB_INDEX_DELETING + || bdb->bi_attrs[ i ]->ai_newmask == 0 ) + { + continue; + } + bdb->bi_attrs[ i ]->ai_indexmask = bdb->bi_attrs[ i ]->ai_newmask; + bdb->bi_attrs[ i ]->ai_newmask = 0; + } + + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + ldap_pvt_runqueue_stoptask( &slapd_rq, rtask ); + bdb->bi_index_task = NULL; + ldap_pvt_runqueue_remove( &slapd_rq, rtask ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + return NULL; +} + +/* Cleanup loose ends after Modify completes */ +static int +bdb_cf_cleanup( ConfigArgs *c ) +{ + struct bdb_info *bdb = c->be->be_private; + int rc = 0; + BerVarray bva; + + if ( bdb->bi_flags & BDB_DEL_INDEX ) { + bdb_attr_flush( bdb ); + bdb->bi_flags ^= BDB_DEL_INDEX; + } + + if ( bdb->bi_flags & BDB_RE_OPEN ) { + bdb->bi_flags ^= BDB_RE_OPEN; + bva = bdb->bi_db_config; + bdb->bi_db_config = NULL; + rc = c->be->bd_info->bi_db_close( c->be, &c->reply ); + if ( rc == 0 ) { + if ( bdb->bi_flags & BDB_UPD_CONFIG ) { + if ( bva ) { + int i; + FILE *f = fopen( bdb->bi_db_config_path, "w" ); + if ( f ) { + bdb->bi_db_config = bva; + bva = NULL; + for (i=0; bdb->bi_db_config[i].bv_val; i++) + fprintf( f, "%s\n", bdb->bi_db_config[i].bv_val ); + fclose( f ); + } else { + ber_bvarray_free( bva ); + } + } else { + unlink( bdb->bi_db_config_path ); + } + bdb->bi_flags ^= BDB_UPD_CONFIG; + } + rc = c->be->bd_info->bi_db_open( c->be, &c->reply ); + } + /* If this fails, we need to restart */ + if ( rc ) { + slapd_shutdown = 2; + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "failed to reopen database, rc=%d", rc ); + Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(bdb_cf_cleanup) + ": %s\n", c->cr_msg, 0, 0 ); + rc = LDAP_OTHER; + } + } + return rc; +} + +static int +bdb_cf_gen( ConfigArgs *c ) +{ + struct bdb_info *bdb = c->be->be_private; + int rc; + + if ( c->op == SLAP_CONFIG_EMIT ) { + rc = 0; + switch( c->type ) { + case BDB_MODE: { + char buf[64]; + struct berval bv; + bv.bv_len = snprintf( buf, sizeof(buf), "0%o", bdb->bi_dbenv_mode ); + if ( bv.bv_len > 0 && bv.bv_len < sizeof(buf) ) { + bv.bv_val = buf; + value_add_one( &c->rvalue_vals, &bv ); + } else { + rc = 1; + } + } break; + + case BDB_CHKPT: + if ( bdb->bi_txn_cp ) { + char buf[64]; + struct berval bv; + bv.bv_len = snprintf( buf, sizeof(buf), "%ld %ld", + (long) bdb->bi_txn_cp_kbyte, (long) bdb->bi_txn_cp_min ); + if ( bv.bv_len > 0 && bv.bv_len < sizeof(buf) ) { + bv.bv_val = buf; + value_add_one( &c->rvalue_vals, &bv ); + } else { + rc = 1; + } + } else { + rc = 1; + } + break; + + case BDB_CRYPTFILE: + if ( bdb->bi_db_crypt_file ) { + c->value_string = ch_strdup( bdb->bi_db_crypt_file ); + } else { + rc = 1; + } + break; + + /* If a crypt file has been set, its contents are copied here. + * But we don't want the key to be incorporated here. + */ + case BDB_CRYPTKEY: + if ( !bdb->bi_db_crypt_file && !BER_BVISNULL( &bdb->bi_db_crypt_key )) { + value_add_one( &c->rvalue_vals, &bdb->bi_db_crypt_key ); + } else { + rc = 1; + } + break; + + case BDB_DIRECTORY: + if ( bdb->bi_dbenv_home ) { + c->value_string = ch_strdup( bdb->bi_dbenv_home ); + } else { + rc = 1; + } + break; + + case BDB_CONFIG: + if ( !( bdb->bi_flags & BDB_IS_OPEN ) + && !bdb->bi_db_config ) + { + char buf[SLAP_TEXT_BUFLEN]; + FILE *f = fopen( bdb->bi_db_config_path, "r" ); + struct berval bv; + + if ( f ) { + bdb->bi_flags |= BDB_HAS_CONFIG; + while ( fgets( buf, sizeof(buf), f )) { + ber_str2bv( buf, 0, 1, &bv ); + if ( bv.bv_len > 0 && bv.bv_val[bv.bv_len-1] == '\n' ) { + bv.bv_len--; + bv.bv_val[bv.bv_len] = '\0'; + } + /* shouldn't need this, but ... */ + if ( bv.bv_len > 0 && bv.bv_val[bv.bv_len-1] == '\r' ) { + bv.bv_len--; + bv.bv_val[bv.bv_len] = '\0'; + } + ber_bvarray_add( &bdb->bi_db_config, &bv ); + } + fclose( f ); + } + } + if ( bdb->bi_db_config ) { + int i; + struct berval bv; + + bv.bv_val = c->log; + for (i=0; !BER_BVISNULL(&bdb->bi_db_config[i]); i++) { + bv.bv_len = sprintf( bv.bv_val, "{%d}%s", i, + bdb->bi_db_config[i].bv_val ); + value_add_one( &c->rvalue_vals, &bv ); + } + } + if ( !c->rvalue_vals ) rc = 1; + break; + + case BDB_NOSYNC: + if ( bdb->bi_dbenv_xflags & DB_TXN_NOSYNC ) + c->value_int = 1; + break; + + case BDB_CHECKSUM: + if ( bdb->bi_flags & BDB_CHKSUM ) + c->value_int = 1; + break; + + case BDB_INDEX: + bdb_attr_index_unparse( bdb, &c->rvalue_vals ); + if ( !c->rvalue_vals ) rc = 1; + break; + + case BDB_LOCKD: + rc = 1; + if ( bdb->bi_lock_detect != DB_LOCK_DEFAULT ) { + int i; + for (i=0; !BER_BVISNULL(&bdb_lockd[i].word); i++) { + if ( bdb->bi_lock_detect == (u_int32_t)bdb_lockd[i].mask ) { + value_add_one( &c->rvalue_vals, &bdb_lockd[i].word ); + rc = 0; + break; + } + } + } + break; + + case BDB_SSTACK: + c->value_int = bdb->bi_search_stack_depth; + break; + + case BDB_PGSIZE: { + struct bdb_db_pgsize *ps; + char buf[SLAP_TEXT_BUFLEN]; + struct berval bv; + int rc = 1; + + bv.bv_val = buf; + for ( ps = bdb->bi_pagesizes; ps; ps = ps->bdp_next ) { + bv.bv_len = sprintf( buf, "%s %d", ps->bdp_name.bv_val, + ps->bdp_size / 1024 ); + value_add_one( &c->rvalue_vals, &bv ); + rc = 0; + + } + break; + } + } + return rc; + } else if ( c->op == LDAP_MOD_DELETE ) { + rc = 0; + switch( c->type ) { + case BDB_MODE: +#if 0 + /* FIXME: does it make any sense to change the mode, + * if we don't exec a chmod()? */ + bdb->bi_dbenv_mode = SLAPD_DEFAULT_DB_MODE; + break; +#endif + + /* single-valued no-ops */ + case BDB_LOCKD: + case BDB_SSTACK: + break; + + case BDB_CHKPT: + if ( bdb->bi_txn_cp_task ) { + struct re_s *re = bdb->bi_txn_cp_task; + bdb->bi_txn_cp_task = NULL; + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re ) ) + ldap_pvt_runqueue_stoptask( &slapd_rq, re ); + ldap_pvt_runqueue_remove( &slapd_rq, re ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + bdb->bi_txn_cp = 0; + break; + case BDB_CONFIG: + if ( c->valx < 0 ) { + ber_bvarray_free( bdb->bi_db_config ); + bdb->bi_db_config = NULL; + } else { + int i = c->valx; + ch_free( bdb->bi_db_config[i].bv_val ); + for (; bdb->bi_db_config[i].bv_val; i++) + bdb->bi_db_config[i] = bdb->bi_db_config[i+1]; + } + bdb->bi_flags |= BDB_UPD_CONFIG|BDB_RE_OPEN; + c->cleanup = bdb_cf_cleanup; + break; + /* Doesn't really make sense to change these on the fly; + * the entire DB must be dumped and reloaded + */ + case BDB_CRYPTFILE: + if ( bdb->bi_db_crypt_file ) { + ch_free( bdb->bi_db_crypt_file ); + bdb->bi_db_crypt_file = NULL; + } + /* FALLTHRU */ + case BDB_CRYPTKEY: + if ( !BER_BVISNULL( &bdb->bi_db_crypt_key )) { + ch_free( bdb->bi_db_crypt_key.bv_val ); + BER_BVZERO( &bdb->bi_db_crypt_key ); + } + break; + case BDB_DIRECTORY: + bdb->bi_flags |= BDB_RE_OPEN; + bdb->bi_flags ^= BDB_HAS_CONFIG; + ch_free( bdb->bi_dbenv_home ); + bdb->bi_dbenv_home = NULL; + ch_free( bdb->bi_db_config_path ); + bdb->bi_db_config_path = NULL; + c->cleanup = bdb_cf_cleanup; + ldap_pvt_thread_pool_purgekey( bdb->bi_dbenv ); + break; + case BDB_NOSYNC: + bdb->bi_dbenv->set_flags( bdb->bi_dbenv, DB_TXN_NOSYNC, 0 ); + break; + case BDB_CHECKSUM: + bdb->bi_flags &= ~BDB_CHKSUM; + break; + case BDB_INDEX: + if ( c->valx == -1 ) { + int i; + + /* delete all */ + for ( i = 0; i < bdb->bi_nattrs; i++ ) { + bdb->bi_attrs[i]->ai_indexmask |= BDB_INDEX_DELETING; + } + bdb->bi_defaultmask = 0; + bdb->bi_flags |= BDB_DEL_INDEX; + c->cleanup = bdb_cf_cleanup; + + } else { + struct berval bv, def = BER_BVC("default"); + char *ptr; + + for (ptr = c->line; !isspace( (unsigned char) *ptr ); ptr++); + + bv.bv_val = c->line; + bv.bv_len = ptr - bv.bv_val; + if ( bvmatch( &bv, &def )) { + bdb->bi_defaultmask = 0; + + } else { + int i; + char **attrs; + char sep; + + sep = bv.bv_val[ bv.bv_len ]; + bv.bv_val[ bv.bv_len ] = '\0'; + attrs = ldap_str2charray( bv.bv_val, "," ); + + for ( i = 0; attrs[ i ]; i++ ) { + AttributeDescription *ad = NULL; + const char *text; + AttrInfo *ai; + + slap_str2ad( attrs[ i ], &ad, &text ); + /* if we got here... */ + assert( ad != NULL ); + + ai = bdb_attr_mask( bdb, ad ); + /* if we got here... */ + assert( ai != NULL ); + + ai->ai_indexmask |= BDB_INDEX_DELETING; + bdb->bi_flags |= BDB_DEL_INDEX; + c->cleanup = bdb_cf_cleanup; + } + + bv.bv_val[ bv.bv_len ] = sep; + ldap_charray_free( attrs ); + } + } + break; + /* doesn't make sense on the fly; the DB file must be + * recreated + */ + case BDB_PGSIZE: { + struct bdb_db_pgsize *ps, **prev; + int i; + + for ( i = 0, prev = &bdb->bi_pagesizes, ps = *prev; ps; + prev = &ps->bdp_next, ps = ps->bdp_next, i++ ) { + if ( c->valx == -1 || i == c->valx ) { + *prev = ps->bdp_next; + ch_free( ps ); + ps = *prev; + if ( i == c->valx ) break; + } + } + } + break; + } + return rc; + } + + switch( c->type ) { + case BDB_MODE: + if ( ASCII_DIGIT( c->argv[1][0] ) ) { + long mode; + char *next; + errno = 0; + mode = strtol( c->argv[1], &next, 0 ); + if ( errno != 0 || next == c->argv[1] || next[0] != '\0' ) { + fprintf( stderr, "%s: " + "unable to parse mode=\"%s\".\n", + c->log, c->argv[1] ); + return 1; + } + bdb->bi_dbenv_mode = mode; + + } else { + char *m = c->argv[1]; + int who, what, mode = 0; + + if ( strlen( m ) != STRLENOF("-rwxrwxrwx") ) { + return 1; + } + + if ( m[0] != '-' ) { + return 1; + } + + m++; + for ( who = 0; who < 3; who++ ) { + for ( what = 0; what < 3; what++, m++ ) { + if ( m[0] == '-' ) { + continue; + } else if ( m[0] != "rwx"[what] ) { + return 1; + } + mode += ((1 << (2 - what)) << 3*(2 - who)); + } + } + bdb->bi_dbenv_mode = mode; + } + break; + case BDB_CHKPT: { + long l; + bdb->bi_txn_cp = 1; + if ( lutil_atolx( &l, c->argv[1], 0 ) != 0 ) { + fprintf( stderr, "%s: " + "invalid kbyte \"%s\" in \"checkpoint\".\n", + c->log, c->argv[1] ); + return 1; + } + bdb->bi_txn_cp_kbyte = l; + if ( lutil_atolx( &l, c->argv[2], 0 ) != 0 ) { + fprintf( stderr, "%s: " + "invalid minutes \"%s\" in \"checkpoint\".\n", + c->log, c->argv[2] ); + return 1; + } + bdb->bi_txn_cp_min = l; + /* If we're in server mode and time-based checkpointing is enabled, + * submit a task to perform periodic checkpoints. + */ + if ((slapMode & SLAP_SERVER_MODE) && bdb->bi_txn_cp_min ) { + struct re_s *re = bdb->bi_txn_cp_task; + if ( re ) { + re->interval.tv_sec = bdb->bi_txn_cp_min * 60; + } else { + if ( c->be->be_suffix == NULL || BER_BVISNULL( &c->be->be_suffix[0] ) ) { + fprintf( stderr, "%s: " + "\"checkpoint\" must occur after \"suffix\".\n", + c->log ); + return 1; + } + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + bdb->bi_txn_cp_task = ldap_pvt_runqueue_insert( &slapd_rq, + bdb->bi_txn_cp_min * 60, bdb_checkpoint, bdb, + LDAP_XSTRING(bdb_checkpoint), c->be->be_suffix[0].bv_val ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + } + } break; + + case BDB_CONFIG: { + char *ptr = c->line; + struct berval bv; + + if ( c->op == SLAP_CONFIG_ADD ) { + ptr += STRLENOF("dbconfig"); + while (!isspace((unsigned char)*ptr)) ptr++; + while (isspace((unsigned char)*ptr)) ptr++; + } + + if ( bdb->bi_flags & BDB_IS_OPEN ) { + bdb->bi_flags |= BDB_UPD_CONFIG|BDB_RE_OPEN; + c->cleanup = bdb_cf_cleanup; + } else { + /* If we're just starting up... + */ + FILE *f; + /* If a DB_CONFIG file exists, or we don't know the path + * to the DB_CONFIG file, ignore these directives + */ + if (( bdb->bi_flags & BDB_HAS_CONFIG ) || !bdb->bi_db_config_path ) + break; + f = fopen( bdb->bi_db_config_path, "a" ); + if ( f ) { + /* FIXME: EBCDIC probably needs special handling */ + fprintf( f, "%s\n", ptr ); + fclose( f ); + } + } + ber_str2bv( ptr, 0, 1, &bv ); + ber_bvarray_add( &bdb->bi_db_config, &bv ); + } + break; + + case BDB_CRYPTFILE: + rc = lutil_get_filed_password( c->value_string, &bdb->bi_db_crypt_key ); + if ( rc == 0 ) { + bdb->bi_db_crypt_file = c->value_string; + } + break; + + /* Cannot set key if file was already set */ + case BDB_CRYPTKEY: + if ( bdb->bi_db_crypt_file ) { + rc = 1; + } else { + bdb->bi_db_crypt_key = c->value_bv; + } + break; + + case BDB_DIRECTORY: { + FILE *f; + char *ptr, *testpath; + int len; + + len = strlen( c->value_string ); + testpath = ch_malloc( len + STRLENOF(LDAP_DIRSEP) + STRLENOF("DUMMY") + 1 ); + ptr = lutil_strcopy( testpath, c->value_string ); + *ptr++ = LDAP_DIRSEP[0]; + strcpy( ptr, "DUMMY" ); + f = fopen( testpath, "w" ); + if ( f ) { + fclose( f ); + unlink( testpath ); + } + ch_free( testpath ); + if ( !f ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: invalid path: %s", + c->log, strerror( errno )); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + return -1; + } + + if ( bdb->bi_dbenv_home ) + ch_free( bdb->bi_dbenv_home ); + bdb->bi_dbenv_home = c->value_string; + + /* See if a DB_CONFIG file already exists here */ + if ( bdb->bi_db_config_path ) + ch_free( bdb->bi_db_config_path ); + bdb->bi_db_config_path = ch_malloc( len + + STRLENOF(LDAP_DIRSEP) + STRLENOF("DB_CONFIG") + 1 ); + ptr = lutil_strcopy( bdb->bi_db_config_path, bdb->bi_dbenv_home ); + *ptr++ = LDAP_DIRSEP[0]; + strcpy( ptr, "DB_CONFIG" ); + + f = fopen( bdb->bi_db_config_path, "r" ); + if ( f ) { + bdb->bi_flags |= BDB_HAS_CONFIG; + fclose(f); + } + } + break; + + case BDB_NOSYNC: + if ( c->value_int ) + bdb->bi_dbenv_xflags |= DB_TXN_NOSYNC; + else + bdb->bi_dbenv_xflags &= ~DB_TXN_NOSYNC; + if ( bdb->bi_flags & BDB_IS_OPEN ) { + bdb->bi_dbenv->set_flags( bdb->bi_dbenv, DB_TXN_NOSYNC, + c->value_int ); + } + break; + + case BDB_CHECKSUM: + if ( c->value_int ) + bdb->bi_flags |= BDB_CHKSUM; + else + bdb->bi_flags &= ~BDB_CHKSUM; + break; + + case BDB_INDEX: + rc = bdb_attr_index_config( bdb, c->fname, c->lineno, + c->argc - 1, &c->argv[1], &c->reply); + + if( rc != LDAP_SUCCESS ) return 1; + if (( bdb->bi_flags & BDB_IS_OPEN ) && !bdb->bi_index_task ) { + /* Start the task as soon as we finish here. Set a long + * interval (10 hours) so that it only gets scheduled once. + */ + if ( c->be->be_suffix == NULL || BER_BVISNULL( &c->be->be_suffix[0] ) ) { + fprintf( stderr, "%s: " + "\"index\" must occur after \"suffix\".\n", + c->log ); + return 1; + } + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + bdb->bi_index_task = ldap_pvt_runqueue_insert( &slapd_rq, 36000, + bdb_online_index, c->be, + LDAP_XSTRING(bdb_online_index), c->be->be_suffix[0].bv_val ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + break; + + case BDB_LOCKD: + rc = verb_to_mask( c->argv[1], bdb_lockd ); + if ( BER_BVISNULL(&bdb_lockd[rc].word) ) { + fprintf( stderr, "%s: " + "bad policy (%s) in \"lockDetect <policy>\" line\n", + c->log, c->argv[1] ); + return 1; + } + bdb->bi_lock_detect = (u_int32_t)rc; + break; + + case BDB_SSTACK: + if ( c->value_int < MINIMUM_SEARCH_STACK_DEPTH ) { + fprintf( stderr, + "%s: depth %d too small, using %d\n", + c->log, c->value_int, MINIMUM_SEARCH_STACK_DEPTH ); + c->value_int = MINIMUM_SEARCH_STACK_DEPTH; + } + bdb->bi_search_stack_depth = c->value_int; + break; + + case BDB_PGSIZE: { + struct bdb_db_pgsize *ps, **prev; + int i, s; + + s = atoi(c->argv[2]); + if ( s < 1 || s > 64 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s: size must be > 0 and <= 64: %d", + c->log, s ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + return -1; + } + i = strlen(c->argv[1]); + ps = ch_malloc( sizeof(struct bdb_db_pgsize) + i + 1 ); + ps->bdp_next = NULL; + ps->bdp_name.bv_len = i; + ps->bdp_name.bv_val = (char *)(ps+1); + strcpy( ps->bdp_name.bv_val, c->argv[1] ); + ps->bdp_size = s * 1024; + for ( prev = &bdb->bi_pagesizes; *prev; prev = &(*prev)->bdp_next ) + ; + *prev = ps; + } + break; + } + return 0; +} + +int bdb_back_init_cf( BackendInfo *bi ) +{ + int rc; + bi->bi_cf_ocs = bdbocs; + + rc = config_register_schema( bdbcfg, bdbocs ); + if ( rc ) return rc; + return 0; +} diff --git a/servers/slapd/back-bdb/dbcache.c b/servers/slapd/back-bdb/dbcache.c new file mode 100644 index 0000000..74c304a --- /dev/null +++ b/servers/slapd/back-bdb/dbcache.c @@ -0,0 +1,210 @@ +/* dbcache.c - manage cache of open databases */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> +#include <sys/stat.h> + +#include "slap.h" +#include "back-bdb.h" +#include "lutil_hash.h" + +#ifdef BDB_INDEX_USE_HASH +/* Pass-thru hash function. Since the indexer is already giving us hash + * values as keys, we don't need BDB to re-hash them. + */ +static u_int32_t +bdb_db_hash( + DB *db, + const void *bytes, + u_int32_t length +) +{ + u_int32_t ret = 0; + unsigned char *dst = (unsigned char *)&ret; + const unsigned char *src = (const unsigned char *)bytes; + + if ( length > sizeof(u_int32_t) ) + length = sizeof(u_int32_t); + + while ( length ) { + *dst++ = *src++; + length--; + } + return ret; +} +#define BDB_INDEXTYPE DB_HASH +#else +#define BDB_INDEXTYPE DB_BTREE +#endif + +/* If a configured size is found, return it, otherwise return 0 */ +int +bdb_db_findsize( + struct bdb_info *bdb, + struct berval *name +) +{ + struct bdb_db_pgsize *bp; + int rc; + + for ( bp = bdb->bi_pagesizes; bp; bp=bp->bdp_next ) { + rc = strncmp( name->bv_val, bp->bdp_name.bv_val, name->bv_len ); + if ( !rc ) { + if ( name->bv_len == bp->bdp_name.bv_len ) + return bp->bdp_size; + if ( name->bv_len < bp->bdp_name.bv_len && + bp->bdp_name.bv_val[name->bv_len] == '.' ) + return bp->bdp_size; + } + } + return 0; +} + +int +bdb_db_cache( + Backend *be, + struct berval *name, + DB **dbout ) +{ + int i, flags; + int rc; + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + struct bdb_db_info *db; + char *file; + + *dbout = NULL; + + for( i=BDB_NDB; i < bdb->bi_ndatabases; i++ ) { + if( !ber_bvcmp( &bdb->bi_databases[i]->bdi_name, name) ) { + *dbout = bdb->bi_databases[i]->bdi_db; + return 0; + } + } + + ldap_pvt_thread_mutex_lock( &bdb->bi_database_mutex ); + + /* check again! may have been added by another thread */ + for( i=BDB_NDB; i < bdb->bi_ndatabases; i++ ) { + if( !ber_bvcmp( &bdb->bi_databases[i]->bdi_name, name) ) { + *dbout = bdb->bi_databases[i]->bdi_db; + ldap_pvt_thread_mutex_unlock( &bdb->bi_database_mutex ); + return 0; + } + } + + if( i >= BDB_INDICES ) { + ldap_pvt_thread_mutex_unlock( &bdb->bi_database_mutex ); + return -1; + } + + db = (struct bdb_db_info *) ch_calloc(1, sizeof(struct bdb_db_info)); + + ber_dupbv( &db->bdi_name, name ); + + rc = db_create( &db->bdi_db, bdb->bi_dbenv, 0 ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "bdb_db_cache: db_create(%s) failed: %s (%d)\n", + bdb->bi_dbenv_home, db_strerror(rc), rc ); + ldap_pvt_thread_mutex_unlock( &bdb->bi_database_mutex ); + ch_free( db ); + return rc; + } + + if( !BER_BVISNULL( &bdb->bi_db_crypt_key )) { + rc = db->bdi_db->set_flags( db->bdi_db, DB_ENCRYPT ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "bdb_db_cache: db set_flags(DB_ENCRYPT)(%s) failed: %s (%d)\n", + bdb->bi_dbenv_home, db_strerror(rc), rc ); + ldap_pvt_thread_mutex_unlock( &bdb->bi_database_mutex ); + db->bdi_db->close( db->bdi_db, 0 ); + ch_free( db ); + return rc; + } + } + + if( bdb->bi_flags & BDB_CHKSUM ) { + rc = db->bdi_db->set_flags( db->bdi_db, DB_CHKSUM ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "bdb_db_cache: db set_flags(DB_CHKSUM)(%s) failed: %s (%d)\n", + bdb->bi_dbenv_home, db_strerror(rc), rc ); + ldap_pvt_thread_mutex_unlock( &bdb->bi_database_mutex ); + db->bdi_db->close( db->bdi_db, 0 ); + ch_free( db ); + return rc; + } + } + + /* If no explicit size set, use the FS default */ + flags = bdb_db_findsize( bdb, name ); + if ( flags ) + rc = db->bdi_db->set_pagesize( db->bdi_db, flags ); + +#ifdef BDB_INDEX_USE_HASH + rc = db->bdi_db->set_h_hash( db->bdi_db, bdb_db_hash ); +#endif + rc = db->bdi_db->set_flags( db->bdi_db, DB_DUP | DB_DUPSORT ); + + file = ch_malloc( db->bdi_name.bv_len + sizeof(BDB_SUFFIX) ); + strcpy( file, db->bdi_name.bv_val ); + strcpy( file+db->bdi_name.bv_len, BDB_SUFFIX ); + +#ifdef HAVE_EBCDIC + __atoe( file ); +#endif + flags = DB_CREATE | DB_THREAD; +#ifdef DB_AUTO_COMMIT + if ( !( slapMode & SLAP_TOOL_QUICK )) + flags |= DB_AUTO_COMMIT; +#endif + /* Cannot Truncate when Transactions are in use */ + if ( (slapMode & (SLAP_TOOL_QUICK|SLAP_TRUNCATE_MODE)) == + (SLAP_TOOL_QUICK|SLAP_TRUNCATE_MODE)) + flags |= DB_TRUNCATE; + + rc = DB_OPEN( db->bdi_db, + file, NULL /* name */, + BDB_INDEXTYPE, bdb->bi_db_opflags | flags, bdb->bi_dbenv_mode ); + + ch_free( file ); + + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "bdb_db_cache: db_open(%s) failed: %s (%d)\n", + name->bv_val, db_strerror(rc), rc ); + ldap_pvt_thread_mutex_unlock( &bdb->bi_database_mutex ); + db->bdi_db->close( db->bdi_db, 0 ); + ch_free( db ); + return rc; + } + + bdb->bi_databases[i] = db; + bdb->bi_ndatabases = i+1; + + *dbout = db->bdi_db; + + ldap_pvt_thread_mutex_unlock( &bdb->bi_database_mutex ); + return 0; +} diff --git a/servers/slapd/back-bdb/delete.c b/servers/slapd/back-bdb/delete.c new file mode 100644 index 0000000..14b34fa --- /dev/null +++ b/servers/slapd/back-bdb/delete.c @@ -0,0 +1,605 @@ +/* delete.c - bdb backend delete routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "lutil.h" +#include "back-bdb.h" + +int +bdb_delete( Operation *op, SlapReply *rs ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + Entry *matched = NULL; + struct berval pdn = {0, NULL}; + Entry *e = NULL; + Entry *p = NULL; + EntryInfo *ei = NULL, *eip = NULL; + int manageDSAit = get_manageDSAit( op ); + AttributeDescription *children = slap_schema.si_ad_children; + AttributeDescription *entry = slap_schema.si_ad_entry; + DB_TXN *ltid = NULL, *lt2; + struct bdb_op_info opinfo = {{{ 0 }}}; + ID eid; + + DB_LOCK lock, plock; + + int num_retries = 0; + + int rc; + + LDAPControl **preread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + + int parent_is_glue = 0; + int parent_is_leaf = 0; + +#ifdef LDAP_X_TXN + int settle = 0; +#endif + + Debug( LDAP_DEBUG_ARGS, "==> " LDAP_XSTRING(bdb_delete) ": %s\n", + op->o_req_dn.bv_val, 0, 0 ); + +#ifdef LDAP_X_TXN + if( op->o_txnSpec ) { + /* acquire connection lock */ + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + if( op->o_conn->c_txn == CONN_TXN_INACTIVE ) { + rs->sr_text = "invalid transaction identifier"; + rs->sr_err = LDAP_X_TXN_ID_INVALID; + goto txnReturn; + } else if( op->o_conn->c_txn == CONN_TXN_SETTLE ) { + settle=1; + goto txnReturn; + } + + if( op->o_conn->c_txn_backend == NULL ) { + op->o_conn->c_txn_backend = op->o_bd; + + } else if( op->o_conn->c_txn_backend != op->o_bd ) { + rs->sr_text = "transaction cannot span multiple database contexts"; + rs->sr_err = LDAP_AFFECTS_MULTIPLE_DSAS; + goto txnReturn; + } + + /* insert operation into transaction */ + + rs->sr_text = "transaction specified"; + rs->sr_err = LDAP_X_TXN_SPECIFY_OKAY; + +txnReturn: + /* release connection lock */ + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + + if( !settle ) { + send_ldap_result( op, rs ); + return rs->sr_err; + } + } +#endif + + ctrls[num_ctrls] = 0; + + /* allocate CSN */ + if ( BER_BVISNULL( &op->o_csn ) ) { + struct berval csn; + char csnbuf[LDAP_PVT_CSNSTR_BUFSIZE]; + + csn.bv_val = csnbuf; + csn.bv_len = sizeof(csnbuf); + slap_get_csn( op, &csn, 1 ); + } + + if( 0 ) { +retry: /* transaction retry */ + if( e != NULL ) { + bdb_unlocked_cache_return_entry_w(&bdb->bi_cache, e); + e = NULL; + } + if( p != NULL ) { + bdb_unlocked_cache_return_entry_r(&bdb->bi_cache, p); + p = NULL; + } + Debug( LDAP_DEBUG_TRACE, + "==> " LDAP_XSTRING(bdb_delete) ": retrying...\n", + 0, 0, 0 ); + rs->sr_err = TXN_ABORT( ltid ); + ltid = NULL; + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + opinfo.boi_oe.oe_key = NULL; + op->o_do_not_cache = opinfo.boi_acl_cache; + if( rs->sr_err != 0 ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto return_results; + } + parent_is_glue = 0; + parent_is_leaf = 0; + bdb_trans_backoff( ++num_retries ); + } + + /* begin transaction */ + rs->sr_err = TXN_BEGIN( bdb->bi_dbenv, NULL, <id, + bdb->bi_db_opflags ); + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_delete) ": txn1 id: %x\n", + ltid->id(ltid), 0, 0 ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_delete) ": txn_begin failed: " + "%s (%d)\n", db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + opinfo.boi_oe.oe_key = bdb; + opinfo.boi_txn = ltid; + opinfo.boi_err = 0; + opinfo.boi_acl_cache = op->o_do_not_cache; + LDAP_SLIST_INSERT_HEAD( &op->o_extra, &opinfo.boi_oe, oe_next ); + + if ( !be_issuffix( op->o_bd, &op->o_req_ndn ) ) { + dnParent( &op->o_req_ndn, &pdn ); + } + + /* get entry */ + rs->sr_err = bdb_dn2entry( op, ltid, &op->o_req_ndn, &ei, 1, + &lock ); + + switch( rs->sr_err ) { + case 0: + case DB_NOTFOUND: + break; + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + if ( rs->sr_err == 0 ) { + e = ei->bei_e; + eip = ei->bei_parent; + } else { + matched = ei->bei_e; + } + + /* FIXME : dn2entry() should return non-glue entry */ + if ( e == NULL || ( !manageDSAit && is_entry_glue( e ))) { + Debug( LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(bdb_delete) ": no such object %s\n", + op->o_req_dn.bv_val, 0, 0); + + if ( matched != NULL ) { + rs->sr_matched = ch_strdup( matched->e_dn ); + rs->sr_ref = is_entry_referral( matched ) + ? get_entry_referrals( op, matched ) + : NULL; + bdb_unlocked_cache_return_entry_r(&bdb->bi_cache, matched); + matched = NULL; + + } else { + rs->sr_ref = referral_rewrite( default_referral, NULL, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + } + + rs->sr_err = LDAP_REFERRAL; + rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED; + goto return_results; + } + + rc = bdb_cache_find_id( op, ltid, eip->bei_id, &eip, 0, &plock ); + switch( rc ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case 0: + case DB_NOTFOUND: + break; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + if ( eip ) p = eip->bei_e; + + if ( pdn.bv_len != 0 ) { + if( p == NULL || !bvmatch( &pdn, &p->e_nname )) { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_delete) ": parent " + "does not exist\n", 0, 0, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "could not locate parent of entry"; + goto return_results; + } + + /* check parent for "children" acl */ + rs->sr_err = access_allowed( op, p, + children, NULL, ACL_WDEL, NULL ); + + if ( !rs->sr_err ) { + switch( opinfo.boi_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_delete) ": no write " + "access to parent\n", 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to parent"; + goto return_results; + } + + } else { + /* no parent, must be root to delete */ + if( ! be_isroot( op ) ) { + if ( be_issuffix( op->o_bd, (struct berval *)&slap_empty_bv ) + || be_shadow_update( op ) ) { + p = (Entry *)&slap_entry_root; + + /* check parent for "children" acl */ + rs->sr_err = access_allowed( op, p, + children, NULL, ACL_WDEL, NULL ); + + p = NULL; + + if ( !rs->sr_err ) { + switch( opinfo.boi_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_delete) + ": no access to parent\n", + 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to parent"; + goto return_results; + } + + } else { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_delete) + ": no parent and not root\n", 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto return_results; + } + } + } + + if ( get_assert( op ) && + ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto return_results; + } + + rs->sr_err = access_allowed( op, e, + entry, NULL, ACL_WDEL, NULL ); + + if ( !rs->sr_err ) { + switch( opinfo.boi_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_delete) ": no write access " + "to entry\n", 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to entry"; + goto return_results; + } + + if ( !manageDSAit && is_entry_referral( e ) ) { + /* entry is a referral, don't allow delete */ + rs->sr_ref = get_entry_referrals( op, e ); + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_delete) ": entry is referral\n", + 0, 0, 0 ); + + rs->sr_err = LDAP_REFERRAL; + rs->sr_matched = ch_strdup( e->e_name.bv_val ); + rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED; + goto return_results; + } + + /* pre-read */ + if( op->o_preread ) { + if( preread_ctrl == NULL ) { + preread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if( slap_read_controls( op, rs, e, + &slap_pre_read_bv, preread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_delete) ": pre-read " + "failed!\n", 0, 0, 0 ); + if ( op->o_preread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + /* nested transaction */ + rs->sr_err = TXN_BEGIN( bdb->bi_dbenv, ltid, <2, + bdb->bi_db_opflags ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_delete) ": txn_begin(2) failed: " + "%s (%d)\n", db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_delete) ": txn2 id: %x\n", + lt2->id(lt2), 0, 0 ); + + BDB_LOG_PRINTF( bdb->bi_dbenv, lt2, "slapd Starting delete %s(%d)", + e->e_nname.bv_val, e->e_id ); + + /* Can't do it if we have kids */ + rs->sr_err = bdb_cache_children( op, lt2, e ); + if( rs->sr_err != DB_NOTFOUND ) { + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case 0: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(bdb_delete) + ": non-leaf %s\n", + op->o_req_dn.bv_val, 0, 0); + rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF; + rs->sr_text = "subordinate objects must be deleted first"; + break; + default: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(bdb_delete) + ": has_children failed: %s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + } + goto return_results; + } + + /* delete from dn2id */ + rs->sr_err = bdb_dn2id_delete( op, lt2, eip, e ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_delete) ": dn2id failed: " + "%s (%d)\n", db_strerror(rs->sr_err), rs->sr_err, 0 ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + rs->sr_text = "DN index delete failed"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + + /* delete indices for old attributes */ + rs->sr_err = bdb_index_entry_del( op, lt2, e ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_delete) ": index failed: " + "%s (%d)\n", db_strerror(rs->sr_err), rs->sr_err, 0 ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + rs->sr_text = "entry index delete failed"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + + /* fixup delete CSN */ + if ( !SLAP_SHADOW( op->o_bd )) { + struct berval vals[2]; + + assert( !BER_BVISNULL( &op->o_csn ) ); + vals[0] = op->o_csn; + BER_BVZERO( &vals[1] ); + rs->sr_err = bdb_index_values( op, lt2, slap_schema.si_ad_entryCSN, + vals, 0, SLAP_INDEX_ADD_OP ); + if ( rs->sr_err != LDAP_SUCCESS ) { + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + rs->sr_text = "entryCSN index update failed"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + } + + /* delete from id2entry */ + rs->sr_err = bdb_id2entry_delete( op->o_bd, lt2, e ); + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_delete) ": id2entry failed: " + "%s (%d)\n", db_strerror(rs->sr_err), rs->sr_err, 0 ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + rs->sr_text = "entry delete failed"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + + if ( pdn.bv_len != 0 ) { + parent_is_glue = is_entry_glue(p); + rs->sr_err = bdb_cache_children( op, lt2, p ); + if ( rs->sr_err != DB_NOTFOUND ) { + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case 0: + break; + default: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(bdb_delete) + ": has_children failed: %s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + parent_is_leaf = 1; + } + bdb_unlocked_cache_return_entry_r(&bdb->bi_cache, p); + p = NULL; + } + + BDB_LOG_PRINTF( bdb->bi_dbenv, lt2, "slapd Commit1 delete %s(%d)", + e->e_nname.bv_val, e->e_id ); + + if ( TXN_COMMIT( lt2, 0 ) != 0 ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "txn_commit(2) failed"; + goto return_results; + } + + eid = e->e_id; + +#if 0 /* Do we want to reclaim deleted IDs? */ + ldap_pvt_thread_mutex_lock( &bdb->bi_lastid_mutex ); + if ( e->e_id == bdb->bi_lastid ) { + bdb_last_id( op->o_bd, ltid ); + } + ldap_pvt_thread_mutex_unlock( &bdb->bi_lastid_mutex ); +#endif + + if( op->o_noop ) { + if ( ( rs->sr_err = TXN_ABORT( ltid ) ) != 0 ) { + rs->sr_text = "txn_abort (no-op) failed"; + } else { + rs->sr_err = LDAP_X_NO_OPERATION; + ltid = NULL; + goto return_results; + } + } else { + + BDB_LOG_PRINTF( bdb->bi_dbenv, ltid, "slapd Cache delete %s(%d)", + e->e_nname.bv_val, e->e_id ); + + rc = bdb_cache_delete( bdb, e, ltid, &lock ); + switch( rc ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + rs->sr_err = TXN_COMMIT( ltid, 0 ); + } + ltid = NULL; + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + opinfo.boi_oe.oe_key = NULL; + + BDB_LOG_PRINTF( bdb->bi_dbenv, NULL, "slapd Committed delete %s(%d)", + e->e_nname.bv_val, e->e_id ); + + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_delete) ": txn_%s failed: %s (%d)\n", + op->o_noop ? "abort (no-op)" : "commit", + db_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "commit failed"; + + goto return_results; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_delete) ": deleted%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + eid, op->o_req_dn.bv_val ); + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + if( num_ctrls ) rs->sr_ctrls = ctrls; + +return_results: + if ( rs->sr_err == LDAP_SUCCESS && parent_is_glue && parent_is_leaf ) { + op->o_delete_glue_parent = 1; + } + + if ( p ) + bdb_unlocked_cache_return_entry_r(&bdb->bi_cache, p); + + /* free entry */ + if( e != NULL ) { + if ( rs->sr_err == LDAP_SUCCESS ) { + /* Free the EntryInfo and the Entry */ + bdb_cache_entryinfo_lock( BEI(e) ); + bdb_cache_delete_cleanup( &bdb->bi_cache, BEI(e) ); + } else { + bdb_unlocked_cache_return_entry_w(&bdb->bi_cache, e); + } + } + + if( ltid != NULL ) { + TXN_ABORT( ltid ); + } + if ( opinfo.boi_oe.oe_key ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + } + + send_ldap_result( op, rs ); + slap_graduate_commit_csn( op ); + + if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) { + slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *preread_ctrl, op->o_tmpmemctx ); + } + + if( rs->sr_err == LDAP_SUCCESS && bdb->bi_txn_cp_kbyte ) { + TXN_CHECKPOINT( bdb->bi_dbenv, + bdb->bi_txn_cp_kbyte, bdb->bi_txn_cp_min, 0 ); + } + return rs->sr_err; +} diff --git a/servers/slapd/back-bdb/dn2entry.c b/servers/slapd/back-bdb/dn2entry.c new file mode 100644 index 0000000..2213fe9 --- /dev/null +++ b/servers/slapd/back-bdb/dn2entry.c @@ -0,0 +1,84 @@ +/* dn2entry.c - routines to deal with the dn2id / id2entry glue */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-bdb.h" + +/* + * dn2entry - look up dn in the cache/indexes and return the corresponding + * entry. If the requested DN is not found and matched is TRUE, return info + * for the closest ancestor of the DN. Otherwise e is NULL. + */ + +int +bdb_dn2entry( + Operation *op, + DB_TXN *tid, + struct berval *dn, + EntryInfo **e, + int matched, + DB_LOCK *lock ) +{ + EntryInfo *ei = NULL; + int rc, rc2; + + Debug(LDAP_DEBUG_TRACE, "bdb_dn2entry(\"%s\")\n", + dn->bv_val, 0, 0 ); + + *e = NULL; + + rc = bdb_cache_find_ndn( op, tid, dn, &ei ); + if ( rc ) { + if ( matched && rc == DB_NOTFOUND ) { + /* Set the return value, whether we have its entry + * or not. + */ + *e = ei; + if ( ei && ei->bei_id ) { + rc2 = bdb_cache_find_id( op, tid, ei->bei_id, + &ei, ID_LOCKED, lock ); + if ( rc2 ) rc = rc2; + } else if ( ei ) { + bdb_cache_entryinfo_unlock( ei ); + memset( lock, 0, sizeof( *lock )); + lock->mode = DB_LOCK_NG; + } + } else if ( ei ) { + bdb_cache_entryinfo_unlock( ei ); + } + } else { + rc = bdb_cache_find_id( op, tid, ei->bei_id, &ei, ID_LOCKED, + lock ); + if ( rc == 0 ) { + *e = ei; + } else if ( matched && rc == DB_NOTFOUND ) { + /* always return EntryInfo */ + if ( ei->bei_parent ) { + ei = ei->bei_parent; + rc2 = bdb_cache_find_id( op, tid, ei->bei_id, &ei, 0, + lock ); + if ( rc2 ) rc = rc2; + } + *e = ei; + } + } + + return rc; +} diff --git a/servers/slapd/back-bdb/dn2id.c b/servers/slapd/back-bdb/dn2id.c new file mode 100644 index 0000000..1904c9c --- /dev/null +++ b/servers/slapd/back-bdb/dn2id.c @@ -0,0 +1,1215 @@ +/* dn2id.c - routines to deal with the dn2id index */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-bdb.h" +#include "idl.h" +#include "lutil.h" + +#ifndef BDB_HIER +int +bdb_dn2id_add( + Operation *op, + DB_TXN *txn, + EntryInfo *eip, + Entry *e ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db = bdb->bi_dn2id->bdi_db; + int rc; + DBT key, data; + ID nid; + char *buf; + struct berval ptr, pdn; + + Debug( LDAP_DEBUG_TRACE, "=> bdb_dn2id_add 0x%lx: \"%s\"\n", + e->e_id, e->e_ndn, 0 ); + assert( e->e_id != NOID ); + + DBTzero( &key ); + key.size = e->e_nname.bv_len + 2; + key.ulen = key.size; + key.flags = DB_DBT_USERMEM; + buf = op->o_tmpalloc( key.size, op->o_tmpmemctx ); + key.data = buf; + buf[0] = DN_BASE_PREFIX; + ptr.bv_val = buf + 1; + ptr.bv_len = e->e_nname.bv_len; + AC_MEMCPY( ptr.bv_val, e->e_nname.bv_val, e->e_nname.bv_len ); + ptr.bv_val[ptr.bv_len] = '\0'; + + DBTzero( &data ); + data.data = &nid; + data.size = sizeof( nid ); + BDB_ID2DISK( e->e_id, &nid ); + + /* store it -- don't override */ + rc = db->put( db, txn, &key, &data, DB_NOOVERWRITE ); + if( rc != 0 ) { + char buf[ SLAP_TEXT_BUFLEN ]; + snprintf( buf, sizeof( buf ), "%s => bdb_dn2id_add dn=\"%s\" ID=0x%lx", + op->o_log_prefix, e->e_name.bv_val, e->e_id ); + Debug( LDAP_DEBUG_ANY, "%s: put failed: %s %d\n", + buf, db_strerror(rc), rc ); + goto done; + } + +#ifndef BDB_MULTIPLE_SUFFIXES + if( !be_issuffix( op->o_bd, &ptr )) +#endif + { + buf[0] = DN_SUBTREE_PREFIX; + rc = db->put( db, txn, &key, &data, DB_NOOVERWRITE ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> bdb_dn2id_add 0x%lx: subtree (%s) put failed: %d\n", + e->e_id, ptr.bv_val, rc ); + goto done; + } + +#ifdef BDB_MULTIPLE_SUFFIXES + if( !be_issuffix( op->o_bd, &ptr )) +#endif + { + dnParent( &ptr, &pdn ); + + key.size = pdn.bv_len + 2; + key.ulen = key.size; + pdn.bv_val[-1] = DN_ONE_PREFIX; + key.data = pdn.bv_val-1; + ptr = pdn; + + rc = bdb_idl_insert_key( op->o_bd, db, txn, &key, e->e_id ); + + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> bdb_dn2id_add 0x%lx: parent (%s) insert failed: %d\n", + e->e_id, ptr.bv_val, rc ); + goto done; + } + } + +#ifndef BDB_MULTIPLE_SUFFIXES + while( !be_issuffix( op->o_bd, &ptr )) +#else + for (;;) +#endif + { + ptr.bv_val[-1] = DN_SUBTREE_PREFIX; + + rc = bdb_idl_insert_key( op->o_bd, db, txn, &key, e->e_id ); + + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> bdb_dn2id_add 0x%lx: subtree (%s) insert failed: %d\n", + e->e_id, ptr.bv_val, rc ); + break; + } +#ifdef BDB_MULTIPLE_SUFFIXES + if( be_issuffix( op->o_bd, &ptr )) break; +#endif + dnParent( &ptr, &pdn ); + + key.size = pdn.bv_len + 2; + key.ulen = key.size; + key.data = pdn.bv_val - 1; + ptr = pdn; + } + } + +done: + op->o_tmpfree( buf, op->o_tmpmemctx ); + Debug( LDAP_DEBUG_TRACE, "<= bdb_dn2id_add 0x%lx: %d\n", e->e_id, rc, 0 ); + return rc; +} + +int +bdb_dn2id_delete( + Operation *op, + DB_TXN *txn, + EntryInfo *eip, + Entry *e ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db = bdb->bi_dn2id->bdi_db; + char *buf; + DBT key; + struct berval pdn, ptr; + int rc; + + Debug( LDAP_DEBUG_TRACE, "=> bdb_dn2id_delete 0x%lx: \"%s\"\n", + e->e_id, e->e_ndn, 0 ); + + DBTzero( &key ); + key.size = e->e_nname.bv_len + 2; + buf = op->o_tmpalloc( key.size, op->o_tmpmemctx ); + key.data = buf; + key.flags = DB_DBT_USERMEM; + buf[0] = DN_BASE_PREFIX; + ptr.bv_val = buf+1; + ptr.bv_len = e->e_nname.bv_len; + AC_MEMCPY( ptr.bv_val, e->e_nname.bv_val, e->e_nname.bv_len ); + ptr.bv_val[ptr.bv_len] = '\0'; + + /* delete it */ + rc = db->del( db, txn, &key, 0 ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "=> bdb_dn2id_delete 0x%lx: delete failed: %s %d\n", + e->e_id, db_strerror(rc), rc ); + goto done; + } + +#ifndef BDB_MULTIPLE_SUFFIXES + if( !be_issuffix( op->o_bd, &ptr )) +#endif + { + buf[0] = DN_SUBTREE_PREFIX; + rc = bdb_idl_delete_key( op->o_bd, db, txn, &key, e->e_id ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> bdb_dn2id_delete 0x%lx: subtree (%s) delete failed: %d\n", + e->e_id, ptr.bv_val, rc ); + goto done; + } + +#ifdef BDB_MULTIPLE_SUFFIXES + if( !be_issuffix( op->o_bd, &ptr )) +#endif + { + dnParent( &ptr, &pdn ); + + key.size = pdn.bv_len + 2; + key.ulen = key.size; + pdn.bv_val[-1] = DN_ONE_PREFIX; + key.data = pdn.bv_val - 1; + ptr = pdn; + + rc = bdb_idl_delete_key( op->o_bd, db, txn, &key, e->e_id ); + + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> bdb_dn2id_delete 0x%lx: parent (%s) delete failed: %d\n", + e->e_id, ptr.bv_val, rc ); + goto done; + } + } + +#ifndef BDB_MULTIPLE_SUFFIXES + while( !be_issuffix( op->o_bd, &ptr )) +#else + for (;;) +#endif + { + ptr.bv_val[-1] = DN_SUBTREE_PREFIX; + + rc = bdb_idl_delete_key( op->o_bd, db, txn, &key, e->e_id ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> bdb_dn2id_delete 0x%lx: subtree (%s) delete failed: %d\n", + e->e_id, ptr.bv_val, rc ); + goto done; + } +#ifdef BDB_MULTIPLE_SUFFIXES + if( be_issuffix( op->o_bd, &ptr )) break; +#endif + dnParent( &ptr, &pdn ); + + key.size = pdn.bv_len + 2; + key.ulen = key.size; + key.data = pdn.bv_val - 1; + ptr = pdn; + } + } + +done: + op->o_tmpfree( buf, op->o_tmpmemctx ); + Debug( LDAP_DEBUG_TRACE, "<= bdb_dn2id_delete 0x%lx: %d\n", e->e_id, rc, 0 ); + return rc; +} + +int +bdb_dn2id( + Operation *op, + struct berval *dn, + EntryInfo *ei, + DB_TXN *txn, + DBC **cursor ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db = bdb->bi_dn2id->bdi_db; + int rc; + DBT key, data; + ID nid; + + Debug( LDAP_DEBUG_TRACE, "=> bdb_dn2id(\"%s\")\n", dn->bv_val, 0, 0 ); + + DBTzero( &key ); + key.size = dn->bv_len + 2; + key.data = op->o_tmpalloc( key.size, op->o_tmpmemctx ); + ((char *)key.data)[0] = DN_BASE_PREFIX; + AC_MEMCPY( &((char *)key.data)[1], dn->bv_val, key.size - 1 ); + + /* store the ID */ + DBTzero( &data ); + data.data = &nid; + data.ulen = sizeof(ID); + data.flags = DB_DBT_USERMEM; + + rc = db->cursor( db, txn, cursor, bdb->bi_db_opflags ); + + /* fetch it */ + if ( !rc ) + rc = (*cursor)->c_get( *cursor, &key, &data, DB_SET ); + + if( rc != 0 ) { + Debug( LDAP_DEBUG_TRACE, "<= bdb_dn2id: get failed: %s (%d)\n", + db_strerror( rc ), rc, 0 ); + } else { + BDB_DISK2ID( &nid, &ei->bei_id ); + Debug( LDAP_DEBUG_TRACE, "<= bdb_dn2id: got id=0x%lx\n", + ei->bei_id, 0, 0 ); + } + op->o_tmpfree( key.data, op->o_tmpmemctx ); + return rc; +} + +int +bdb_dn2id_children( + Operation *op, + DB_TXN *txn, + Entry *e ) +{ + DBT key, data; + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db = bdb->bi_dn2id->bdi_db; + ID id; + int rc; + + Debug( LDAP_DEBUG_TRACE, "=> bdb_dn2id_children(\"%s\")\n", + e->e_nname.bv_val, 0, 0 ); + DBTzero( &key ); + key.size = e->e_nname.bv_len + 2; + key.data = op->o_tmpalloc( key.size, op->o_tmpmemctx ); + ((char *)key.data)[0] = DN_ONE_PREFIX; + AC_MEMCPY( &((char *)key.data)[1], e->e_nname.bv_val, key.size - 1 ); + + if ( bdb->bi_idl_cache_size ) { + rc = bdb_idl_cache_get( bdb, db, &key, NULL ); + if ( rc != LDAP_NO_SUCH_OBJECT ) { + op->o_tmpfree( key.data, op->o_tmpmemctx ); + return rc; + } + } + /* we actually could do a empty get... */ + DBTzero( &data ); + data.data = &id; + data.ulen = sizeof(id); + data.flags = DB_DBT_USERMEM; + data.doff = 0; + data.dlen = sizeof(id); + + rc = db->get( db, txn, &key, &data, bdb->bi_db_opflags ); + op->o_tmpfree( key.data, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, "<= bdb_dn2id_children(\"%s\"): %s (%d)\n", + e->e_nname.bv_val, + rc == 0 ? "" : ( rc == DB_NOTFOUND ? "no " : + db_strerror(rc) ), rc ); + + return rc; +} + +int +bdb_dn2idl( + Operation *op, + DB_TXN *txn, + struct berval *ndn, + EntryInfo *ei, + ID *ids, + ID *stack ) +{ + int rc; + DBT key; + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db = bdb->bi_dn2id->bdi_db; + int prefix = ( op->ors_scope == LDAP_SCOPE_ONELEVEL ) + ? DN_ONE_PREFIX : DN_SUBTREE_PREFIX; + + Debug( LDAP_DEBUG_TRACE, "=> bdb_dn2idl(\"%s\")\n", + ndn->bv_val, 0, 0 ); + +#ifndef BDB_MULTIPLE_SUFFIXES + if ( prefix == DN_SUBTREE_PREFIX + && ( ei->bei_id == 0 || + ( ei->bei_parent->bei_id == 0 && op->o_bd->be_suffix[0].bv_len ))) { + BDB_IDL_ALL(bdb, ids); + return 0; + } +#endif + + DBTzero( &key ); + key.size = ndn->bv_len + 2; + key.ulen = key.size; + key.flags = DB_DBT_USERMEM; + key.data = op->o_tmpalloc( key.size, op->o_tmpmemctx ); + ((char *)key.data)[0] = prefix; + AC_MEMCPY( &((char *)key.data)[1], ndn->bv_val, key.size - 1 ); + + BDB_IDL_ZERO( ids ); + rc = bdb_idl_fetch_key( op->o_bd, db, txn, &key, ids, NULL, 0 ); + + if( rc != 0 ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_dn2idl: get failed: %s (%d)\n", + db_strerror( rc ), rc, 0 ); + + } else { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_dn2idl: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) BDB_IDL_FIRST( ids ), (long) BDB_IDL_LAST( ids ) ); + } + + op->o_tmpfree( key.data, op->o_tmpmemctx ); + return rc; +} + +#else /* BDB_HIER */ +/* Management routines for a hierarchically structured database. + * + * Instead of a ldbm-style dn2id database, we use a hierarchical one. Each + * entry in this database is a struct diskNode, keyed by entryID and with + * the data containing the RDN and entryID of the node's children. We use + * a B-Tree with sorted duplicates to store all the children of a node under + * the same key. Also, the first item under the key contains the entry's own + * rdn and the ID of the node's parent, to allow bottom-up tree traversal as + * well as top-down. To keep this info first in the list, the high bit of all + * subsequent nrdnlen's is always set. This means we can only accomodate + * RDNs up to length 32767, but that's fine since full DNs are already + * restricted to 8192. + * + * The diskNode is a variable length structure. This definition is not + * directly usable for in-memory manipulation. + */ +typedef struct diskNode { + unsigned char nrdnlen[2]; + char nrdn[1]; + char rdn[1]; /* variable placement */ + unsigned char entryID[sizeof(ID)]; /* variable placement */ +} diskNode; + +/* Sort function for the sorted duplicate data items of a dn2id key. + * Sorts based on normalized RDN, in length order. + */ +int +hdb_dup_compare( + DB *db, + const DBT *usrkey, + const DBT *curkey +) +{ + diskNode *un, *cn; + int rc; + + un = (diskNode *)usrkey->data; + cn = (diskNode *)curkey->data; + + /* data is not aligned, cannot compare directly */ + rc = un->nrdnlen[0] - cn->nrdnlen[0]; + if ( rc ) return rc; + rc = un->nrdnlen[1] - cn->nrdnlen[1]; + if ( rc ) return rc; + + return strcmp( un->nrdn, cn->nrdn ); +} + +/* This function constructs a full DN for a given entry. + */ +int hdb_fix_dn( + Entry *e, + int checkit ) +{ + EntryInfo *ei; + int rlen = 0, nrlen = 0; + char *ptr, *nptr; + int max = 0; + + if ( !e->e_id ) + return 0; + + /* count length of all DN components */ + for ( ei = BEI(e); ei && ei->bei_id; ei=ei->bei_parent ) { + rlen += ei->bei_rdn.bv_len + 1; + nrlen += ei->bei_nrdn.bv_len + 1; + if (ei->bei_modrdns > max) max = ei->bei_modrdns; + } + + /* See if the entry DN was invalidated by a subtree rename */ + if ( checkit ) { + if ( BEI(e)->bei_modrdns >= max ) { + return 0; + } + /* We found a mismatch, tell the caller to lock it */ + if ( checkit == 1 ) { + return 1; + } + /* checkit == 2. do the fix. */ + free( e->e_name.bv_val ); + free( e->e_nname.bv_val ); + } + + e->e_name.bv_len = rlen - 1; + e->e_nname.bv_len = nrlen - 1; + e->e_name.bv_val = ch_malloc(rlen); + e->e_nname.bv_val = ch_malloc(nrlen); + ptr = e->e_name.bv_val; + nptr = e->e_nname.bv_val; + for ( ei = BEI(e); ei && ei->bei_id; ei=ei->bei_parent ) { + ptr = lutil_strcopy(ptr, ei->bei_rdn.bv_val); + nptr = lutil_strcopy(nptr, ei->bei_nrdn.bv_val); + if ( ei->bei_parent ) { + *ptr++ = ','; + *nptr++ = ','; + } + } + BEI(e)->bei_modrdns = max; + if ( ptr > e->e_name.bv_val ) ptr[-1] = '\0'; + if ( nptr > e->e_nname.bv_val ) nptr[-1] = '\0'; + + return 0; +} + +/* We add two elements to the DN2ID database - a data item under the parent's + * entryID containing the child's RDN and entryID, and an item under the + * child's entryID containing the parent's entryID. + */ +int +hdb_dn2id_add( + Operation *op, + DB_TXN *txn, + EntryInfo *eip, + Entry *e ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db = bdb->bi_dn2id->bdi_db; + DBT key, data; + ID nid; + int rc, rlen, nrlen; + diskNode *d; + char *ptr; + + Debug( LDAP_DEBUG_TRACE, "=> hdb_dn2id_add 0x%lx: \"%s\"\n", + e->e_id, e->e_ndn, 0 ); + + nrlen = dn_rdnlen( op->o_bd, &e->e_nname ); + if (nrlen) { + rlen = dn_rdnlen( op->o_bd, &e->e_name ); + } else { + nrlen = e->e_nname.bv_len; + rlen = e->e_name.bv_len; + } + + d = op->o_tmpalloc(sizeof(diskNode) + rlen + nrlen, op->o_tmpmemctx); + d->nrdnlen[1] = nrlen & 0xff; + d->nrdnlen[0] = (nrlen >> 8) | 0x80; + ptr = lutil_strncopy( d->nrdn, e->e_nname.bv_val, nrlen ); + *ptr++ = '\0'; + ptr = lutil_strncopy( ptr, e->e_name.bv_val, rlen ); + *ptr++ = '\0'; + BDB_ID2DISK( e->e_id, ptr ); + + DBTzero(&key); + DBTzero(&data); + key.size = sizeof(ID); + key.flags = DB_DBT_USERMEM; + BDB_ID2DISK( eip->bei_id, &nid ); + + key.data = &nid; + + /* Need to make dummy root node once. Subsequent attempts + * will fail harmlessly. + */ + if ( eip->bei_id == 0 ) { + diskNode dummy = {{0, 0}, "", "", ""}; + data.data = &dummy; + data.size = sizeof(diskNode); + data.flags = DB_DBT_USERMEM; + + db->put( db, txn, &key, &data, DB_NODUPDATA ); + } + + data.data = d; + data.size = sizeof(diskNode) + rlen + nrlen; + data.flags = DB_DBT_USERMEM; + + rc = db->put( db, txn, &key, &data, DB_NODUPDATA ); + + if (rc == 0) { + BDB_ID2DISK( e->e_id, &nid ); + BDB_ID2DISK( eip->bei_id, ptr ); + d->nrdnlen[0] ^= 0x80; + + rc = db->put( db, txn, &key, &data, DB_NODUPDATA ); + } + + /* Update all parents' IDL cache entries */ + if ( rc == 0 && bdb->bi_idl_cache_size ) { + ID tmp[2]; + char *ptr = ((char *)&tmp[1])-1; + key.data = ptr; + key.size = sizeof(ID)+1; + tmp[1] = eip->bei_id; + *ptr = DN_ONE_PREFIX; + bdb_idl_cache_add_id( bdb, db, &key, e->e_id ); + if ( eip->bei_parent ) { + *ptr = DN_SUBTREE_PREFIX; + for (; eip && eip->bei_parent->bei_id; eip = eip->bei_parent) { + tmp[1] = eip->bei_id; + bdb_idl_cache_add_id( bdb, db, &key, e->e_id ); + } + /* Handle DB with empty suffix */ + if ( !op->o_bd->be_suffix[0].bv_len && eip ) { + tmp[1] = eip->bei_id; + bdb_idl_cache_add_id( bdb, db, &key, e->e_id ); + } + } + } + + op->o_tmpfree( d, op->o_tmpmemctx ); + Debug( LDAP_DEBUG_TRACE, "<= hdb_dn2id_add 0x%lx: %d\n", e->e_id, rc, 0 ); + + return rc; +} + +int +hdb_dn2id_delete( + Operation *op, + DB_TXN *txn, + EntryInfo *eip, + Entry *e ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db = bdb->bi_dn2id->bdi_db; + DBT key, data; + DBC *cursor; + diskNode *d; + int rc; + ID nid; + unsigned char dlen[2]; + + Debug( LDAP_DEBUG_TRACE, "=> hdb_dn2id_delete 0x%lx: \"%s\"\n", + e->e_id, e->e_ndn, 0 ); + + DBTzero(&key); + key.size = sizeof(ID); + key.ulen = key.size; + key.flags = DB_DBT_USERMEM; + BDB_ID2DISK( eip->bei_id, &nid ); + + DBTzero(&data); + data.size = sizeof(diskNode) + BEI(e)->bei_nrdn.bv_len - sizeof(ID) - 1; + data.ulen = data.size; + data.dlen = data.size; + data.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL; + + key.data = &nid; + + d = op->o_tmpalloc( data.size, op->o_tmpmemctx ); + d->nrdnlen[1] = BEI(e)->bei_nrdn.bv_len & 0xff; + d->nrdnlen[0] = (BEI(e)->bei_nrdn.bv_len >> 8) | 0x80; + dlen[0] = d->nrdnlen[0]; + dlen[1] = d->nrdnlen[1]; + memcpy( d->nrdn, BEI(e)->bei_nrdn.bv_val, BEI(e)->bei_nrdn.bv_len+1 ); + data.data = d; + + rc = db->cursor( db, txn, &cursor, bdb->bi_db_opflags ); + if ( rc ) goto func_leave; + + /* Delete our ID from the parent's list */ + rc = cursor->c_get( cursor, &key, &data, DB_GET_BOTH_RANGE ); + if ( rc == 0 ) { + if ( dlen[1] == d->nrdnlen[1] && dlen[0] == d->nrdnlen[0] && + !strcmp( d->nrdn, BEI(e)->bei_nrdn.bv_val )) + rc = cursor->c_del( cursor, 0 ); + else + rc = DB_NOTFOUND; + } + + /* Delete our ID from the tree. With sorted duplicates, this + * will leave any child nodes still hanging around. This is OK + * for modrdn, which will add our info back in later. + */ + if ( rc == 0 ) { + BDB_ID2DISK( e->e_id, &nid ); + rc = cursor->c_get( cursor, &key, &data, DB_SET ); + if ( rc == 0 ) + rc = cursor->c_del( cursor, 0 ); + } + + cursor->c_close( cursor ); +func_leave: + op->o_tmpfree( d, op->o_tmpmemctx ); + + /* Delete IDL cache entries */ + if ( rc == 0 && bdb->bi_idl_cache_size ) { + ID tmp[2]; + char *ptr = ((char *)&tmp[1])-1; + key.data = ptr; + key.size = sizeof(ID)+1; + tmp[1] = eip->bei_id; + *ptr = DN_ONE_PREFIX; + bdb_idl_cache_del_id( bdb, db, &key, e->e_id ); + if ( eip ->bei_parent ) { + *ptr = DN_SUBTREE_PREFIX; + for (; eip && eip->bei_parent->bei_id; eip = eip->bei_parent) { + tmp[1] = eip->bei_id; + bdb_idl_cache_del_id( bdb, db, &key, e->e_id ); + } + /* Handle DB with empty suffix */ + if ( !op->o_bd->be_suffix[0].bv_len && eip ) { + tmp[1] = eip->bei_id; + bdb_idl_cache_del_id( bdb, db, &key, e->e_id ); + } + } + } + Debug( LDAP_DEBUG_TRACE, "<= hdb_dn2id_delete 0x%lx: %d\n", e->e_id, rc, 0 ); + return rc; +} + + +int +hdb_dn2id( + Operation *op, + struct berval *in, + EntryInfo *ei, + DB_TXN *txn, + DBC **cursor ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db = bdb->bi_dn2id->bdi_db; + DBT key, data; + int rc = 0, nrlen; + diskNode *d; + char *ptr; + unsigned char dlen[2]; + ID idp, parentID; + + Debug( LDAP_DEBUG_TRACE, "=> hdb_dn2id(\"%s\")\n", in->bv_val, 0, 0 ); + + nrlen = dn_rdnlen( op->o_bd, in ); + if (!nrlen) nrlen = in->bv_len; + + DBTzero(&key); + key.size = sizeof(ID); + key.data = &idp; + key.ulen = sizeof(ID); + key.flags = DB_DBT_USERMEM; + parentID = ( ei->bei_parent != NULL ) ? ei->bei_parent->bei_id : 0; + BDB_ID2DISK( parentID, &idp ); + + DBTzero(&data); + data.size = sizeof(diskNode) + nrlen - sizeof(ID) - 1; + data.ulen = data.size * 3; + data.dlen = data.ulen; + data.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL; + + rc = db->cursor( db, txn, cursor, bdb->bi_db_opflags ); + if ( rc ) return rc; + + d = op->o_tmpalloc( data.size * 3, op->o_tmpmemctx ); + d->nrdnlen[1] = nrlen & 0xff; + d->nrdnlen[0] = (nrlen >> 8) | 0x80; + dlen[0] = d->nrdnlen[0]; + dlen[1] = d->nrdnlen[1]; + ptr = lutil_strncopy( d->nrdn, in->bv_val, nrlen ); + *ptr = '\0'; + data.data = d; + + rc = (*cursor)->c_get( *cursor, &key, &data, DB_GET_BOTH_RANGE ); + if ( rc == 0 && (dlen[1] != d->nrdnlen[1] || dlen[0] != d->nrdnlen[0] || + strncmp( d->nrdn, in->bv_val, nrlen ))) { + rc = DB_NOTFOUND; + } + if ( rc == 0 ) { + ptr = (char *) data.data + data.size - sizeof(ID); + BDB_DISK2ID( ptr, &ei->bei_id ); + ei->bei_rdn.bv_len = data.size - sizeof(diskNode) - nrlen; + ptr = d->nrdn + nrlen + 1; + ber_str2bv( ptr, ei->bei_rdn.bv_len, 1, &ei->bei_rdn ); + if ( ei->bei_parent != NULL && !ei->bei_parent->bei_dkids ) { + db_recno_t dkids; + /* How many children does the parent have? */ + /* FIXME: do we need to lock the parent + * entryinfo? Seems safe... + */ + (*cursor)->c_count( *cursor, &dkids, 0 ); + ei->bei_parent->bei_dkids = dkids; + } + } + + op->o_tmpfree( d, op->o_tmpmemctx ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_TRACE, "<= hdb_dn2id: get failed: %s (%d)\n", + db_strerror( rc ), rc, 0 ); + } else { + Debug( LDAP_DEBUG_TRACE, "<= hdb_dn2id: got id=0x%lx\n", + ei->bei_id, 0, 0 ); + } + + return rc; +} + +int +hdb_dn2id_parent( + Operation *op, + DB_TXN *txn, + EntryInfo *ei, + ID *idp ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db = bdb->bi_dn2id->bdi_db; + DBT key, data; + DBC *cursor; + int rc = 0; + diskNode *d; + char *ptr; + ID nid; + + DBTzero(&key); + key.size = sizeof(ID); + key.data = &nid; + key.ulen = sizeof(ID); + key.flags = DB_DBT_USERMEM; + BDB_ID2DISK( ei->bei_id, &nid ); + + DBTzero(&data); + data.flags = DB_DBT_USERMEM; + + rc = db->cursor( db, txn, &cursor, bdb->bi_db_opflags ); + if ( rc ) return rc; + + data.ulen = sizeof(diskNode) + (SLAP_LDAPDN_MAXLEN * 2); + d = op->o_tmpalloc( data.ulen, op->o_tmpmemctx ); + data.data = d; + + rc = cursor->c_get( cursor, &key, &data, DB_SET ); + if ( rc == 0 ) { + if (d->nrdnlen[0] & 0x80) { + rc = LDAP_OTHER; + } else { + db_recno_t dkids; + ptr = (char *) data.data + data.size - sizeof(ID); + BDB_DISK2ID( ptr, idp ); + ei->bei_nrdn.bv_len = (d->nrdnlen[0] << 8) | d->nrdnlen[1]; + ber_str2bv( d->nrdn, ei->bei_nrdn.bv_len, 1, &ei->bei_nrdn ); + ei->bei_rdn.bv_len = data.size - sizeof(diskNode) - + ei->bei_nrdn.bv_len; + ptr = d->nrdn + ei->bei_nrdn.bv_len + 1; + ber_str2bv( ptr, ei->bei_rdn.bv_len, 1, &ei->bei_rdn ); + /* How many children does this node have? */ + cursor->c_count( cursor, &dkids, 0 ); + ei->bei_dkids = dkids; + } + } + cursor->c_close( cursor ); + op->o_tmpfree( d, op->o_tmpmemctx ); + return rc; +} + +int +hdb_dn2id_children( + Operation *op, + DB_TXN *txn, + Entry *e ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db = bdb->bi_dn2id->bdi_db; + DBT key, data; + DBC *cursor; + int rc; + ID id; + diskNode d; + + DBTzero(&key); + key.size = sizeof(ID); + key.data = &e->e_id; + key.flags = DB_DBT_USERMEM; + BDB_ID2DISK( e->e_id, &id ); + + /* IDL cache is in host byte order */ + if ( bdb->bi_idl_cache_size ) { + rc = bdb_idl_cache_get( bdb, db, &key, NULL ); + if ( rc != LDAP_NO_SUCH_OBJECT ) { + return rc; + } + } + + key.data = &id; + DBTzero(&data); + data.data = &d; + data.ulen = sizeof(d); + data.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL; + data.dlen = sizeof(d); + + rc = db->cursor( db, txn, &cursor, bdb->bi_db_opflags ); + if ( rc ) return rc; + + rc = cursor->c_get( cursor, &key, &data, DB_SET ); + if ( rc == 0 ) { + db_recno_t dkids; + rc = cursor->c_count( cursor, &dkids, 0 ); + if ( rc == 0 ) { + BEI(e)->bei_dkids = dkids; + if ( dkids < 2 ) rc = DB_NOTFOUND; + } + } + cursor->c_close( cursor ); + return rc; +} + +/* bdb_dn2idl: + * We can't just use bdb_idl_fetch_key because + * 1 - our data items are longer than just an entry ID + * 2 - our data items are sorted alphabetically by nrdn, not by ID. + * + * We descend the tree recursively, so we define this cookie + * to hold our necessary state information. The bdb_dn2idl_internal + * function uses this cookie when calling itself. + */ + +struct dn2id_cookie { + struct bdb_info *bdb; + Operation *op; + DB_TXN *txn; + EntryInfo *ei; + ID *ids; + ID *tmp; + ID *buf; + DB *db; + DBC *dbc; + DBT key; + DBT data; + ID dbuf; + ID id; + ID nid; + int rc; + int depth; + char need_sort; + char prefix; +}; + +static int +apply_func( + void *data, + void *arg ) +{ + EntryInfo *ei = data; + ID *idl = arg; + + bdb_idl_append_one( idl, ei->bei_id ); + return 0; +} + +static int +hdb_dn2idl_internal( + struct dn2id_cookie *cx +) +{ + BDB_IDL_ZERO( cx->tmp ); + + if ( cx->bdb->bi_idl_cache_size ) { + char *ptr = ((char *)&cx->id)-1; + + cx->key.data = ptr; + cx->key.size = sizeof(ID)+1; + if ( cx->prefix == DN_SUBTREE_PREFIX ) { + ID *ids = cx->depth ? cx->tmp : cx->ids; + *ptr = cx->prefix; + cx->rc = bdb_idl_cache_get(cx->bdb, cx->db, &cx->key, ids); + if ( cx->rc == LDAP_SUCCESS ) { + if ( cx->depth ) { + bdb_idl_delete( cx->tmp, cx->id ); /* ITS#6983, drop our own ID */ + bdb_idl_append( cx->ids, cx->tmp ); + cx->need_sort = 1; + } + return cx->rc; + } + } + *ptr = DN_ONE_PREFIX; + cx->rc = bdb_idl_cache_get(cx->bdb, cx->db, &cx->key, cx->tmp); + if ( cx->rc == LDAP_SUCCESS ) { + goto gotit; + } + if ( cx->rc == DB_NOTFOUND ) { + return cx->rc; + } + } + + bdb_cache_entryinfo_lock( cx->ei ); + + /* If number of kids in the cache differs from on-disk, load + * up all the kids from the database + */ + if ( cx->ei->bei_ckids+1 != cx->ei->bei_dkids ) { + EntryInfo ei; + db_recno_t dkids = cx->ei->bei_dkids; + ei.bei_parent = cx->ei; + + /* Only one thread should load the cache */ + while ( cx->ei->bei_state & CACHE_ENTRY_ONELEVEL ) { + bdb_cache_entryinfo_unlock( cx->ei ); + ldap_pvt_thread_yield(); + bdb_cache_entryinfo_lock( cx->ei ); + if ( cx->ei->bei_ckids+1 == cx->ei->bei_dkids ) { + goto synced; + } + } + + cx->ei->bei_state |= CACHE_ENTRY_ONELEVEL; + + bdb_cache_entryinfo_unlock( cx->ei ); + + cx->rc = cx->db->cursor( cx->db, NULL, &cx->dbc, + cx->bdb->bi_db_opflags ); + if ( cx->rc ) + goto done_one; + + cx->data.data = &cx->dbuf; + cx->data.ulen = sizeof(ID); + cx->data.dlen = sizeof(ID); + cx->data.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL; + + /* The first item holds the parent ID. Ignore it. */ + cx->key.data = &cx->nid; + cx->key.size = sizeof(ID); + cx->rc = cx->dbc->c_get( cx->dbc, &cx->key, &cx->data, DB_SET ); + if ( cx->rc ) { + cx->dbc->c_close( cx->dbc ); + goto done_one; + } + + /* If the on-disk count is zero we've never checked it. + * Count it now. + */ + if ( !dkids ) { + cx->dbc->c_count( cx->dbc, &dkids, 0 ); + cx->ei->bei_dkids = dkids; + } + + cx->data.data = cx->buf; + cx->data.ulen = BDB_IDL_UM_SIZE * sizeof(ID); + cx->data.flags = DB_DBT_USERMEM; + + if ( dkids > 1 ) { + /* Fetch the rest of the IDs in a loop... */ + while ( (cx->rc = cx->dbc->c_get( cx->dbc, &cx->key, &cx->data, + DB_MULTIPLE | DB_NEXT_DUP )) == 0 ) { + u_int8_t *j; + size_t len; + void *ptr; + DB_MULTIPLE_INIT( ptr, &cx->data ); + while (ptr) { + DB_MULTIPLE_NEXT( ptr, &cx->data, j, len ); + if (j) { + EntryInfo *ei2; + diskNode *d = (diskNode *)j; + short nrlen; + + BDB_DISK2ID( j + len - sizeof(ID), &ei.bei_id ); + nrlen = ((d->nrdnlen[0] ^ 0x80) << 8) | d->nrdnlen[1]; + ei.bei_nrdn.bv_len = nrlen; + /* nrdn/rdn are set in-place. + * hdb_cache_load will copy them as needed + */ + ei.bei_nrdn.bv_val = d->nrdn; + ei.bei_rdn.bv_len = len - sizeof(diskNode) + - ei.bei_nrdn.bv_len; + ei.bei_rdn.bv_val = d->nrdn + ei.bei_nrdn.bv_len + 1; + bdb_idl_append_one( cx->tmp, ei.bei_id ); + hdb_cache_load( cx->bdb, &ei, &ei2 ); + } + } + } + } + + cx->rc = cx->dbc->c_close( cx->dbc ); +done_one: + bdb_cache_entryinfo_lock( cx->ei ); + cx->ei->bei_state &= ~CACHE_ENTRY_ONELEVEL; + bdb_cache_entryinfo_unlock( cx->ei ); + if ( cx->rc ) + return cx->rc; + + } else { + /* The in-memory cache is in sync with the on-disk data. + * do we have any kids? + */ +synced: + cx->rc = 0; + if ( cx->ei->bei_ckids > 0 ) { + /* Walk the kids tree; order is irrelevant since bdb_idl_sort + * will sort it later. + */ + avl_apply( cx->ei->bei_kids, apply_func, + cx->tmp, -1, AVL_POSTORDER ); + } + bdb_cache_entryinfo_unlock( cx->ei ); + } + + if ( !BDB_IDL_IS_RANGE( cx->tmp ) && cx->tmp[0] > 3 ) + bdb_idl_sort( cx->tmp, cx->buf ); + if ( cx->bdb->bi_idl_cache_max_size && !BDB_IDL_IS_ZERO( cx->tmp )) { + char *ptr = ((char *)&cx->id)-1; + cx->key.data = ptr; + cx->key.size = sizeof(ID)+1; + *ptr = DN_ONE_PREFIX; + bdb_idl_cache_put( cx->bdb, cx->db, &cx->key, cx->tmp, cx->rc ); + } + +gotit: + if ( !BDB_IDL_IS_ZERO( cx->tmp )) { + if ( cx->prefix == DN_SUBTREE_PREFIX ) { + bdb_idl_append( cx->ids, cx->tmp ); + cx->need_sort = 1; + if ( !(cx->ei->bei_state & CACHE_ENTRY_NO_GRANDKIDS)) { + ID *save, idcurs; + EntryInfo *ei = cx->ei; + int nokids = 1; + save = cx->op->o_tmpalloc( BDB_IDL_SIZEOF( cx->tmp ), + cx->op->o_tmpmemctx ); + BDB_IDL_CPY( save, cx->tmp ); + + idcurs = 0; + cx->depth++; + for ( cx->id = bdb_idl_first( save, &idcurs ); + cx->id != NOID; + cx->id = bdb_idl_next( save, &idcurs )) { + EntryInfo *ei2; + cx->ei = NULL; + if ( bdb_cache_find_id( cx->op, cx->txn, cx->id, &cx->ei, + ID_NOENTRY, NULL )) + continue; + if ( cx->ei ) { + ei2 = cx->ei; + if ( !( ei2->bei_state & CACHE_ENTRY_NO_KIDS )) { + BDB_ID2DISK( cx->id, &cx->nid ); + hdb_dn2idl_internal( cx ); + if ( !BDB_IDL_IS_ZERO( cx->tmp )) + nokids = 0; + } + bdb_cache_entryinfo_lock( ei2 ); + ei2->bei_finders--; + bdb_cache_entryinfo_unlock( ei2 ); + } + } + cx->depth--; + cx->op->o_tmpfree( save, cx->op->o_tmpmemctx ); + if ( nokids ) { + bdb_cache_entryinfo_lock( ei ); + ei->bei_state |= CACHE_ENTRY_NO_GRANDKIDS; + bdb_cache_entryinfo_unlock( ei ); + } + } + /* Make sure caller knows it had kids! */ + cx->tmp[0]=1; + + cx->rc = 0; + } else { + BDB_IDL_CPY( cx->ids, cx->tmp ); + } + } + return cx->rc; +} + +int +hdb_dn2idl( + Operation *op, + DB_TXN *txn, + struct berval *ndn, + EntryInfo *ei, + ID *ids, + ID *stack ) +{ + struct bdb_info *bdb = (struct bdb_info *)op->o_bd->be_private; + struct dn2id_cookie cx; + + Debug( LDAP_DEBUG_TRACE, "=> hdb_dn2idl(\"%s\")\n", + ndn->bv_val, 0, 0 ); + +#ifndef BDB_MULTIPLE_SUFFIXES + if ( op->ors_scope != LDAP_SCOPE_ONELEVEL && + ( ei->bei_id == 0 || + ( ei->bei_parent->bei_id == 0 && op->o_bd->be_suffix[0].bv_len ))) + { + BDB_IDL_ALL( bdb, ids ); + return 0; + } +#endif + + cx.id = ei->bei_id; + BDB_ID2DISK( cx.id, &cx.nid ); + cx.ei = ei; + cx.bdb = bdb; + cx.db = cx.bdb->bi_dn2id->bdi_db; + cx.prefix = (op->ors_scope == LDAP_SCOPE_ONELEVEL) ? + DN_ONE_PREFIX : DN_SUBTREE_PREFIX; + cx.ids = ids; + cx.tmp = stack; + cx.buf = stack + BDB_IDL_UM_SIZE; + cx.op = op; + cx.txn = txn; + cx.need_sort = 0; + cx.depth = 0; + + if ( cx.prefix == DN_SUBTREE_PREFIX ) { + ids[0] = 1; + ids[1] = cx.id; + } else { + BDB_IDL_ZERO( ids ); + } + if ( cx.ei->bei_state & CACHE_ENTRY_NO_KIDS ) + return LDAP_SUCCESS; + + DBTzero(&cx.key); + cx.key.ulen = sizeof(ID); + cx.key.size = sizeof(ID); + cx.key.flags = DB_DBT_USERMEM; + + DBTzero(&cx.data); + + hdb_dn2idl_internal(&cx); + if ( cx.need_sort ) { + char *ptr = ((char *)&cx.id)-1; + if ( !BDB_IDL_IS_RANGE( cx.ids ) && cx.ids[0] > 3 ) + bdb_idl_sort( cx.ids, cx.tmp ); + cx.key.data = ptr; + cx.key.size = sizeof(ID)+1; + *ptr = cx.prefix; + cx.id = ei->bei_id; + if ( cx.bdb->bi_idl_cache_max_size ) + bdb_idl_cache_put( cx.bdb, cx.db, &cx.key, cx.ids, cx.rc ); + } + + if ( cx.rc == DB_NOTFOUND ) + cx.rc = LDAP_SUCCESS; + + return cx.rc; +} +#endif /* BDB_HIER */ diff --git a/servers/slapd/back-bdb/error.c b/servers/slapd/back-bdb/error.c new file mode 100644 index 0000000..788ef5c --- /dev/null +++ b/servers/slapd/back-bdb/error.c @@ -0,0 +1,62 @@ +/* error.c - BDB errcall routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-bdb.h" + +#if DB_VERSION_FULL < 0x04030000 +void bdb_errcall( const char *pfx, char * msg ) +#else +void bdb_errcall( const DB_ENV *env, const char *pfx, const char * msg ) +#endif +{ +#ifdef HAVE_EBCDIC + if ( msg[0] > 0x7f ) + __etoa( msg ); +#endif + Debug( LDAP_DEBUG_ANY, "bdb(%s): %s\n", pfx, msg, 0 ); +} + +#if DB_VERSION_FULL >= 0x04030000 +void bdb_msgcall( const DB_ENV *env, const char *msg ) +{ +#ifdef HAVE_EBCDIC + if ( msg[0] > 0x7f ) + __etoa( msg ); +#endif + Debug( LDAP_DEBUG_TRACE, "bdb: %s\n", msg, 0, 0 ); +} +#endif + +#ifdef HAVE_EBCDIC + +#undef db_strerror + +/* Not re-entrant! */ +char *ebcdic_dberror( int rc ) +{ + static char msg[1024]; + + strcpy( msg, db_strerror( rc ) ); + __etoa( msg ); + return msg; +} +#endif diff --git a/servers/slapd/back-bdb/extended.c b/servers/slapd/back-bdb/extended.c new file mode 100644 index 0000000..018608e --- /dev/null +++ b/servers/slapd/back-bdb/extended.c @@ -0,0 +1,54 @@ +/* extended.c - bdb backend extended routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-bdb.h" +#include "lber_pvt.h" + +static struct exop { + struct berval *oid; + BI_op_extended *extended; +} exop_table[] = { + { NULL, NULL } +}; + +int +bdb_extended( Operation *op, SlapReply *rs ) +/* struct berval *reqoid, + struct berval *reqdata, + char **rspoid, + struct berval **rspdata, + LDAPControl *** rspctrls, + const char** text, + BerVarray *refs +) */ +{ + int i; + + for( i=0; exop_table[i].extended != NULL; i++ ) { + if( ber_bvcmp( exop_table[i].oid, &op->oq_extended.rs_reqoid ) == 0 ) { + return (exop_table[i].extended)( op, rs ); + } + } + + rs->sr_text = "not supported within naming context"; + return rs->sr_err = LDAP_UNWILLING_TO_PERFORM; +} + diff --git a/servers/slapd/back-bdb/filterindex.c b/servers/slapd/back-bdb/filterindex.c new file mode 100644 index 0000000..7f6c64f --- /dev/null +++ b/servers/slapd/back-bdb/filterindex.c @@ -0,0 +1,1183 @@ +/* filterindex.c - generate the list of candidate entries from a filter */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-bdb.h" +#include "idl.h" +#ifdef LDAP_COMP_MATCH +#include <component.h> +#endif + +static int presence_candidates( + Operation *op, + DB_TXN *rtxn, + AttributeDescription *desc, + ID *ids ); + +static int equality_candidates( + Operation *op, + DB_TXN *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp ); +static int inequality_candidates( + Operation *op, + DB_TXN *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp, + int gtorlt ); +static int approx_candidates( + Operation *op, + DB_TXN *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp ); +static int substring_candidates( + Operation *op, + DB_TXN *rtxn, + SubstringsAssertion *sub, + ID *ids, + ID *tmp ); + +static int list_candidates( + Operation *op, + DB_TXN *rtxn, + Filter *flist, + int ftype, + ID *ids, + ID *tmp, + ID *stack ); + +static int +ext_candidates( + Operation *op, + DB_TXN *rtxn, + MatchingRuleAssertion *mra, + ID *ids, + ID *tmp, + ID *stack); + +#ifdef LDAP_COMP_MATCH +static int +comp_candidates ( + Operation *op, + DB_TXN *rtxn, + MatchingRuleAssertion *mra, + ComponentFilter *f, + ID *ids, + ID *tmp, + ID *stack); + +static int +ava_comp_candidates ( + Operation *op, + DB_TXN *rtxn, + AttributeAssertion *ava, + AttributeAliasing *aa, + ID *ids, + ID *tmp, + ID *stack); +#endif + +int +bdb_filter_candidates( + Operation *op, + DB_TXN *rtxn, + Filter *f, + ID *ids, + ID *tmp, + ID *stack ) +{ + int rc = 0; +#ifdef LDAP_COMP_MATCH + AttributeAliasing *aa; +#endif + Debug( LDAP_DEBUG_FILTER, "=> bdb_filter_candidates\n", 0, 0, 0 ); + + if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) { + BDB_IDL_ZERO( ids ); + goto out; + } + + switch ( f->f_choice ) { + case SLAPD_FILTER_COMPUTED: + switch( f->f_result ) { + case SLAPD_COMPARE_UNDEFINED: + /* This technically is not the same as FALSE, but it + * certainly will produce no matches. + */ + /* FALL THRU */ + case LDAP_COMPARE_FALSE: + BDB_IDL_ZERO( ids ); + break; + case LDAP_COMPARE_TRUE: { + struct bdb_info *bdb = (struct bdb_info *)op->o_bd->be_private; + BDB_IDL_ALL( bdb, ids ); + } break; + case LDAP_SUCCESS: + /* this is a pre-computed scope, leave it alone */ + break; + } + break; + case LDAP_FILTER_PRESENT: + Debug( LDAP_DEBUG_FILTER, "\tPRESENT\n", 0, 0, 0 ); + rc = presence_candidates( op, rtxn, f->f_desc, ids ); + break; + + case LDAP_FILTER_EQUALITY: + Debug( LDAP_DEBUG_FILTER, "\tEQUALITY\n", 0, 0, 0 ); +#ifdef LDAP_COMP_MATCH + if ( is_aliased_attribute && ( aa = is_aliased_attribute ( f->f_ava->aa_desc ) ) ) { + rc = ava_comp_candidates ( op, rtxn, f->f_ava, aa, ids, tmp, stack ); + } + else +#endif + { + rc = equality_candidates( op, rtxn, f->f_ava, ids, tmp ); + } + break; + + case LDAP_FILTER_APPROX: + Debug( LDAP_DEBUG_FILTER, "\tAPPROX\n", 0, 0, 0 ); + rc = approx_candidates( op, rtxn, f->f_ava, ids, tmp ); + break; + + case LDAP_FILTER_SUBSTRINGS: + Debug( LDAP_DEBUG_FILTER, "\tSUBSTRINGS\n", 0, 0, 0 ); + rc = substring_candidates( op, rtxn, f->f_sub, ids, tmp ); + break; + + case LDAP_FILTER_GE: + /* if no GE index, use pres */ + Debug( LDAP_DEBUG_FILTER, "\tGE\n", 0, 0, 0 ); + if( f->f_ava->aa_desc->ad_type->sat_ordering && + ( f->f_ava->aa_desc->ad_type->sat_ordering->smr_usage & SLAP_MR_ORDERED_INDEX ) ) + rc = inequality_candidates( op, rtxn, f->f_ava, ids, tmp, LDAP_FILTER_GE ); + else + rc = presence_candidates( op, rtxn, f->f_ava->aa_desc, ids ); + break; + + case LDAP_FILTER_LE: + /* if no LE index, use pres */ + Debug( LDAP_DEBUG_FILTER, "\tLE\n", 0, 0, 0 ); + if( f->f_ava->aa_desc->ad_type->sat_ordering && + ( f->f_ava->aa_desc->ad_type->sat_ordering->smr_usage & SLAP_MR_ORDERED_INDEX ) ) + rc = inequality_candidates( op, rtxn, f->f_ava, ids, tmp, LDAP_FILTER_LE ); + else + rc = presence_candidates( op, rtxn, f->f_ava->aa_desc, ids ); + break; + + case LDAP_FILTER_NOT: + /* no indexing to support NOT filters */ + Debug( LDAP_DEBUG_FILTER, "\tNOT\n", 0, 0, 0 ); + { struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + BDB_IDL_ALL( bdb, ids ); + } + break; + + case LDAP_FILTER_AND: + Debug( LDAP_DEBUG_FILTER, "\tAND\n", 0, 0, 0 ); + rc = list_candidates( op, rtxn, + f->f_and, LDAP_FILTER_AND, ids, tmp, stack ); + break; + + case LDAP_FILTER_OR: + Debug( LDAP_DEBUG_FILTER, "\tOR\n", 0, 0, 0 ); + rc = list_candidates( op, rtxn, + f->f_or, LDAP_FILTER_OR, ids, tmp, stack ); + break; + case LDAP_FILTER_EXT: + Debug( LDAP_DEBUG_FILTER, "\tEXT\n", 0, 0, 0 ); + rc = ext_candidates( op, rtxn, f->f_mra, ids, tmp, stack ); + break; + default: + Debug( LDAP_DEBUG_FILTER, "\tUNKNOWN %lu\n", + (unsigned long) f->f_choice, 0, 0 ); + /* Must not return NULL, otherwise extended filters break */ + { struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + BDB_IDL_ALL( bdb, ids ); + } + } + +out: + Debug( LDAP_DEBUG_FILTER, + "<= bdb_filter_candidates: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) BDB_IDL_FIRST( ids ), + (long) BDB_IDL_LAST( ids ) ); + + return rc; +} + +#ifdef LDAP_COMP_MATCH +static int +comp_list_candidates( + Operation *op, + DB_TXN *rtxn, + MatchingRuleAssertion* mra, + ComponentFilter *flist, + int ftype, + ID *ids, + ID *tmp, + ID *save ) +{ + int rc = 0; + ComponentFilter *f; + + Debug( LDAP_DEBUG_FILTER, "=> comp_list_candidates 0x%x\n", ftype, 0, 0 ); + for ( f = flist; f != NULL; f = f->cf_next ) { + /* ignore precomputed scopes */ + if ( f->cf_choice == SLAPD_FILTER_COMPUTED && + f->cf_result == LDAP_SUCCESS ) { + continue; + } + BDB_IDL_ZERO( save ); + rc = comp_candidates( op, rtxn, mra, f, save, tmp, save+BDB_IDL_UM_SIZE ); + + if ( rc != 0 ) { + if ( ftype == LDAP_COMP_FILTER_AND ) { + rc = 0; + continue; + } + break; + } + + if ( ftype == LDAP_COMP_FILTER_AND ) { + if ( f == flist ) { + BDB_IDL_CPY( ids, save ); + } else { + bdb_idl_intersection( ids, save ); + } + if( BDB_IDL_IS_ZERO( ids ) ) + break; + } else { + if ( f == flist ) { + BDB_IDL_CPY( ids, save ); + } else { + bdb_idl_union( ids, save ); + } + } + } + + if( rc == LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_FILTER, + "<= comp_list_candidates: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) BDB_IDL_FIRST(ids), + (long) BDB_IDL_LAST(ids) ); + + } else { + Debug( LDAP_DEBUG_FILTER, + "<= comp_list_candidates: undefined rc=%d\n", + rc, 0, 0 ); + } + + return rc; +} + +static int +comp_equality_candidates ( + Operation *op, + DB_TXN *rtxn, + MatchingRuleAssertion *mra, + ComponentAssertion *ca, + ID *ids, + ID *tmp, + ID *stack) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db; + int i; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + struct berval *keys = NULL; + MatchingRule *mr = mra->ma_rule; + Syntax *sat_syntax; + ComponentReference* cr_list, *cr; + AttrInfo *ai; + + BDB_IDL_ALL( bdb, ids ); + + if ( !ca->ca_comp_ref ) + return 0; + + ai = bdb_attr_mask( op->o_bd->be_private, mra->ma_desc ); + if( ai ) { + cr_list = ai->ai_cr; + } + else { + return 0; + } + /* find a component reference to be indexed */ + sat_syntax = ca->ca_ma_rule->smr_syntax; + for ( cr = cr_list ; cr ; cr = cr->cr_next ) { + if ( cr->cr_string.bv_len == ca->ca_comp_ref->cr_string.bv_len && + strncmp( cr->cr_string.bv_val, ca->ca_comp_ref->cr_string.bv_val,cr->cr_string.bv_len ) == 0 ) + break; + } + + if ( !cr ) + return 0; + + rc = bdb_index_param( op->o_bd, mra->ma_desc, LDAP_FILTER_EQUALITY, + &db, &mask, &prefix ); + + if( rc != LDAP_SUCCESS ) { + return 0; + } + + if( !mr ) { + return 0; + } + + if( !mr->smr_filter ) { + return 0; + } + + rc = (ca->ca_ma_rule->smr_filter)( + LDAP_FILTER_EQUALITY, + cr->cr_indexmask, + sat_syntax, + ca->ca_ma_rule, + &prefix, + &ca->ca_ma_value, + &keys, op->o_tmpmemctx ); + + if( rc != LDAP_SUCCESS ) { + return 0; + } + + if( keys == NULL ) { + return 0; + } + for ( i= 0; keys[i].bv_val != NULL; i++ ) { + rc = bdb_key_read( op->o_bd, db, rtxn, &keys[i], tmp, NULL, 0 ); + + if( rc == DB_NOTFOUND ) { + BDB_IDL_ZERO( ids ); + rc = 0; + break; + } else if( rc != LDAP_SUCCESS ) { + break; + } + + if( BDB_IDL_IS_ZERO( tmp ) ) { + BDB_IDL_ZERO( ids ); + break; + } + + if ( i == 0 ) { + BDB_IDL_CPY( ids, tmp ); + } else { + bdb_idl_intersection( ids, tmp ); + } + + if( BDB_IDL_IS_ZERO( ids ) ) + break; + } + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, + "<= comp_equality_candidates: id=%ld, first=%ld, last=%ld\n", + (long) ids[0], + (long) BDB_IDL_FIRST(ids), + (long) BDB_IDL_LAST(ids) ); + return( rc ); +} + +static int +ava_comp_candidates ( + Operation *op, + DB_TXN *rtxn, + AttributeAssertion *ava, + AttributeAliasing *aa, + ID *ids, + ID *tmp, + ID *stack ) +{ + MatchingRuleAssertion mra; + + mra.ma_rule = ava->aa_desc->ad_type->sat_equality; + if ( !mra.ma_rule ) { + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + BDB_IDL_ALL( bdb, ids ); + return 0; + } + mra.ma_desc = aa->aa_aliased_ad; + mra.ma_rule = ava->aa_desc->ad_type->sat_equality; + + return comp_candidates ( op, rtxn, &mra, ava->aa_cf, ids, tmp, stack ); +} + +static int +comp_candidates ( + Operation *op, + DB_TXN *rtxn, + MatchingRuleAssertion *mra, + ComponentFilter *f, + ID *ids, + ID *tmp, + ID *stack) +{ + int rc; + + if ( !f ) return LDAP_PROTOCOL_ERROR; + + Debug( LDAP_DEBUG_FILTER, "comp_candidates\n", 0, 0, 0 ); + switch ( f->cf_choice ) { + case SLAPD_FILTER_COMPUTED: + rc = f->cf_result; + break; + case LDAP_COMP_FILTER_AND: + rc = comp_list_candidates( op, rtxn, mra, f->cf_and, LDAP_COMP_FILTER_AND, ids, tmp, stack ); + break; + case LDAP_COMP_FILTER_OR: + rc = comp_list_candidates( op, rtxn, mra, f->cf_or, LDAP_COMP_FILTER_OR, ids, tmp, stack ); + break; + case LDAP_COMP_FILTER_NOT: + /* No component indexing supported for NOT filter */ + Debug( LDAP_DEBUG_FILTER, "\tComponent NOT\n", 0, 0, 0 ); + { + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + BDB_IDL_ALL( bdb, ids ); + } + rc = LDAP_PROTOCOL_ERROR; + break; + case LDAP_COMP_FILTER_ITEM: + rc = comp_equality_candidates( op, rtxn, mra, f->cf_ca, ids, tmp, stack ); + break; + default: + { + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + BDB_IDL_ALL( bdb, ids ); + } + rc = LDAP_PROTOCOL_ERROR; + } + + return( rc ); +} +#endif + +static int +ext_candidates( + Operation *op, + DB_TXN *rtxn, + MatchingRuleAssertion *mra, + ID *ids, + ID *tmp, + ID *stack) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + +#ifdef LDAP_COMP_MATCH + /* + * Currently Only Component Indexing for componentFilterMatch is supported + * Indexing for an extensible filter is not supported yet + */ + if ( mra->ma_cf ) { + return comp_candidates ( op, rtxn, mra, mra->ma_cf, ids, tmp, stack); + } +#endif + if ( mra->ma_desc == slap_schema.si_ad_entryDN ) { + int rc; + EntryInfo *ei; + + BDB_IDL_ZERO( ids ); + if ( mra->ma_rule == slap_schema.si_mr_distinguishedNameMatch ) { + ei = NULL; + rc = bdb_cache_find_ndn( op, rtxn, &mra->ma_value, &ei ); + if ( rc == LDAP_SUCCESS ) + bdb_idl_insert( ids, ei->bei_id ); + if ( ei ) + bdb_cache_entryinfo_unlock( ei ); + return 0; + } else if ( mra->ma_rule && mra->ma_rule->smr_match == + dnRelativeMatch && dnIsSuffix( &mra->ma_value, + op->o_bd->be_nsuffix )) { + int scope; + if ( mra->ma_rule == slap_schema.si_mr_dnSuperiorMatch ) { + struct berval pdn; + ei = NULL; + dnParent( &mra->ma_value, &pdn ); + bdb_cache_find_ndn( op, rtxn, &pdn, &ei ); + if ( ei ) { + bdb_cache_entryinfo_unlock( ei ); + while ( ei && ei->bei_id ) { + bdb_idl_insert( ids, ei->bei_id ); + ei = ei->bei_parent; + } + } + return 0; + } + if ( mra->ma_rule == slap_schema.si_mr_dnSubtreeMatch ) + scope = LDAP_SCOPE_SUBTREE; + else if ( mra->ma_rule == slap_schema.si_mr_dnOneLevelMatch ) + scope = LDAP_SCOPE_ONELEVEL; + else if ( mra->ma_rule == slap_schema.si_mr_dnSubordinateMatch ) + scope = LDAP_SCOPE_SUBORDINATE; + else + scope = LDAP_SCOPE_BASE; + if ( scope > LDAP_SCOPE_BASE ) { + ei = NULL; + rc = bdb_cache_find_ndn( op, rtxn, &mra->ma_value, &ei ); + if ( ei ) + bdb_cache_entryinfo_unlock( ei ); + if ( rc == LDAP_SUCCESS ) { + int sc = op->ors_scope; + op->ors_scope = scope; + rc = bdb_dn2idl( op, rtxn, &mra->ma_value, ei, ids, + stack ); + op->ors_scope = sc; + } + return 0; + } + } + } + + BDB_IDL_ALL( bdb, ids ); + return 0; +} + +static int +list_candidates( + Operation *op, + DB_TXN *rtxn, + Filter *flist, + int ftype, + ID *ids, + ID *tmp, + ID *save ) +{ + int rc = 0; + Filter *f; + + Debug( LDAP_DEBUG_FILTER, "=> bdb_list_candidates 0x%x\n", ftype, 0, 0 ); + for ( f = flist; f != NULL; f = f->f_next ) { + /* ignore precomputed scopes */ + if ( f->f_choice == SLAPD_FILTER_COMPUTED && + f->f_result == LDAP_SUCCESS ) { + continue; + } + BDB_IDL_ZERO( save ); + rc = bdb_filter_candidates( op, rtxn, f, save, tmp, + save+BDB_IDL_UM_SIZE ); + + if ( rc != 0 ) { + if ( rc == DB_LOCK_DEADLOCK ) + return rc; + + if ( ftype == LDAP_FILTER_AND ) { + rc = 0; + continue; + } + break; + } + + + if ( ftype == LDAP_FILTER_AND ) { + if ( f == flist ) { + BDB_IDL_CPY( ids, save ); + } else { + bdb_idl_intersection( ids, save ); + } + if( BDB_IDL_IS_ZERO( ids ) ) + break; + } else { + if ( f == flist ) { + BDB_IDL_CPY( ids, save ); + } else { + bdb_idl_union( ids, save ); + } + } + } + + if( rc == LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_FILTER, + "<= bdb_list_candidates: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) BDB_IDL_FIRST(ids), + (long) BDB_IDL_LAST(ids) ); + + } else { + Debug( LDAP_DEBUG_FILTER, + "<= bdb_list_candidates: undefined rc=%d\n", + rc, 0, 0 ); + } + + return rc; +} + +static int +presence_candidates( + Operation *op, + DB_TXN *rtxn, + AttributeDescription *desc, + ID *ids ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + + Debug( LDAP_DEBUG_TRACE, "=> bdb_presence_candidates (%s)\n", + desc->ad_cname.bv_val, 0, 0 ); + + BDB_IDL_ALL( bdb, ids ); + + if( desc == slap_schema.si_ad_objectClass ) { + return 0; + } + + rc = bdb_index_param( op->o_bd, desc, LDAP_FILTER_PRESENT, + &db, &mask, &prefix ); + + if( rc == LDAP_INAPPROPRIATE_MATCHING ) { + /* not indexed */ + Debug( LDAP_DEBUG_TRACE, + "<= bdb_presence_candidates: (%s) not indexed\n", + desc->ad_cname.bv_val, 0, 0 ); + return 0; + } + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_presence_candidates: (%s) index_param " + "returned=%d\n", + desc->ad_cname.bv_val, rc, 0 ); + return 0; + } + + if( prefix.bv_val == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_presence_candidates: (%s) no prefix\n", + desc->ad_cname.bv_val, 0, 0 ); + return -1; + } + + rc = bdb_key_read( op->o_bd, db, rtxn, &prefix, ids, NULL, 0 ); + + if( rc == DB_NOTFOUND ) { + BDB_IDL_ZERO( ids ); + rc = 0; + } else if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_presense_candidates: (%s) " + "key read failed (%d)\n", + desc->ad_cname.bv_val, rc, 0 ); + goto done; + } + + Debug(LDAP_DEBUG_TRACE, + "<= bdb_presence_candidates: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) BDB_IDL_FIRST(ids), + (long) BDB_IDL_LAST(ids) ); + +done: + return rc; +} + +static int +equality_candidates( + Operation *op, + DB_TXN *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db; + int i; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + struct berval *keys = NULL; + MatchingRule *mr; + + Debug( LDAP_DEBUG_TRACE, "=> bdb_equality_candidates (%s)\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + + if ( ava->aa_desc == slap_schema.si_ad_entryDN ) { + EntryInfo *ei = NULL; + rc = bdb_cache_find_ndn( op, rtxn, &ava->aa_value, &ei ); + if ( rc == LDAP_SUCCESS ) { + /* exactly one ID can match */ + ids[0] = 1; + ids[1] = ei->bei_id; + } + if ( ei ) { + bdb_cache_entryinfo_unlock( ei ); + } + if ( rc == DB_NOTFOUND ) { + BDB_IDL_ZERO( ids ); + rc = 0; + } + return rc; + } + + BDB_IDL_ALL( bdb, ids ); + + rc = bdb_index_param( op->o_bd, ava->aa_desc, LDAP_FILTER_EQUALITY, + &db, &mask, &prefix ); + + if ( rc == LDAP_INAPPROPRIATE_MATCHING ) { + Debug( LDAP_DEBUG_ANY, + "<= bdb_equality_candidates: (%s) not indexed\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + return 0; + } + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "<= bdb_equality_candidates: (%s) " + "index_param failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc, 0 ); + return 0; + } + + mr = ava->aa_desc->ad_type->sat_equality; + if( !mr ) { + return 0; + } + + if( !mr->smr_filter ) { + return 0; + } + + rc = (mr->smr_filter)( + LDAP_FILTER_EQUALITY, + mask, + ava->aa_desc->ad_type->sat_syntax, + mr, + &prefix, + &ava->aa_value, + &keys, op->o_tmpmemctx ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_equality_candidates: (%s, %s) " + "MR filter failed (%d)\n", + prefix.bv_val, ava->aa_desc->ad_cname.bv_val, rc ); + return 0; + } + + if( keys == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_equality_candidates: (%s) no keys\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + return 0; + } + + for ( i= 0; keys[i].bv_val != NULL; i++ ) { + rc = bdb_key_read( op->o_bd, db, rtxn, &keys[i], tmp, NULL, 0 ); + + if( rc == DB_NOTFOUND ) { + BDB_IDL_ZERO( ids ); + rc = 0; + break; + } else if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_equality_candidates: (%s) " + "key read failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc, 0 ); + break; + } + + if( BDB_IDL_IS_ZERO( tmp ) ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_equality_candidates: (%s) NULL\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + BDB_IDL_ZERO( ids ); + break; + } + + if ( i == 0 ) { + BDB_IDL_CPY( ids, tmp ); + } else { + bdb_idl_intersection( ids, tmp ); + } + + if( BDB_IDL_IS_ZERO( ids ) ) + break; + } + + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, + "<= bdb_equality_candidates: id=%ld, first=%ld, last=%ld\n", + (long) ids[0], + (long) BDB_IDL_FIRST(ids), + (long) BDB_IDL_LAST(ids) ); + return( rc ); +} + + +static int +approx_candidates( + Operation *op, + DB_TXN *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db; + int i; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + struct berval *keys = NULL; + MatchingRule *mr; + + Debug( LDAP_DEBUG_TRACE, "=> bdb_approx_candidates (%s)\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + + BDB_IDL_ALL( bdb, ids ); + + rc = bdb_index_param( op->o_bd, ava->aa_desc, LDAP_FILTER_APPROX, + &db, &mask, &prefix ); + + if ( rc == LDAP_INAPPROPRIATE_MATCHING ) { + Debug( LDAP_DEBUG_ANY, + "<= bdb_approx_candidates: (%s) not indexed\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + return 0; + } + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "<= bdb_approx_candidates: (%s) " + "index_param failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc, 0 ); + return 0; + } + + mr = ava->aa_desc->ad_type->sat_approx; + if( !mr ) { + /* no approx matching rule, try equality matching rule */ + mr = ava->aa_desc->ad_type->sat_equality; + } + + if( !mr ) { + return 0; + } + + if( !mr->smr_filter ) { + return 0; + } + + rc = (mr->smr_filter)( + LDAP_FILTER_APPROX, + mask, + ava->aa_desc->ad_type->sat_syntax, + mr, + &prefix, + &ava->aa_value, + &keys, op->o_tmpmemctx ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_approx_candidates: (%s, %s) " + "MR filter failed (%d)\n", + prefix.bv_val, ava->aa_desc->ad_cname.bv_val, rc ); + return 0; + } + + if( keys == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_approx_candidates: (%s) no keys (%s)\n", + prefix.bv_val, ava->aa_desc->ad_cname.bv_val, 0 ); + return 0; + } + + for ( i= 0; keys[i].bv_val != NULL; i++ ) { + rc = bdb_key_read( op->o_bd, db, rtxn, &keys[i], tmp, NULL, 0 ); + + if( rc == DB_NOTFOUND ) { + BDB_IDL_ZERO( ids ); + rc = 0; + break; + } else if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_approx_candidates: (%s) " + "key read failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc, 0 ); + break; + } + + if( BDB_IDL_IS_ZERO( tmp ) ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_approx_candidates: (%s) NULL\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + BDB_IDL_ZERO( ids ); + break; + } + + if ( i == 0 ) { + BDB_IDL_CPY( ids, tmp ); + } else { + bdb_idl_intersection( ids, tmp ); + } + + if( BDB_IDL_IS_ZERO( ids ) ) + break; + } + + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, "<= bdb_approx_candidates %ld, first=%ld, last=%ld\n", + (long) ids[0], + (long) BDB_IDL_FIRST(ids), + (long) BDB_IDL_LAST(ids) ); + return( rc ); +} + +static int +substring_candidates( + Operation *op, + DB_TXN *rtxn, + SubstringsAssertion *sub, + ID *ids, + ID *tmp ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db; + int i; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + struct berval *keys = NULL; + MatchingRule *mr; + + Debug( LDAP_DEBUG_TRACE, "=> bdb_substring_candidates (%s)\n", + sub->sa_desc->ad_cname.bv_val, 0, 0 ); + + BDB_IDL_ALL( bdb, ids ); + + rc = bdb_index_param( op->o_bd, sub->sa_desc, LDAP_FILTER_SUBSTRINGS, + &db, &mask, &prefix ); + + if ( rc == LDAP_INAPPROPRIATE_MATCHING ) { + Debug( LDAP_DEBUG_ANY, + "<= bdb_substring_candidates: (%s) not indexed\n", + sub->sa_desc->ad_cname.bv_val, 0, 0 ); + return 0; + } + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "<= bdb_substring_candidates: (%s) " + "index_param failed (%d)\n", + sub->sa_desc->ad_cname.bv_val, rc, 0 ); + return 0; + } + + mr = sub->sa_desc->ad_type->sat_substr; + + if( !mr ) { + return 0; + } + + if( !mr->smr_filter ) { + return 0; + } + + rc = (mr->smr_filter)( + LDAP_FILTER_SUBSTRINGS, + mask, + sub->sa_desc->ad_type->sat_syntax, + mr, + &prefix, + sub, + &keys, op->o_tmpmemctx ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_substring_candidates: (%s) " + "MR filter failed (%d)\n", + sub->sa_desc->ad_cname.bv_val, rc, 0 ); + return 0; + } + + if( keys == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_substring_candidates: (0x%04lx) no keys (%s)\n", + mask, sub->sa_desc->ad_cname.bv_val, 0 ); + return 0; + } + + for ( i= 0; keys[i].bv_val != NULL; i++ ) { + rc = bdb_key_read( op->o_bd, db, rtxn, &keys[i], tmp, NULL, 0 ); + + if( rc == DB_NOTFOUND ) { + BDB_IDL_ZERO( ids ); + rc = 0; + break; + } else if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_substring_candidates: (%s) " + "key read failed (%d)\n", + sub->sa_desc->ad_cname.bv_val, rc, 0 ); + break; + } + + if( BDB_IDL_IS_ZERO( tmp ) ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_substring_candidates: (%s) NULL\n", + sub->sa_desc->ad_cname.bv_val, 0, 0 ); + BDB_IDL_ZERO( ids ); + break; + } + + if ( i == 0 ) { + BDB_IDL_CPY( ids, tmp ); + } else { + bdb_idl_intersection( ids, tmp ); + } + + if( BDB_IDL_IS_ZERO( ids ) ) + break; + } + + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, "<= bdb_substring_candidates: %ld, first=%ld, last=%ld\n", + (long) ids[0], + (long) BDB_IDL_FIRST(ids), + (long) BDB_IDL_LAST(ids) ); + return( rc ); +} + +static int +inequality_candidates( + Operation *op, + DB_TXN *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp, + int gtorlt ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + struct berval *keys = NULL; + MatchingRule *mr; + DBC * cursor = NULL; + + Debug( LDAP_DEBUG_TRACE, "=> bdb_inequality_candidates (%s)\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + + BDB_IDL_ALL( bdb, ids ); + + rc = bdb_index_param( op->o_bd, ava->aa_desc, LDAP_FILTER_EQUALITY, + &db, &mask, &prefix ); + + if ( rc == LDAP_INAPPROPRIATE_MATCHING ) { + Debug( LDAP_DEBUG_ANY, + "<= bdb_inequality_candidates: (%s) not indexed\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + return 0; + } + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "<= bdb_inequality_candidates: (%s) " + "index_param failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc, 0 ); + return 0; + } + + mr = ava->aa_desc->ad_type->sat_equality; + if( !mr ) { + return 0; + } + + if( !mr->smr_filter ) { + return 0; + } + + rc = (mr->smr_filter)( + LDAP_FILTER_EQUALITY, + mask, + ava->aa_desc->ad_type->sat_syntax, + mr, + &prefix, + &ava->aa_value, + &keys, op->o_tmpmemctx ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_inequality_candidates: (%s, %s) " + "MR filter failed (%d)\n", + prefix.bv_val, ava->aa_desc->ad_cname.bv_val, rc ); + return 0; + } + + if( keys == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_inequality_candidates: (%s) no keys\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + return 0; + } + + BDB_IDL_ZERO( ids ); + while(1) { + rc = bdb_key_read( op->o_bd, db, rtxn, &keys[0], tmp, &cursor, gtorlt ); + + if( rc == DB_NOTFOUND ) { + rc = 0; + break; + } else if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_inequality_candidates: (%s) " + "key read failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc, 0 ); + break; + } + + if( BDB_IDL_IS_ZERO( tmp ) ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_inequality_candidates: (%s) NULL\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + break; + } + + bdb_idl_union( ids, tmp ); + + if( op->ors_limit && op->ors_limit->lms_s_unchecked != -1 && + BDB_IDL_N( ids ) >= (unsigned) op->ors_limit->lms_s_unchecked ) { + cursor->c_close( cursor ); + break; + } + } + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, + "<= bdb_inequality_candidates: id=%ld, first=%ld, last=%ld\n", + (long) ids[0], + (long) BDB_IDL_FIRST(ids), + (long) BDB_IDL_LAST(ids) ); + return( rc ); +} diff --git a/servers/slapd/back-bdb/id2entry.c b/servers/slapd/back-bdb/id2entry.c new file mode 100644 index 0000000..d0e76ab --- /dev/null +++ b/servers/slapd/back-bdb/id2entry.c @@ -0,0 +1,446 @@ +/* id2entry.c - routines to deal with the id2entry database */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/errno.h> + +#include "back-bdb.h" + +static int bdb_id2entry_put( + BackendDB *be, + DB_TXN *tid, + Entry *e, + int flag ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + DB *db = bdb->bi_id2entry->bdi_db; + DBT key, data; + struct berval bv; + int rc; + ID nid; +#ifdef BDB_HIER + struct berval odn, ondn; + + /* We only store rdns, and they go in the dn2id database. */ + + odn = e->e_name; ondn = e->e_nname; + + e->e_name = slap_empty_bv; + e->e_nname = slap_empty_bv; +#endif + DBTzero( &key ); + + /* Store ID in BigEndian format */ + key.data = &nid; + key.size = sizeof(ID); + BDB_ID2DISK( e->e_id, &nid ); + + rc = entry_encode( e, &bv ); +#ifdef BDB_HIER + e->e_name = odn; e->e_nname = ondn; +#endif + if( rc != LDAP_SUCCESS ) { + return -1; + } + + DBTzero( &data ); + bv2DBT( &bv, &data ); + + rc = db->put( db, tid, &key, &data, flag ); + + free( bv.bv_val ); + return rc; +} + +/* + * This routine adds (or updates) an entry on disk. + * The cache should be already be updated. + */ + + +int bdb_id2entry_add( + BackendDB *be, + DB_TXN *tid, + Entry *e ) +{ + return bdb_id2entry_put(be, tid, e, DB_NOOVERWRITE); +} + +int bdb_id2entry_update( + BackendDB *be, + DB_TXN *tid, + Entry *e ) +{ + return bdb_id2entry_put(be, tid, e, 0); +} + +int bdb_id2entry( + BackendDB *be, + DB_TXN *tid, + ID id, + Entry **e ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + DB *db = bdb->bi_id2entry->bdi_db; + DBT key, data; + DBC *cursor; + EntryHeader eh; + char buf[16]; + int rc = 0, off; + ID nid; + + *e = NULL; + + DBTzero( &key ); + key.data = &nid; + key.size = sizeof(ID); + BDB_ID2DISK( id, &nid ); + + DBTzero( &data ); + data.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL; + + /* fetch it */ + rc = db->cursor( db, tid, &cursor, bdb->bi_db_opflags ); + if ( rc ) return rc; + + /* Get the nattrs / nvals counts first */ + data.ulen = data.dlen = sizeof(buf); + data.data = buf; + rc = cursor->c_get( cursor, &key, &data, DB_SET ); + if ( rc ) goto finish; + + + eh.bv.bv_val = buf; + eh.bv.bv_len = data.size; + rc = entry_header( &eh ); + if ( rc ) goto finish; + + if ( eh.nvals ) { + /* Get the size */ + data.flags ^= DB_DBT_PARTIAL; + data.ulen = 0; + rc = cursor->c_get( cursor, &key, &data, DB_CURRENT ); + if ( rc != DB_BUFFER_SMALL ) goto finish; + + /* Allocate a block and retrieve the data */ + off = eh.data - eh.bv.bv_val; + eh.bv.bv_len = eh.nvals * sizeof( struct berval ) + data.size; + eh.bv.bv_val = ch_malloc( eh.bv.bv_len ); + eh.data = eh.bv.bv_val + eh.nvals * sizeof( struct berval ); + data.data = eh.data; + data.ulen = data.size; + + /* skip past already parsed nattr/nvals */ + eh.data += off; + + rc = cursor->c_get( cursor, &key, &data, DB_CURRENT ); + } + +finish: + cursor->c_close( cursor ); + + if( rc != 0 ) { + return rc; + } + + if ( eh.nvals ) { +#ifdef SLAP_ZONE_ALLOC + rc = entry_decode(&eh, e, bdb->bi_cache.c_zctx); +#else + rc = entry_decode(&eh, e); +#endif + } else { + *e = entry_alloc(); + } + + if( rc == 0 ) { + (*e)->e_id = id; + } else { + /* only free on error. On success, the entry was + * decoded in place. + */ +#ifndef SLAP_ZONE_ALLOC + ch_free(eh.bv.bv_val); +#endif + } +#ifdef SLAP_ZONE_ALLOC + ch_free(eh.bv.bv_val); +#endif + + return rc; +} + +int bdb_id2entry_delete( + BackendDB *be, + DB_TXN *tid, + Entry *e ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + DB *db = bdb->bi_id2entry->bdi_db; + DBT key; + int rc; + ID nid; + + DBTzero( &key ); + key.data = &nid; + key.size = sizeof(ID); + BDB_ID2DISK( e->e_id, &nid ); + + /* delete from database */ + rc = db->del( db, tid, &key, 0 ); + + return rc; +} + +int bdb_entry_return( + Entry *e +) +{ + /* Our entries are allocated in two blocks; the data comes from + * the db itself and the Entry structure and associated pointers + * are allocated in entry_decode. The db data pointer is saved + * in e_bv. + */ + if ( e->e_bv.bv_val ) { + /* See if the DNs were changed by modrdn */ + if( e->e_nname.bv_val < e->e_bv.bv_val || e->e_nname.bv_val > + e->e_bv.bv_val + e->e_bv.bv_len ) { + ch_free(e->e_name.bv_val); + ch_free(e->e_nname.bv_val); + } + e->e_name.bv_val = NULL; + e->e_nname.bv_val = NULL; + /* In tool mode the e_bv buffer is realloc'd, leave it alone */ + if( !(slapMode & SLAP_TOOL_MODE) ) { + free( e->e_bv.bv_val ); + } + BER_BVZERO( &e->e_bv ); + } + entry_free( e ); + return 0; +} + +int bdb_entry_release( + Operation *op, + Entry *e, + int rw ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + struct bdb_op_info *boi; + OpExtra *oex; + + /* slapMode : SLAP_SERVER_MODE, SLAP_TOOL_MODE, + SLAP_TRUNCATE_MODE, SLAP_UNDEFINED_MODE */ + + if ( slapMode & SLAP_SERVER_MODE ) { + /* If not in our cache, just free it */ + if ( !e->e_private ) { +#ifdef SLAP_ZONE_ALLOC + return bdb_entry_return( bdb, e, -1 ); +#else + return bdb_entry_return( e ); +#endif + } + /* free entry and reader or writer lock */ + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == bdb ) break; + } + boi = (struct bdb_op_info *)oex; + + /* lock is freed with txn */ + if ( !boi || boi->boi_txn ) { + bdb_unlocked_cache_return_entry_rw( bdb, e, rw ); + } else { + struct bdb_lock_info *bli, *prev; + for ( prev=(struct bdb_lock_info *)&boi->boi_locks, + bli = boi->boi_locks; bli; prev=bli, bli=bli->bli_next ) { + if ( bli->bli_id == e->e_id ) { + bdb_cache_return_entry_rw( bdb, e, rw, &bli->bli_lock ); + prev->bli_next = bli->bli_next; + /* Cleanup, or let caller know we unlocked */ + if ( bli->bli_flag & BLI_DONTFREE ) + bli->bli_flag = 0; + else + op->o_tmpfree( bli, op->o_tmpmemctx ); + break; + } + } + if ( !boi->boi_locks ) { + LDAP_SLIST_REMOVE( &op->o_extra, &boi->boi_oe, OpExtra, oe_next ); + if ( !(boi->boi_flag & BOI_DONTFREE)) + op->o_tmpfree( boi, op->o_tmpmemctx ); + } + } + } else { +#ifdef SLAP_ZONE_ALLOC + int zseq = -1; + if (e->e_private != NULL) { + BEI(e)->bei_e = NULL; + zseq = BEI(e)->bei_zseq; + } +#else + if (e->e_private != NULL) + BEI(e)->bei_e = NULL; +#endif + e->e_private = NULL; +#ifdef SLAP_ZONE_ALLOC + bdb_entry_return ( bdb, e, zseq ); +#else + bdb_entry_return ( e ); +#endif + } + + return 0; +} + +/* return LDAP_SUCCESS IFF we can retrieve the specified entry. + */ +int bdb_entry_get( + Operation *op, + struct berval *ndn, + ObjectClass *oc, + AttributeDescription *at, + int rw, + Entry **ent ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + struct bdb_op_info *boi = NULL; + DB_TXN *txn = NULL; + Entry *e = NULL; + EntryInfo *ei; + int rc; + const char *at_name = at ? at->ad_cname.bv_val : "(null)"; + + DB_LOCK lock; + + Debug( LDAP_DEBUG_ARGS, + "=> bdb_entry_get: ndn: \"%s\"\n", ndn->bv_val, 0, 0 ); + Debug( LDAP_DEBUG_ARGS, + "=> bdb_entry_get: oc: \"%s\", at: \"%s\"\n", + oc ? oc->soc_cname.bv_val : "(null)", at_name, 0); + + if( op ) { + OpExtra *oex; + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == bdb ) break; + } + boi = (struct bdb_op_info *)oex; + if ( boi ) + txn = boi->boi_txn; + } + + if ( !txn ) { + rc = bdb_reader_get( op, bdb->bi_dbenv, &txn ); + switch(rc) { + case 0: + break; + default: + return LDAP_OTHER; + } + } + +dn2entry_retry: + /* can we find entry */ + rc = bdb_dn2entry( op, txn, ndn, &ei, 0, &lock ); + switch( rc ) { + case DB_NOTFOUND: + case 0: + break; + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + /* the txn must abort and retry */ + if ( txn ) { + if ( boi ) boi->boi_err = rc; + return LDAP_BUSY; + } + ldap_pvt_thread_yield(); + goto dn2entry_retry; + default: + if ( boi ) boi->boi_err = rc; + return (rc != LDAP_BUSY) ? LDAP_OTHER : LDAP_BUSY; + } + if (ei) e = ei->bei_e; + if (e == NULL) { + Debug( LDAP_DEBUG_ACL, + "=> bdb_entry_get: cannot find entry: \"%s\"\n", + ndn->bv_val, 0, 0 ); + return LDAP_NO_SUCH_OBJECT; + } + + Debug( LDAP_DEBUG_ACL, + "=> bdb_entry_get: found entry: \"%s\"\n", + ndn->bv_val, 0, 0 ); + + if ( oc && !is_entry_objectclass( e, oc, 0 )) { + Debug( LDAP_DEBUG_ACL, + "<= bdb_entry_get: failed to find objectClass %s\n", + oc->soc_cname.bv_val, 0, 0 ); + rc = LDAP_NO_SUCH_ATTRIBUTE; + goto return_results; + } + + /* NOTE: attr_find() or attrs_find()? */ + if ( at && attr_find( e->e_attrs, at ) == NULL ) { + Debug( LDAP_DEBUG_ACL, + "<= bdb_entry_get: failed to find attribute %s\n", + at->ad_cname.bv_val, 0, 0 ); + rc = LDAP_NO_SUCH_ATTRIBUTE; + goto return_results; + } + +return_results: + if( rc != LDAP_SUCCESS ) { + /* free entry */ + bdb_cache_return_entry_rw(bdb, e, rw, &lock); + + } else { + if ( slapMode & SLAP_SERVER_MODE ) { + *ent = e; + /* big drag. we need a place to store a read lock so we can + * release it later?? If we're in a txn, nothing is needed + * here because the locks will go away with the txn. + */ + if ( op ) { + if ( !boi ) { + boi = op->o_tmpcalloc(1,sizeof(struct bdb_op_info),op->o_tmpmemctx); + boi->boi_oe.oe_key = bdb; + LDAP_SLIST_INSERT_HEAD( &op->o_extra, &boi->boi_oe, oe_next ); + } + if ( !boi->boi_txn ) { + struct bdb_lock_info *bli; + bli = op->o_tmpalloc( sizeof(struct bdb_lock_info), + op->o_tmpmemctx ); + bli->bli_next = boi->boi_locks; + bli->bli_id = e->e_id; + bli->bli_flag = 0; + bli->bli_lock = lock; + boi->boi_locks = bli; + } + } + } else { + *ent = entry_dup( e ); + bdb_cache_return_entry_rw(bdb, e, rw, &lock); + } + } + + Debug( LDAP_DEBUG_TRACE, + "bdb_entry_get: rc=%d\n", + rc, 0, 0 ); + return(rc); +} diff --git a/servers/slapd/back-bdb/idl.c b/servers/slapd/back-bdb/idl.c new file mode 100644 index 0000000..dce5d57 --- /dev/null +++ b/servers/slapd/back-bdb/idl.c @@ -0,0 +1,1570 @@ +/* idl.c - ldap id list handling routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-bdb.h" +#include "idl.h" + +#define IDL_MAX(x,y) ( (x) > (y) ? (x) : (y) ) +#define IDL_MIN(x,y) ( (x) < (y) ? (x) : (y) ) +#define IDL_CMP(x,y) ( (x) < (y) ? -1 : (x) > (y) ) + +#define IDL_LRU_DELETE( bdb, e ) do { \ + if ( (e) == (bdb)->bi_idl_lru_head ) { \ + if ( (e)->idl_lru_next == (bdb)->bi_idl_lru_head ) { \ + (bdb)->bi_idl_lru_head = NULL; \ + } else { \ + (bdb)->bi_idl_lru_head = (e)->idl_lru_next; \ + } \ + } \ + if ( (e) == (bdb)->bi_idl_lru_tail ) { \ + if ( (e)->idl_lru_prev == (bdb)->bi_idl_lru_tail ) { \ + assert( (bdb)->bi_idl_lru_head == NULL ); \ + (bdb)->bi_idl_lru_tail = NULL; \ + } else { \ + (bdb)->bi_idl_lru_tail = (e)->idl_lru_prev; \ + } \ + } \ + (e)->idl_lru_next->idl_lru_prev = (e)->idl_lru_prev; \ + (e)->idl_lru_prev->idl_lru_next = (e)->idl_lru_next; \ +} while ( 0 ) + +static int +bdb_idl_entry_cmp( const void *v_idl1, const void *v_idl2 ) +{ + const bdb_idl_cache_entry_t *idl1 = v_idl1, *idl2 = v_idl2; + int rc; + + if ((rc = SLAP_PTRCMP( idl1->db, idl2->db ))) return rc; + if ((rc = idl1->kstr.bv_len - idl2->kstr.bv_len )) return rc; + return ( memcmp ( idl1->kstr.bv_val, idl2->kstr.bv_val , idl1->kstr.bv_len ) ); +} + +#if IDL_DEBUG > 0 +static void idl_check( ID *ids ) +{ + if( BDB_IDL_IS_RANGE( ids ) ) { + assert( BDB_IDL_RANGE_FIRST(ids) <= BDB_IDL_RANGE_LAST(ids) ); + } else { + ID i; + for( i=1; i < ids[0]; i++ ) { + assert( ids[i+1] > ids[i] ); + } + } +} + +#if IDL_DEBUG > 1 +static void idl_dump( ID *ids ) +{ + if( BDB_IDL_IS_RANGE( ids ) ) { + Debug( LDAP_DEBUG_ANY, + "IDL: range ( %ld - %ld )\n", + (long) BDB_IDL_RANGE_FIRST( ids ), + (long) BDB_IDL_RANGE_LAST( ids ) ); + + } else { + ID i; + Debug( LDAP_DEBUG_ANY, "IDL: size %ld", (long) ids[0], 0, 0 ); + + for( i=1; i<=ids[0]; i++ ) { + if( i % 16 == 1 ) { + Debug( LDAP_DEBUG_ANY, "\n", 0, 0, 0 ); + } + Debug( LDAP_DEBUG_ANY, " %02lx", (long) ids[i], 0, 0 ); + } + + Debug( LDAP_DEBUG_ANY, "\n", 0, 0, 0 ); + } + + idl_check( ids ); +} +#endif /* IDL_DEBUG > 1 */ +#endif /* IDL_DEBUG > 0 */ + +unsigned bdb_idl_search( ID *ids, ID id ) +{ +#define IDL_BINARY_SEARCH 1 +#ifdef IDL_BINARY_SEARCH + /* + * binary search of id in ids + * if found, returns position of id + * if not found, returns first postion greater than id + */ + unsigned base = 0; + unsigned cursor = 1; + int val = 0; + unsigned n = ids[0]; + +#if IDL_DEBUG > 0 + idl_check( ids ); +#endif + + while( 0 < n ) { + unsigned pivot = n >> 1; + cursor = base + pivot + 1; + val = IDL_CMP( id, ids[cursor] ); + + if( val < 0 ) { + n = pivot; + + } else if ( val > 0 ) { + base = cursor; + n -= pivot + 1; + + } else { + return cursor; + } + } + + if( val > 0 ) { + ++cursor; + } + return cursor; + +#else + /* (reverse) linear search */ + int i; + +#if IDL_DEBUG > 0 + idl_check( ids ); +#endif + + for( i=ids[0]; i; i-- ) { + if( id > ids[i] ) { + break; + } + } + + return i+1; +#endif +} + +int bdb_idl_insert( ID *ids, ID id ) +{ + unsigned x; + +#if IDL_DEBUG > 1 + Debug( LDAP_DEBUG_ANY, "insert: %04lx at %d\n", (long) id, x, 0 ); + idl_dump( ids ); +#elif IDL_DEBUG > 0 + idl_check( ids ); +#endif + + if (BDB_IDL_IS_RANGE( ids )) { + /* if already in range, treat as a dup */ + if (id >= BDB_IDL_RANGE_FIRST(ids) && id <= BDB_IDL_RANGE_LAST(ids)) + return -1; + if (id < BDB_IDL_RANGE_FIRST(ids)) + ids[1] = id; + else if (id > BDB_IDL_RANGE_LAST(ids)) + ids[2] = id; + return 0; + } + + x = bdb_idl_search( ids, id ); + assert( x > 0 ); + + if( x < 1 ) { + /* internal error */ + return -2; + } + + if ( x <= ids[0] && ids[x] == id ) { + /* duplicate */ + return -1; + } + + if ( ++ids[0] >= BDB_IDL_DB_MAX ) { + if( id < ids[1] ) { + ids[1] = id; + ids[2] = ids[ids[0]-1]; + } else if ( ids[ids[0]-1] < id ) { + ids[2] = id; + } else { + ids[2] = ids[ids[0]-1]; + } + ids[0] = NOID; + + } else { + /* insert id */ + AC_MEMCPY( &ids[x+1], &ids[x], (ids[0]-x) * sizeof(ID) ); + ids[x] = id; + } + +#if IDL_DEBUG > 1 + idl_dump( ids ); +#elif IDL_DEBUG > 0 + idl_check( ids ); +#endif + + return 0; +} + +int bdb_idl_delete( ID *ids, ID id ) +{ + unsigned x; + +#if IDL_DEBUG > 1 + Debug( LDAP_DEBUG_ANY, "delete: %04lx at %d\n", (long) id, x, 0 ); + idl_dump( ids ); +#elif IDL_DEBUG > 0 + idl_check( ids ); +#endif + + if (BDB_IDL_IS_RANGE( ids )) { + /* If deleting a range boundary, adjust */ + if ( ids[1] == id ) + ids[1]++; + else if ( ids[2] == id ) + ids[2]--; + /* deleting from inside a range is a no-op */ + + /* If the range has collapsed, re-adjust */ + if ( ids[1] > ids[2] ) + ids[0] = 0; + else if ( ids[1] == ids[2] ) + ids[1] = 1; + return 0; + } + + x = bdb_idl_search( ids, id ); + assert( x > 0 ); + + if( x <= 0 ) { + /* internal error */ + return -2; + } + + if( x > ids[0] || ids[x] != id ) { + /* not found */ + return -1; + + } else if ( --ids[0] == 0 ) { + if( x != 1 ) { + return -3; + } + + } else { + AC_MEMCPY( &ids[x], &ids[x+1], (1+ids[0]-x) * sizeof(ID) ); + } + +#if IDL_DEBUG > 1 + idl_dump( ids ); +#elif IDL_DEBUG > 0 + idl_check( ids ); +#endif + + return 0; +} + +static char * +bdb_show_key( + DBT *key, + char *buf ) +{ + if ( key->size == 4 /* LUTIL_HASH_BYTES */ ) { + unsigned char *c = key->data; + sprintf( buf, "[%02x%02x%02x%02x]", c[0], c[1], c[2], c[3] ); + return buf; + } else { + return key->data; + } +} + +/* Find a db/key pair in the IDL cache. If ids is non-NULL, + * copy the cached IDL into it, otherwise just return the status. + */ +int +bdb_idl_cache_get( + struct bdb_info *bdb, + DB *db, + DBT *key, + ID *ids ) +{ + bdb_idl_cache_entry_t idl_tmp; + bdb_idl_cache_entry_t *matched_idl_entry; + int rc = LDAP_NO_SUCH_OBJECT; + + DBT2bv( key, &idl_tmp.kstr ); + idl_tmp.db = db; + ldap_pvt_thread_rdwr_rlock( &bdb->bi_idl_tree_rwlock ); + matched_idl_entry = avl_find( bdb->bi_idl_tree, &idl_tmp, + bdb_idl_entry_cmp ); + if ( matched_idl_entry != NULL ) { + if ( matched_idl_entry->idl && ids ) + BDB_IDL_CPY( ids, matched_idl_entry->idl ); + matched_idl_entry->idl_flags |= CACHE_ENTRY_REFERENCED; + if ( matched_idl_entry->idl ) + rc = LDAP_SUCCESS; + else + rc = DB_NOTFOUND; + } + ldap_pvt_thread_rdwr_runlock( &bdb->bi_idl_tree_rwlock ); + + return rc; +} + +void +bdb_idl_cache_put( + struct bdb_info *bdb, + DB *db, + DBT *key, + ID *ids, + int rc ) +{ + bdb_idl_cache_entry_t idl_tmp; + bdb_idl_cache_entry_t *ee, *eprev; + + if ( rc == DB_NOTFOUND || BDB_IDL_IS_ZERO( ids )) + return; + + DBT2bv( key, &idl_tmp.kstr ); + + ee = (bdb_idl_cache_entry_t *) ch_malloc( + sizeof( bdb_idl_cache_entry_t ) ); + ee->db = db; + ee->idl = (ID*) ch_malloc( BDB_IDL_SIZEOF ( ids ) ); + BDB_IDL_CPY( ee->idl, ids ); + + ee->idl_lru_prev = NULL; + ee->idl_lru_next = NULL; + ee->idl_flags = 0; + ber_dupbv( &ee->kstr, &idl_tmp.kstr ); + ldap_pvt_thread_rdwr_wlock( &bdb->bi_idl_tree_rwlock ); + if ( avl_insert( &bdb->bi_idl_tree, (caddr_t) ee, + bdb_idl_entry_cmp, avl_dup_error )) + { + ch_free( ee->kstr.bv_val ); + ch_free( ee->idl ); + ch_free( ee ); + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_idl_tree_rwlock ); + return; + } + ldap_pvt_thread_mutex_lock( &bdb->bi_idl_tree_lrulock ); + /* LRU_ADD */ + if ( bdb->bi_idl_lru_head ) { + assert( bdb->bi_idl_lru_tail != NULL ); + assert( bdb->bi_idl_lru_head->idl_lru_prev != NULL ); + assert( bdb->bi_idl_lru_head->idl_lru_next != NULL ); + + ee->idl_lru_next = bdb->bi_idl_lru_head; + ee->idl_lru_prev = bdb->bi_idl_lru_head->idl_lru_prev; + bdb->bi_idl_lru_head->idl_lru_prev->idl_lru_next = ee; + bdb->bi_idl_lru_head->idl_lru_prev = ee; + } else { + ee->idl_lru_next = ee->idl_lru_prev = ee; + bdb->bi_idl_lru_tail = ee; + } + bdb->bi_idl_lru_head = ee; + + if ( bdb->bi_idl_cache_size >= bdb->bi_idl_cache_max_size ) { + int i; + eprev = bdb->bi_idl_lru_tail; + for ( i = 0; (ee = eprev) != NULL && i < 10; i++ ) { + eprev = ee->idl_lru_prev; + if ( eprev == ee ) { + eprev = NULL; + } + if ( ee->idl_flags & CACHE_ENTRY_REFERENCED ) { + ee->idl_flags ^= CACHE_ENTRY_REFERENCED; + continue; + } + if ( avl_delete( &bdb->bi_idl_tree, (caddr_t) ee, + bdb_idl_entry_cmp ) == NULL ) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_cache_put: " + "AVL delete failed\n", + 0, 0, 0 ); + } + IDL_LRU_DELETE( bdb, ee ); + i++; + --bdb->bi_idl_cache_size; + ch_free( ee->kstr.bv_val ); + ch_free( ee->idl ); + ch_free( ee ); + } + bdb->bi_idl_lru_tail = eprev; + assert( bdb->bi_idl_lru_tail != NULL + || bdb->bi_idl_lru_head == NULL ); + } + bdb->bi_idl_cache_size++; + ldap_pvt_thread_mutex_unlock( &bdb->bi_idl_tree_lrulock ); + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_idl_tree_rwlock ); +} + +void +bdb_idl_cache_del( + struct bdb_info *bdb, + DB *db, + DBT *key ) +{ + bdb_idl_cache_entry_t *matched_idl_entry, idl_tmp; + DBT2bv( key, &idl_tmp.kstr ); + idl_tmp.db = db; + ldap_pvt_thread_rdwr_wlock( &bdb->bi_idl_tree_rwlock ); + matched_idl_entry = avl_find( bdb->bi_idl_tree, &idl_tmp, + bdb_idl_entry_cmp ); + if ( matched_idl_entry != NULL ) { + if ( avl_delete( &bdb->bi_idl_tree, (caddr_t) matched_idl_entry, + bdb_idl_entry_cmp ) == NULL ) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_cache_del: " + "AVL delete failed\n", + 0, 0, 0 ); + } + --bdb->bi_idl_cache_size; + ldap_pvt_thread_mutex_lock( &bdb->bi_idl_tree_lrulock ); + IDL_LRU_DELETE( bdb, matched_idl_entry ); + ldap_pvt_thread_mutex_unlock( &bdb->bi_idl_tree_lrulock ); + free( matched_idl_entry->kstr.bv_val ); + if ( matched_idl_entry->idl ) + free( matched_idl_entry->idl ); + free( matched_idl_entry ); + } + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_idl_tree_rwlock ); +} + +void +bdb_idl_cache_add_id( + struct bdb_info *bdb, + DB *db, + DBT *key, + ID id ) +{ + bdb_idl_cache_entry_t *cache_entry, idl_tmp; + DBT2bv( key, &idl_tmp.kstr ); + idl_tmp.db = db; + ldap_pvt_thread_rdwr_wlock( &bdb->bi_idl_tree_rwlock ); + cache_entry = avl_find( bdb->bi_idl_tree, &idl_tmp, + bdb_idl_entry_cmp ); + if ( cache_entry != NULL ) { + if ( !BDB_IDL_IS_RANGE( cache_entry->idl ) && + cache_entry->idl[0] < BDB_IDL_DB_MAX ) { + size_t s = BDB_IDL_SIZEOF( cache_entry->idl ) + sizeof(ID); + cache_entry->idl = ch_realloc( cache_entry->idl, s ); + } + bdb_idl_insert( cache_entry->idl, id ); + } + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_idl_tree_rwlock ); +} + +void +bdb_idl_cache_del_id( + struct bdb_info *bdb, + DB *db, + DBT *key, + ID id ) +{ + bdb_idl_cache_entry_t *cache_entry, idl_tmp; + DBT2bv( key, &idl_tmp.kstr ); + idl_tmp.db = db; + ldap_pvt_thread_rdwr_wlock( &bdb->bi_idl_tree_rwlock ); + cache_entry = avl_find( bdb->bi_idl_tree, &idl_tmp, + bdb_idl_entry_cmp ); + if ( cache_entry != NULL ) { + bdb_idl_delete( cache_entry->idl, id ); + if ( cache_entry->idl[0] == 0 ) { + if ( avl_delete( &bdb->bi_idl_tree, (caddr_t) cache_entry, + bdb_idl_entry_cmp ) == NULL ) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_cache_del: " + "AVL delete failed\n", + 0, 0, 0 ); + } + --bdb->bi_idl_cache_size; + ldap_pvt_thread_mutex_lock( &bdb->bi_idl_tree_lrulock ); + IDL_LRU_DELETE( bdb, cache_entry ); + ldap_pvt_thread_mutex_unlock( &bdb->bi_idl_tree_lrulock ); + free( cache_entry->kstr.bv_val ); + free( cache_entry->idl ); + free( cache_entry ); + } + } + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_idl_tree_rwlock ); +} + +int +bdb_idl_fetch_key( + BackendDB *be, + DB *db, + DB_TXN *txn, + DBT *key, + ID *ids, + DBC **saved_cursor, + int get_flag ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + int rc; + DBT data, key2, *kptr; + DBC *cursor; + ID *i; + void *ptr; + size_t len; + int rc2; + int flags = bdb->bi_db_opflags | DB_MULTIPLE; + int opflag; + + /* If using BerkeleyDB 4.0, the buf must be large enough to + * grab the entire IDL in one get(), otherwise BDB will leak + * resources on subsequent get's. We can safely call get() + * twice - once for the data, and once to get the DB_NOTFOUND + * result meaning there's no more data. See ITS#2040 for details. + * This bug is fixed in BDB 4.1 so a smaller buffer will work if + * stack space is too limited. + * + * configure now requires Berkeley DB 4.1. + */ +#if DB_VERSION_FULL < 0x04010000 +# define BDB_ENOUGH 5 +#else + /* We sometimes test with tiny IDLs, and BDB always wants buffers + * that are at least one page in size. + */ +# if BDB_IDL_DB_SIZE < 4096 +# define BDB_ENOUGH 2048 +# else +# define BDB_ENOUGH 1 +# endif +#endif + ID buf[BDB_IDL_DB_SIZE*BDB_ENOUGH]; + + char keybuf[16]; + + Debug( LDAP_DEBUG_ARGS, + "bdb_idl_fetch_key: %s\n", + bdb_show_key( key, keybuf ), 0, 0 ); + + assert( ids != NULL ); + + if ( saved_cursor && *saved_cursor ) { + opflag = DB_NEXT; + } else if ( get_flag == LDAP_FILTER_GE ) { + opflag = DB_SET_RANGE; + } else if ( get_flag == LDAP_FILTER_LE ) { + opflag = DB_FIRST; + } else { + opflag = DB_SET; + } + + /* only non-range lookups can use the IDL cache */ + if ( bdb->bi_idl_cache_size && opflag == DB_SET ) { + rc = bdb_idl_cache_get( bdb, db, key, ids ); + if ( rc != LDAP_NO_SUCH_OBJECT ) return rc; + } + + DBTzero( &data ); + + data.data = buf; + data.ulen = sizeof(buf); + data.flags = DB_DBT_USERMEM; + + /* If we're not reusing an existing cursor, get a new one */ + if( opflag != DB_NEXT ) { + rc = db->cursor( db, txn, &cursor, bdb->bi_db_opflags ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_fetch_key: " + "cursor failed: %s (%d)\n", db_strerror(rc), rc, 0 ); + return rc; + } + } else { + cursor = *saved_cursor; + } + + /* If this is a LE lookup, save original key so we can determine + * when to stop. If this is a GE lookup, save the key since it + * will be overwritten. + */ + if ( get_flag == LDAP_FILTER_LE || get_flag == LDAP_FILTER_GE ) { + DBTzero( &key2 ); + key2.flags = DB_DBT_USERMEM; + key2.ulen = sizeof(keybuf); + key2.data = keybuf; + key2.size = key->size; + AC_MEMCPY( keybuf, key->data, key->size ); + kptr = &key2; + } else { + kptr = key; + } + len = key->size; + rc = cursor->c_get( cursor, kptr, &data, flags | opflag ); + + /* skip presence key on range inequality lookups */ + while (rc == 0 && kptr->size != len) { + rc = cursor->c_get( cursor, kptr, &data, flags | DB_NEXT_NODUP ); + } + /* If we're doing a LE compare and the new key is greater than + * our search key, we're done + */ + if (rc == 0 && get_flag == LDAP_FILTER_LE && memcmp( kptr->data, + key->data, key->size ) > 0 ) { + rc = DB_NOTFOUND; + } + if (rc == 0) { + i = ids; + while (rc == 0) { + u_int8_t *j; + + DB_MULTIPLE_INIT( ptr, &data ); + while (ptr) { + DB_MULTIPLE_NEXT(ptr, &data, j, len); + if (j) { + ++i; + BDB_DISK2ID( j, i ); + } + } + rc = cursor->c_get( cursor, key, &data, flags | DB_NEXT_DUP ); + } + if ( rc == DB_NOTFOUND ) rc = 0; + ids[0] = i - ids; + /* On disk, a range is denoted by 0 in the first element */ + if (ids[1] == 0) { + if (ids[0] != BDB_IDL_RANGE_SIZE) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_fetch_key: " + "range size mismatch: expected %d, got %ld\n", + BDB_IDL_RANGE_SIZE, ids[0], 0 ); + cursor->c_close( cursor ); + return -1; + } + BDB_IDL_RANGE( ids, ids[2], ids[3] ); + } + data.size = BDB_IDL_SIZEOF(ids); + } + + if ( saved_cursor && rc == 0 ) { + if ( !*saved_cursor ) + *saved_cursor = cursor; + rc2 = 0; + } + else + rc2 = cursor->c_close( cursor ); + if (rc2) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_fetch_key: " + "close failed: %s (%d)\n", db_strerror(rc2), rc2, 0 ); + return rc2; + } + + if( rc == DB_NOTFOUND ) { + return rc; + + } else if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_fetch_key: " + "get failed: %s (%d)\n", + db_strerror(rc), rc, 0 ); + return rc; + + } else if ( data.size == 0 || data.size % sizeof( ID ) ) { + /* size not multiple of ID size */ + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_fetch_key: " + "odd size: expected %ld multiple, got %ld\n", + (long) sizeof( ID ), (long) data.size, 0 ); + return -1; + + } else if ( data.size != BDB_IDL_SIZEOF(ids) ) { + /* size mismatch */ + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_fetch_key: " + "get size mismatch: expected %ld, got %ld\n", + (long) ((1 + ids[0]) * sizeof( ID )), (long) data.size, 0 ); + return -1; + } + + if ( bdb->bi_idl_cache_max_size ) { + bdb_idl_cache_put( bdb, db, key, ids, rc ); + } + + return rc; +} + + +int +bdb_idl_insert_key( + BackendDB *be, + DB *db, + DB_TXN *tid, + DBT *key, + ID id ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + int rc; + DBT data; + DBC *cursor; + ID lo, hi, nlo, nhi, nid; + char *err; + + { + char buf[16]; + Debug( LDAP_DEBUG_ARGS, + "bdb_idl_insert_key: %lx %s\n", + (long) id, bdb_show_key( key, buf ), 0 ); + } + + assert( id != NOID ); + + DBTzero( &data ); + data.size = sizeof( ID ); + data.ulen = data.size; + data.flags = DB_DBT_USERMEM; + + BDB_ID2DISK( id, &nid ); + + rc = db->cursor( db, tid, &cursor, bdb->bi_db_opflags ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_insert_key: " + "cursor failed: %s (%d)\n", db_strerror(rc), rc, 0 ); + return rc; + } + data.data = &nlo; + /* Fetch the first data item for this key, to see if it + * exists and if it's a range. + */ + rc = cursor->c_get( cursor, key, &data, DB_SET ); + err = "c_get"; + if ( rc == 0 ) { + if ( nlo != 0 ) { + /* not a range, count the number of items */ + db_recno_t count; + rc = cursor->c_count( cursor, &count, 0 ); + if ( rc != 0 ) { + err = "c_count"; + goto fail; + } + if ( count >= BDB_IDL_DB_MAX ) { + /* No room, convert to a range */ + DBT key2 = *key; + db_recno_t i; + + key2.dlen = key2.ulen; + key2.flags |= DB_DBT_PARTIAL; + + BDB_DISK2ID( &nlo, &lo ); + data.data = &nhi; + + rc = cursor->c_get( cursor, &key2, &data, DB_NEXT_NODUP ); + if ( rc != 0 && rc != DB_NOTFOUND ) { + err = "c_get next_nodup"; + goto fail; + } + if ( rc == DB_NOTFOUND ) { + rc = cursor->c_get( cursor, key, &data, DB_LAST ); + if ( rc != 0 ) { + err = "c_get last"; + goto fail; + } + } else { + rc = cursor->c_get( cursor, key, &data, DB_PREV ); + if ( rc != 0 ) { + err = "c_get prev"; + goto fail; + } + } + BDB_DISK2ID( &nhi, &hi ); + /* Update hi/lo if needed, then delete all the items + * between lo and hi + */ + if ( id < lo ) { + lo = id; + nlo = nid; + } else if ( id > hi ) { + hi = id; + nhi = nid; + } + data.data = &nid; + /* Don't fetch anything, just position cursor */ + data.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL; + data.dlen = data.ulen = 0; + rc = cursor->c_get( cursor, key, &data, DB_SET ); + if ( rc != 0 ) { + err = "c_get 2"; + goto fail; + } + rc = cursor->c_del( cursor, 0 ); + if ( rc != 0 ) { + err = "c_del range1"; + goto fail; + } + /* Delete all the records */ + for ( i=1; i<count; i++ ) { + rc = cursor->c_get( cursor, &key2, &data, DB_NEXT_DUP ); + if ( rc != 0 ) { + err = "c_get next_dup"; + goto fail; + } + rc = cursor->c_del( cursor, 0 ); + if ( rc != 0 ) { + err = "c_del range"; + goto fail; + } + } + /* Store the range marker */ + data.size = data.ulen = sizeof(ID); + data.flags = DB_DBT_USERMEM; + nid = 0; + rc = cursor->c_put( cursor, key, &data, DB_KEYFIRST ); + if ( rc != 0 ) { + err = "c_put range"; + goto fail; + } + nid = nlo; + rc = cursor->c_put( cursor, key, &data, DB_KEYLAST ); + if ( rc != 0 ) { + err = "c_put lo"; + goto fail; + } + nid = nhi; + rc = cursor->c_put( cursor, key, &data, DB_KEYLAST ); + if ( rc != 0 ) { + err = "c_put hi"; + goto fail; + } + } else { + /* There's room, just store it */ + goto put1; + } + } else { + /* It's a range, see if we need to rewrite + * the boundaries + */ + hi = id; + data.data = &nlo; + rc = cursor->c_get( cursor, key, &data, DB_NEXT_DUP ); + if ( rc != 0 ) { + err = "c_get lo"; + goto fail; + } + BDB_DISK2ID( &nlo, &lo ); + if ( id > lo ) { + data.data = &nhi; + rc = cursor->c_get( cursor, key, &data, DB_NEXT_DUP ); + if ( rc != 0 ) { + err = "c_get hi"; + goto fail; + } + BDB_DISK2ID( &nhi, &hi ); + } + if ( id < lo || id > hi ) { + /* Delete the current lo/hi */ + rc = cursor->c_del( cursor, 0 ); + if ( rc != 0 ) { + err = "c_del"; + goto fail; + } + data.data = &nid; + rc = cursor->c_put( cursor, key, &data, DB_KEYFIRST ); + if ( rc != 0 ) { + err = "c_put lo/hi"; + goto fail; + } + } + } + } else if ( rc == DB_NOTFOUND ) { +put1: data.data = &nid; + rc = cursor->c_put( cursor, key, &data, DB_NODUPDATA ); + /* Don't worry if it's already there */ + if ( rc != 0 && rc != DB_KEYEXIST ) { + err = "c_put id"; + goto fail; + } + } else { + /* initial c_get failed, nothing was done */ +fail: + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_insert_key: " + "%s failed: %s (%d)\n", err, db_strerror(rc), rc ); + cursor->c_close( cursor ); + return rc; + } + /* If key was added (didn't already exist) and using IDL cache, + * update key in IDL cache. + */ + if ( !rc && bdb->bi_idl_cache_max_size ) { + bdb_idl_cache_add_id( bdb, db, key, id ); + } + rc = cursor->c_close( cursor ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_insert_key: " + "c_close failed: %s (%d)\n", + db_strerror(rc), rc, 0 ); + } + return rc; +} + +int +bdb_idl_delete_key( + BackendDB *be, + DB *db, + DB_TXN *tid, + DBT *key, + ID id ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + int rc; + DBT data; + DBC *cursor; + ID lo, hi, tmp, nid, nlo, nhi; + char *err; + + { + char buf[16]; + Debug( LDAP_DEBUG_ARGS, + "bdb_idl_delete_key: %lx %s\n", + (long) id, bdb_show_key( key, buf ), 0 ); + } + assert( id != NOID ); + + if ( bdb->bi_idl_cache_size ) { + bdb_idl_cache_del( bdb, db, key ); + } + + BDB_ID2DISK( id, &nid ); + + DBTzero( &data ); + data.data = &tmp; + data.size = sizeof( id ); + data.ulen = data.size; + data.flags = DB_DBT_USERMEM; + + rc = db->cursor( db, tid, &cursor, bdb->bi_db_opflags ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_delete_key: " + "cursor failed: %s (%d)\n", db_strerror(rc), rc, 0 ); + return rc; + } + /* Fetch the first data item for this key, to see if it + * exists and if it's a range. + */ + rc = cursor->c_get( cursor, key, &data, DB_SET ); + err = "c_get"; + if ( rc == 0 ) { + if ( tmp != 0 ) { + /* Not a range, just delete it */ + if (tmp != nid) { + /* position to correct item */ + tmp = nid; + rc = cursor->c_get( cursor, key, &data, DB_GET_BOTH ); + if ( rc != 0 ) { + err = "c_get id"; + goto fail; + } + } + rc = cursor->c_del( cursor, 0 ); + if ( rc != 0 ) { + err = "c_del id"; + goto fail; + } + } else { + /* It's a range, see if we need to rewrite + * the boundaries + */ + data.data = &nlo; + rc = cursor->c_get( cursor, key, &data, DB_NEXT_DUP ); + if ( rc != 0 ) { + err = "c_get lo"; + goto fail; + } + BDB_DISK2ID( &nlo, &lo ); + data.data = &nhi; + rc = cursor->c_get( cursor, key, &data, DB_NEXT_DUP ); + if ( rc != 0 ) { + err = "c_get hi"; + goto fail; + } + BDB_DISK2ID( &nhi, &hi ); + if ( id == lo || id == hi ) { + if ( id == lo ) { + id++; + lo = id; + } else if ( id == hi ) { + id--; + hi = id; + } + if ( lo >= hi ) { + /* The range has collapsed... */ + rc = db->del( db, tid, key, 0 ); + if ( rc != 0 ) { + err = "del"; + goto fail; + } + } else { + if ( id == lo ) { + /* reposition on lo slot */ + data.data = &nlo; + cursor->c_get( cursor, key, &data, DB_PREV ); + } + rc = cursor->c_del( cursor, 0 ); + if ( rc != 0 ) { + err = "c_del"; + goto fail; + } + } + if ( lo <= hi ) { + BDB_ID2DISK( id, &nid ); + data.data = &nid; + rc = cursor->c_put( cursor, key, &data, DB_KEYFIRST ); + if ( rc != 0 ) { + err = "c_put lo/hi"; + goto fail; + } + } + } + } + } else { + /* initial c_get failed, nothing was done */ +fail: + if ( rc != DB_NOTFOUND ) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_delete_key: " + "%s failed: %s (%d)\n", err, db_strerror(rc), rc ); + } + cursor->c_close( cursor ); + return rc; + } + rc = cursor->c_close( cursor ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> bdb_idl_delete_key: c_close failed: %s (%d)\n", + db_strerror(rc), rc, 0 ); + } + + return rc; +} + + +/* + * idl_intersection - return a = a intersection b + */ +int +bdb_idl_intersection( + ID *a, + ID *b ) +{ + ID ida, idb; + ID idmax, idmin; + ID cursora = 0, cursorb = 0, cursorc; + int swap = 0; + + if ( BDB_IDL_IS_ZERO( a ) || BDB_IDL_IS_ZERO( b ) ) { + a[0] = 0; + return 0; + } + + idmin = IDL_MAX( BDB_IDL_FIRST(a), BDB_IDL_FIRST(b) ); + idmax = IDL_MIN( BDB_IDL_LAST(a), BDB_IDL_LAST(b) ); + if ( idmin > idmax ) { + a[0] = 0; + return 0; + } else if ( idmin == idmax ) { + a[0] = 1; + a[1] = idmin; + return 0; + } + + if ( BDB_IDL_IS_RANGE( a ) ) { + if ( BDB_IDL_IS_RANGE(b) ) { + /* If both are ranges, just shrink the boundaries */ + a[1] = idmin; + a[2] = idmax; + return 0; + } else { + /* Else swap so that b is the range, a is a list */ + ID *tmp = a; + a = b; + b = tmp; + swap = 1; + } + } + + /* If a range completely covers the list, the result is + * just the list. + */ + if ( BDB_IDL_IS_RANGE( b ) + && BDB_IDL_RANGE_FIRST( b ) <= BDB_IDL_FIRST( a ) + && BDB_IDL_RANGE_LAST( b ) >= BDB_IDL_LLAST( a ) ) { + goto done; + } + + /* Fine, do the intersection one element at a time. + * First advance to idmin in both IDLs. + */ + cursora = cursorb = idmin; + ida = bdb_idl_first( a, &cursora ); + idb = bdb_idl_first( b, &cursorb ); + cursorc = 0; + + while( ida <= idmax || idb <= idmax ) { + if( ida == idb ) { + a[++cursorc] = ida; + ida = bdb_idl_next( a, &cursora ); + idb = bdb_idl_next( b, &cursorb ); + } else if ( ida < idb ) { + ida = bdb_idl_next( a, &cursora ); + } else { + idb = bdb_idl_next( b, &cursorb ); + } + } + a[0] = cursorc; +done: + if (swap) + BDB_IDL_CPY( b, a ); + + return 0; +} + + +/* + * idl_union - return a = a union b + */ +int +bdb_idl_union( + ID *a, + ID *b ) +{ + ID ida, idb; + ID cursora = 0, cursorb = 0, cursorc; + + if ( BDB_IDL_IS_ZERO( b ) ) { + return 0; + } + + if ( BDB_IDL_IS_ZERO( a ) ) { + BDB_IDL_CPY( a, b ); + return 0; + } + + if ( BDB_IDL_IS_RANGE( a ) || BDB_IDL_IS_RANGE(b) ) { +over: ida = IDL_MIN( BDB_IDL_FIRST(a), BDB_IDL_FIRST(b) ); + idb = IDL_MAX( BDB_IDL_LAST(a), BDB_IDL_LAST(b) ); + a[0] = NOID; + a[1] = ida; + a[2] = idb; + return 0; + } + + ida = bdb_idl_first( a, &cursora ); + idb = bdb_idl_first( b, &cursorb ); + + cursorc = b[0]; + + /* The distinct elements of a are cat'd to b */ + while( ida != NOID || idb != NOID ) { + if ( ida < idb ) { + if( ++cursorc > BDB_IDL_UM_MAX ) { + goto over; + } + b[cursorc] = ida; + ida = bdb_idl_next( a, &cursora ); + + } else { + if ( ida == idb ) + ida = bdb_idl_next( a, &cursora ); + idb = bdb_idl_next( b, &cursorb ); + } + } + + /* b is copied back to a in sorted order */ + a[0] = cursorc; + cursora = 1; + cursorb = 1; + cursorc = b[0]+1; + while (cursorb <= b[0] || cursorc <= a[0]) { + if (cursorc > a[0]) + idb = NOID; + else + idb = b[cursorc]; + if (cursorb <= b[0] && b[cursorb] < idb) + a[cursora++] = b[cursorb++]; + else { + a[cursora++] = idb; + cursorc++; + } + } + + return 0; +} + + +#if 0 +/* + * bdb_idl_notin - return a intersection ~b (or a minus b) + */ +int +bdb_idl_notin( + ID *a, + ID *b, + ID *ids ) +{ + ID ida, idb; + ID cursora = 0, cursorb = 0; + + if( BDB_IDL_IS_ZERO( a ) || + BDB_IDL_IS_ZERO( b ) || + BDB_IDL_IS_RANGE( b ) ) + { + BDB_IDL_CPY( ids, a ); + return 0; + } + + if( BDB_IDL_IS_RANGE( a ) ) { + BDB_IDL_CPY( ids, a ); + return 0; + } + + ida = bdb_idl_first( a, &cursora ), + idb = bdb_idl_first( b, &cursorb ); + + ids[0] = 0; + + while( ida != NOID ) { + if ( idb == NOID ) { + /* we could shortcut this */ + ids[++ids[0]] = ida; + ida = bdb_idl_next( a, &cursora ); + + } else if ( ida < idb ) { + ids[++ids[0]] = ida; + ida = bdb_idl_next( a, &cursora ); + + } else if ( ida > idb ) { + idb = bdb_idl_next( b, &cursorb ); + + } else { + ida = bdb_idl_next( a, &cursora ); + idb = bdb_idl_next( b, &cursorb ); + } + } + + return 0; +} +#endif + +ID bdb_idl_first( ID *ids, ID *cursor ) +{ + ID pos; + + if ( ids[0] == 0 ) { + *cursor = NOID; + return NOID; + } + + if ( BDB_IDL_IS_RANGE( ids ) ) { + if( *cursor < ids[1] ) { + *cursor = ids[1]; + } + return *cursor; + } + + if ( *cursor == 0 ) + pos = 1; + else + pos = bdb_idl_search( ids, *cursor ); + + if( pos > ids[0] ) { + return NOID; + } + + *cursor = pos; + return ids[pos]; +} + +ID bdb_idl_next( ID *ids, ID *cursor ) +{ + if ( BDB_IDL_IS_RANGE( ids ) ) { + if( ids[2] < ++(*cursor) ) { + return NOID; + } + return *cursor; + } + + if ( ++(*cursor) <= ids[0] ) { + return ids[*cursor]; + } + + return NOID; +} + +#ifdef BDB_HIER + +/* Add one ID to an unsorted list. We ensure that the first element is the + * minimum and the last element is the maximum, for fast range compaction. + * this means IDLs up to length 3 are always sorted... + */ +int bdb_idl_append_one( ID *ids, ID id ) +{ + if (BDB_IDL_IS_RANGE( ids )) { + /* if already in range, treat as a dup */ + if (id >= BDB_IDL_RANGE_FIRST(ids) && id <= BDB_IDL_RANGE_LAST(ids)) + return -1; + if (id < BDB_IDL_RANGE_FIRST(ids)) + ids[1] = id; + else if (id > BDB_IDL_RANGE_LAST(ids)) + ids[2] = id; + return 0; + } + if ( ids[0] ) { + ID tmp; + + if (id < ids[1]) { + tmp = ids[1]; + ids[1] = id; + id = tmp; + } + if ( ids[0] > 1 && id < ids[ids[0]] ) { + tmp = ids[ids[0]]; + ids[ids[0]] = id; + id = tmp; + } + } + ids[0]++; + if ( ids[0] >= BDB_IDL_UM_MAX ) { + ids[0] = NOID; + ids[2] = id; + } else { + ids[ids[0]] = id; + } + return 0; +} + +/* Append sorted list b to sorted list a. The result is unsorted but + * a[1] is the min of the result and a[a[0]] is the max. + */ +int bdb_idl_append( ID *a, ID *b ) +{ + ID ida, idb, tmp, swap = 0; + + if ( BDB_IDL_IS_ZERO( b ) ) { + return 0; + } + + if ( BDB_IDL_IS_ZERO( a ) ) { + BDB_IDL_CPY( a, b ); + return 0; + } + + if ( b[0] == 1 ) { + return bdb_idl_append_one( a, BDB_IDL_FIRST( b )); + } + + ida = BDB_IDL_LAST( a ); + idb = BDB_IDL_LAST( b ); + if ( BDB_IDL_IS_RANGE( a ) || BDB_IDL_IS_RANGE(b) || + a[0] + b[0] >= BDB_IDL_UM_MAX ) { + a[2] = IDL_MAX( ida, idb ); + a[1] = IDL_MIN( a[1], b[1] ); + a[0] = NOID; + return 0; + } + + if ( ida > idb ) { + swap = idb; + a[a[0]] = idb; + b[b[0]] = ida; + } + + if ( b[1] < a[1] ) { + tmp = a[1]; + a[1] = b[1]; + } else { + tmp = b[1]; + } + a[0]++; + a[a[0]] = tmp; + + { + int i = b[0] - 1; + AC_MEMCPY(a+a[0]+1, b+2, i * sizeof(ID)); + a[0] += i; + } + if ( swap ) { + b[b[0]] = swap; + } + return 0; +} + +#if 1 + +/* Quicksort + Insertion sort for small arrays */ + +#define SMALL 8 +#define SWAP(a,b) itmp=(a);(a)=(b);(b)=itmp + +void +bdb_idl_sort( ID *ids, ID *tmp ) +{ + int *istack = (int *)tmp; + int i,j,k,l,ir,jstack; + ID a, itmp; + + if ( BDB_IDL_IS_RANGE( ids )) + return; + + ir = ids[0]; + l = 1; + jstack = 0; + for(;;) { + if (ir - l < SMALL) { /* Insertion sort */ + for (j=l+1;j<=ir;j++) { + a = ids[j]; + for (i=j-1;i>=1;i--) { + if (ids[i] <= a) break; + ids[i+1] = ids[i]; + } + ids[i+1] = a; + } + if (jstack == 0) break; + ir = istack[jstack--]; + l = istack[jstack--]; + } else { + k = (l + ir) >> 1; /* Choose median of left, center, right */ + SWAP(ids[k], ids[l+1]); + if (ids[l] > ids[ir]) { + SWAP(ids[l], ids[ir]); + } + if (ids[l+1] > ids[ir]) { + SWAP(ids[l+1], ids[ir]); + } + if (ids[l] > ids[l+1]) { + SWAP(ids[l], ids[l+1]); + } + i = l+1; + j = ir; + a = ids[l+1]; + for(;;) { + do i++; while(ids[i] < a); + do j--; while(ids[j] > a); + if (j < i) break; + SWAP(ids[i],ids[j]); + } + ids[l+1] = ids[j]; + ids[j] = a; + jstack += 2; + if (ir-i+1 >= j-1) { + istack[jstack] = ir; + istack[jstack-1] = i; + ir = j-1; + } else { + istack[jstack] = j-1; + istack[jstack-1] = l; + l = i; + } + } + } +} + +#else + +/* 8 bit Radix sort + insertion sort + * + * based on code from http://www.cubic.org/docs/radix.htm + * with improvements by ebackes@symas.com and hyc@symas.com + * + * This code is O(n) but has a relatively high constant factor. For lists + * up to ~50 Quicksort is slightly faster; up to ~100 they are even. + * Much faster than quicksort for lists longer than ~100. Insertion + * sort is actually superior for lists <50. + */ + +#define BUCKETS (1<<8) +#define SMALL 50 + +void +bdb_idl_sort( ID *ids, ID *tmp ) +{ + int count, soft_limit, phase = 0, size = ids[0]; + ID *idls[2]; + unsigned char *maxv = (unsigned char *)&ids[size]; + + if ( BDB_IDL_IS_RANGE( ids )) + return; + + /* Use insertion sort for small lists */ + if ( size <= SMALL ) { + int i,j; + ID a; + + for (j=1;j<=size;j++) { + a = ids[j]; + for (i=j-1;i>=1;i--) { + if (ids[i] <= a) break; + ids[i+1] = ids[i]; + } + ids[i+1] = a; + } + return; + } + + tmp[0] = size; + idls[0] = ids; + idls[1] = tmp; + +#if BYTE_ORDER == BIG_ENDIAN + for (soft_limit = 0; !maxv[soft_limit]; soft_limit++); +#else + for (soft_limit = sizeof(ID)-1; !maxv[soft_limit]; soft_limit--); +#endif + + for ( +#if BYTE_ORDER == BIG_ENDIAN + count = sizeof(ID)-1; count >= soft_limit; --count +#else + count = 0; count <= soft_limit; ++count +#endif + ) { + unsigned int num[BUCKETS], * np, n, sum; + int i; + ID *sp, *source, *dest; + unsigned char *bp, *source_start; + + source = idls[phase]+1; + dest = idls[phase^1]+1; + source_start = ((unsigned char *) source) + count; + + np = num; + for ( i = BUCKETS; i > 0; --i ) *np++ = 0; + + /* count occurences of every byte value */ + bp = source_start; + for ( i = size; i > 0; --i, bp += sizeof(ID) ) + num[*bp]++; + + /* transform count into index by summing elements and storing + * into same array + */ + sum = 0; + np = num; + for ( i = BUCKETS; i > 0; --i ) { + n = *np; + *np++ = sum; + sum += n; + } + + /* fill dest with the right values in the right place */ + bp = source_start; + sp = source; + for ( i = size; i > 0; --i, bp += sizeof(ID) ) { + np = num + *bp; + dest[*np] = *sp++; + ++(*np); + } + phase ^= 1; + } + + /* copy back from temp if needed */ + if ( phase ) { + ids++; tmp++; + for ( count = 0; count < size; ++count ) + *ids++ = *tmp++; + } +} +#endif /* Quick vs Radix */ + +#endif /* BDB_HIER */ diff --git a/servers/slapd/back-bdb/idl.h b/servers/slapd/back-bdb/idl.h new file mode 100644 index 0000000..1909054 --- /dev/null +++ b/servers/slapd/back-bdb/idl.h @@ -0,0 +1,75 @@ +/* idl.h - ldap bdb back-end ID list header file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#ifndef _BDB_IDL_H_ +#define _BDB_IDL_H_ + +/* IDL sizes - likely should be even bigger + * limiting factors: sizeof(ID), thread stack size + */ +#define BDB_IDL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */ +#define BDB_IDL_DB_SIZE (1<<BDB_IDL_LOGN) +#define BDB_IDL_UM_SIZE (1<<(BDB_IDL_LOGN+1)) +#define BDB_IDL_UM_SIZEOF (BDB_IDL_UM_SIZE * sizeof(ID)) + +#define BDB_IDL_DB_MAX (BDB_IDL_DB_SIZE-1) + +#define BDB_IDL_UM_MAX (BDB_IDL_UM_SIZE-1) + +#define BDB_IDL_IS_RANGE(ids) ((ids)[0] == NOID) +#define BDB_IDL_RANGE_SIZE (3) +#define BDB_IDL_RANGE_SIZEOF (BDB_IDL_RANGE_SIZE * sizeof(ID)) +#define BDB_IDL_SIZEOF(ids) ((BDB_IDL_IS_RANGE(ids) \ + ? BDB_IDL_RANGE_SIZE : ((ids)[0]+1)) * sizeof(ID)) + +#define BDB_IDL_RANGE_FIRST(ids) ((ids)[1]) +#define BDB_IDL_RANGE_LAST(ids) ((ids)[2]) + +#define BDB_IDL_RANGE( ids, f, l ) \ + do { \ + (ids)[0] = NOID; \ + (ids)[1] = (f); \ + (ids)[2] = (l); \ + } while(0) + +#define BDB_IDL_ZERO(ids) \ + do { \ + (ids)[0] = 0; \ + (ids)[1] = 0; \ + (ids)[2] = 0; \ + } while(0) + +#define BDB_IDL_IS_ZERO(ids) ( (ids)[0] == 0 ) +#define BDB_IDL_IS_ALL( range, ids ) ( (ids)[0] == NOID \ + && (ids)[1] <= (range)[1] && (range)[2] <= (ids)[2] ) + +#define BDB_IDL_CPY( dst, src ) (AC_MEMCPY( dst, src, BDB_IDL_SIZEOF( src ) )) + +#define BDB_IDL_ID( bdb, ids, id ) BDB_IDL_RANGE( ids, id, ((bdb)->bi_lastid) ) +#define BDB_IDL_ALL( bdb, ids ) BDB_IDL_RANGE( ids, 1, ((bdb)->bi_lastid) ) + +#define BDB_IDL_FIRST( ids ) ( (ids)[1] ) +#define BDB_IDL_LLAST( ids ) ( (ids)[(ids)[0]] ) +#define BDB_IDL_LAST( ids ) ( BDB_IDL_IS_RANGE(ids) \ + ? (ids)[2] : (ids)[(ids)[0]] ) + +#define BDB_IDL_N( ids ) ( BDB_IDL_IS_RANGE(ids) \ + ? ((ids)[2]-(ids)[1])+1 : (ids)[0] ) + +LDAP_BEGIN_DECL +LDAP_END_DECL + +#endif diff --git a/servers/slapd/back-bdb/index.c b/servers/slapd/back-bdb/index.c new file mode 100644 index 0000000..9666e46 --- /dev/null +++ b/servers/slapd/back-bdb/index.c @@ -0,0 +1,574 @@ +/* index.c - routines for dealing with attribute indexes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-bdb.h" +#include "lutil_hash.h" + +static char presence_keyval[] = {0,0}; +static struct berval presence_key = BER_BVC(presence_keyval); + +AttrInfo *bdb_index_mask( + Backend *be, + AttributeDescription *desc, + struct berval *atname ) +{ + AttributeType *at; + AttrInfo *ai = bdb_attr_mask( be->be_private, desc ); + + if( ai ) { + *atname = desc->ad_cname; + return ai; + } + + /* If there is a tagging option, did we ever index the base + * type? If so, check for mask, otherwise it's not there. + */ + if( slap_ad_is_tagged( desc ) && desc != desc->ad_type->sat_ad ) { + /* has tagging option */ + ai = bdb_attr_mask( be->be_private, desc->ad_type->sat_ad ); + + if ( ai && !( ai->ai_indexmask & SLAP_INDEX_NOTAGS ) ) { + *atname = desc->ad_type->sat_cname; + return ai; + } + } + + /* see if supertype defined mask for its subtypes */ + for( at = desc->ad_type; at != NULL ; at = at->sat_sup ) { + /* If no AD, we've never indexed this type */ + if ( !at->sat_ad ) continue; + + ai = bdb_attr_mask( be->be_private, at->sat_ad ); + + if ( ai && !( ai->ai_indexmask & SLAP_INDEX_NOSUBTYPES ) ) { + *atname = at->sat_cname; + return ai; + } + } + + return 0; +} + +/* This function is only called when evaluating search filters. + */ +int bdb_index_param( + Backend *be, + AttributeDescription *desc, + int ftype, + DB **dbp, + slap_mask_t *maskp, + struct berval *prefixp ) +{ + AttrInfo *ai; + int rc; + slap_mask_t mask, type = 0; + DB *db; + + ai = bdb_index_mask( be, desc, prefixp ); + + if ( !ai ) { +#ifdef BDB_MONITOR_IDX + switch ( ftype ) { + case LDAP_FILTER_PRESENT: + type = SLAP_INDEX_PRESENT; + break; + case LDAP_FILTER_APPROX: + type = SLAP_INDEX_APPROX; + break; + case LDAP_FILTER_EQUALITY: + type = SLAP_INDEX_EQUALITY; + break; + case LDAP_FILTER_SUBSTRINGS: + type = SLAP_INDEX_SUBSTR; + break; + default: + return LDAP_INAPPROPRIATE_MATCHING; + } + bdb_monitor_idx_add( be->be_private, desc, type ); +#endif /* BDB_MONITOR_IDX */ + + return LDAP_INAPPROPRIATE_MATCHING; + } + mask = ai->ai_indexmask; + + rc = bdb_db_cache( be, prefixp, &db ); + + if( rc != LDAP_SUCCESS ) { + return rc; + } + + switch( ftype ) { + case LDAP_FILTER_PRESENT: + type = SLAP_INDEX_PRESENT; + if( IS_SLAP_INDEX( mask, SLAP_INDEX_PRESENT ) ) { + *prefixp = presence_key; + goto done; + } + break; + + case LDAP_FILTER_APPROX: + type = SLAP_INDEX_APPROX; + if ( desc->ad_type->sat_approx ) { + if( IS_SLAP_INDEX( mask, SLAP_INDEX_APPROX ) ) { + goto done; + } + break; + } + + /* Use EQUALITY rule and index for approximate match */ + /* fall thru */ + + case LDAP_FILTER_EQUALITY: + type = SLAP_INDEX_EQUALITY; + if( IS_SLAP_INDEX( mask, SLAP_INDEX_EQUALITY ) ) { + goto done; + } + break; + + case LDAP_FILTER_SUBSTRINGS: + type = SLAP_INDEX_SUBSTR; + if( IS_SLAP_INDEX( mask, SLAP_INDEX_SUBSTR ) ) { + goto done; + } + break; + + default: + return LDAP_OTHER; + } + +#ifdef BDB_MONITOR_IDX + bdb_monitor_idx_add( be->be_private, desc, type ); +#endif /* BDB_MONITOR_IDX */ + + return LDAP_INAPPROPRIATE_MATCHING; + +done: + *dbp = db; + *maskp = mask; + return LDAP_SUCCESS; +} + +static int indexer( + Operation *op, + DB_TXN *txn, + AttributeDescription *ad, + struct berval *atname, + BerVarray vals, + ID id, + int opid, + slap_mask_t mask ) +{ + int rc, i; + DB *db; + struct berval *keys; + + assert( mask != 0 ); + + rc = bdb_db_cache( op->o_bd, atname, &db ); + + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "bdb_index_read: Could not open DB %s\n", + atname->bv_val, 0, 0 ); + return LDAP_OTHER; + } + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_PRESENT ) ) { + rc = bdb_key_change( op->o_bd, db, txn, &presence_key, id, opid ); + if( rc ) { + goto done; + } + } + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_EQUALITY ) ) { + rc = ad->ad_type->sat_equality->smr_indexer( + LDAP_FILTER_EQUALITY, + mask, + ad->ad_type->sat_syntax, + ad->ad_type->sat_equality, + atname, vals, &keys, op->o_tmpmemctx ); + + if( rc == LDAP_SUCCESS && keys != NULL ) { + for( i=0; keys[i].bv_val != NULL; i++ ) { + rc = bdb_key_change( op->o_bd, db, txn, &keys[i], id, opid ); + if( rc ) { + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + goto done; + } + } + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + } + rc = LDAP_SUCCESS; + } + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_APPROX ) ) { + rc = ad->ad_type->sat_approx->smr_indexer( + LDAP_FILTER_APPROX, + mask, + ad->ad_type->sat_syntax, + ad->ad_type->sat_approx, + atname, vals, &keys, op->o_tmpmemctx ); + + if( rc == LDAP_SUCCESS && keys != NULL ) { + for( i=0; keys[i].bv_val != NULL; i++ ) { + rc = bdb_key_change( op->o_bd, db, txn, &keys[i], id, opid ); + if( rc ) { + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + goto done; + } + } + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + } + + rc = LDAP_SUCCESS; + } + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_SUBSTR ) ) { + rc = ad->ad_type->sat_substr->smr_indexer( + LDAP_FILTER_SUBSTRINGS, + mask, + ad->ad_type->sat_syntax, + ad->ad_type->sat_substr, + atname, vals, &keys, op->o_tmpmemctx ); + + if( rc == LDAP_SUCCESS && keys != NULL ) { + for( i=0; keys[i].bv_val != NULL; i++ ) { + rc = bdb_key_change( op->o_bd, db, txn, &keys[i], id, opid ); + if( rc ) { + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + goto done; + } + } + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + } + + rc = LDAP_SUCCESS; + } + +done: + switch( rc ) { + /* The callers all know how to deal with these results */ + case 0: + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + break; + /* Anything else is bad news */ + default: + rc = LDAP_OTHER; + } + return rc; +} + +static int index_at_values( + Operation *op, + DB_TXN *txn, + AttributeDescription *ad, + AttributeType *type, + struct berval *tags, + BerVarray vals, + ID id, + int opid ) +{ + int rc; + slap_mask_t mask = 0; + int ixop = opid; + AttrInfo *ai = NULL; + + if ( opid == BDB_INDEX_UPDATE_OP ) + ixop = SLAP_INDEX_ADD_OP; + + if( type->sat_sup ) { + /* recurse */ + rc = index_at_values( op, txn, NULL, + type->sat_sup, tags, + vals, id, opid ); + + if( rc ) return rc; + } + + /* If this type has no AD, we've never used it before */ + if( type->sat_ad ) { + ai = bdb_attr_mask( op->o_bd->be_private, type->sat_ad ); + if ( ai ) { +#ifdef LDAP_COMP_MATCH + /* component indexing */ + if ( ai->ai_cr ) { + ComponentReference *cr; + for( cr = ai->ai_cr ; cr ; cr = cr->cr_next ) { + rc = indexer( op, txn, cr->cr_ad, &type->sat_cname, + cr->cr_nvals, id, ixop, + cr->cr_indexmask ); + } + } +#endif + ad = type->sat_ad; + /* If we're updating the index, just set the new bits that aren't + * already in the old mask. + */ + if ( opid == BDB_INDEX_UPDATE_OP ) + mask = ai->ai_newmask & ~ai->ai_indexmask; + else + /* For regular updates, if there is a newmask use it. Otherwise + * just use the old mask. + */ + mask = ai->ai_newmask ? ai->ai_newmask : ai->ai_indexmask; + if( mask ) { + rc = indexer( op, txn, ad, &type->sat_cname, + vals, id, ixop, mask ); + + if( rc ) return rc; + } + } + } + + if( tags->bv_len ) { + AttributeDescription *desc; + + desc = ad_find_tags( type, tags ); + if( desc ) { + ai = bdb_attr_mask( op->o_bd->be_private, desc ); + + if( ai ) { + if ( opid == BDB_INDEX_UPDATE_OP ) + mask = ai->ai_newmask & ~ai->ai_indexmask; + else + mask = ai->ai_newmask ? ai->ai_newmask : ai->ai_indexmask; + if ( mask ) { + rc = indexer( op, txn, desc, &desc->ad_cname, + vals, id, ixop, mask ); + + if( rc ) { + return rc; + } + } + } + } + } + + return LDAP_SUCCESS; +} + +int bdb_index_values( + Operation *op, + DB_TXN *txn, + AttributeDescription *desc, + BerVarray vals, + ID id, + int opid ) +{ + int rc; + + /* Never index ID 0 */ + if ( id == 0 ) + return 0; + + rc = index_at_values( op, txn, desc, + desc->ad_type, &desc->ad_tags, + vals, id, opid ); + + return rc; +} + +/* Get the list of which indices apply to this attr */ +int +bdb_index_recset( + struct bdb_info *bdb, + Attribute *a, + AttributeType *type, + struct berval *tags, + IndexRec *ir ) +{ + int rc, slot; + AttrList *al; + + if( type->sat_sup ) { + /* recurse */ + rc = bdb_index_recset( bdb, a, type->sat_sup, tags, ir ); + if( rc ) return rc; + } + /* If this type has no AD, we've never used it before */ + if( type->sat_ad ) { + slot = bdb_attr_slot( bdb, type->sat_ad, NULL ); + if ( slot >= 0 ) { + ir[slot].ai = bdb->bi_attrs[slot]; + al = ch_malloc( sizeof( AttrList )); + al->attr = a; + al->next = ir[slot].attrs; + ir[slot].attrs = al; + } + } + if( tags->bv_len ) { + AttributeDescription *desc; + + desc = ad_find_tags( type, tags ); + if( desc ) { + slot = bdb_attr_slot( bdb, desc, NULL ); + if ( slot >= 0 ) { + ir[slot].ai = bdb->bi_attrs[slot]; + al = ch_malloc( sizeof( AttrList )); + al->attr = a; + al->next = ir[slot].attrs; + ir[slot].attrs = al; + } + } + } + return LDAP_SUCCESS; +} + +/* Apply the indices for the recset */ +int bdb_index_recrun( + Operation *op, + struct bdb_info *bdb, + IndexRec *ir0, + ID id, + int base ) +{ + IndexRec *ir; + AttrList *al; + int i, rc = 0; + + /* Never index ID 0 */ + if ( id == 0 ) + return 0; + + for (i=base; i<bdb->bi_nattrs; i+=slap_tool_thread_max-1) { + ir = ir0 + i; + if ( !ir->ai ) continue; + while (( al = ir->attrs )) { + ir->attrs = al->next; + rc = indexer( op, NULL, ir->ai->ai_desc, + &ir->ai->ai_desc->ad_type->sat_cname, + al->attr->a_nvals, id, SLAP_INDEX_ADD_OP, + ir->ai->ai_indexmask ); + free( al ); + if ( rc ) break; + } + } + return rc; +} + +int +bdb_index_entry( + Operation *op, + DB_TXN *txn, + int opid, + Entry *e ) +{ + int rc; + Attribute *ap = e->e_attrs; +#if 0 /* ifdef LDAP_COMP_MATCH */ + ComponentReference *cr_list = NULL; + ComponentReference *cr = NULL, *dupped_cr = NULL; + void* decoded_comp; + ComponentSyntaxInfo* csi_attr; + Syntax* syn; + AttributeType* at; + int i, num_attr; + void* mem_op; + struct berval value = {0}; +#endif + + /* Never index ID 0 */ + if ( e->e_id == 0 ) + return 0; + + Debug( LDAP_DEBUG_TRACE, "=> index_entry_%s( %ld, \"%s\" )\n", + opid == SLAP_INDEX_DELETE_OP ? "del" : "add", + (long) e->e_id, e->e_dn ); + + /* add each attribute to the indexes */ + for ( ; ap != NULL; ap = ap->a_next ) { +#if 0 /* ifdef LDAP_COMP_MATCH */ + AttrInfo *ai; + /* see if attribute has components to be indexed */ + ai = bdb_attr_mask( op->o_bd->be_private, ap->a_desc->ad_type->sat_ad ); + if ( !ai ) continue; + cr_list = ai->ai_cr; + if ( attr_converter && cr_list ) { + syn = ap->a_desc->ad_type->sat_syntax; + ap->a_comp_data = op->o_tmpalloc( sizeof( ComponentData ), op->o_tmpmemctx ); + /* Memory chunk(nibble) pre-allocation for decoders */ + mem_op = nibble_mem_allocator ( 1024*16, 1024*4 ); + ap->a_comp_data->cd_mem_op = mem_op; + for( cr = cr_list ; cr ; cr = cr->cr_next ) { + /* count how many values in an attribute */ + for( num_attr=0; ap->a_vals[num_attr].bv_val != NULL; num_attr++ ); + num_attr++; + cr->cr_nvals = (BerVarray)op->o_tmpalloc( sizeof( struct berval )*num_attr, op->o_tmpmemctx ); + for( i=0; ap->a_vals[i].bv_val != NULL; i++ ) { + /* decoding attribute value */ + decoded_comp = attr_converter ( ap, syn, &ap->a_vals[i] ); + if ( !decoded_comp ) + return LDAP_DECODING_ERROR; + /* extracting the referenced component */ + dupped_cr = dup_comp_ref( op, cr ); + csi_attr = ((ComponentSyntaxInfo*)decoded_comp)->csi_comp_desc->cd_extract_i( mem_op, dupped_cr, decoded_comp ); + if ( !csi_attr ) + return LDAP_DECODING_ERROR; + cr->cr_asn_type_id = csi_attr->csi_comp_desc->cd_type_id; + cr->cr_ad = (AttributeDescription*)get_component_description ( cr->cr_asn_type_id ); + if ( !cr->cr_ad ) + return LDAP_INVALID_SYNTAX; + at = cr->cr_ad->ad_type; + /* encoding the value of component in GSER */ + rc = component_encoder( mem_op, csi_attr, &value ); + if ( rc != LDAP_SUCCESS ) + return LDAP_ENCODING_ERROR; + /* Normalize the encoded component values */ + if ( at->sat_equality && at->sat_equality->smr_normalize ) { + rc = at->sat_equality->smr_normalize ( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + at->sat_syntax, at->sat_equality, + &value, &cr->cr_nvals[i], op->o_tmpmemctx ); + } else { + cr->cr_nvals[i] = value; + } + } + /* The end of BerVarray */ + cr->cr_nvals[num_attr-1].bv_val = NULL; + cr->cr_nvals[num_attr-1].bv_len = 0; + } + op->o_tmpfree( ap->a_comp_data, op->o_tmpmemctx ); + nibble_mem_free ( mem_op ); + ap->a_comp_data = NULL; + } +#endif + rc = bdb_index_values( op, txn, ap->a_desc, + ap->a_nvals, e->e_id, opid ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= index_entry_%s( %ld, \"%s\" ) failure\n", + opid == SLAP_INDEX_ADD_OP ? "add" : "del", + (long) e->e_id, e->e_dn ); + return rc; + } + } + + Debug( LDAP_DEBUG_TRACE, "<= index_entry_%s( %ld, \"%s\" ) success\n", + opid == SLAP_INDEX_DELETE_OP ? "del" : "add", + (long) e->e_id, e->e_dn ); + + return LDAP_SUCCESS; +} diff --git a/servers/slapd/back-bdb/init.c b/servers/slapd/back-bdb/init.c new file mode 100644 index 0000000..86a1a97 --- /dev/null +++ b/servers/slapd/back-bdb/init.c @@ -0,0 +1,874 @@ +/* init.c - initialize bdb backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/unistd.h> +#include <ac/stdlib.h> +#include <ac/errno.h> +#include <sys/stat.h> +#include "back-bdb.h" +#include <lutil.h> +#include <ldap_rq.h> +#include "alock.h" +#include "config.h" + +static const struct bdbi_database { + char *file; + struct berval name; + int type; + int flags; +} bdbi_databases[] = { + { "id2entry" BDB_SUFFIX, BER_BVC("id2entry"), DB_BTREE, 0 }, + { "dn2id" BDB_SUFFIX, BER_BVC("dn2id"), DB_BTREE, 0 }, + { NULL, BER_BVNULL, 0, 0 } +}; + +typedef void * db_malloc(size_t); +typedef void * db_realloc(void *, size_t); + +#define bdb_db_init BDB_SYMBOL(db_init) +#define bdb_db_open BDB_SYMBOL(db_open) +#define bdb_db_close BDB_SYMBOL(db_close) + +static int +bdb_db_init( BackendDB *be, ConfigReply *cr ) +{ + struct bdb_info *bdb; + int rc; + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_db_init) ": Initializing " BDB_UCTYPE " database\n", + 0, 0, 0 ); + + /* allocate backend-database-specific stuff */ + bdb = (struct bdb_info *) ch_calloc( 1, sizeof(struct bdb_info) ); + + /* DBEnv parameters */ + bdb->bi_dbenv_home = ch_strdup( SLAPD_DEFAULT_DB_DIR ); + bdb->bi_dbenv_xflags = DB_TIME_NOTGRANTED; + bdb->bi_dbenv_mode = SLAPD_DEFAULT_DB_MODE; + + bdb->bi_cache.c_maxsize = DEFAULT_CACHE_SIZE; + bdb->bi_cache.c_minfree = 1; + + bdb->bi_lock_detect = DB_LOCK_DEFAULT; + bdb->bi_search_stack_depth = DEFAULT_SEARCH_STACK_DEPTH; + bdb->bi_search_stack = NULL; + + ldap_pvt_thread_mutex_init( &bdb->bi_database_mutex ); + ldap_pvt_thread_mutex_init( &bdb->bi_lastid_mutex ); +#ifdef BDB_HIER + ldap_pvt_thread_mutex_init( &bdb->bi_modrdns_mutex ); +#endif + ldap_pvt_thread_mutex_init( &bdb->bi_cache.c_lru_mutex ); + ldap_pvt_thread_mutex_init( &bdb->bi_cache.c_count_mutex ); + ldap_pvt_thread_mutex_init( &bdb->bi_cache.c_eifree_mutex ); + ldap_pvt_thread_mutex_init( &bdb->bi_cache.c_dntree.bei_kids_mutex ); + ldap_pvt_thread_rdwr_init ( &bdb->bi_cache.c_rwlock ); + ldap_pvt_thread_rdwr_init( &bdb->bi_idl_tree_rwlock ); + ldap_pvt_thread_mutex_init( &bdb->bi_idl_tree_lrulock ); + + be->be_private = bdb; + be->be_cf_ocs = be->bd_info->bi_cf_ocs; + +#ifndef BDB_MULTIPLE_SUFFIXES + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_ONE_SUFFIX; +#endif + + rc = bdb_monitor_db_init( be ); + + return rc; +} + +static int +bdb_db_close( BackendDB *be, ConfigReply *cr ); + +static int +bdb_db_open( BackendDB *be, ConfigReply *cr ) +{ + int rc, i; + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + struct stat stat1, stat2; + u_int32_t flags; + char path[MAXPATHLEN]; + char *dbhome; + Entry *e = NULL; + int do_recover = 0, do_alock_recover = 0; + int alockt, quick = 0; + int do_retry = 1; + + if ( be->be_suffix == NULL ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": need suffix.\n", + 1, 0, 0 ); + return -1; + } + + Debug( LDAP_DEBUG_ARGS, + LDAP_XSTRING(bdb_db_open) ": \"%s\"\n", + be->be_suffix[0].bv_val, 0, 0 ); + + /* Check existence of dbenv_home. Any error means trouble */ + rc = stat( bdb->bi_dbenv_home, &stat1 ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "cannot access database directory \"%s\" (%d).\n", + be->be_suffix[0].bv_val, bdb->bi_dbenv_home, errno ); + return -1; + } + + /* Perform database use arbitration/recovery logic */ + alockt = (slapMode & SLAP_TOOL_READONLY) ? ALOCK_LOCKED : ALOCK_UNIQUE; + if ( slapMode & SLAP_TOOL_QUICK ) { + alockt |= ALOCK_NOSAVE; + quick = 1; + } + + rc = alock_open( &bdb->bi_alock_info, + "slapd", + bdb->bi_dbenv_home, alockt ); + + /* alockt is TRUE if the existing environment was created in Quick mode */ + alockt = (rc & ALOCK_NOSAVE) ? 1 : 0; + rc &= ~ALOCK_NOSAVE; + + if( rc == ALOCK_RECOVER ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "unclean shutdown detected; attempting recovery.\n", + be->be_suffix[0].bv_val, 0, 0 ); + do_alock_recover = 1; + do_recover = DB_RECOVER; + } else if( rc == ALOCK_BUSY ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "database already in use.\n", + be->be_suffix[0].bv_val, 0, 0 ); + return -1; + } else if( rc != ALOCK_CLEAN ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "alock package is unstable.\n", + be->be_suffix[0].bv_val, 0, 0 ); + return -1; + } + if ( rc == ALOCK_CLEAN ) + be->be_flags |= SLAP_DBFLAG_CLEAN; + + /* + * The DB_CONFIG file may have changed. If so, recover the + * database so that new settings are put into effect. Also + * note the possible absence of DB_CONFIG in the log. + */ + if( stat( bdb->bi_db_config_path, &stat1 ) == 0 ) { + if ( !do_recover ) { + char *ptr = lutil_strcopy(path, bdb->bi_dbenv_home); + *ptr++ = LDAP_DIRSEP[0]; + strcpy( ptr, "__db.001" ); + if( stat( path, &stat2 ) == 0 ) { + if( stat2.st_mtime < stat1.st_mtime ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": DB_CONFIG for suffix \"%s\" has changed.\n", + be->be_suffix[0].bv_val, 0, 0 ); + if ( quick ) { + Debug( LDAP_DEBUG_ANY, + "Cannot use Quick mode; perform manual recovery first.\n", + 0, 0, 0 ); + slapMode ^= SLAP_TOOL_QUICK; + rc = -1; + goto fail; + } else { + Debug( LDAP_DEBUG_ANY, + "Performing database recovery to activate new settings.\n", + 0, 0, 0 ); + } + do_recover = DB_RECOVER; + } + } + } + } + else { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": warning - no DB_CONFIG file found " + "in directory %s: (%d).\n" + "Expect poor performance for suffix \"%s\".\n", + bdb->bi_dbenv_home, errno, be->be_suffix[0].bv_val ); + } + + /* Always let slapcat run, regardless of environment state. + * This can be used to cause a cache flush after an unclean + * shutdown. + */ + if ( do_recover && ( slapMode & SLAP_TOOL_READONLY )) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "recovery skipped in read-only mode. " + "Run manual recovery if errors are encountered.\n", + be->be_suffix[0].bv_val, 0, 0 ); + do_recover = 0; + do_alock_recover = 0; + quick = alockt; + } + + /* An existing environment in Quick mode has nothing to recover. */ + if ( alockt && do_recover ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "cannot recover, database must be reinitialized.\n", + be->be_suffix[0].bv_val, 0, 0 ); + rc = -1; + goto fail; + } + + rc = db_env_create( &bdb->bi_dbenv, 0 ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "db_env_create failed: %s (%d).\n", + be->be_suffix[0].bv_val, db_strerror(rc), rc ); + goto fail; + } + +#ifdef HAVE_EBCDIC + strcpy( path, bdb->bi_dbenv_home ); + __atoe( path ); + dbhome = path; +#else + dbhome = bdb->bi_dbenv_home; +#endif + + /* If existing environment is clean but doesn't support + * currently requested modes, remove it. + */ + if ( !do_recover && ( alockt ^ quick )) { +shm_retry: + rc = bdb->bi_dbenv->remove( bdb->bi_dbenv, dbhome, DB_FORCE ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "dbenv remove failed: %s (%d).\n", + be->be_suffix[0].bv_val, db_strerror(rc), rc ); + bdb->bi_dbenv = NULL; + goto fail; + } + rc = db_env_create( &bdb->bi_dbenv, 0 ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "db_env_create failed: %s (%d).\n", + be->be_suffix[0].bv_val, db_strerror(rc), rc ); + goto fail; + } + } + + bdb->bi_dbenv->set_errpfx( bdb->bi_dbenv, be->be_suffix[0].bv_val ); + bdb->bi_dbenv->set_errcall( bdb->bi_dbenv, bdb_errcall ); + + bdb->bi_dbenv->set_lk_detect( bdb->bi_dbenv, bdb->bi_lock_detect ); + + if ( !BER_BVISNULL( &bdb->bi_db_crypt_key )) { + rc = bdb->bi_dbenv->set_encrypt( bdb->bi_dbenv, bdb->bi_db_crypt_key.bv_val, + DB_ENCRYPT_AES ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "dbenv set_encrypt failed: %s (%d).\n", + be->be_suffix[0].bv_val, db_strerror(rc), rc ); + goto fail; + } + } + + /* One long-lived TXN per thread, two TXNs per write op */ + bdb->bi_dbenv->set_tx_max( bdb->bi_dbenv, connection_pool_max * 3 ); + + if( bdb->bi_dbenv_xflags != 0 ) { + rc = bdb->bi_dbenv->set_flags( bdb->bi_dbenv, + bdb->bi_dbenv_xflags, 1); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "dbenv_set_flags failed: %s (%d).\n", + be->be_suffix[0].bv_val, db_strerror(rc), rc ); + goto fail; + } + } + +#define BDB_TXN_FLAGS (DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_TXN) + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "dbenv_open(%s).\n", + be->be_suffix[0].bv_val, bdb->bi_dbenv_home, 0); + + flags = DB_INIT_MPOOL | DB_CREATE | DB_THREAD; + + if ( !quick ) + flags |= BDB_TXN_FLAGS; + + /* If a key was set, use shared memory for the BDB environment */ + if ( bdb->bi_shm_key ) { + bdb->bi_dbenv->set_shm_key( bdb->bi_dbenv, bdb->bi_shm_key ); + flags |= DB_SYSTEM_MEM; + } + rc = (bdb->bi_dbenv->open)( bdb->bi_dbenv, dbhome, + flags | do_recover, bdb->bi_dbenv_mode ); + + if ( rc ) { + /* Regular open failed, probably a missing shm environment. + * Start over, do a recovery. + */ + if ( !do_recover && bdb->bi_shm_key && do_retry ) { + bdb->bi_dbenv->close( bdb->bi_dbenv, 0 ); + rc = db_env_create( &bdb->bi_dbenv, 0 ); + if( rc == 0 ) { + Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(bdb_db_open) + ": database \"%s\": " + "shared memory env open failed, assuming stale env.\n", + be->be_suffix[0].bv_val, 0, 0 ); + do_retry = 0; + goto shm_retry; + } + } + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\" cannot be %s, err %d. " + "Restore from backup!\n", + be->be_suffix[0].bv_val, do_recover ? "recovered" : "opened", rc ); + goto fail; + } + + if ( do_alock_recover && alock_recover (&bdb->bi_alock_info) != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": alock_recover failed\n", + be->be_suffix[0].bv_val, 0, 0 ); + rc = -1; + goto fail; + } + +#ifdef SLAP_ZONE_ALLOC + if ( bdb->bi_cache.c_maxsize ) { + bdb->bi_cache.c_zctx = slap_zn_mem_create( + SLAP_ZONE_INITSIZE, SLAP_ZONE_MAXSIZE, + SLAP_ZONE_DELTA, SLAP_ZONE_SIZE); + } +#endif + + /* dncache defaults to 0 == unlimited + * must be >= entrycache + */ + if ( bdb->bi_cache.c_eimax && bdb->bi_cache.c_eimax < bdb->bi_cache.c_maxsize ) { + bdb->bi_cache.c_eimax = bdb->bi_cache.c_maxsize; + } + + if ( bdb->bi_idl_cache_max_size ) { + bdb->bi_idl_tree = NULL; + bdb->bi_idl_cache_size = 0; + } + + flags = DB_THREAD | bdb->bi_db_opflags; + +#ifdef DB_AUTO_COMMIT + if ( !quick ) + flags |= DB_AUTO_COMMIT; +#endif + + bdb->bi_databases = (struct bdb_db_info **) ch_malloc( + BDB_INDICES * sizeof(struct bdb_db_info *) ); + + /* open (and create) main database */ + for( i = 0; bdbi_databases[i].name.bv_val; i++ ) { + struct bdb_db_info *db; + + db = (struct bdb_db_info *) ch_calloc(1, sizeof(struct bdb_db_info)); + + rc = db_create( &db->bdi_db, bdb->bi_dbenv, 0 ); + if( rc != 0 ) { + snprintf(cr->msg, sizeof(cr->msg), + "database \"%s\": db_create(%s) failed: %s (%d).", + be->be_suffix[0].bv_val, + bdb->bi_dbenv_home, db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": %s\n", + cr->msg, 0, 0 ); + ch_free( db ); + goto fail; + } + + if( !BER_BVISNULL( &bdb->bi_db_crypt_key )) { + rc = db->bdi_db->set_flags( db->bdi_db, DB_ENCRYPT ); + if ( rc ) { + snprintf(cr->msg, sizeof(cr->msg), + "database \"%s\": db set_flags(DB_ENCRYPT)(%s) failed: %s (%d).", + be->be_suffix[0].bv_val, + bdb->bi_dbenv_home, db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": %s\n", + cr->msg, 0, 0 ); + db->bdi_db->close( db->bdi_db, 0 ); + ch_free( db ); + goto fail; + } + } + + if( bdb->bi_flags & BDB_CHKSUM ) { + rc = db->bdi_db->set_flags( db->bdi_db, DB_CHKSUM ); + if ( rc ) { + snprintf(cr->msg, sizeof(cr->msg), + "database \"%s\": db set_flags(DB_CHKSUM)(%s) failed: %s (%d).", + be->be_suffix[0].bv_val, + bdb->bi_dbenv_home, db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": %s\n", + cr->msg, 0, 0 ); + db->bdi_db->close( db->bdi_db, 0 ); + ch_free( db ); + goto fail; + } + } + + rc = bdb_db_findsize( bdb, (struct berval *)&bdbi_databases[i].name ); + + if( i == BDB_ID2ENTRY ) { + if ( !rc ) rc = BDB_ID2ENTRY_PAGESIZE; + rc = db->bdi_db->set_pagesize( db->bdi_db, rc ); + + if ( slapMode & SLAP_TOOL_MODE ) + db->bdi_db->mpf->set_priority( db->bdi_db->mpf, + DB_PRIORITY_VERY_LOW ); + + if ( slapMode & SLAP_TOOL_READMAIN ) { + flags |= DB_RDONLY; + } else { + flags |= DB_CREATE; + } + } else { + /* Use FS default size if not configured */ + if ( rc ) + rc = db->bdi_db->set_pagesize( db->bdi_db, rc ); + + rc = db->bdi_db->set_flags( db->bdi_db, + DB_DUP | DB_DUPSORT ); +#ifndef BDB_HIER + if ( slapMode & SLAP_TOOL_READONLY ) { + flags |= DB_RDONLY; + } else { + flags |= DB_CREATE; + } +#else + rc = db->bdi_db->set_dup_compare( db->bdi_db, + bdb_dup_compare ); + if ( slapMode & (SLAP_TOOL_READONLY|SLAP_TOOL_READMAIN) ) { + flags |= DB_RDONLY; + } else { + flags |= DB_CREATE; + } +#endif + } + +#ifdef HAVE_EBCDIC + strcpy( path, bdbi_databases[i].file ); + __atoe( path ); + rc = DB_OPEN( db->bdi_db, + path, + /* bdbi_databases[i].name, */ NULL, + bdbi_databases[i].type, + bdbi_databases[i].flags | flags, + bdb->bi_dbenv_mode ); +#else + rc = DB_OPEN( db->bdi_db, + bdbi_databases[i].file, + /* bdbi_databases[i].name, */ NULL, + bdbi_databases[i].type, + bdbi_databases[i].flags | flags, + bdb->bi_dbenv_mode ); +#endif + + if ( rc != 0 ) { + snprintf( cr->msg, sizeof(cr->msg), "database \"%s\": " + "db_open(%s/%s) failed: %s (%d).", + be->be_suffix[0].bv_val, + bdb->bi_dbenv_home, bdbi_databases[i].file, + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": %s\n", + cr->msg, 0, 0 ); + db->bdi_db->close( db->bdi_db, 0 ); + ch_free( db ); + goto fail; + } + + flags &= ~(DB_CREATE | DB_RDONLY); + db->bdi_name = bdbi_databases[i].name; + bdb->bi_databases[i] = db; + } + + bdb->bi_databases[i] = NULL; + bdb->bi_ndatabases = i; + + /* get nextid */ + rc = bdb_last_id( be, NULL ); + if( rc != 0 ) { + snprintf( cr->msg, sizeof(cr->msg), "database \"%s\": " + "last_id(%s) failed: %s (%d).", + be->be_suffix[0].bv_val, bdb->bi_dbenv_home, + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": %s\n", + cr->msg, 0, 0 ); + goto fail; + } + + if ( !quick ) { + int txflag = DB_READ_COMMITTED; + /* avoid deadlocks in server; tools should + * wait since they have no deadlock retry mechanism. + */ + if ( slapMode & SLAP_SERVER_MODE ) + txflag |= DB_TXN_NOWAIT; + TXN_BEGIN(bdb->bi_dbenv, NULL, &bdb->bi_cache.c_txn, txflag); + } + + entry_prealloc( bdb->bi_cache.c_maxsize ); + attr_prealloc( bdb->bi_cache.c_maxsize * 20 ); + + /* setup for empty-DN contexts */ + if ( BER_BVISEMPTY( &be->be_nsuffix[0] )) { + rc = bdb_id2entry( be, NULL, 0, &e ); + } + if ( !e ) { + struct berval gluebv = BER_BVC("glue"); + Operation op = {0}; + Opheader ohdr = {0}; + e = entry_alloc(); + e->e_id = 0; + ber_dupbv( &e->e_name, (struct berval *)&slap_empty_bv ); + ber_dupbv( &e->e_nname, (struct berval *)&slap_empty_bv ); + attr_merge_one( e, slap_schema.si_ad_objectClass, + &gluebv, NULL ); + attr_merge_one( e, slap_schema.si_ad_structuralObjectClass, + &gluebv, NULL ); + op.o_hdr = &ohdr; + op.o_bd = be; + op.ora_e = e; + op.o_dn = be->be_rootdn; + op.o_ndn = be->be_rootndn; + slap_add_opattrs( &op, NULL, NULL, 0, 0 ); + } + e->e_ocflags = SLAP_OC_GLUE|SLAP_OC__END; + e->e_private = &bdb->bi_cache.c_dntree; + bdb->bi_cache.c_dntree.bei_e = e; + + /* monitor setup */ + rc = bdb_monitor_db_open( be ); + if ( rc != 0 ) { + goto fail; + } + + bdb->bi_flags |= BDB_IS_OPEN; + + return 0; + +fail: + bdb_db_close( be, NULL ); + return rc; +} + +static int +bdb_db_close( BackendDB *be, ConfigReply *cr ) +{ + int rc; + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + struct bdb_db_info *db; + bdb_idl_cache_entry_t *entry, *next_entry; + + /* monitor handling */ + (void)bdb_monitor_db_close( be ); + + { + Entry *e = bdb->bi_cache.c_dntree.bei_e; + if ( e ) { + bdb->bi_cache.c_dntree.bei_e = NULL; + e->e_private = NULL; + bdb_entry_return( e ); + } + } + + bdb->bi_flags &= ~BDB_IS_OPEN; + + ber_bvarray_free( bdb->bi_db_config ); + bdb->bi_db_config = NULL; + + if( bdb->bi_dbenv ) { + /* Free cache locker if we enabled locking. + * TXNs must all be closed before DBs... + */ + if ( !( slapMode & SLAP_TOOL_QUICK ) && bdb->bi_cache.c_txn ) { + TXN_ABORT( bdb->bi_cache.c_txn ); + bdb->bi_cache.c_txn = NULL; + } + bdb_reader_flush( bdb->bi_dbenv ); + } + + while( bdb->bi_databases && bdb->bi_ndatabases-- ) { + db = bdb->bi_databases[bdb->bi_ndatabases]; + rc = db->bdi_db->close( db->bdi_db, 0 ); + /* Lower numbered names are not strdup'd */ + if( bdb->bi_ndatabases >= BDB_NDB ) + free( db->bdi_name.bv_val ); + free( db ); + } + free( bdb->bi_databases ); + bdb->bi_databases = NULL; + + bdb_cache_release_all (&bdb->bi_cache); + + if ( bdb->bi_idl_cache_size ) { + avl_free( bdb->bi_idl_tree, NULL ); + bdb->bi_idl_tree = NULL; + entry = bdb->bi_idl_lru_head; + do { + next_entry = entry->idl_lru_next; + if ( entry->idl ) + free( entry->idl ); + free( entry->kstr.bv_val ); + free( entry ); + entry = next_entry; + } while ( entry != bdb->bi_idl_lru_head ); + bdb->bi_idl_lru_head = bdb->bi_idl_lru_tail = NULL; + } + + /* close db environment */ + if( bdb->bi_dbenv ) { + /* force a checkpoint, but not if we were ReadOnly, + * and not in Quick mode since there are no transactions there. + */ + if ( !( slapMode & ( SLAP_TOOL_QUICK|SLAP_TOOL_READONLY ))) { + rc = TXN_CHECKPOINT( bdb->bi_dbenv, 0, 0, DB_FORCE ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "bdb_db_close: database \"%s\": " + "txn_checkpoint failed: %s (%d).\n", + be->be_suffix[0].bv_val, db_strerror(rc), rc ); + } + } + + rc = bdb->bi_dbenv->close( bdb->bi_dbenv, 0 ); + bdb->bi_dbenv = NULL; + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "bdb_db_close: database \"%s\": " + "close failed: %s (%d)\n", + be->be_suffix[0].bv_val, db_strerror(rc), rc ); + return rc; + } + } + + rc = alock_close( &bdb->bi_alock_info, slapMode & SLAP_TOOL_QUICK ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "bdb_db_close: database \"%s\": alock_close failed\n", + be->be_suffix[0].bv_val, 0, 0 ); + return -1; + } + + return 0; +} + +static int +bdb_db_destroy( BackendDB *be, ConfigReply *cr ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + + /* stop and remove checkpoint task */ + if ( bdb->bi_txn_cp_task ) { + struct re_s *re = bdb->bi_txn_cp_task; + bdb->bi_txn_cp_task = NULL; + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re ) ) + ldap_pvt_runqueue_stoptask( &slapd_rq, re ); + ldap_pvt_runqueue_remove( &slapd_rq, re ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + + /* monitor handling */ + (void)bdb_monitor_db_destroy( be ); + + if( bdb->bi_dbenv_home ) ch_free( bdb->bi_dbenv_home ); + if( bdb->bi_db_config_path ) ch_free( bdb->bi_db_config_path ); + + bdb_attr_index_destroy( bdb ); + + ldap_pvt_thread_rdwr_destroy ( &bdb->bi_cache.c_rwlock ); + ldap_pvt_thread_mutex_destroy( &bdb->bi_cache.c_lru_mutex ); + ldap_pvt_thread_mutex_destroy( &bdb->bi_cache.c_count_mutex ); + ldap_pvt_thread_mutex_destroy( &bdb->bi_cache.c_eifree_mutex ); + ldap_pvt_thread_mutex_destroy( &bdb->bi_cache.c_dntree.bei_kids_mutex ); +#ifdef BDB_HIER + ldap_pvt_thread_mutex_destroy( &bdb->bi_modrdns_mutex ); +#endif + ldap_pvt_thread_mutex_destroy( &bdb->bi_lastid_mutex ); + ldap_pvt_thread_mutex_destroy( &bdb->bi_database_mutex ); + ldap_pvt_thread_rdwr_destroy( &bdb->bi_idl_tree_rwlock ); + ldap_pvt_thread_mutex_destroy( &bdb->bi_idl_tree_lrulock ); + + ch_free( bdb ); + be->be_private = NULL; + + return 0; +} + +int +bdb_back_initialize( + BackendInfo *bi ) +{ + int rc; + + static char *controls[] = { + LDAP_CONTROL_ASSERT, + LDAP_CONTROL_MANAGEDSAIT, + LDAP_CONTROL_NOOP, + LDAP_CONTROL_PAGEDRESULTS, + LDAP_CONTROL_PRE_READ, + LDAP_CONTROL_POST_READ, + LDAP_CONTROL_SUBENTRIES, + LDAP_CONTROL_X_PERMISSIVE_MODIFY, +#ifdef LDAP_X_TXN + LDAP_CONTROL_X_TXN_SPEC, +#endif + NULL + }; + + /* initialize the underlying database system */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_back_initialize) ": initialize " + BDB_UCTYPE " backend\n", 0, 0, 0 ); + + bi->bi_flags |= + SLAP_BFLAG_INCREMENT | + SLAP_BFLAG_SUBENTRIES | + SLAP_BFLAG_ALIASES | + SLAP_BFLAG_REFERRALS; + + bi->bi_controls = controls; + + { /* version check */ + int major, minor, patch, ver; + char *version = db_version( &major, &minor, &patch ); +#ifdef HAVE_EBCDIC + char v2[1024]; + + /* All our stdio does an ASCII to EBCDIC conversion on + * the output. Strings from the BDB library are already + * in EBCDIC; we have to go back and forth... + */ + strcpy( v2, version ); + __etoa( v2 ); + version = v2; +#endif + + ver = (major << 24) | (minor << 16) | patch; + if( ver != DB_VERSION_FULL ) { + /* fail if a versions don't match */ + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_back_initialize) ": " + "BDB library version mismatch:" + " expected " DB_VERSION_STRING "," + " got %s\n", version, 0, 0 ); + return -1; + } + + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_back_initialize) + ": %s\n", version, 0, 0 ); + } + + db_env_set_func_free( ber_memfree ); + db_env_set_func_malloc( (db_malloc *)ber_memalloc ); + db_env_set_func_realloc( (db_realloc *)ber_memrealloc ); +#if !defined(NO_THREAD) && DB_VERSION_FULL <= 0x04070000 + /* This is a no-op on a NO_THREAD build. Leave the default + * alone so that BDB will sleep on interprocess conflicts. + * Don't bother on BDB 4.7... + */ + db_env_set_func_yield( ldap_pvt_thread_yield ); +#endif + + bi->bi_open = 0; + bi->bi_close = 0; + bi->bi_config = 0; + bi->bi_destroy = 0; + + bi->bi_db_init = bdb_db_init; + bi->bi_db_config = config_generic_wrapper; + bi->bi_db_open = bdb_db_open; + bi->bi_db_close = bdb_db_close; + bi->bi_db_destroy = bdb_db_destroy; + + bi->bi_op_add = bdb_add; + bi->bi_op_bind = bdb_bind; + bi->bi_op_compare = bdb_compare; + bi->bi_op_delete = bdb_delete; + bi->bi_op_modify = bdb_modify; + bi->bi_op_modrdn = bdb_modrdn; + bi->bi_op_search = bdb_search; + + bi->bi_op_unbind = 0; + + bi->bi_extended = bdb_extended; + + bi->bi_chk_referrals = bdb_referrals; + bi->bi_operational = bdb_operational; + bi->bi_has_subordinates = bdb_hasSubordinates; + bi->bi_entry_release_rw = bdb_entry_release; + bi->bi_entry_get_rw = bdb_entry_get; + + /* + * hooks for slap tools + */ + bi->bi_tool_entry_open = bdb_tool_entry_open; + bi->bi_tool_entry_close = bdb_tool_entry_close; + bi->bi_tool_entry_first = backend_tool_entry_first; + bi->bi_tool_entry_first_x = bdb_tool_entry_first_x; + bi->bi_tool_entry_next = bdb_tool_entry_next; + bi->bi_tool_entry_get = bdb_tool_entry_get; + bi->bi_tool_entry_put = bdb_tool_entry_put; + bi->bi_tool_entry_reindex = bdb_tool_entry_reindex; + bi->bi_tool_sync = 0; + bi->bi_tool_dn2id_get = bdb_tool_dn2id_get; + bi->bi_tool_entry_modify = bdb_tool_entry_modify; + + bi->bi_connection_init = 0; + bi->bi_connection_destroy = 0; + + rc = bdb_back_init_cf( bi ); + + return rc; +} + +#if (SLAPD_BDB == SLAPD_MOD_DYNAMIC && !defined(BDB_HIER)) || \ + (SLAPD_HDB == SLAPD_MOD_DYNAMIC && defined(BDB_HIER)) + +/* conditionally define the init_module() function */ +#ifdef BDB_HIER +SLAP_BACKEND_INIT_MODULE( hdb ) +#else /* !BDB_HIER */ +SLAP_BACKEND_INIT_MODULE( bdb ) +#endif /* !BDB_HIER */ + +#endif /* SLAPD_[BH]DB == SLAPD_MOD_DYNAMIC */ + diff --git a/servers/slapd/back-bdb/key.c b/servers/slapd/back-bdb/key.c new file mode 100644 index 0000000..6db8677 --- /dev/null +++ b/servers/slapd/back-bdb/key.c @@ -0,0 +1,104 @@ +/* index.c - routines for dealing with attribute indexes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-bdb.h" +#include "idl.h" + +/* read a key */ +int +bdb_key_read( + Backend *be, + DB *db, + DB_TXN *txn, + struct berval *k, + ID *ids, + DBC **saved_cursor, + int get_flag +) +{ + int rc; + DBT key; + + Debug( LDAP_DEBUG_TRACE, "=> key_read\n", 0, 0, 0 ); + + DBTzero( &key ); + bv2DBT(k,&key); + key.ulen = key.size; + key.flags = DB_DBT_USERMEM; + + rc = bdb_idl_fetch_key( be, db, txn, &key, ids, saved_cursor, get_flag ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "<= bdb_index_read: failed (%d)\n", + rc, 0, 0 ); + } else { + Debug( LDAP_DEBUG_TRACE, "<= bdb_index_read %ld candidates\n", + (long) BDB_IDL_N(ids), 0, 0 ); + } + + return rc; +} + +/* Add or remove stuff from index files */ +int +bdb_key_change( + Backend *be, + DB *db, + DB_TXN *txn, + struct berval *k, + ID id, + int op +) +{ + int rc; + DBT key; + + Debug( LDAP_DEBUG_TRACE, "=> key_change(%s,%lx)\n", + op == SLAP_INDEX_ADD_OP ? "ADD":"DELETE", (long) id, 0 ); + + DBTzero( &key ); + bv2DBT(k,&key); + key.ulen = key.size; + key.flags = DB_DBT_USERMEM; + + if (op == SLAP_INDEX_ADD_OP) { + /* Add values */ + +#ifdef BDB_TOOL_IDL_CACHING + if ( slapMode & SLAP_TOOL_QUICK ) + rc = bdb_tool_idl_add( be, db, txn, &key, id ); + else +#endif + rc = bdb_idl_insert_key( be, db, txn, &key, id ); + if ( rc == DB_KEYEXIST ) rc = 0; + } else { + /* Delete values */ + rc = bdb_idl_delete_key( be, db, txn, &key, id ); + if ( rc == DB_NOTFOUND ) rc = 0; + } + + Debug( LDAP_DEBUG_TRACE, "<= key_change %d\n", rc, 0, 0 ); + + return rc; +} diff --git a/servers/slapd/back-bdb/modify.c b/servers/slapd/back-bdb/modify.c new file mode 100644 index 0000000..038deaa --- /dev/null +++ b/servers/slapd/back-bdb/modify.c @@ -0,0 +1,835 @@ +/* modify.c - bdb backend modify routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "back-bdb.h" + +static struct berval scbva[] = { + BER_BVC("glue"), + BER_BVNULL +}; + +static void +bdb_modify_idxflags( + Operation *op, + AttributeDescription *desc, + int got_delete, + Attribute *newattrs, + Attribute *oldattrs ) +{ + struct berval ix_at; + AttrInfo *ai; + + /* check if modified attribute was indexed + * but not in case of NOOP... */ + ai = bdb_index_mask( op->o_bd, desc, &ix_at ); + if ( ai ) { + if ( got_delete ) { + Attribute *ap; + struct berval ix2; + + ap = attr_find( oldattrs, desc ); + if ( ap ) ap->a_flags |= SLAP_ATTR_IXDEL; + + /* Find all other attrs that index to same slot */ + for ( ap = newattrs; ap; ap = ap->a_next ) { + ai = bdb_index_mask( op->o_bd, ap->a_desc, &ix2 ); + if ( ai && ix2.bv_val == ix_at.bv_val ) + ap->a_flags |= SLAP_ATTR_IXADD; + } + + } else { + Attribute *ap; + + ap = attr_find( newattrs, desc ); + if ( ap ) ap->a_flags |= SLAP_ATTR_IXADD; + } + } +} + +int bdb_modify_internal( + Operation *op, + DB_TXN *tid, + Modifications *modlist, + Entry *e, + const char **text, + char *textbuf, + size_t textlen ) +{ + int rc, err; + Modification *mod; + Modifications *ml; + Attribute *save_attrs; + Attribute *ap; + int glue_attr_delete = 0; + int got_delete; + + Debug( LDAP_DEBUG_TRACE, "bdb_modify_internal: 0x%08lx: %s\n", + e->e_id, e->e_dn, 0); + + if ( !acl_check_modlist( op, e, modlist )) { + return LDAP_INSUFFICIENT_ACCESS; + } + + /* save_attrs will be disposed of by bdb_cache_modify */ + save_attrs = e->e_attrs; + e->e_attrs = attrs_dup( e->e_attrs ); + + for ( ml = modlist; ml != NULL; ml = ml->sml_next ) { + int match; + mod = &ml->sml_mod; + switch( mod->sm_op ) { + case LDAP_MOD_ADD: + case LDAP_MOD_REPLACE: + if ( mod->sm_desc == slap_schema.si_ad_structuralObjectClass ) { + value_match( &match, slap_schema.si_ad_structuralObjectClass, + slap_schema.si_ad_structuralObjectClass-> + ad_type->sat_equality, + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + &mod->sm_values[0], &scbva[0], text ); + if ( !match ) glue_attr_delete = 1; + } + } + if ( glue_attr_delete ) + break; + } + + if ( glue_attr_delete ) { + Attribute **app = &e->e_attrs; + while ( *app != NULL ) { + if ( !is_at_operational( (*app)->a_desc->ad_type )) { + Attribute *save = *app; + *app = (*app)->a_next; + attr_free( save ); + continue; + } + app = &(*app)->a_next; + } + } + + for ( ml = modlist; ml != NULL; ml = ml->sml_next ) { + mod = &ml->sml_mod; + got_delete = 0; + + switch ( mod->sm_op ) { + case LDAP_MOD_ADD: + Debug(LDAP_DEBUG_ARGS, + "bdb_modify_internal: add %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + err = modify_add_values( e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "bdb_modify_internal: %d %s\n", + err, *text, 0); + } + break; + + case LDAP_MOD_DELETE: + if ( glue_attr_delete ) { + err = LDAP_SUCCESS; + break; + } + + Debug(LDAP_DEBUG_ARGS, + "bdb_modify_internal: delete %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + err = modify_delete_values( e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "bdb_modify_internal: %d %s\n", + err, *text, 0); + } else { + got_delete = 1; + } + break; + + case LDAP_MOD_REPLACE: + Debug(LDAP_DEBUG_ARGS, + "bdb_modify_internal: replace %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + err = modify_replace_values( e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "bdb_modify_internal: %d %s\n", + err, *text, 0); + } else { + got_delete = 1; + } + break; + + case LDAP_MOD_INCREMENT: + Debug(LDAP_DEBUG_ARGS, + "bdb_modify_internal: increment %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + err = modify_increment_values( e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, + "bdb_modify_internal: %d %s\n", + err, *text, 0); + } else { + got_delete = 1; + } + break; + + case SLAP_MOD_SOFTADD: + Debug(LDAP_DEBUG_ARGS, + "bdb_modify_internal: softadd %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + /* Avoid problems in index_add_mods() + * We need to add index if necessary. + */ + mod->sm_op = LDAP_MOD_ADD; + + err = modify_add_values( e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + + mod->sm_op = SLAP_MOD_SOFTADD; + + if ( err == LDAP_TYPE_OR_VALUE_EXISTS ) { + err = LDAP_SUCCESS; + } + + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "bdb_modify_internal: %d %s\n", + err, *text, 0); + } + break; + + case SLAP_MOD_SOFTDEL: + Debug(LDAP_DEBUG_ARGS, + "bdb_modify_internal: softdel %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + /* Avoid problems in index_delete_mods() + * We need to add index if necessary. + */ + mod->sm_op = LDAP_MOD_DELETE; + + err = modify_delete_values( e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + + mod->sm_op = SLAP_MOD_SOFTDEL; + + if ( err == LDAP_SUCCESS ) { + got_delete = 1; + } else if ( err == LDAP_NO_SUCH_ATTRIBUTE ) { + err = LDAP_SUCCESS; + } + + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "bdb_modify_internal: %d %s\n", + err, *text, 0); + } + break; + + case SLAP_MOD_ADD_IF_NOT_PRESENT: + if ( attr_find( e->e_attrs, mod->sm_desc ) != NULL ) { + /* skip */ + err = LDAP_SUCCESS; + break; + } + + Debug(LDAP_DEBUG_ARGS, + "bdb_modify_internal: add_if_not_present %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + /* Avoid problems in index_add_mods() + * We need to add index if necessary. + */ + mod->sm_op = LDAP_MOD_ADD; + + err = modify_add_values( e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + + mod->sm_op = SLAP_MOD_ADD_IF_NOT_PRESENT; + + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "bdb_modify_internal: %d %s\n", + err, *text, 0); + } + break; + + default: + Debug(LDAP_DEBUG_ANY, "bdb_modify_internal: invalid op %d\n", + mod->sm_op, 0, 0); + *text = "Invalid modify operation"; + err = LDAP_OTHER; + Debug(LDAP_DEBUG_ARGS, "bdb_modify_internal: %d %s\n", + err, *text, 0); + } + + if ( err != LDAP_SUCCESS ) { + attrs_free( e->e_attrs ); + e->e_attrs = save_attrs; + /* unlock entry, delete from cache */ + return err; + } + + /* If objectClass was modified, reset the flags */ + if ( mod->sm_desc == slap_schema.si_ad_objectClass ) { + e->e_ocflags = 0; + } + + if ( glue_attr_delete ) e->e_ocflags = 0; + + + /* check if modified attribute was indexed + * but not in case of NOOP... */ + if ( !op->o_noop ) { + bdb_modify_idxflags( op, mod->sm_desc, got_delete, e->e_attrs, save_attrs ); + } + } + + /* check that the entry still obeys the schema */ + ap = NULL; + rc = entry_schema_check( op, e, save_attrs, get_relax(op), 0, &ap, + text, textbuf, textlen ); + if ( rc != LDAP_SUCCESS || op->o_noop ) { + attrs_free( e->e_attrs ); + /* clear the indexing flags */ + for ( ap = save_attrs; ap != NULL; ap = ap->a_next ) { + ap->a_flags &= ~(SLAP_ATTR_IXADD|SLAP_ATTR_IXDEL); + } + e->e_attrs = save_attrs; + + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "entry failed schema check: %s\n", + *text, 0, 0 ); + } + + /* if NOOP then silently revert to saved attrs */ + return rc; + } + + /* structuralObjectClass modified! */ + if ( ap ) { + assert( ap->a_desc == slap_schema.si_ad_structuralObjectClass ); + if ( !op->o_noop ) { + bdb_modify_idxflags( op, slap_schema.si_ad_structuralObjectClass, + 1, e->e_attrs, save_attrs ); + } + } + + /* update the indices of the modified attributes */ + + /* start with deleting the old index entries */ + for ( ap = save_attrs; ap != NULL; ap = ap->a_next ) { + if ( ap->a_flags & SLAP_ATTR_IXDEL ) { + struct berval *vals; + Attribute *a2; + ap->a_flags &= ~SLAP_ATTR_IXDEL; + a2 = attr_find( e->e_attrs, ap->a_desc ); + if ( a2 ) { + /* need to detect which values were deleted */ + int i, j; + /* let add know there were deletes */ + if ( a2->a_flags & SLAP_ATTR_IXADD ) + a2->a_flags |= SLAP_ATTR_IXDEL; + vals = op->o_tmpalloc( (ap->a_numvals + 1) * + sizeof(struct berval), op->o_tmpmemctx ); + j = 0; + for ( i=0; i < ap->a_numvals; i++ ) { + rc = attr_valfind( a2, SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + &ap->a_nvals[i], NULL, op->o_tmpmemctx ); + /* Save deleted values */ + if ( rc == LDAP_NO_SUCH_ATTRIBUTE ) + vals[j++] = ap->a_nvals[i]; + } + BER_BVZERO(vals+j); + } else { + /* attribute was completely deleted */ + vals = ap->a_nvals; + } + rc = 0; + if ( !BER_BVISNULL( vals )) { + rc = bdb_index_values( op, tid, ap->a_desc, + vals, e->e_id, SLAP_INDEX_DELETE_OP ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "%s: attribute \"%s\" index delete failure\n", + op->o_log_prefix, ap->a_desc->ad_cname.bv_val, 0 ); + attrs_free( e->e_attrs ); + e->e_attrs = save_attrs; + } + } + if ( vals != ap->a_nvals ) + op->o_tmpfree( vals, op->o_tmpmemctx ); + if ( rc ) return rc; + } + } + + /* add the new index entries */ + for ( ap = e->e_attrs; ap != NULL; ap = ap->a_next ) { + if (ap->a_flags & SLAP_ATTR_IXADD) { + ap->a_flags &= ~SLAP_ATTR_IXADD; + if ( ap->a_flags & SLAP_ATTR_IXDEL ) { + /* if any values were deleted, we must readd index + * for all remaining values. + */ + ap->a_flags &= ~SLAP_ATTR_IXDEL; + rc = bdb_index_values( op, tid, ap->a_desc, + ap->a_nvals, + e->e_id, SLAP_INDEX_ADD_OP ); + } else { + int found = 0; + /* if this was only an add, we only need to index + * the added values. + */ + for ( ml = modlist; ml != NULL; ml = ml->sml_next ) { + struct berval *vals; + if ( ml->sml_desc != ap->a_desc || !ml->sml_numvals ) + continue; + found = 1; + switch( ml->sml_op ) { + case LDAP_MOD_ADD: + case LDAP_MOD_REPLACE: + case LDAP_MOD_INCREMENT: + case SLAP_MOD_SOFTADD: + case SLAP_MOD_ADD_IF_NOT_PRESENT: + if ( ml->sml_op == LDAP_MOD_INCREMENT ) + vals = ap->a_nvals; + else if ( ml->sml_nvalues ) + vals = ml->sml_nvalues; + else + vals = ml->sml_values; + rc = bdb_index_values( op, tid, ap->a_desc, + vals, e->e_id, SLAP_INDEX_ADD_OP ); + break; + } + if ( rc ) + break; + } + /* This attr was affected by a modify of a subtype, so + * there was no direct match in the modlist. Just readd + * all of its values. + */ + if ( !found ) { + rc = bdb_index_values( op, tid, ap->a_desc, + ap->a_nvals, + e->e_id, SLAP_INDEX_ADD_OP ); + } + } + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "%s: attribute \"%s\" index add failure\n", + op->o_log_prefix, ap->a_desc->ad_cname.bv_val, 0 ); + attrs_free( e->e_attrs ); + e->e_attrs = save_attrs; + return rc; + } + } + } + + return rc; +} + + +int +bdb_modify( Operation *op, SlapReply *rs ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + Entry *e = NULL; + EntryInfo *ei = NULL; + int manageDSAit = get_manageDSAit( op ); + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof textbuf; + DB_TXN *ltid = NULL, *lt2; + struct bdb_op_info opinfo = {{{ 0 }}}; + Entry dummy = {0}; + + DB_LOCK lock; + + int num_retries = 0; + + LDAPControl **preread_ctrl = NULL; + LDAPControl **postread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + + int rc; + +#ifdef LDAP_X_TXN + int settle = 0; +#endif + + Debug( LDAP_DEBUG_ARGS, LDAP_XSTRING(bdb_modify) ": %s\n", + op->o_req_dn.bv_val, 0, 0 ); + +#ifdef LDAP_X_TXN + if( op->o_txnSpec ) { + /* acquire connection lock */ + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + if( op->o_conn->c_txn == CONN_TXN_INACTIVE ) { + rs->sr_text = "invalid transaction identifier"; + rs->sr_err = LDAP_X_TXN_ID_INVALID; + goto txnReturn; + } else if( op->o_conn->c_txn == CONN_TXN_SETTLE ) { + settle=1; + goto txnReturn; + } + + if( op->o_conn->c_txn_backend == NULL ) { + op->o_conn->c_txn_backend = op->o_bd; + + } else if( op->o_conn->c_txn_backend != op->o_bd ) { + rs->sr_text = "transaction cannot span multiple database contexts"; + rs->sr_err = LDAP_AFFECTS_MULTIPLE_DSAS; + goto txnReturn; + } + + /* insert operation into transaction */ + + rs->sr_text = "transaction specified"; + rs->sr_err = LDAP_X_TXN_SPECIFY_OKAY; + +txnReturn: + /* release connection lock */ + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + + if( !settle ) { + send_ldap_result( op, rs ); + return rs->sr_err; + } + } +#endif + + ctrls[num_ctrls] = NULL; + + /* Don't touch the opattrs, if this is a contextCSN update + * initiated from updatedn */ + if ( !be_isupdate(op) || !op->orm_modlist || op->orm_modlist->sml_next || + op->orm_modlist->sml_desc != slap_schema.si_ad_contextCSN ) { + + slap_mods_opattrs( op, &op->orm_modlist, 1 ); + } + + if( 0 ) { +retry: /* transaction retry */ + if ( dummy.e_attrs ) { + attrs_free( dummy.e_attrs ); + dummy.e_attrs = NULL; + } + if( e != NULL ) { + bdb_unlocked_cache_return_entry_w(&bdb->bi_cache, e); + e = NULL; + } + Debug(LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modify) ": retrying...\n", 0, 0, 0); + + rs->sr_err = TXN_ABORT( ltid ); + ltid = NULL; + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + opinfo.boi_oe.oe_key = NULL; + op->o_do_not_cache = opinfo.boi_acl_cache; + if( rs->sr_err != 0 ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto return_results; + } + bdb_trans_backoff( ++num_retries ); + } + + /* begin transaction */ + rs->sr_err = TXN_BEGIN( bdb->bi_dbenv, NULL, <id, + bdb->bi_db_opflags ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modify) ": txn_begin failed: " + "%s (%d)\n", db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_modify) ": txn1 id: %x\n", + ltid->id(ltid), 0, 0 ); + + opinfo.boi_oe.oe_key = bdb; + opinfo.boi_txn = ltid; + opinfo.boi_err = 0; + opinfo.boi_acl_cache = op->o_do_not_cache; + LDAP_SLIST_INSERT_HEAD( &op->o_extra, &opinfo.boi_oe, oe_next ); + + /* get entry or ancestor */ + rs->sr_err = bdb_dn2entry( op, ltid, &op->o_req_ndn, &ei, 1, + &lock ); + + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modify) ": dn2entry failed (%d)\n", + rs->sr_err, 0, 0 ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case DB_NOTFOUND: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + } + + e = ei->bei_e; + + /* acquire and lock entry */ + /* FIXME: dn2entry() should return non-glue entry */ + if (( rs->sr_err == DB_NOTFOUND ) || + ( !manageDSAit && e && is_entry_glue( e ))) + { + if ( e != NULL ) { + rs->sr_matched = ch_strdup( e->e_dn ); + rs->sr_ref = is_entry_referral( e ) + ? get_entry_referrals( op, e ) + : NULL; + bdb_unlocked_cache_return_entry_r (&bdb->bi_cache, e); + e = NULL; + + } else { + rs->sr_ref = referral_rewrite( default_referral, NULL, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + } + + rs->sr_err = LDAP_REFERRAL; + send_ldap_result( op, rs ); + + if ( rs->sr_ref != default_referral ) { + ber_bvarray_free( rs->sr_ref ); + } + free( (char *)rs->sr_matched ); + rs->sr_ref = NULL; + rs->sr_matched = NULL; + + goto done; + } + + if ( !manageDSAit && is_entry_referral( e ) ) { + /* entry is a referral, don't allow modify */ + rs->sr_ref = get_entry_referrals( op, e ); + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modify) ": entry is referral\n", + 0, 0, 0 ); + + rs->sr_err = LDAP_REFERRAL; + rs->sr_matched = e->e_name.bv_val; + send_ldap_result( op, rs ); + + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + rs->sr_matched = NULL; + goto done; + } + + if ( get_assert( op ) && + ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto return_results; + } + + if( op->o_preread ) { + if( preread_ctrl == NULL ) { + preread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if ( slap_read_controls( op, rs, e, + &slap_pre_read_bv, preread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_modify) ": pre-read " + "failed!\n", 0, 0, 0 ); + if ( op->o_preread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + /* nested transaction */ + rs->sr_err = TXN_BEGIN( bdb->bi_dbenv, ltid, <2, bdb->bi_db_opflags ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modify) ": txn_begin(2) failed: " "%s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_modify) ": txn2 id: %x\n", + lt2->id(lt2), 0, 0 ); + /* Modify the entry */ + dummy = *e; + rs->sr_err = bdb_modify_internal( op, lt2, op->orm_modlist, + &dummy, &rs->sr_text, textbuf, textlen ); + + if( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modify) ": modify failed (%d)\n", + rs->sr_err, 0, 0 ); + if ( (rs->sr_err == LDAP_INSUFFICIENT_ACCESS) && opinfo.boi_err ) { + rs->sr_err = opinfo.boi_err; + } + /* Only free attrs if they were dup'd. */ + if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL; + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + goto return_results; + } + + /* change the entry itself */ + rs->sr_err = bdb_id2entry_update( op->o_bd, lt2, &dummy ); + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modify) ": id2entry update failed " "(%d)\n", + rs->sr_err, 0, 0 ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + rs->sr_text = "entry update failed"; + goto return_results; + } + + if ( TXN_COMMIT( lt2, 0 ) != 0 ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "txn_commit(2) failed"; + goto return_results; + } + + if( op->o_postread ) { + if( postread_ctrl == NULL ) { + postread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if( slap_read_controls( op, rs, &dummy, + &slap_post_read_bv, postread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_modify) + ": post-read failed!\n", 0, 0, 0 ); + if ( op->o_postread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + if( op->o_noop ) { + if ( ( rs->sr_err = TXN_ABORT( ltid ) ) != 0 ) { + rs->sr_text = "txn_abort (no-op) failed"; + } else { + rs->sr_err = LDAP_X_NO_OPERATION; + ltid = NULL; + /* Only free attrs if they were dup'd. */ + if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL; + goto return_results; + } + } else { + /* may have changed in bdb_modify_internal() */ + e->e_ocflags = dummy.e_ocflags; + rc = bdb_cache_modify( bdb, e, dummy.e_attrs, ltid, &lock ); + switch( rc ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + dummy.e_attrs = NULL; + + rs->sr_err = TXN_COMMIT( ltid, 0 ); + } + ltid = NULL; + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + opinfo.boi_oe.oe_key = NULL; + + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modify) ": txn_%s failed: %s (%d)\n", + op->o_noop ? "abort (no-op)" : "commit", + db_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "commit failed"; + + goto return_results; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modify) ": updated%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + dummy.e_id, op->o_req_dn.bv_val ); + + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + if( num_ctrls ) rs->sr_ctrls = ctrls; + +return_results: + if( dummy.e_attrs ) { + attrs_free( dummy.e_attrs ); + } + send_ldap_result( op, rs ); + + if( rs->sr_err == LDAP_SUCCESS && bdb->bi_txn_cp_kbyte ) { + TXN_CHECKPOINT( bdb->bi_dbenv, + bdb->bi_txn_cp_kbyte, bdb->bi_txn_cp_min, 0 ); + } + +done: + slap_graduate_commit_csn( op ); + + if( ltid != NULL ) { + TXN_ABORT( ltid ); + } + if ( opinfo.boi_oe.oe_key ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + } + + if( e != NULL ) { + bdb_unlocked_cache_return_entry_w (&bdb->bi_cache, e); + } + + if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) { + slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *preread_ctrl, op->o_tmpmemctx ); + } + if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) { + slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *postread_ctrl, op->o_tmpmemctx ); + } + + rs->sr_text = NULL; + + return rs->sr_err; +} diff --git a/servers/slapd/back-bdb/modrdn.c b/servers/slapd/back-bdb/modrdn.c new file mode 100644 index 0000000..2e18e5e --- /dev/null +++ b/servers/slapd/back-bdb/modrdn.c @@ -0,0 +1,842 @@ +/* modrdn.c - bdb backend modrdn routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-bdb.h" + +int +bdb_modrdn( Operation *op, SlapReply *rs ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + AttributeDescription *children = slap_schema.si_ad_children; + AttributeDescription *entry = slap_schema.si_ad_entry; + struct berval p_dn, p_ndn; + struct berval new_dn = {0, NULL}, new_ndn = {0, NULL}; + Entry *e = NULL; + Entry *p = NULL; + EntryInfo *ei = NULL, *eip = NULL, *nei = NULL, *neip = NULL; + /* LDAP v2 supporting correct attribute handling. */ + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof textbuf; + DB_TXN *ltid = NULL, *lt2; + struct bdb_op_info opinfo = {{{ 0 }}}; + Entry dummy = {0}; + + Entry *np = NULL; /* newSuperior Entry */ + struct berval *np_dn = NULL; /* newSuperior dn */ + struct berval *np_ndn = NULL; /* newSuperior ndn */ + struct berval *new_parent_dn = NULL; /* np_dn, p_dn, or NULL */ + + int manageDSAit = get_manageDSAit( op ); + + DB_LOCK lock, plock, nplock; + + int num_retries = 0; + + LDAPControl **preread_ctrl = NULL; + LDAPControl **postread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + + int rc; + + int parent_is_glue = 0; + int parent_is_leaf = 0; + +#ifdef LDAP_X_TXN + int settle = 0; +#endif + + Debug( LDAP_DEBUG_TRACE, "==>" LDAP_XSTRING(bdb_modrdn) "(%s,%s,%s)\n", + op->o_req_dn.bv_val,op->oq_modrdn.rs_newrdn.bv_val, + op->oq_modrdn.rs_newSup ? op->oq_modrdn.rs_newSup->bv_val : "NULL" ); + +#ifdef LDAP_X_TXN + if( op->o_txnSpec ) { + /* acquire connection lock */ + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + if( op->o_conn->c_txn == CONN_TXN_INACTIVE ) { + rs->sr_text = "invalid transaction identifier"; + rs->sr_err = LDAP_X_TXN_ID_INVALID; + goto txnReturn; + } else if( op->o_conn->c_txn == CONN_TXN_SETTLE ) { + settle=1; + goto txnReturn; + } + + if( op->o_conn->c_txn_backend == NULL ) { + op->o_conn->c_txn_backend = op->o_bd; + + } else if( op->o_conn->c_txn_backend != op->o_bd ) { + rs->sr_text = "transaction cannot span multiple database contexts"; + rs->sr_err = LDAP_AFFECTS_MULTIPLE_DSAS; + goto txnReturn; + } + + /* insert operation into transaction */ + + rs->sr_text = "transaction specified"; + rs->sr_err = LDAP_X_TXN_SPECIFY_OKAY; + +txnReturn: + /* release connection lock */ + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + + if( !settle ) { + send_ldap_result( op, rs ); + return rs->sr_err; + } + } +#endif + + ctrls[num_ctrls] = NULL; + + slap_mods_opattrs( op, &op->orr_modlist, 1 ); + + if( 0 ) { +retry: /* transaction retry */ + if ( dummy.e_attrs ) { + attrs_free( dummy.e_attrs ); + dummy.e_attrs = NULL; + } + if (e != NULL) { + bdb_unlocked_cache_return_entry_w(&bdb->bi_cache, e); + e = NULL; + } + if (p != NULL) { + bdb_unlocked_cache_return_entry_r(&bdb->bi_cache, p); + p = NULL; + } + if (np != NULL) { + bdb_unlocked_cache_return_entry_r(&bdb->bi_cache, np); + np = NULL; + } + Debug( LDAP_DEBUG_TRACE, "==>" LDAP_XSTRING(bdb_modrdn) + ": retrying...\n", 0, 0, 0 ); + + rs->sr_err = TXN_ABORT( ltid ); + ltid = NULL; + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + opinfo.boi_oe.oe_key = NULL; + op->o_do_not_cache = opinfo.boi_acl_cache; + if( rs->sr_err != 0 ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto return_results; + } + parent_is_glue = 0; + parent_is_leaf = 0; + bdb_trans_backoff( ++num_retries ); + } + + /* begin transaction */ + rs->sr_err = TXN_BEGIN( bdb->bi_dbenv, NULL, <id, + bdb->bi_db_opflags ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modrdn) ": txn_begin failed: " + "%s (%d)\n", db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_modrdn) ": txn1 id: %x\n", + ltid->id(ltid), 0, 0 ); + + opinfo.boi_oe.oe_key = bdb; + opinfo.boi_txn = ltid; + opinfo.boi_err = 0; + opinfo.boi_acl_cache = op->o_do_not_cache; + LDAP_SLIST_INSERT_HEAD( &op->o_extra, &opinfo.boi_oe, oe_next ); + + /* get entry */ + rs->sr_err = bdb_dn2entry( op, ltid, &op->o_req_ndn, &ei, 1, + &lock ); + + switch( rs->sr_err ) { + case 0: + case DB_NOTFOUND: + break; + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + e = ei->bei_e; + /* FIXME: dn2entry() should return non-glue entry */ + if (( rs->sr_err == DB_NOTFOUND ) || + ( !manageDSAit && e && is_entry_glue( e ))) + { + if( e != NULL ) { + rs->sr_matched = ch_strdup( e->e_dn ); + rs->sr_ref = is_entry_referral( e ) + ? get_entry_referrals( op, e ) + : NULL; + bdb_unlocked_cache_return_entry_r( &bdb->bi_cache, e); + e = NULL; + + } else { + rs->sr_ref = referral_rewrite( default_referral, NULL, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + } + + rs->sr_err = LDAP_REFERRAL; + send_ldap_result( op, rs ); + + ber_bvarray_free( rs->sr_ref ); + free( (char *)rs->sr_matched ); + rs->sr_ref = NULL; + rs->sr_matched = NULL; + + goto done; + } + + if ( get_assert( op ) && + ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto return_results; + } + + /* check write on old entry */ + rs->sr_err = access_allowed( op, e, entry, NULL, ACL_WRITE, NULL ); + if ( ! rs->sr_err ) { + switch( opinfo.boi_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + Debug( LDAP_DEBUG_TRACE, "no access to entry\n", 0, + 0, 0 ); + rs->sr_text = "no write access to old entry"; + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto return_results; + } + +#ifndef BDB_HIER + rs->sr_err = bdb_cache_children( op, ltid, e ); + if ( rs->sr_err != DB_NOTFOUND ) { + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case 0: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(bdb_modrdn) + ": non-leaf %s\n", + op->o_req_dn.bv_val, 0, 0); + rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF; + rs->sr_text = "subtree rename not supported"; + break; + default: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(bdb_modrdn) + ": has_children failed: %s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + } + goto return_results; + } + ei->bei_state |= CACHE_ENTRY_NO_KIDS; +#endif + + if (!manageDSAit && is_entry_referral( e ) ) { + /* parent is a referral, don't allow add */ + rs->sr_ref = get_entry_referrals( op, e ); + + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_modrdn) + ": entry %s is referral\n", e->e_dn, 0, 0 ); + + rs->sr_err = LDAP_REFERRAL, + rs->sr_matched = e->e_name.bv_val; + send_ldap_result( op, rs ); + + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + rs->sr_matched = NULL; + goto done; + } + + if ( be_issuffix( op->o_bd, &e->e_nname ) ) { +#ifdef BDB_MULTIPLE_SUFFIXES + /* Allow renaming one suffix entry to another */ + p_ndn = slap_empty_bv; +#else + /* There can only be one suffix entry */ + rs->sr_err = LDAP_NAMING_VIOLATION; + rs->sr_text = "cannot rename suffix entry"; + goto return_results; +#endif + } else { + dnParent( &e->e_nname, &p_ndn ); + } + np_ndn = &p_ndn; + eip = ei->bei_parent; + if ( eip && eip->bei_id ) { + /* Make sure parent entry exist and we can write its + * children. + */ + rs->sr_err = bdb_cache_find_id( op, ltid, + eip->bei_id, &eip, 0, &plock ); + + switch( rs->sr_err ) { + case 0: + case DB_NOTFOUND: + break; + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + p = eip->bei_e; + if( p == NULL) { + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_modrdn) + ": parent does not exist\n", 0, 0, 0); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "old entry's parent does not exist"; + goto return_results; + } + } else { + p = (Entry *)&slap_entry_root; + } + + /* check parent for "children" acl */ + rs->sr_err = access_allowed( op, p, + children, NULL, + op->oq_modrdn.rs_newSup == NULL ? + ACL_WRITE : ACL_WDEL, + NULL ); + + if ( !p_ndn.bv_len ) + p = NULL; + + if ( ! rs->sr_err ) { + switch( opinfo.boi_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + Debug( LDAP_DEBUG_TRACE, "no access to parent\n", 0, + 0, 0 ); + rs->sr_text = "no write access to old parent's children"; + goto return_results; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modrdn) ": wr to children " + "of entry %s OK\n", p_ndn.bv_val, 0, 0 ); + + if ( p_ndn.bv_val == slap_empty_bv.bv_val ) { + p_dn = slap_empty_bv; + } else { + dnParent( &e->e_name, &p_dn ); + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modrdn) ": parent dn=%s\n", + p_dn.bv_val, 0, 0 ); + + new_parent_dn = &p_dn; /* New Parent unless newSuperior given */ + + if ( op->oq_modrdn.rs_newSup != NULL ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modrdn) + ": new parent \"%s\" requested...\n", + op->oq_modrdn.rs_newSup->bv_val, 0, 0 ); + + /* newSuperior == oldParent? */ + if( dn_match( &p_ndn, op->oq_modrdn.rs_nnewSup ) ) { + Debug( LDAP_DEBUG_TRACE, "bdb_back_modrdn: " + "new parent \"%s\" same as the old parent \"%s\"\n", + op->oq_modrdn.rs_newSup->bv_val, p_dn.bv_val, 0 ); + op->oq_modrdn.rs_newSup = NULL; /* ignore newSuperior */ + } + } + + /* There's a BDB_MULTIPLE_SUFFIXES case here that this code doesn't + * support. E.g., two suffixes dc=foo,dc=com and dc=bar,dc=net. + * We do not allow modDN + * dc=foo,dc=com + * newrdn dc=bar + * newsup dc=net + * and we probably should. But since MULTIPLE_SUFFIXES is deprecated + * I'm ignoring this problem for now. + */ + if ( op->oq_modrdn.rs_newSup != NULL ) { + if ( op->oq_modrdn.rs_newSup->bv_len ) { + np_dn = op->oq_modrdn.rs_newSup; + np_ndn = op->oq_modrdn.rs_nnewSup; + + /* newSuperior == oldParent? - checked above */ + /* newSuperior == entry being moved?, if so ==> ERROR */ + if ( dnIsSuffix( np_ndn, &e->e_nname )) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = "new superior not found"; + goto return_results; + } + /* Get Entry with dn=newSuperior. Does newSuperior exist? */ + + rs->sr_err = bdb_dn2entry( op, ltid, np_ndn, + &neip, 0, &nplock ); + + switch( rs->sr_err ) { + case 0: np = neip->bei_e; + case DB_NOTFOUND: + break; + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + if( np == NULL) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modrdn) + ": newSup(ndn=%s) not here!\n", + np_ndn->bv_val, 0, 0); + rs->sr_text = "new superior not found"; + rs->sr_err = LDAP_NO_SUCH_OBJECT; + goto return_results; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modrdn) + ": wr to new parent OK np=%p, id=%ld\n", + (void *) np, (long) np->e_id, 0 ); + + /* check newSuperior for "children" acl */ + rs->sr_err = access_allowed( op, np, children, + NULL, ACL_WADD, NULL ); + + if( ! rs->sr_err ) { + switch( opinfo.boi_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modrdn) + ": no wr to newSup children\n", + 0, 0, 0 ); + rs->sr_text = "no write access to new superior's children"; + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto return_results; + } + + if ( is_entry_alias( np ) ) { + /* parent is an alias, don't allow add */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modrdn) + ": entry is alias\n", + 0, 0, 0 ); + rs->sr_text = "new superior is an alias"; + rs->sr_err = LDAP_ALIAS_PROBLEM; + goto return_results; + } + + if ( is_entry_referral( np ) ) { + /* parent is a referral, don't allow add */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modrdn) + ": entry is referral\n", + 0, 0, 0 ); + rs->sr_text = "new superior is a referral"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + + } else { + np_dn = NULL; + + /* no parent, modrdn entry directly under root */ + if ( be_issuffix( op->o_bd, (struct berval *)&slap_empty_bv ) + || be_isupdate( op ) ) { + np = (Entry *)&slap_entry_root; + + /* check parent for "children" acl */ + rs->sr_err = access_allowed( op, np, + children, NULL, ACL_WADD, NULL ); + + np = NULL; + + if ( ! rs->sr_err ) { + switch( opinfo.boi_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + Debug( LDAP_DEBUG_TRACE, + "no access to new superior\n", + 0, 0, 0 ); + rs->sr_text = + "no write access to new superior's children"; + goto return_results; + } + } + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modrdn) + ": wr to new parent's children OK\n", + 0, 0, 0 ); + + new_parent_dn = np_dn; + } + + /* Build target dn and make sure target entry doesn't exist already. */ + if (!new_dn.bv_val) { + build_new_dn( &new_dn, new_parent_dn, &op->oq_modrdn.rs_newrdn, NULL ); + } + + if (!new_ndn.bv_val) { + struct berval bv = {0, NULL}; + dnNormalize( 0, NULL, NULL, &new_dn, &bv, op->o_tmpmemctx ); + ber_dupbv( &new_ndn, &bv ); + /* FIXME: why not call dnNormalize() w/o ctx? */ + op->o_tmpfree( bv.bv_val, op->o_tmpmemctx ); + } + + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_modrdn) ": new ndn=%s\n", + new_ndn.bv_val, 0, 0 ); + + /* Shortcut the search */ + nei = neip ? neip : eip; + rs->sr_err = bdb_cache_find_ndn ( op, ltid, &new_ndn, &nei ); + if ( nei ) bdb_cache_entryinfo_unlock( nei ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case DB_NOTFOUND: + break; + case 0: + /* Allow rename to same DN */ + if ( nei == ei ) + break; + rs->sr_err = LDAP_ALREADY_EXISTS; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + assert( op->orr_modlist != NULL ); + + if( op->o_preread ) { + if( preread_ctrl == NULL ) { + preread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if( slap_read_controls( op, rs, e, + &slap_pre_read_bv, preread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_modrdn) + ": pre-read failed!\n", 0, 0, 0 ); + if ( op->o_preread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + /* nested transaction */ + rs->sr_err = TXN_BEGIN( bdb->bi_dbenv, ltid, <2, bdb->bi_db_opflags ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modrdn) + ": txn_begin(2) failed: %s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_modrdn) ": txn2 id: %x\n", + lt2->id(lt2), 0, 0 ); + + /* delete old DN */ + rs->sr_err = bdb_dn2id_delete( op, lt2, eip, e ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_modrdn) + ": dn2id del failed: %s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + rs->sr_err = LDAP_OTHER; + rs->sr_text = "DN index delete fail"; + goto return_results; + } + + /* copy the entry, then override some fields */ + dummy = *e; + dummy.e_name = new_dn; + dummy.e_nname = new_ndn; + dummy.e_attrs = NULL; + + /* add new DN */ + rs->sr_err = bdb_dn2id_add( op, lt2, neip ? neip : eip, &dummy ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_modrdn) + ": dn2id add failed: %s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + rs->sr_err = LDAP_OTHER; + rs->sr_text = "DN index add failed"; + goto return_results; + } + + dummy.e_attrs = e->e_attrs; + + /* modify entry */ + rs->sr_err = bdb_modify_internal( op, lt2, op->orr_modlist, &dummy, + &rs->sr_text, textbuf, textlen ); + if( rs->sr_err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_modrdn) + ": modify failed: %s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + if ( ( rs->sr_err == LDAP_INSUFFICIENT_ACCESS ) && opinfo.boi_err ) { + rs->sr_err = opinfo.boi_err; + } + if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL; + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + goto return_results; + } + + /* id2entry index */ + rs->sr_err = bdb_id2entry_update( op->o_bd, lt2, &dummy ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_modrdn) + ": id2entry failed: %s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + rs->sr_err = LDAP_OTHER; + rs->sr_text = "entry update failed"; + goto return_results; + } + + if ( p_ndn.bv_len != 0 ) { + parent_is_glue = is_entry_glue(p); + rs->sr_err = bdb_cache_children( op, lt2, p ); + if ( rs->sr_err != DB_NOTFOUND ) { + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case 0: + break; + default: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(bdb_modrdn) + ": has_children failed: %s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + parent_is_leaf = 1; + } + bdb_unlocked_cache_return_entry_r(&bdb->bi_cache, p); + p = NULL; + } + + if ( TXN_COMMIT( lt2, 0 ) != 0 ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "txn_commit(2) failed"; + goto return_results; + } + + if( op->o_postread ) { + if( postread_ctrl == NULL ) { + postread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if( slap_read_controls( op, rs, &dummy, + &slap_post_read_bv, postread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_modrdn) + ": post-read failed!\n", 0, 0, 0 ); + if ( op->o_postread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + if( op->o_noop ) { + if(( rs->sr_err=TXN_ABORT( ltid )) != 0 ) { + rs->sr_text = "txn_abort (no-op) failed"; + } else { + rs->sr_err = LDAP_X_NO_OPERATION; + ltid = NULL; + /* Only free attrs if they were dup'd. */ + if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL; + goto return_results; + } + + } else { + rc = bdb_cache_modrdn( bdb, e, &op->orr_nnewrdn, &dummy, neip, + ltid, &lock ); + switch( rc ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + dummy.e_attrs = NULL; + new_dn.bv_val = NULL; + new_ndn.bv_val = NULL; + + if(( rs->sr_err=TXN_COMMIT( ltid, 0 )) != 0 ) { + rs->sr_text = "txn_commit failed"; + } else { + rs->sr_err = LDAP_SUCCESS; + } + } + + ltid = NULL; + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + opinfo.boi_oe.oe_key = NULL; + + if( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modrdn) ": %s : %s (%d)\n", + rs->sr_text, db_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + + goto return_results; + } + + Debug(LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modrdn) + ": rdn modified%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + dummy.e_id, op->o_req_dn.bv_val ); + rs->sr_text = NULL; + if( num_ctrls ) rs->sr_ctrls = ctrls; + +return_results: + if ( dummy.e_attrs ) { + attrs_free( dummy.e_attrs ); + } + send_ldap_result( op, rs ); + + if( rs->sr_err == LDAP_SUCCESS && bdb->bi_txn_cp_kbyte ) { + TXN_CHECKPOINT( bdb->bi_dbenv, + bdb->bi_txn_cp_kbyte, bdb->bi_txn_cp_min, 0 ); + } + + if ( rs->sr_err == LDAP_SUCCESS && parent_is_glue && parent_is_leaf ) { + op->o_delete_glue_parent = 1; + } + +done: + slap_graduate_commit_csn( op ); + + if( new_dn.bv_val != NULL ) free( new_dn.bv_val ); + if( new_ndn.bv_val != NULL ) free( new_ndn.bv_val ); + + /* LDAP v3 Support */ + if( np != NULL ) { + /* free new parent and reader lock */ + bdb_unlocked_cache_return_entry_r(&bdb->bi_cache, np); + } + + if( p != NULL ) { + /* free parent and reader lock */ + bdb_unlocked_cache_return_entry_r(&bdb->bi_cache, p); + } + + /* free entry */ + if( e != NULL ) { + bdb_unlocked_cache_return_entry_w( &bdb->bi_cache, e); + } + + if( ltid != NULL ) { + TXN_ABORT( ltid ); + } + if ( opinfo.boi_oe.oe_key ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + } + + if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) { + slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *preread_ctrl, op->o_tmpmemctx ); + } + if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) { + slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *postread_ctrl, op->o_tmpmemctx ); + } + return rs->sr_err; +} diff --git a/servers/slapd/back-bdb/monitor.c b/servers/slapd/back-bdb/monitor.c new file mode 100644 index 0000000..8603bcd --- /dev/null +++ b/servers/slapd/back-bdb/monitor.c @@ -0,0 +1,724 @@ +/* monitor.c - monitor bdb backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/unistd.h> +#include <ac/stdlib.h> +#include <ac/errno.h> +#include <sys/stat.h> +#include "lutil.h" +#include "back-bdb.h" + +#include "../back-monitor/back-monitor.h" + +#include "config.h" + +static ObjectClass *oc_olmBDBDatabase; + +static AttributeDescription *ad_olmBDBEntryCache, + *ad_olmBDBDNCache, *ad_olmBDBIDLCache, + *ad_olmDbDirectory; + +#ifdef BDB_MONITOR_IDX +static int +bdb_monitor_idx_entry_add( + struct bdb_info *bdb, + Entry *e ); + +static AttributeDescription *ad_olmDbNotIndexed; +#endif /* BDB_MONITOR_IDX */ + +/* + * NOTE: there's some confusion in monitor OID arc; + * by now, let's consider: + * + * Subsystems monitor attributes 1.3.6.1.4.1.4203.666.1.55.0 + * Databases monitor attributes 1.3.6.1.4.1.4203.666.1.55.0.1 + * BDB database monitor attributes 1.3.6.1.4.1.4203.666.1.55.0.1.1 + * + * Subsystems monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0 + * Databases monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0.1 + * BDB database monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0.1.1 + */ + +static struct { + char *name; + char *oid; +} s_oid[] = { + { "olmBDBAttributes", "olmDatabaseAttributes:1" }, + { "olmBDBObjectClasses", "olmDatabaseObjectClasses:1" }, + + { NULL } +}; + +static struct { + char *desc; + AttributeDescription **ad; +} s_at[] = { + { "( olmBDBAttributes:1 " + "NAME ( 'olmBDBEntryCache' ) " + "DESC 'Number of items in Entry Cache' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmBDBEntryCache }, + + { "( olmBDBAttributes:2 " + "NAME ( 'olmBDBDNCache' ) " + "DESC 'Number of items in DN Cache' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmBDBDNCache }, + + { "( olmBDBAttributes:3 " + "NAME ( 'olmBDBIDLCache' ) " + "DESC 'Number of items in IDL Cache' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmBDBIDLCache }, + + { "( olmDatabaseAttributes:1 " + "NAME ( 'olmDbDirectory' ) " + "DESC 'Path name of the directory " + "where the database environment resides' " + "SUP monitoredInfo " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmDbDirectory }, + +#ifdef BDB_MONITOR_IDX + { "( olmDatabaseAttributes:2 " + "NAME ( 'olmDbNotIndexed' ) " + "DESC 'Missing indexes resulting from candidate selection' " + "SUP monitoredInfo " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmDbNotIndexed }, +#endif /* BDB_MONITOR_IDX */ + + { NULL } +}; + +static struct { + char *desc; + ObjectClass **oc; +} s_oc[] = { + /* augments an existing object, so it must be AUXILIARY + * FIXME: derive from some ABSTRACT "monitoredEntity"? */ + { "( olmBDBObjectClasses:1 " + "NAME ( 'olmBDBDatabase' ) " + "SUP top AUXILIARY " + "MAY ( " + "olmBDBEntryCache " + "$ olmBDBDNCache " + "$ olmBDBIDLCache " + "$ olmDbDirectory " +#ifdef BDB_MONITOR_IDX + "$ olmDbNotIndexed " +#endif /* BDB_MONITOR_IDX */ + ") )", + &oc_olmBDBDatabase }, + + { NULL } +}; + +static int +bdb_monitor_update( + Operation *op, + SlapReply *rs, + Entry *e, + void *priv ) +{ + struct bdb_info *bdb = (struct bdb_info *) priv; + Attribute *a; + + char buf[ BUFSIZ ]; + struct berval bv; + + assert( ad_olmBDBEntryCache != NULL ); + + a = attr_find( e->e_attrs, ad_olmBDBEntryCache ); + assert( a != NULL ); + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", bdb->bi_cache.c_cursize ); + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + + a = attr_find( e->e_attrs, ad_olmBDBDNCache ); + assert( a != NULL ); + bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", bdb->bi_cache.c_eiused ); + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + + a = attr_find( e->e_attrs, ad_olmBDBIDLCache ); + assert( a != NULL ); + bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", bdb->bi_idl_cache_size ); + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + +#ifdef BDB_MONITOR_IDX + bdb_monitor_idx_entry_add( bdb, e ); +#endif /* BDB_MONITOR_IDX */ + + return SLAP_CB_CONTINUE; +} + +#if 0 /* uncomment if required */ +static int +bdb_monitor_modify( + Operation *op, + SlapReply *rs, + Entry *e, + void *priv ) +{ + return SLAP_CB_CONTINUE; +} +#endif + +static int +bdb_monitor_free( + Entry *e, + void **priv ) +{ + struct berval values[ 2 ]; + Modification mod = { 0 }; + + const char *text; + char textbuf[ SLAP_TEXT_BUFLEN ]; + + int i, rc; + + /* NOTE: if slap_shutdown != 0, priv might have already been freed */ + *priv = NULL; + + /* Remove objectClass */ + mod.sm_op = LDAP_MOD_DELETE; + mod.sm_desc = slap_schema.si_ad_objectClass; + mod.sm_values = values; + mod.sm_numvals = 1; + values[ 0 ] = oc_olmBDBDatabase->soc_cname; + BER_BVZERO( &values[ 1 ] ); + + rc = modify_delete_values( e, &mod, 1, &text, + textbuf, sizeof( textbuf ) ); + /* don't care too much about return code... */ + + /* remove attrs */ + mod.sm_values = NULL; + mod.sm_numvals = 0; + for ( i = 0; s_at[ i ].desc != NULL; i++ ) { + mod.sm_desc = *s_at[ i ].ad; + rc = modify_delete_values( e, &mod, 1, &text, + textbuf, sizeof( textbuf ) ); + /* don't care too much about return code... */ + } + + return SLAP_CB_CONTINUE; +} + +#define bdb_monitor_initialize BDB_SYMBOL(monitor_initialize) + +/* + * call from within bdb_initialize() + */ +static int +bdb_monitor_initialize( void ) +{ + int i, code; + ConfigArgs c; + char *argv[ 3 ]; + + static int bdb_monitor_initialized = 0; + + /* set to 0 when successfully initialized; otherwise, remember failure */ + static int bdb_monitor_initialized_failure = 1; + + if ( bdb_monitor_initialized++ ) { + return bdb_monitor_initialized_failure; + } + + if ( backend_info( "monitor" ) == NULL ) { + return -1; + } + + /* register schema here */ + + argv[ 0 ] = "back-bdb/back-hdb monitor"; + c.argv = argv; + c.argc = 3; + c.fname = argv[0]; + + for ( i = 0; s_oid[ i ].name; i++ ) { + c.lineno = i; + argv[ 1 ] = s_oid[ i ].name; + argv[ 2 ] = s_oid[ i ].oid; + + if ( parse_oidm( &c, 0, NULL ) != 0 ) { + Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(bdb_monitor_initialize) + ": unable to add " + "objectIdentifier \"%s=%s\"\n", + s_oid[ i ].name, s_oid[ i ].oid, 0 ); + return 2; + } + } + + for ( i = 0; s_at[ i ].desc != NULL; i++ ) { + code = register_at( s_at[ i ].desc, s_at[ i ].ad, 1 ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(bdb_monitor_initialize) + ": register_at failed for attributeType (%s)\n", + s_at[ i ].desc, 0, 0 ); + return 3; + + } else { + (*s_at[ i ].ad)->ad_type->sat_flags |= SLAP_AT_HIDE; + } + } + + for ( i = 0; s_oc[ i ].desc != NULL; i++ ) { + code = register_oc( s_oc[ i ].desc, s_oc[ i ].oc, 1 ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(bdb_monitor_initialize) + ": register_oc failed for objectClass (%s)\n", + s_oc[ i ].desc, 0, 0 ); + return 4; + + } else { + (*s_oc[ i ].oc)->soc_flags |= SLAP_OC_HIDE; + } + } + + return ( bdb_monitor_initialized_failure = LDAP_SUCCESS ); +} + +/* + * call from within bdb_db_init() + */ +int +bdb_monitor_db_init( BackendDB *be ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + + if ( bdb_monitor_initialize() == LDAP_SUCCESS ) { + /* monitoring in back-bdb is on by default */ + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_MONITORING; + } + +#ifdef BDB_MONITOR_IDX + bdb->bi_idx = NULL; + ldap_pvt_thread_mutex_init( &bdb->bi_idx_mutex ); +#endif /* BDB_MONITOR_IDX */ + + return 0; +} + +/* + * call from within bdb_db_open() + */ +int +bdb_monitor_db_open( BackendDB *be ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + Attribute *a, *next; + monitor_callback_t *cb = NULL; + int rc = 0; + BackendInfo *mi; + monitor_extra_t *mbe; + + if ( !SLAP_DBMONITORING( be ) ) { + return 0; + } + + mi = backend_info( "monitor" ); + if ( !mi || !mi->bi_extra ) { + SLAP_DBFLAGS( be ) ^= SLAP_DBFLAG_MONITORING; + return 0; + } + mbe = mi->bi_extra; + + /* don't bother if monitor is not configured */ + if ( !mbe->is_configured() ) { + static int warning = 0; + + if ( warning++ == 0 ) { + Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(bdb_monitor_db_open) + ": monitoring disabled; " + "configure monitor database to enable\n", + 0, 0, 0 ); + } + + return 0; + } + + /* alloc as many as required (plus 1 for objectClass) */ + a = attrs_alloc( 1 + 4 ); + if ( a == NULL ) { + rc = 1; + goto cleanup; + } + + a->a_desc = slap_schema.si_ad_objectClass; + attr_valadd( a, &oc_olmBDBDatabase->soc_cname, NULL, 1 ); + next = a->a_next; + + { + struct berval bv = BER_BVC( "0" ); + + next->a_desc = ad_olmBDBEntryCache; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + + next->a_desc = ad_olmBDBDNCache; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + + next->a_desc = ad_olmBDBIDLCache; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + } + + { + struct berval bv, nbv; + ber_len_t pathlen = 0, len = 0; + char path[ MAXPATHLEN ] = { '\0' }; + char *fname = bdb->bi_dbenv_home, + *ptr; + + len = strlen( fname ); + if ( fname[ 0 ] != '/' ) { + /* get full path name */ + getcwd( path, sizeof( path ) ); + pathlen = strlen( path ); + + if ( fname[ 0 ] == '.' && fname[ 1 ] == '/' ) { + fname += 2; + len -= 2; + } + } + + bv.bv_len = pathlen + STRLENOF( "/" ) + len; + ptr = bv.bv_val = ch_malloc( bv.bv_len + STRLENOF( "/" ) + 1 ); + if ( pathlen ) { + ptr = lutil_strncopy( ptr, path, pathlen ); + ptr[ 0 ] = '/'; + ptr++; + } + ptr = lutil_strncopy( ptr, fname, len ); + if ( ptr[ -1 ] != '/' ) { + ptr[ 0 ] = '/'; + ptr++; + } + ptr[ 0 ] = '\0'; + + attr_normalize_one( ad_olmDbDirectory, &bv, &nbv, NULL ); + + next->a_desc = ad_olmDbDirectory; + next->a_vals = ch_calloc( sizeof( struct berval ), 2 ); + next->a_vals[ 0 ] = bv; + next->a_numvals = 1; + + if ( BER_BVISNULL( &nbv ) ) { + next->a_nvals = next->a_vals; + + } else { + next->a_nvals = ch_calloc( sizeof( struct berval ), 2 ); + next->a_nvals[ 0 ] = nbv; + } + + next = next->a_next; + } + + cb = ch_calloc( sizeof( monitor_callback_t ), 1 ); + cb->mc_update = bdb_monitor_update; +#if 0 /* uncomment if required */ + cb->mc_modify = bdb_monitor_modify; +#endif + cb->mc_free = bdb_monitor_free; + cb->mc_private = (void *)bdb; + + /* make sure the database is registered; then add monitor attributes */ + rc = mbe->register_database( be, &bdb->bi_monitor.bdm_ndn ); + if ( rc == 0 ) { + rc = mbe->register_entry_attrs( &bdb->bi_monitor.bdm_ndn, a, cb, + NULL, 0, NULL ); + } + +cleanup:; + if ( rc != 0 ) { + if ( cb != NULL ) { + ch_free( cb ); + cb = NULL; + } + + if ( a != NULL ) { + attrs_free( a ); + a = NULL; + } + } + + /* store for cleanup */ + bdb->bi_monitor.bdm_cb = (void *)cb; + + /* we don't need to keep track of the attributes, because + * bdb_monitor_free() takes care of everything */ + if ( a != NULL ) { + attrs_free( a ); + } + + return rc; +} + +/* + * call from within bdb_db_close() + */ +int +bdb_monitor_db_close( BackendDB *be ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + + if ( !BER_BVISNULL( &bdb->bi_monitor.bdm_ndn ) ) { + BackendInfo *mi = backend_info( "monitor" ); + monitor_extra_t *mbe; + + if ( mi && &mi->bi_extra ) { + mbe = mi->bi_extra; + mbe->unregister_entry_callback( &bdb->bi_monitor.bdm_ndn, + (monitor_callback_t *)bdb->bi_monitor.bdm_cb, + NULL, 0, NULL ); + } + + memset( &bdb->bi_monitor, 0, sizeof( bdb->bi_monitor ) ); + } + + return 0; +} + +/* + * call from within bdb_db_destroy() + */ +int +bdb_monitor_db_destroy( BackendDB *be ) +{ +#ifdef BDB_MONITOR_IDX + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + + /* TODO: free tree */ + ldap_pvt_thread_mutex_destroy( &bdb->bi_idx_mutex ); + avl_free( bdb->bi_idx, ch_free ); +#endif /* BDB_MONITOR_IDX */ + + return 0; +} + +#ifdef BDB_MONITOR_IDX + +#define BDB_MONITOR_IDX_TYPES (4) + +typedef struct monitor_idx_t monitor_idx_t; + +struct monitor_idx_t { + AttributeDescription *idx_ad; + unsigned long idx_count[BDB_MONITOR_IDX_TYPES]; +}; + +static int +bdb_monitor_bitmask2key( slap_mask_t bitmask ) +{ + int key; + + for ( key = 0; key < 8 * (int)sizeof(slap_mask_t) && !( bitmask & 0x1U ); + key++ ) + bitmask >>= 1; + + return key; +} + +static struct berval idxbv[] = { + BER_BVC( "present=" ), + BER_BVC( "equality=" ), + BER_BVC( "approx=" ), + BER_BVC( "substr=" ), + BER_BVNULL +}; + +static ber_len_t +bdb_monitor_idx2len( monitor_idx_t *idx ) +{ + int i; + ber_len_t len = 0; + + for ( i = 0; i < BDB_MONITOR_IDX_TYPES; i++ ) { + if ( idx->idx_count[ i ] != 0 ) { + len += idxbv[i].bv_len; + } + } + + return len; +} + +static int +monitor_idx_cmp( const void *p1, const void *p2 ) +{ + const monitor_idx_t *idx1 = (const monitor_idx_t *)p1; + const monitor_idx_t *idx2 = (const monitor_idx_t *)p2; + + return SLAP_PTRCMP( idx1->idx_ad, idx2->idx_ad ); +} + +static int +monitor_idx_dup( void *p1, void *p2 ) +{ + monitor_idx_t *idx1 = (monitor_idx_t *)p1; + monitor_idx_t *idx2 = (monitor_idx_t *)p2; + + return SLAP_PTRCMP( idx1->idx_ad, idx2->idx_ad ) == 0 ? -1 : 0; +} + +int +bdb_monitor_idx_add( + struct bdb_info *bdb, + AttributeDescription *desc, + slap_mask_t type ) +{ + monitor_idx_t idx_dummy = { 0 }, + *idx; + int rc = 0, key; + + idx_dummy.idx_ad = desc; + key = bdb_monitor_bitmask2key( type ) - 1; + if ( key >= BDB_MONITOR_IDX_TYPES ) { + /* invalid index type */ + return -1; + } + + ldap_pvt_thread_mutex_lock( &bdb->bi_idx_mutex ); + + idx = (monitor_idx_t *)avl_find( bdb->bi_idx, + (caddr_t)&idx_dummy, monitor_idx_cmp ); + if ( idx == NULL ) { + idx = (monitor_idx_t *)ch_calloc( sizeof( monitor_idx_t ), 1 ); + idx->idx_ad = desc; + idx->idx_count[ key ] = 1; + + switch ( avl_insert( &bdb->bi_idx, (caddr_t)idx, + monitor_idx_cmp, monitor_idx_dup ) ) + { + case 0: + break; + + default: + ch_free( idx ); + rc = -1; + } + + } else { + idx->idx_count[ key ]++; + } + + ldap_pvt_thread_mutex_unlock( &bdb->bi_idx_mutex ); + + return rc; +} + +static int +bdb_monitor_idx_apply( void *v_idx, void *v_valp ) +{ + monitor_idx_t *idx = (monitor_idx_t *)v_idx; + BerVarray *valp = (BerVarray *)v_valp; + + struct berval bv; + char *ptr; + char count_buf[ BDB_MONITOR_IDX_TYPES ][ SLAP_TEXT_BUFLEN ]; + ber_len_t count_len[ BDB_MONITOR_IDX_TYPES ], + idx_len; + int i, num = 0; + + idx_len = bdb_monitor_idx2len( idx ); + + bv.bv_len = 0; + for ( i = 0; i < BDB_MONITOR_IDX_TYPES; i++ ) { + if ( idx->idx_count[ i ] == 0 ) { + continue; + } + + count_len[ i ] = snprintf( count_buf[ i ], + sizeof( count_buf[ i ] ), "%lu", idx->idx_count[ i ] ); + bv.bv_len += count_len[ i ]; + num++; + } + + bv.bv_len += idx->idx_ad->ad_cname.bv_len + + num + + idx_len; + ptr = bv.bv_val = ch_malloc( bv.bv_len + 1 ); + ptr = lutil_strcopy( ptr, idx->idx_ad->ad_cname.bv_val ); + for ( i = 0; i < BDB_MONITOR_IDX_TYPES; i++ ) { + if ( idx->idx_count[ i ] == 0 ) { + continue; + } + + ptr[ 0 ] = '#'; + ++ptr; + ptr = lutil_strcopy( ptr, idxbv[ i ].bv_val ); + ptr = lutil_strcopy( ptr, count_buf[ i ] ); + } + + ber_bvarray_add( valp, &bv ); + + return 0; +} + +static int +bdb_monitor_idx_entry_add( + struct bdb_info *bdb, + Entry *e ) +{ + BerVarray vals = NULL; + Attribute *a; + + a = attr_find( e->e_attrs, ad_olmDbNotIndexed ); + + ldap_pvt_thread_mutex_lock( &bdb->bi_idx_mutex ); + + avl_apply( bdb->bi_idx, bdb_monitor_idx_apply, + &vals, -1, AVL_INORDER ); + + ldap_pvt_thread_mutex_unlock( &bdb->bi_idx_mutex ); + + if ( vals != NULL ) { + if ( a != NULL ) { + assert( a->a_nvals == a->a_vals ); + + ber_bvarray_free( a->a_vals ); + + } else { + Attribute **ap; + + for ( ap = &e->e_attrs; *ap != NULL; ap = &(*ap)->a_next ) + ; + *ap = attr_alloc( ad_olmDbNotIndexed ); + a = *ap; + } + a->a_vals = vals; + a->a_nvals = a->a_vals; + } + + return 0; +} + +#endif /* BDB_MONITOR_IDX */ diff --git a/servers/slapd/back-bdb/nextid.c b/servers/slapd/back-bdb/nextid.c new file mode 100644 index 0000000..caad4f6 --- /dev/null +++ b/servers/slapd/back-bdb/nextid.c @@ -0,0 +1,80 @@ +/* init.c - initialize bdb backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-bdb.h" + +int bdb_next_id( BackendDB *be, ID *out ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + + ldap_pvt_thread_mutex_lock( &bdb->bi_lastid_mutex ); + *out = ++bdb->bi_lastid; + ldap_pvt_thread_mutex_unlock( &bdb->bi_lastid_mutex ); + + return 0; +} + +int bdb_last_id( BackendDB *be, DB_TXN *tid ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + int rc; + ID id = 0; + unsigned char idbuf[sizeof(ID)]; + DBT key, data; + DBC *cursor; + + DBTzero( &key ); + key.flags = DB_DBT_USERMEM; + key.data = (char *) idbuf; + key.ulen = sizeof( idbuf ); + + DBTzero( &data ); + data.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL; + + /* Get a read cursor */ + rc = bdb->bi_id2entry->bdi_db->cursor( bdb->bi_id2entry->bdi_db, + tid, &cursor, 0 ); + + if (rc == 0) { + rc = cursor->c_get(cursor, &key, &data, DB_LAST); + cursor->c_close(cursor); + } + + switch(rc) { + case DB_NOTFOUND: + rc = 0; + break; + case 0: + BDB_DISK2ID( idbuf, &id ); + break; + + default: + Debug( LDAP_DEBUG_ANY, + "=> bdb_last_id: get failed: %s (%d)\n", + db_strerror(rc), rc, 0 ); + goto done; + } + + bdb->bi_lastid = id; + +done: + return rc; +} diff --git a/servers/slapd/back-bdb/operational.c b/servers/slapd/back-bdb/operational.c new file mode 100644 index 0000000..9ca3dd1 --- /dev/null +++ b/servers/slapd/back-bdb/operational.c @@ -0,0 +1,151 @@ +/* operational.c - bdb backend operational attributes function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-bdb.h" + +/* + * sets *hasSubordinates to LDAP_COMPARE_TRUE/LDAP_COMPARE_FALSE + * if the entry has children or not. + */ +int +bdb_hasSubordinates( + Operation *op, + Entry *e, + int *hasSubordinates ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + struct bdb_op_info *opinfo; + OpExtra *oex; + DB_TXN *rtxn; + int rc; + int release = 0; + + assert( e != NULL ); + + /* NOTE: this should never happen, but it actually happens + * when using back-relay; until we find a better way to + * preserve entry's private information while rewriting it, + * let's disable the hasSubordinate feature for back-relay. + */ + if ( BEI( e ) == NULL ) { + Entry *ee = NULL; + rc = be_entry_get_rw( op, &e->e_nname, NULL, NULL, 0, &ee ); + if ( rc != LDAP_SUCCESS || ee == NULL ) { + rc = LDAP_OTHER; + goto done; + } + e = ee; + release = 1; + if ( BEI( ee ) == NULL ) { + rc = LDAP_OTHER; + goto done; + } + } + + /* Check for a txn in a parent op, otherwise use reader txn */ + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == bdb ) + break; + } + opinfo = (struct bdb_op_info *) oex; + if ( opinfo && opinfo->boi_txn ) { + rtxn = opinfo->boi_txn; + } else { + rc = bdb_reader_get(op, bdb->bi_dbenv, &rtxn); + if ( rc ) { + rc = LDAP_OTHER; + goto done; + } + } + +retry: + /* FIXME: we can no longer assume the entry's e_private + * field is correctly populated; so we need to reacquire + * it with reader lock */ + rc = bdb_cache_children( op, rtxn, e ); + + switch( rc ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + + case 0: + *hasSubordinates = LDAP_COMPARE_TRUE; + break; + + case DB_NOTFOUND: + *hasSubordinates = LDAP_COMPARE_FALSE; + rc = LDAP_SUCCESS; + break; + + default: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(bdb_hasSubordinates) + ": has_children failed: %s (%d)\n", + db_strerror(rc), rc, 0 ); + rc = LDAP_OTHER; + } + +done:; + if ( release && e != NULL ) be_entry_release_r( op, e ); + return rc; +} + +/* + * sets the supported operational attributes (if required) + */ +int +bdb_operational( + Operation *op, + SlapReply *rs ) +{ + Attribute **ap; + + assert( rs->sr_entry != NULL ); + + for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next ) { + if ( (*ap)->a_desc == slap_schema.si_ad_hasSubordinates ) { + break; + } + } + + if ( *ap == NULL && + attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_hasSubordinates ) == NULL && + ( SLAP_OPATTRS( rs->sr_attr_flags ) || + ad_inlist( slap_schema.si_ad_hasSubordinates, rs->sr_attrs ) ) ) + { + int hasSubordinates, rc; + + rc = bdb_hasSubordinates( op, rs->sr_entry, &hasSubordinates ); + if ( rc == LDAP_SUCCESS ) { + *ap = slap_operational_hasSubordinate( hasSubordinates == LDAP_COMPARE_TRUE ); + assert( *ap != NULL ); + + ap = &(*ap)->a_next; + } + } + + return LDAP_SUCCESS; +} + diff --git a/servers/slapd/back-bdb/proto-bdb.h b/servers/slapd/back-bdb/proto-bdb.h new file mode 100644 index 0000000..f61e881 --- /dev/null +++ b/servers/slapd/back-bdb/proto-bdb.h @@ -0,0 +1,678 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#ifndef _PROTO_BDB_H +#define _PROTO_BDB_H + +LDAP_BEGIN_DECL + +#ifdef BDB_HIER +#define BDB_SYMBOL(x) LDAP_CONCAT(hdb_,x) +#define BDB_UCTYPE "HDB" +#else +#define BDB_SYMBOL(x) LDAP_CONCAT(bdb_,x) +#define BDB_UCTYPE "BDB" +#endif + +/* + * attr.c + */ + +#define bdb_attr_mask BDB_SYMBOL(attr_mask) +#define bdb_attr_flush BDB_SYMBOL(attr_flush) +#define bdb_attr_slot BDB_SYMBOL(attr_slot) +#define bdb_attr_index_config BDB_SYMBOL(attr_index_config) +#define bdb_attr_index_destroy BDB_SYMBOL(attr_index_destroy) +#define bdb_attr_index_free BDB_SYMBOL(attr_index_free) +#define bdb_attr_index_unparse BDB_SYMBOL(attr_index_unparse) +#define bdb_attr_info_free BDB_SYMBOL(attr_info_free) + +AttrInfo *bdb_attr_mask( struct bdb_info *bdb, + AttributeDescription *desc ); + +void bdb_attr_flush( struct bdb_info *bdb ); + +int bdb_attr_slot( struct bdb_info *bdb, + AttributeDescription *desc, int *insert ); + +int bdb_attr_index_config LDAP_P(( struct bdb_info *bdb, + const char *fname, int lineno, + int argc, char **argv, struct config_reply_s *cr )); + +void bdb_attr_index_unparse LDAP_P(( struct bdb_info *bdb, BerVarray *bva )); +void bdb_attr_index_destroy LDAP_P(( struct bdb_info *bdb )); +void bdb_attr_index_free LDAP_P(( struct bdb_info *bdb, + AttributeDescription *ad )); + +void bdb_attr_info_free( AttrInfo *ai ); + +/* + * config.c + */ + +#define bdb_back_init_cf BDB_SYMBOL(back_init_cf) + +int bdb_back_init_cf( BackendInfo *bi ); + +/* + * dbcache.c + */ +#define bdb_db_cache BDB_SYMBOL(db_cache) +#define bdb_db_findsize BDB_SYMBOL(db_findsize) + +int +bdb_db_cache( + Backend *be, + struct berval *name, + DB **db ); + +int +bdb_db_findsize( + struct bdb_info *bdb, + struct berval *name ); + +/* + * dn2entry.c + */ +#define bdb_dn2entry BDB_SYMBOL(dn2entry) + +int bdb_dn2entry LDAP_P(( Operation *op, DB_TXN *tid, + struct berval *dn, EntryInfo **e, int matched, + DB_LOCK *lock )); + +/* + * dn2id.c + */ +#define bdb_dn2id BDB_SYMBOL(dn2id) +#define bdb_dn2id_add BDB_SYMBOL(dn2id_add) +#define bdb_dn2id_delete BDB_SYMBOL(dn2id_delete) +#define bdb_dn2id_children BDB_SYMBOL(dn2id_children) +#define bdb_dn2idl BDB_SYMBOL(dn2idl) + +int bdb_dn2id( + Operation *op, + struct berval *dn, + EntryInfo *ei, + DB_TXN *txn, + DBC **cursor ); + +int bdb_dn2id_add( + Operation *op, + DB_TXN *tid, + EntryInfo *eip, + Entry *e ); + +int bdb_dn2id_delete( + Operation *op, + DB_TXN *tid, + EntryInfo *eip, + Entry *e ); + +int bdb_dn2id_children( + Operation *op, + DB_TXN *tid, + Entry *e ); + +int bdb_dn2idl( + Operation *op, + DB_TXN *txn, + struct berval *ndn, + EntryInfo *ei, + ID *ids, + ID *stack ); + +#ifdef BDB_HIER +#define bdb_dn2id_parent BDB_SYMBOL(dn2id_parent) +#define bdb_dup_compare BDB_SYMBOL(dup_compare) +#define bdb_fix_dn BDB_SYMBOL(fix_dn) + +int bdb_dn2id_parent( + Operation *op, + DB_TXN *txn, + EntryInfo *ei, + ID *idp ); + +int bdb_dup_compare( + DB *db, + const DBT *usrkey, + const DBT *curkey ); + +int bdb_fix_dn( Entry *e, int checkit ); +#endif + + +/* + * error.c + */ +#define bdb_errcall BDB_SYMBOL(errcall) + +#if DB_VERSION_FULL < 0x04030000 +void bdb_errcall( const char *pfx, char * msg ); +#else +#define bdb_msgcall BDB_SYMBOL(msgcall) +void bdb_errcall( const DB_ENV *env, const char *pfx, const char * msg ); +void bdb_msgcall( const DB_ENV *env, const char * msg ); +#endif + +#ifdef HAVE_EBCDIC +#define ebcdic_dberror BDB_SYMBOL(ebcdic_dberror) + +char *ebcdic_dberror( int rc ); +#define db_strerror(x) ebcdic_dberror(x) +#endif + +/* + * filterentry.c + */ +#define bdb_filter_candidates BDB_SYMBOL(filter_candidates) + +int bdb_filter_candidates( + Operation *op, + DB_TXN *txn, + Filter *f, + ID *ids, + ID *tmp, + ID *stack ); + +/* + * id2entry.c + */ +#define bdb_id2entry BDB_SYMBOL(id2entry) +#define bdb_id2entry_add BDB_SYMBOL(id2entry_add) +#define bdb_id2entry_update BDB_SYMBOL(id2entry_update) +#define bdb_id2entry_delete BDB_SYMBOL(id2entry_delete) + +int bdb_id2entry_add( + BackendDB *be, + DB_TXN *tid, + Entry *e ); + +int bdb_id2entry_update( + BackendDB *be, + DB_TXN *tid, + Entry *e ); + +int bdb_id2entry_delete( + BackendDB *be, + DB_TXN *tid, + Entry *e); + +#ifdef SLAP_ZONE_ALLOC +#else +int bdb_id2entry( + BackendDB *be, + DB_TXN *tid, + ID id, + Entry **e); +#endif + +#define bdb_entry_free BDB_SYMBOL(entry_free) +#define bdb_entry_return BDB_SYMBOL(entry_return) +#define bdb_entry_release BDB_SYMBOL(entry_release) +#define bdb_entry_get BDB_SYMBOL(entry_get) + +void bdb_entry_free ( Entry *e ); +#ifdef SLAP_ZONE_ALLOC +int bdb_entry_return( struct bdb_info *bdb, Entry *e, int seqno ); +#else +int bdb_entry_return( Entry *e ); +#endif +BI_entry_release_rw bdb_entry_release; +BI_entry_get_rw bdb_entry_get; + + +/* + * idl.c + */ + +#define bdb_idl_cache_get BDB_SYMBOL(idl_cache_get) +#define bdb_idl_cache_put BDB_SYMBOL(idl_cache_put) +#define bdb_idl_cache_del BDB_SYMBOL(idl_cache_del) +#define bdb_idl_cache_add_id BDB_SYMBOL(idl_cache_add_id) +#define bdb_idl_cache_del_id BDB_SYMBOL(idl_cache_del_id) + +int bdb_idl_cache_get( + struct bdb_info *bdb, + DB *db, + DBT *key, + ID *ids ); + +void +bdb_idl_cache_put( + struct bdb_info *bdb, + DB *db, + DBT *key, + ID *ids, + int rc ); + +void +bdb_idl_cache_del( + struct bdb_info *bdb, + DB *db, + DBT *key ); + +void +bdb_idl_cache_add_id( + struct bdb_info *bdb, + DB *db, + DBT *key, + ID id ); + +void +bdb_idl_cache_del_id( + struct bdb_info *bdb, + DB *db, + DBT *key, + ID id ); + +#define bdb_idl_first BDB_SYMBOL(idl_first) +#define bdb_idl_next BDB_SYMBOL(idl_next) +#define bdb_idl_search BDB_SYMBOL(idl_search) +#define bdb_idl_insert BDB_SYMBOL(idl_insert) +#define bdb_idl_delete BDB_SYMBOL(idl_delete) +#define bdb_idl_intersection BDB_SYMBOL(idl_intersection) +#define bdb_idl_union BDB_SYMBOL(idl_union) +#define bdb_idl_sort BDB_SYMBOL(idl_sort) +#define bdb_idl_append BDB_SYMBOL(idl_append) +#define bdb_idl_append_one BDB_SYMBOL(idl_append_one) + +#define bdb_idl_fetch_key BDB_SYMBOL(idl_fetch_key) +#define bdb_idl_insert_key BDB_SYMBOL(idl_insert_key) +#define bdb_idl_delete_key BDB_SYMBOL(idl_delete_key) + +unsigned bdb_idl_search( ID *ids, ID id ); + +int bdb_idl_fetch_key( + BackendDB *be, + DB *db, + DB_TXN *txn, + DBT *key, + ID *ids, + DBC **saved_cursor, + int get_flag ); + +int bdb_idl_insert( ID *ids, ID id ); +int bdb_idl_delete( ID *ids, ID id ); + +int bdb_idl_insert_key( + BackendDB *be, + DB *db, + DB_TXN *txn, + DBT *key, + ID id ); + +int bdb_idl_delete_key( + BackendDB *be, + DB *db, + DB_TXN *txn, + DBT *key, + ID id ); + +int +bdb_idl_intersection( + ID *a, + ID *b ); + +int +bdb_idl_union( + ID *a, + ID *b ); + +ID bdb_idl_first( ID *ids, ID *cursor ); +ID bdb_idl_next( ID *ids, ID *cursor ); + +void bdb_idl_sort( ID *ids, ID *tmp ); +int bdb_idl_append( ID *a, ID *b ); +int bdb_idl_append_one( ID *ids, ID id ); + + +/* + * index.c + */ +#define bdb_index_mask BDB_SYMBOL(index_mask) +#define bdb_index_param BDB_SYMBOL(index_param) +#define bdb_index_values BDB_SYMBOL(index_values) +#define bdb_index_entry BDB_SYMBOL(index_entry) +#define bdb_index_recset BDB_SYMBOL(index_recset) +#define bdb_index_recrun BDB_SYMBOL(index_recrun) + +extern AttrInfo * +bdb_index_mask LDAP_P(( + Backend *be, + AttributeDescription *desc, + struct berval *name )); + +extern int +bdb_index_param LDAP_P(( + Backend *be, + AttributeDescription *desc, + int ftype, + DB **db, + slap_mask_t *mask, + struct berval *prefix )); + +extern int +bdb_index_values LDAP_P(( + Operation *op, + DB_TXN *txn, + AttributeDescription *desc, + BerVarray vals, + ID id, + int opid )); + +extern int +bdb_index_recset LDAP_P(( + struct bdb_info *bdb, + Attribute *a, + AttributeType *type, + struct berval *tags, + IndexRec *ir )); + +extern int +bdb_index_recrun LDAP_P(( + Operation *op, + struct bdb_info *bdb, + IndexRec *ir, + ID id, + int base )); + +int bdb_index_entry LDAP_P(( Operation *op, DB_TXN *t, int r, Entry *e )); + +#define bdb_index_entry_add(op,t,e) \ + bdb_index_entry((op),(t),SLAP_INDEX_ADD_OP,(e)) +#define bdb_index_entry_del(op,t,e) \ + bdb_index_entry((op),(t),SLAP_INDEX_DELETE_OP,(e)) + +/* + * key.c + */ +#define bdb_key_read BDB_SYMBOL(key_read) +#define bdb_key_change BDB_SYMBOL(key_change) + +extern int +bdb_key_read( + Backend *be, + DB *db, + DB_TXN *txn, + struct berval *k, + ID *ids, + DBC **saved_cursor, + int get_flags ); + +extern int +bdb_key_change( + Backend *be, + DB *db, + DB_TXN *txn, + struct berval *k, + ID id, + int op ); + +/* + * nextid.c + */ +#define bdb_next_id BDB_SYMBOL(next_id) +#define bdb_last_id BDB_SYMBOL(last_id) + +int bdb_next_id( BackendDB *be, ID *id ); +int bdb_last_id( BackendDB *be, DB_TXN *tid ); + +/* + * modify.c + */ +#define bdb_modify_internal BDB_SYMBOL(modify_internal) + +int bdb_modify_internal( + Operation *op, + DB_TXN *tid, + Modifications *modlist, + Entry *e, + const char **text, + char *textbuf, + size_t textlen ); + +/* + * monitor.c + */ + +#define bdb_monitor_db_init BDB_SYMBOL(monitor_db_init) +#define bdb_monitor_db_open BDB_SYMBOL(monitor_db_open) +#define bdb_monitor_db_close BDB_SYMBOL(monitor_db_close) +#define bdb_monitor_db_destroy BDB_SYMBOL(monitor_db_destroy) + +int bdb_monitor_db_init( BackendDB *be ); +int bdb_monitor_db_open( BackendDB *be ); +int bdb_monitor_db_close( BackendDB *be ); +int bdb_monitor_db_destroy( BackendDB *be ); + +#ifdef BDB_MONITOR_IDX +#define bdb_monitor_idx_add BDB_SYMBOL(monitor_idx_add) +int +bdb_monitor_idx_add( + struct bdb_info *bdb, + AttributeDescription *desc, + slap_mask_t type ); +#endif /* BDB_MONITOR_IDX */ + +/* + * cache.c + */ +#define bdb_cache_entry_db_unlock BDB_SYMBOL(cache_entry_db_unlock) +#define bdb_cache_return_entry_rw BDB_SYMBOL(cache_return_entry_rw) + +#define bdb_cache_entryinfo_lock(e) \ + ldap_pvt_thread_mutex_lock( &(e)->bei_kids_mutex ) +#define bdb_cache_entryinfo_unlock(e) \ + ldap_pvt_thread_mutex_unlock( &(e)->bei_kids_mutex ) +#define bdb_cache_entryinfo_trylock(e) \ + ldap_pvt_thread_mutex_trylock( &(e)->bei_kids_mutex ) + +/* What a mess. Hopefully the current cache scheme will stabilize + * and we can trim out all of this stuff. + */ +void bdb_cache_return_entry_rw( struct bdb_info *bdb, Entry *e, + int rw, DB_LOCK *lock ); +#define bdb_cache_return_entry_r(bdb, e, l) \ + bdb_cache_return_entry_rw((bdb), (e), 0, (l)) +#define bdb_cache_return_entry_w(bdb, e, l) \ + bdb_cache_return_entry_rw((bdb), (e), 1, (l)) +#if 0 +void bdb_unlocked_cache_return_entry_rw( struct bdb_info *bdb, Entry *e, int rw ); +#else +#define bdb_unlocked_cache_return_entry_rw( a, b, c ) ((void)0) +#endif +#define bdb_unlocked_cache_return_entry_r( c, e ) \ + bdb_unlocked_cache_return_entry_rw((c), (e), 0) +#define bdb_unlocked_cache_return_entry_w( c, e ) \ + bdb_unlocked_cache_return_entry_rw((c), (e), 1) + +#define bdb_cache_add BDB_SYMBOL(cache_add) +#define bdb_cache_children BDB_SYMBOL(cache_children) +#define bdb_cache_delete BDB_SYMBOL(cache_delete) +#define bdb_cache_delete_cleanup BDB_SYMBOL(cache_delete_cleanup) +#define bdb_cache_find_id BDB_SYMBOL(cache_find_id) +#define bdb_cache_find_ndn BDB_SYMBOL(cache_find_ndn) +#define bdb_cache_find_parent BDB_SYMBOL(cache_find_parent) +#define bdb_cache_modify BDB_SYMBOL(cache_modify) +#define bdb_cache_modrdn BDB_SYMBOL(cache_modrdn) +#define bdb_cache_release_all BDB_SYMBOL(cache_release_all) +#define bdb_cache_delete_entry BDB_SYMBOL(cache_delete_entry) +#define bdb_cache_deref BDB_SYMBOL(cache_deref) + +int bdb_cache_children( + Operation *op, + DB_TXN *txn, + Entry *e +); +int bdb_cache_add( + struct bdb_info *bdb, + EntryInfo *pei, + Entry *e, + struct berval *nrdn, + DB_TXN *txn, + DB_LOCK *lock +); +int bdb_cache_modrdn( + struct bdb_info *bdb, + Entry *e, + struct berval *nrdn, + Entry *new, + EntryInfo *ein, + DB_TXN *txn, + DB_LOCK *lock +); +int bdb_cache_modify( + struct bdb_info *bdb, + Entry *e, + Attribute *newAttrs, + DB_TXN *txn, + DB_LOCK *lock +); +int bdb_cache_find_ndn( + Operation *op, + DB_TXN *txn, + struct berval *ndn, + EntryInfo **res +); + +#define ID_LOCKED 1 +#define ID_NOCACHE 2 +#define ID_NOENTRY 4 +#define ID_CHKPURGE 8 +int bdb_cache_find_id( + Operation *op, + DB_TXN *tid, + ID id, + EntryInfo **eip, + int flag, + DB_LOCK *lock +); +int +bdb_cache_find_parent( + Operation *op, + DB_TXN *txn, + ID id, + EntryInfo **res +); +int bdb_cache_delete( + struct bdb_info *bdb, + Entry *e, + DB_TXN *txn, + DB_LOCK *lock +); +void bdb_cache_delete_cleanup( + Cache *cache, + EntryInfo *ei +); +void bdb_cache_release_all( Cache *cache ); +void bdb_cache_deref( EntryInfo *ei ); + +#ifdef BDB_HIER +int hdb_cache_load( + struct bdb_info *bdb, + EntryInfo *ei, + EntryInfo **res +); +#endif + +#define bdb_cache_entry_db_relock BDB_SYMBOL(cache_entry_db_relock) +int bdb_cache_entry_db_relock( + struct bdb_info *bdb, + DB_TXN *txn, + EntryInfo *ei, + int rw, + int tryOnly, + DB_LOCK *lock ); + +int bdb_cache_entry_db_unlock( + struct bdb_info *bdb, + DB_LOCK *lock ); + +#define bdb_reader_get BDB_SYMBOL(reader_get) +#define bdb_reader_flush BDB_SYMBOL(reader_flush) +int bdb_reader_get( Operation *op, DB_ENV *env, DB_TXN **txn ); +void bdb_reader_flush( DB_ENV *env ); + +/* + * trans.c + */ +#define bdb_trans_backoff BDB_SYMBOL(trans_backoff) + +void +bdb_trans_backoff( int num_retries ); + +/* + * former external.h + */ + +#define bdb_back_initialize BDB_SYMBOL(back_initialize) +#define bdb_db_config BDB_SYMBOL(db_config) +#define bdb_add BDB_SYMBOL(add) +#define bdb_bind BDB_SYMBOL(bind) +#define bdb_compare BDB_SYMBOL(compare) +#define bdb_delete BDB_SYMBOL(delete) +#define bdb_modify BDB_SYMBOL(modify) +#define bdb_modrdn BDB_SYMBOL(modrdn) +#define bdb_search BDB_SYMBOL(search) +#define bdb_extended BDB_SYMBOL(extended) +#define bdb_referrals BDB_SYMBOL(referrals) +#define bdb_operational BDB_SYMBOL(operational) +#define bdb_hasSubordinates BDB_SYMBOL(hasSubordinates) +#define bdb_tool_entry_open BDB_SYMBOL(tool_entry_open) +#define bdb_tool_entry_close BDB_SYMBOL(tool_entry_close) +#define bdb_tool_entry_first_x BDB_SYMBOL(tool_entry_first_x) +#define bdb_tool_entry_next BDB_SYMBOL(tool_entry_next) +#define bdb_tool_entry_get BDB_SYMBOL(tool_entry_get) +#define bdb_tool_entry_put BDB_SYMBOL(tool_entry_put) +#define bdb_tool_entry_reindex BDB_SYMBOL(tool_entry_reindex) +#define bdb_tool_dn2id_get BDB_SYMBOL(tool_dn2id_get) +#define bdb_tool_entry_modify BDB_SYMBOL(tool_entry_modify) +#define bdb_tool_idl_add BDB_SYMBOL(tool_idl_add) + +extern BI_init bdb_back_initialize; + +extern BI_db_config bdb_db_config; + +extern BI_op_add bdb_add; +extern BI_op_bind bdb_bind; +extern BI_op_compare bdb_compare; +extern BI_op_delete bdb_delete; +extern BI_op_modify bdb_modify; +extern BI_op_modrdn bdb_modrdn; +extern BI_op_search bdb_search; +extern BI_op_extended bdb_extended; + +extern BI_chk_referrals bdb_referrals; + +extern BI_operational bdb_operational; + +extern BI_has_subordinates bdb_hasSubordinates; + +/* tools.c */ +extern BI_tool_entry_open bdb_tool_entry_open; +extern BI_tool_entry_close bdb_tool_entry_close; +extern BI_tool_entry_first_x bdb_tool_entry_first_x; +extern BI_tool_entry_next bdb_tool_entry_next; +extern BI_tool_entry_get bdb_tool_entry_get; +extern BI_tool_entry_put bdb_tool_entry_put; +extern BI_tool_entry_reindex bdb_tool_entry_reindex; +extern BI_tool_dn2id_get bdb_tool_dn2id_get; +extern BI_tool_entry_modify bdb_tool_entry_modify; + +int bdb_tool_idl_add( BackendDB *be, DB *db, DB_TXN *txn, DBT *key, ID id ); + +LDAP_END_DECL + +#endif /* _PROTO_BDB_H */ diff --git a/servers/slapd/back-bdb/referral.c b/servers/slapd/back-bdb/referral.c new file mode 100644 index 0000000..ad51b2d --- /dev/null +++ b/servers/slapd/back-bdb/referral.c @@ -0,0 +1,152 @@ +/* referral.c - BDB backend referral handler */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" +#include <stdio.h> +#include <ac/string.h> + +#include "back-bdb.h" + +int +bdb_referrals( Operation *op, SlapReply *rs ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + Entry *e = NULL; + EntryInfo *ei; + int rc = LDAP_SUCCESS; + + DB_TXN *rtxn; + DB_LOCK lock; + + if( op->o_tag == LDAP_REQ_SEARCH ) { + /* let search take care of itself */ + return rc; + } + + if( get_manageDSAit( op ) ) { + /* let op take care of DSA management */ + return rc; + } + + rc = bdb_reader_get(op, bdb->bi_dbenv, &rtxn); + switch(rc) { + case 0: + break; + default: + return LDAP_OTHER; + } + +dn2entry_retry: + /* get entry */ + rc = bdb_dn2entry( op, rtxn, &op->o_req_ndn, &ei, 1, &lock ); + + /* bdb_dn2entry() may legally leave ei == NULL + * if rc != 0 and rc != DB_NOTFOUND + */ + if ( ei ) { + e = ei->bei_e; + } + + switch(rc) { + case DB_NOTFOUND: + case 0: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + return LDAP_BUSY; + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto dn2entry_retry; + default: + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_referrals) + ": dn2entry failed: %s (%d)\n", + db_strerror(rc), rc, 0 ); + rs->sr_text = "internal error"; + return LDAP_OTHER; + } + + if ( rc == DB_NOTFOUND ) { + rc = LDAP_SUCCESS; + rs->sr_matched = NULL; + if ( e != NULL ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_referrals) + ": tag=%lu target=\"%s\" matched=\"%s\"\n", + (unsigned long)op->o_tag, op->o_req_dn.bv_val, e->e_name.bv_val ); + + if( is_entry_referral( e ) ) { + BerVarray ref = get_entry_referrals( op, e ); + rc = LDAP_OTHER; + rs->sr_ref = referral_rewrite( ref, &e->e_name, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + ber_bvarray_free( ref ); + if ( rs->sr_ref ) { + rs->sr_matched = ber_strdup_x( + e->e_name.bv_val, op->o_tmpmemctx ); + } + } + + bdb_cache_return_entry_r (bdb, e, &lock); + e = NULL; + } + + if( rs->sr_ref != NULL ) { + /* send referrals */ + rc = rs->sr_err = LDAP_REFERRAL; + send_ldap_result( op, rs ); + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } else if ( rc != LDAP_SUCCESS ) { + rs->sr_text = rs->sr_matched ? "bad referral object" : NULL; + } + + if (rs->sr_matched) { + op->o_tmpfree( (char *)rs->sr_matched, op->o_tmpmemctx ); + rs->sr_matched = NULL; + } + return rc; + } + + if ( is_entry_referral( e ) ) { + /* entry is a referral */ + BerVarray refs = get_entry_referrals( op, e ); + rs->sr_ref = referral_rewrite( + refs, &e->e_name, &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_referrals) + ": tag=%lu target=\"%s\" matched=\"%s\"\n", + (unsigned long)op->o_tag, op->o_req_dn.bv_val, e->e_name.bv_val ); + + rs->sr_matched = e->e_name.bv_val; + if( rs->sr_ref != NULL ) { + rc = rs->sr_err = LDAP_REFERRAL; + send_ldap_result( op, rs ); + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } else { + rc = LDAP_OTHER; + rs->sr_text = "bad referral object"; + } + + rs->sr_matched = NULL; + ber_bvarray_free( refs ); + } + + bdb_cache_return_entry_r(bdb, e, &lock); + return rc; +} diff --git a/servers/slapd/back-bdb/search.c b/servers/slapd/back-bdb/search.c new file mode 100644 index 0000000..04d76b2 --- /dev/null +++ b/servers/slapd/back-bdb/search.c @@ -0,0 +1,1388 @@ +/* search.c - search operation */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-bdb.h" +#include "idl.h" + +static int base_candidate( + BackendDB *be, + Entry *e, + ID *ids ); + +static int search_candidates( + Operation *op, + SlapReply *rs, + Entry *e, + DB_TXN *txn, + ID *ids, + ID *scopes ); + +static int parse_paged_cookie( Operation *op, SlapReply *rs ); + +static void send_paged_response( + Operation *op, + SlapReply *rs, + ID *lastid, + int tentries ); + +/* Dereference aliases for a single alias entry. Return the final + * dereferenced entry on success, NULL on any failure. + */ +static Entry * deref_base ( + Operation *op, + SlapReply *rs, + Entry *e, + Entry **matched, + DB_TXN *txn, + DB_LOCK *lock, + ID *tmp, + ID *visited ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + struct berval ndn; + EntryInfo *ei; + DB_LOCK lockr; + + rs->sr_err = LDAP_ALIAS_DEREF_PROBLEM; + rs->sr_text = "maximum deref depth exceeded"; + + for (;;) { + /* Remember the last entry we looked at, so we can + * report broken links + */ + *matched = e; + + if (BDB_IDL_N(tmp) >= op->o_bd->be_max_deref_depth) { + e = NULL; + break; + } + + /* If this is part of a subtree or onelevel search, + * have we seen this ID before? If so, quit. + */ + if ( visited && bdb_idl_insert( visited, e->e_id ) ) { + e = NULL; + break; + } + + /* If we've seen this ID during this deref iteration, + * we've hit a loop. + */ + if ( bdb_idl_insert( tmp, e->e_id ) ) { + rs->sr_err = LDAP_ALIAS_PROBLEM; + rs->sr_text = "circular alias"; + e = NULL; + break; + } + + /* If there was a problem getting the aliasedObjectName, + * get_alias_dn will have set the error status. + */ + if ( get_alias_dn(e, &ndn, &rs->sr_err, &rs->sr_text) ) { + e = NULL; + break; + } + + rs->sr_err = bdb_dn2entry( op, txn, &ndn, &ei, + 0, &lockr ); + if ( rs->sr_err == DB_LOCK_DEADLOCK ) + return NULL; + + if ( ei ) { + e = ei->bei_e; + } else { + e = NULL; + } + + if (!e) { + rs->sr_err = LDAP_ALIAS_PROBLEM; + rs->sr_text = "aliasedObject not found"; + break; + } + + /* Free the previous entry, continue to work with the + * one we just retrieved. + */ + bdb_cache_return_entry_r( bdb, *matched, lock); + *lock = lockr; + + /* We found a regular entry. Return this to the caller. The + * entry is still locked for Read. + */ + if (!is_entry_alias(e)) { + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + break; + } + } + return e; +} + +/* Look for and dereference all aliases within the search scope. Adds + * the dereferenced entries to the "ids" list. Requires "stack" to be + * able to hold 8 levels of DB_SIZE IDLs. Of course we're hardcoded to + * require a minimum of 8 UM_SIZE IDLs so this is never a problem. + */ +static int search_aliases( + Operation *op, + SlapReply *rs, + Entry *e, + DB_TXN *txn, + ID *ids, + ID *scopes, + ID *stack ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + ID *aliases, *curscop, *subscop, *visited, *newsubs, *oldsubs, *tmp; + ID cursora, ida, cursoro, ido, *subscop2; + Entry *matched, *a; + EntryInfo *ei; + struct berval bv_alias = BER_BVC( "alias" ); + AttributeAssertion aa_alias = ATTRIBUTEASSERTION_INIT; + Filter af; + DB_LOCK locka, lockr; + int first = 1; + + aliases = stack; /* IDL of all aliases in the database */ + curscop = aliases + BDB_IDL_DB_SIZE; /* Aliases in the current scope */ + subscop = curscop + BDB_IDL_DB_SIZE; /* The current scope */ + visited = subscop + BDB_IDL_DB_SIZE; /* IDs we've seen in this search */ + newsubs = visited + BDB_IDL_DB_SIZE; /* New subtrees we've added */ + oldsubs = newsubs + BDB_IDL_DB_SIZE; /* Subtrees added previously */ + tmp = oldsubs + BDB_IDL_DB_SIZE; /* Scratch space for deref_base() */ + + /* A copy of subscop, because subscop gets clobbered by + * the bdb_idl_union/intersection routines + */ + subscop2 = tmp + BDB_IDL_DB_SIZE; + + af.f_choice = LDAP_FILTER_EQUALITY; + af.f_ava = &aa_alias; + af.f_av_desc = slap_schema.si_ad_objectClass; + af.f_av_value = bv_alias; + af.f_next = NULL; + + /* Find all aliases in database */ + BDB_IDL_ZERO( aliases ); + rs->sr_err = bdb_filter_candidates( op, txn, &af, aliases, + curscop, visited ); + if (rs->sr_err != LDAP_SUCCESS || BDB_IDL_IS_ZERO( aliases )) { + return rs->sr_err; + } + oldsubs[0] = 1; + oldsubs[1] = e->e_id; + + BDB_IDL_ZERO( ids ); + BDB_IDL_ZERO( visited ); + BDB_IDL_ZERO( newsubs ); + + cursoro = 0; + ido = bdb_idl_first( oldsubs, &cursoro ); + + for (;;) { + /* Set curscop to only the aliases in the current scope. Start with + * all the aliases, obtain the IDL for the current scope, and then + * get the intersection of these two IDLs. Add the current scope + * to the cumulative list of candidates. + */ + BDB_IDL_CPY( curscop, aliases ); + rs->sr_err = bdb_dn2idl( op, txn, &e->e_nname, BEI(e), subscop, + subscop2+BDB_IDL_DB_SIZE ); + + if (first) { + first = 0; + } else { + bdb_cache_return_entry_r (bdb, e, &locka); + } + if ( rs->sr_err == DB_LOCK_DEADLOCK ) + return rs->sr_err; + + BDB_IDL_CPY(subscop2, subscop); + rs->sr_err = bdb_idl_intersection(curscop, subscop); + bdb_idl_union( ids, subscop2 ); + + /* Dereference all of the aliases in the current scope. */ + cursora = 0; + for (ida = bdb_idl_first(curscop, &cursora); ida != NOID; + ida = bdb_idl_next(curscop, &cursora)) + { + ei = NULL; +retry1: + rs->sr_err = bdb_cache_find_id(op, txn, + ida, &ei, 0, &lockr ); + if (rs->sr_err != LDAP_SUCCESS) { + if ( rs->sr_err == DB_LOCK_DEADLOCK ) + return rs->sr_err; + if ( rs->sr_err == DB_LOCK_NOTGRANTED ) + goto retry1; + continue; + } + a = ei->bei_e; + + /* This should only happen if the curscop IDL has maxed out and + * turned into a range that spans IDs indiscriminately + */ + if (!is_entry_alias(a)) { + bdb_cache_return_entry_r (bdb, a, &lockr); + continue; + } + + /* Actually dereference the alias */ + BDB_IDL_ZERO(tmp); + a = deref_base( op, rs, a, &matched, txn, &lockr, + tmp, visited ); + if (a) { + /* If the target was not already in our current candidates, + * make note of it in the newsubs list. Also + * set it in the scopes list so that bdb_search + * can check it. + */ + if (bdb_idl_insert(ids, a->e_id) == 0) { + bdb_idl_insert(newsubs, a->e_id); + bdb_idl_insert(scopes, a->e_id); + } + bdb_cache_return_entry_r( bdb, a, &lockr); + + } else if ( rs->sr_err == DB_LOCK_DEADLOCK ) { + return rs->sr_err; + } else if (matched) { + /* Alias could not be dereferenced, or it deref'd to + * an ID we've already seen. Ignore it. + */ + bdb_cache_return_entry_r( bdb, matched, &lockr ); + rs->sr_text = NULL; + } + } + /* If this is a OneLevel search, we're done; oldsubs only had one + * ID in it. For a Subtree search, oldsubs may be a list of scope IDs. + */ + if ( op->ors_scope == LDAP_SCOPE_ONELEVEL ) break; +nextido: + ido = bdb_idl_next( oldsubs, &cursoro ); + + /* If we're done processing the old scopes, did we add any new + * scopes in this iteration? If so, go back and do those now. + */ + if (ido == NOID) { + if (BDB_IDL_IS_ZERO(newsubs)) break; + BDB_IDL_CPY(oldsubs, newsubs); + BDB_IDL_ZERO(newsubs); + cursoro = 0; + ido = bdb_idl_first( oldsubs, &cursoro ); + } + + /* Find the entry corresponding to the next scope. If it can't + * be found, ignore it and move on. This should never happen; + * we should never see the ID of an entry that doesn't exist. + * Set the name so that the scope's IDL can be retrieved. + */ + ei = NULL; +sameido: + rs->sr_err = bdb_cache_find_id(op, txn, ido, &ei, + 0, &locka ); + if ( rs->sr_err != LDAP_SUCCESS ) { + if ( rs->sr_err == DB_LOCK_DEADLOCK ) + return rs->sr_err; + if ( rs->sr_err == DB_LOCK_NOTGRANTED ) + goto sameido; + goto nextido; + } + e = ei->bei_e; + } + return rs->sr_err; +} + +/* Get the next ID from the DB. Used if the candidate list is + * a range and simple iteration hits missing entryIDs + */ +static int +bdb_get_nextid(struct bdb_info *bdb, DB_TXN *ltid, ID *cursor) +{ + DBC *curs; + DBT key, data; + ID id, nid; + int rc; + + id = *cursor + 1; + BDB_ID2DISK( id, &nid ); + rc = bdb->bi_id2entry->bdi_db->cursor( + bdb->bi_id2entry->bdi_db, ltid, &curs, bdb->bi_db_opflags ); + if ( rc ) + return rc; + key.data = &nid; + key.size = key.ulen = sizeof(ID); + key.flags = DB_DBT_USERMEM; + data.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL; + data.dlen = data.ulen = 0; + rc = curs->c_get( curs, &key, &data, DB_SET_RANGE ); + curs->c_close( curs ); + if ( rc ) + return rc; + BDB_DISK2ID( &nid, cursor ); + return 0; +} + +int +bdb_search( Operation *op, SlapReply *rs ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + ID id, cursor; + ID lastid = NOID; + ID candidates[BDB_IDL_UM_SIZE]; + ID scopes[BDB_IDL_DB_SIZE]; + Entry *e = NULL, base, *e_root; + Entry *matched = NULL; + EntryInfo *ei; + AttributeName *attrs; + struct berval realbase = BER_BVNULL; + slap_mask_t mask; + time_t stoptime; + int manageDSAit; + int tentries = 0; + unsigned nentries = 0; + int idflag = 0; + + DB_LOCK lock; + struct bdb_op_info *opinfo = NULL; + DB_TXN *ltid = NULL; + OpExtra *oex; + + Debug( LDAP_DEBUG_TRACE, "=> " LDAP_XSTRING(bdb_search) "\n", 0, 0, 0); + attrs = op->oq_search.rs_attrs; + + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == bdb ) + break; + } + opinfo = (struct bdb_op_info *) oex; + + manageDSAit = get_manageDSAit( op ); + + if ( opinfo && opinfo->boi_txn ) { + ltid = opinfo->boi_txn; + } else { + rs->sr_err = bdb_reader_get( op, bdb->bi_dbenv, <id ); + + switch(rs->sr_err) { + case 0: + break; + default: + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return rs->sr_err; + } + } + + e_root = bdb->bi_cache.c_dntree.bei_e; + if ( op->o_req_ndn.bv_len == 0 ) { + /* DIT root special case */ + ei = e_root->e_private; + rs->sr_err = LDAP_SUCCESS; + } else { + if ( op->ors_deref & LDAP_DEREF_FINDING ) { + BDB_IDL_ZERO(candidates); + } +dn2entry_retry: + /* get entry with reader lock */ + rs->sr_err = bdb_dn2entry( op, ltid, &op->o_req_ndn, &ei, + 1, &lock ); + } + + switch(rs->sr_err) { + case DB_NOTFOUND: + matched = ei->bei_e; + break; + case 0: + e = ei->bei_e; + break; + case DB_LOCK_DEADLOCK: + if ( !opinfo ) { + ltid->flags &= ~TXN_DEADLOCK; + goto dn2entry_retry; + } + opinfo->boi_err = rs->sr_err; + /* FALLTHRU */ + case LDAP_BUSY: + send_ldap_error( op, rs, LDAP_BUSY, "ldap server busy" ); + return LDAP_BUSY; + case DB_LOCK_NOTGRANTED: + goto dn2entry_retry; + default: + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return rs->sr_err; + } + + if ( op->ors_deref & LDAP_DEREF_FINDING ) { + if ( matched && is_entry_alias( matched )) { + struct berval stub; + + stub.bv_val = op->o_req_ndn.bv_val; + stub.bv_len = op->o_req_ndn.bv_len - matched->e_nname.bv_len - 1; + e = deref_base( op, rs, matched, &matched, ltid, &lock, + candidates, NULL ); + if ( e ) { + build_new_dn( &op->o_req_ndn, &e->e_nname, &stub, + op->o_tmpmemctx ); + bdb_cache_return_entry_r (bdb, e, &lock); + matched = NULL; + goto dn2entry_retry; + } + } else if ( e && is_entry_alias( e )) { + e = deref_base( op, rs, e, &matched, ltid, &lock, + candidates, NULL ); + } + } + + if ( e == NULL ) { + struct berval matched_dn = BER_BVNULL; + + if ( matched != NULL ) { + BerVarray erefs = NULL; + + /* return referral only if "disclose" + * is granted on the object */ + if ( ! access_allowed( op, matched, + slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + + } else { + ber_dupbv( &matched_dn, &matched->e_name ); + + erefs = is_entry_referral( matched ) + ? get_entry_referrals( op, matched ) + : NULL; + if ( rs->sr_err == DB_NOTFOUND ) + rs->sr_err = LDAP_REFERRAL; + rs->sr_matched = matched_dn.bv_val; + } + +#ifdef SLAP_ZONE_ALLOC + slap_zn_runlock(bdb->bi_cache.c_zctx, matched); +#endif + bdb_cache_return_entry_r (bdb, matched, &lock); + matched = NULL; + + if ( erefs ) { + rs->sr_ref = referral_rewrite( erefs, &matched_dn, + &op->o_req_dn, op->oq_search.rs_scope ); + ber_bvarray_free( erefs ); + } + + } else { +#ifdef SLAP_ZONE_ALLOC + slap_zn_runlock(bdb->bi_cache.c_zctx, matched); +#endif + rs->sr_ref = referral_rewrite( default_referral, + NULL, &op->o_req_dn, op->oq_search.rs_scope ); + rs->sr_err = rs->sr_ref != NULL ? LDAP_REFERRAL : LDAP_NO_SUCH_OBJECT; + } + + send_ldap_result( op, rs ); + + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + if ( !BER_BVISNULL( &matched_dn ) ) { + ber_memfree( matched_dn.bv_val ); + rs->sr_matched = NULL; + } + return rs->sr_err; + } + + /* NOTE: __NEW__ "search" access is required + * on searchBase object */ + if ( ! access_allowed_mask( op, e, slap_schema.si_ad_entry, + NULL, ACL_SEARCH, NULL, &mask ) ) + { + if ( !ACL_GRANT( mask, ACL_DISCLOSE ) ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } else { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + } + +#ifdef SLAP_ZONE_ALLOC + slap_zn_runlock(bdb->bi_cache.c_zctx, e); +#endif + if ( e != e_root ) { + bdb_cache_return_entry_r(bdb, e, &lock); + } + send_ldap_result( op, rs ); + return rs->sr_err; + } + + if ( !manageDSAit && e != e_root && is_entry_referral( e ) ) { + /* entry is a referral, don't allow add */ + struct berval matched_dn = BER_BVNULL; + BerVarray erefs = NULL; + + ber_dupbv( &matched_dn, &e->e_name ); + erefs = get_entry_referrals( op, e ); + + rs->sr_err = LDAP_REFERRAL; + +#ifdef SLAP_ZONE_ALLOC + slap_zn_runlock(bdb->bi_cache.c_zctx, e); +#endif + bdb_cache_return_entry_r( bdb, e, &lock ); + e = NULL; + + if ( erefs ) { + rs->sr_ref = referral_rewrite( erefs, &matched_dn, + &op->o_req_dn, op->oq_search.rs_scope ); + ber_bvarray_free( erefs ); + + if ( !rs->sr_ref ) { + rs->sr_text = "bad_referral object"; + } + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_search) ": entry is referral\n", + 0, 0, 0 ); + + rs->sr_matched = matched_dn.bv_val; + send_ldap_result( op, rs ); + + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + ber_memfree( matched_dn.bv_val ); + rs->sr_matched = NULL; + return 1; + } + + if ( get_assert( op ) && + ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; +#ifdef SLAP_ZONE_ALLOC + slap_zn_runlock(bdb->bi_cache.c_zctx, e); +#endif + if ( e != e_root ) { + bdb_cache_return_entry_r(bdb, e, &lock); + } + send_ldap_result( op, rs ); + return 1; + } + + /* compute it anyway; root does not use it */ + stoptime = op->o_time + op->ors_tlimit; + + /* need normalized dn below */ + ber_dupbv( &realbase, &e->e_nname ); + + /* Copy info to base, must free entry before accessing the database + * in search_candidates, to avoid deadlocks. + */ + base.e_private = e->e_private; + base.e_nname = realbase; + base.e_id = e->e_id; + +#ifdef SLAP_ZONE_ALLOC + slap_zn_runlock(bdb->bi_cache.c_zctx, e); +#endif + if ( e != e_root ) { + bdb_cache_return_entry_r(bdb, e, &lock); + } + e = NULL; + + /* select candidates */ + if ( op->oq_search.rs_scope == LDAP_SCOPE_BASE ) { + rs->sr_err = base_candidate( op->o_bd, &base, candidates ); + + } else { +cand_retry: + BDB_IDL_ZERO( candidates ); + BDB_IDL_ZERO( scopes ); + rs->sr_err = search_candidates( op, rs, &base, + ltid, candidates, scopes ); + if ( rs->sr_err == DB_LOCK_DEADLOCK ) { + if ( !opinfo ) { + ltid->flags &= ~TXN_DEADLOCK; + goto cand_retry; + } + opinfo->boi_err = rs->sr_err; + send_ldap_error( op, rs, LDAP_BUSY, "ldap server busy" ); + return LDAP_BUSY; + } + } + + /* start cursor at beginning of candidates. + */ + cursor = 0; + + if ( candidates[0] == 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_search) ": no candidates\n", + 0, 0, 0 ); + + goto nochange; + } + + /* if not root and candidates exceed to-be-checked entries, abort */ + if ( op->ors_limit /* isroot == FALSE */ && + op->ors_limit->lms_s_unchecked != -1 && + BDB_IDL_N(candidates) > (unsigned) op->ors_limit->lms_s_unchecked ) + { + rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED; + send_ldap_result( op, rs ); + rs->sr_err = LDAP_SUCCESS; + goto done; + } + + if ( op->ors_limit == NULL /* isroot == TRUE */ || + !op->ors_limit->lms_s_pr_hide ) + { + tentries = BDB_IDL_N(candidates); + } + + if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) { + PagedResultsState *ps = op->o_pagedresults_state; + /* deferred cookie parsing */ + rs->sr_err = parse_paged_cookie( op, rs ); + if ( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto done; + } + + cursor = (ID) ps->ps_cookie; + if ( cursor && ps->ps_size == 0 ) { + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = "search abandoned by pagedResult size=0"; + send_ldap_result( op, rs ); + goto done; + } + id = bdb_idl_first( candidates, &cursor ); + if ( id == NOID ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_search) + ": no paged results candidates\n", + 0, 0, 0 ); + send_paged_response( op, rs, &lastid, 0 ); + + rs->sr_err = LDAP_OTHER; + goto done; + } + nentries = ps->ps_count; + if ( id == (ID)ps->ps_cookie ) + id = bdb_idl_next( candidates, &cursor ); + goto loop_begin; + } + + for ( id = bdb_idl_first( candidates, &cursor ); + id != NOID ; id = bdb_idl_next( candidates, &cursor ) ) + { + int scopeok; + +loop_begin: + + /* check for abandon */ + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + send_ldap_result( op, rs ); + goto done; + } + + /* mostly needed by internal searches, + * e.g. related to syncrepl, for whom + * abandon does not get set... */ + if ( slapd_shutdown ) { + rs->sr_err = LDAP_UNAVAILABLE; + send_ldap_disconnect( op, rs ); + goto done; + } + + /* check time limit */ + if ( op->ors_tlimit != SLAP_NO_LIMIT + && slap_get_time() > stoptime ) + { + rs->sr_err = LDAP_TIMELIMIT_EXCEEDED; + rs->sr_ref = rs->sr_v2ref; + send_ldap_result( op, rs ); + rs->sr_err = LDAP_SUCCESS; + goto done; + } + + /* If we inspect more entries than will + * fit into the entry cache, stop caching + * any subsequent entries + */ + nentries++; + if ( nentries > bdb->bi_cache.c_maxsize && !idflag ) { + idflag = ID_NOCACHE; + } + +fetch_entry_retry: + /* get the entry with reader lock */ + ei = NULL; + rs->sr_err = bdb_cache_find_id( op, ltid, + id, &ei, idflag, &lock ); + + if (rs->sr_err == LDAP_BUSY) { + rs->sr_text = "ldap server busy"; + send_ldap_result( op, rs ); + goto done; + + } else if ( rs->sr_err == DB_LOCK_DEADLOCK ) { + if ( !opinfo ) { + ltid->flags &= ~TXN_DEADLOCK; + goto fetch_entry_retry; + } +txnfail: + opinfo->boi_err = rs->sr_err; + send_ldap_error( op, rs, LDAP_BUSY, "ldap server busy" ); + goto done; + + } else if ( rs->sr_err == DB_LOCK_NOTGRANTED ) + { + goto fetch_entry_retry; + } else if ( rs->sr_err == LDAP_OTHER ) { + rs->sr_text = "internal error"; + send_ldap_result( op, rs ); + goto done; + } + + if ( ei && rs->sr_err == LDAP_SUCCESS ) { + e = ei->bei_e; + } else { + e = NULL; + } + + if ( e == NULL ) { + if( !BDB_IDL_IS_RANGE(candidates) ) { + /* only complain for non-range IDLs */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_search) + ": candidate %ld not found\n", + (long) id, 0, 0 ); + } else { + /* get the next ID from the DB */ +id_retry: + rs->sr_err = bdb_get_nextid( bdb, ltid, &cursor ); + if ( rs->sr_err == DB_NOTFOUND ) { + break; + } else if ( rs->sr_err == DB_LOCK_DEADLOCK ) { + if ( opinfo ) + goto txnfail; + ltid->flags &= ~TXN_DEADLOCK; + goto id_retry; + } else if ( rs->sr_err == DB_LOCK_NOTGRANTED ) { + goto id_retry; + } + if ( rs->sr_err ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error in get_nextid"; + send_ldap_result( op, rs ); + goto done; + } + cursor--; + } + + goto loop_continue; + } + + if ( is_entry_subentry( e ) ) { + if( op->oq_search.rs_scope != LDAP_SCOPE_BASE ) { + if(!get_subentries_visibility( op )) { + /* only subentries are visible */ + goto loop_continue; + } + + } else if ( get_subentries( op ) && + !get_subentries_visibility( op )) + { + /* only subentries are visible */ + goto loop_continue; + } + + } else if ( get_subentries_visibility( op )) { + /* only subentries are visible */ + goto loop_continue; + } + + /* Does this candidate actually satisfy the search scope? + * + * Note that we don't lock access to the bei_parent pointer. + * Since only leaf nodes can be deleted, the parent of any + * node will always be a valid node. Also since we have + * a Read lock on the data, it cannot be renamed out of the + * scope while we are looking at it, and unless we're using + * BDB_HIER, its parents cannot be moved either. + */ + scopeok = 0; + switch( op->ors_scope ) { + case LDAP_SCOPE_BASE: + /* This is always true, yes? */ + if ( id == base.e_id ) scopeok = 1; + break; + + case LDAP_SCOPE_ONELEVEL: + if ( ei->bei_parent->bei_id == base.e_id ) scopeok = 1; + break; + +#ifdef LDAP_SCOPE_CHILDREN + case LDAP_SCOPE_CHILDREN: + if ( id == base.e_id ) break; + /* Fall-thru */ +#endif + case LDAP_SCOPE_SUBTREE: { + EntryInfo *tmp; + for ( tmp = BEI(e); tmp; tmp = tmp->bei_parent ) { + if ( tmp->bei_id == base.e_id ) { + scopeok = 1; + break; + } + } + } break; + } + + /* aliases were already dereferenced in candidate list */ + if ( op->ors_deref & LDAP_DEREF_SEARCHING ) { + /* but if the search base is an alias, and we didn't + * deref it when finding, return it. + */ + if ( is_entry_alias(e) && + ((op->ors_deref & LDAP_DEREF_FINDING) || + !bvmatch(&e->e_nname, &op->o_req_ndn))) + { + goto loop_continue; + } + + /* scopes is only non-empty for onelevel or subtree */ + if ( !scopeok && BDB_IDL_N(scopes) ) { + unsigned x; + if ( op->ors_scope == LDAP_SCOPE_ONELEVEL ) { + x = bdb_idl_search( scopes, e->e_id ); + if ( scopes[x] == e->e_id ) scopeok = 1; + } else { + /* subtree, walk up the tree */ + EntryInfo *tmp = BEI(e); + for (;tmp->bei_parent; tmp=tmp->bei_parent) { + x = bdb_idl_search( scopes, tmp->bei_id ); + if ( scopes[x] == tmp->bei_id ) { + scopeok = 1; + break; + } + } + } + } + } + + /* Not in scope, ignore it */ + if ( !scopeok ) + { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_search) + ": %ld scope not okay\n", + (long) id, 0, 0 ); + goto loop_continue; + } + + /* + * if it's a referral, add it to the list of referrals. only do + * this for non-base searches, and don't check the filter + * explicitly here since it's only a candidate anyway. + */ + if ( !manageDSAit && op->oq_search.rs_scope != LDAP_SCOPE_BASE + && is_entry_referral( e ) ) + { + struct bdb_op_info bois; + struct bdb_lock_info blis; + BerVarray erefs = get_entry_referrals( op, e ); + rs->sr_ref = referral_rewrite( erefs, &e->e_name, NULL, + op->oq_search.rs_scope == LDAP_SCOPE_ONELEVEL + ? LDAP_SCOPE_BASE : LDAP_SCOPE_SUBTREE ); + + /* Must set lockinfo so that entry_release will work */ + if (!opinfo) { + bois.boi_oe.oe_key = bdb; + bois.boi_txn = NULL; + bois.boi_err = 0; + bois.boi_acl_cache = op->o_do_not_cache; + bois.boi_flag = BOI_DONTFREE; + bois.boi_locks = &blis; + blis.bli_next = NULL; + LDAP_SLIST_INSERT_HEAD( &op->o_extra, &bois.boi_oe, + oe_next ); + } else { + blis.bli_next = opinfo->boi_locks; + opinfo->boi_locks = &blis; + } + blis.bli_id = e->e_id; + blis.bli_lock = lock; + blis.bli_flag = BLI_DONTFREE; + + rs->sr_entry = e; + rs->sr_flags = REP_ENTRY_MUSTRELEASE; + + send_search_reference( op, rs ); + + if ( blis.bli_flag ) { +#ifdef SLAP_ZONE_ALLOC + slap_zn_runlock(bdb->bi_cache.c_zctx, e); +#endif + bdb_cache_return_entry_r(bdb, e, &lock); + if ( opinfo ) { + opinfo->boi_locks = blis.bli_next; + } else { + LDAP_SLIST_REMOVE( &op->o_extra, &bois.boi_oe, + OpExtra, oe_next ); + } + } + rs->sr_entry = NULL; + e = NULL; + + ber_bvarray_free( rs->sr_ref ); + ber_bvarray_free( erefs ); + rs->sr_ref = NULL; + + goto loop_continue; + } + + if ( !manageDSAit && is_entry_glue( e )) { + goto loop_continue; + } + + /* if it matches the filter and scope, send it */ + rs->sr_err = test_filter( op, e, op->oq_search.rs_filter ); + + if ( rs->sr_err == LDAP_COMPARE_TRUE ) { + /* check size limit */ + if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) { + if ( rs->sr_nentries >= ((PagedResultsState *)op->o_pagedresults_state)->ps_size ) { +#ifdef SLAP_ZONE_ALLOC + slap_zn_runlock(bdb->bi_cache.c_zctx, e); +#endif + bdb_cache_return_entry_r( bdb, e, &lock ); + e = NULL; + send_paged_response( op, rs, &lastid, tentries ); + goto done; + } + lastid = id; + } + + if (e) { + struct bdb_op_info bois; + struct bdb_lock_info blis; + + /* Must set lockinfo so that entry_release will work */ + if (!opinfo) { + bois.boi_oe.oe_key = bdb; + bois.boi_txn = NULL; + bois.boi_err = 0; + bois.boi_acl_cache = op->o_do_not_cache; + bois.boi_flag = BOI_DONTFREE; + bois.boi_locks = &blis; + blis.bli_next = NULL; + LDAP_SLIST_INSERT_HEAD( &op->o_extra, &bois.boi_oe, + oe_next ); + } else { + blis.bli_next = opinfo->boi_locks; + opinfo->boi_locks = &blis; + } + blis.bli_id = e->e_id; + blis.bli_lock = lock; + blis.bli_flag = BLI_DONTFREE; + + /* safe default */ + rs->sr_attrs = op->oq_search.rs_attrs; + rs->sr_operational_attrs = NULL; + rs->sr_ctrls = NULL; + rs->sr_entry = e; + RS_ASSERT( e->e_private != NULL ); + rs->sr_flags = REP_ENTRY_MUSTRELEASE; + rs->sr_err = LDAP_SUCCESS; + rs->sr_err = send_search_entry( op, rs ); + rs->sr_attrs = NULL; + rs->sr_entry = NULL; + + /* send_search_entry will usually free it. + * an overlay might leave its own copy here; + * bli_flag will be 0 if lock was already released. + */ + if ( blis.bli_flag ) { +#ifdef SLAP_ZONE_ALLOC + slap_zn_runlock(bdb->bi_cache.c_zctx, e); +#endif + bdb_cache_return_entry_r(bdb, e, &lock); + if ( opinfo ) { + opinfo->boi_locks = blis.bli_next; + } else { + LDAP_SLIST_REMOVE( &op->o_extra, &bois.boi_oe, + OpExtra, oe_next ); + } + } + e = NULL; + + switch ( rs->sr_err ) { + case LDAP_SUCCESS: /* entry sent ok */ + break; + default: /* entry not sent */ + break; + case LDAP_BUSY: + send_ldap_result( op, rs ); + goto done; + case LDAP_UNAVAILABLE: + case LDAP_SIZELIMIT_EXCEEDED: + if ( rs->sr_err == LDAP_SIZELIMIT_EXCEEDED ) { + rs->sr_ref = rs->sr_v2ref; + send_ldap_result( op, rs ); + rs->sr_err = LDAP_SUCCESS; + + } else { + rs->sr_err = LDAP_OTHER; + } + goto done; + } + } + + } else { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_search) + ": %ld does not match filter\n", + (long) id, 0, 0 ); + } + +loop_continue: + if( e != NULL ) { + /* free reader lock */ +#ifdef SLAP_ZONE_ALLOC + slap_zn_runlock(bdb->bi_cache.c_zctx, e); +#endif + bdb_cache_return_entry_r( bdb, e , &lock ); + RS_ASSERT( rs->sr_entry == NULL ); + e = NULL; + rs->sr_entry = NULL; + } + } + +nochange: + rs->sr_ctrls = NULL; + rs->sr_ref = rs->sr_v2ref; + rs->sr_err = (rs->sr_v2ref == NULL) ? LDAP_SUCCESS : LDAP_REFERRAL; + rs->sr_rspoid = NULL; + if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) { + send_paged_response( op, rs, NULL, 0 ); + } else { + send_ldap_result( op, rs ); + } + + rs->sr_err = LDAP_SUCCESS; + +done: + if( rs->sr_v2ref ) { + ber_bvarray_free( rs->sr_v2ref ); + rs->sr_v2ref = NULL; + } + if( realbase.bv_val ) ch_free( realbase.bv_val ); + + return rs->sr_err; +} + + +static int base_candidate( + BackendDB *be, + Entry *e, + ID *ids ) +{ + Debug(LDAP_DEBUG_ARGS, "base_candidates: base: \"%s\" (0x%08lx)\n", + e->e_nname.bv_val, (long) e->e_id, 0); + + ids[0] = 1; + ids[1] = e->e_id; + return 0; +} + +/* Look for "objectClass Present" in this filter. + * Also count depth of filter tree while we're at it. + */ +static int oc_filter( + Filter *f, + int cur, + int *max ) +{ + int rc = 0; + + assert( f != NULL ); + + if( cur > *max ) *max = cur; + + switch( f->f_choice ) { + case LDAP_FILTER_PRESENT: + if (f->f_desc == slap_schema.si_ad_objectClass) { + rc = 1; + } + break; + + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + cur++; + for ( f=f->f_and; f; f=f->f_next ) { + (void) oc_filter(f, cur, max); + } + break; + + default: + break; + } + return rc; +} + +static void search_stack_free( void *key, void *data ) +{ + ber_memfree_x(data, NULL); +} + +static void *search_stack( Operation *op ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + void *ret = NULL; + + if ( op->o_threadctx ) { + ldap_pvt_thread_pool_getkey( op->o_threadctx, (void *)search_stack, + &ret, NULL ); + } else { + ret = bdb->bi_search_stack; + } + + if ( !ret ) { + ret = ch_malloc( bdb->bi_search_stack_depth * BDB_IDL_UM_SIZE + * sizeof( ID ) ); + if ( op->o_threadctx ) { + ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)search_stack, + ret, search_stack_free, NULL, NULL ); + } else { + bdb->bi_search_stack = ret; + } + } + return ret; +} + +static int search_candidates( + Operation *op, + SlapReply *rs, + Entry *e, + DB_TXN *txn, + ID *ids, + ID *scopes ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + int rc, depth = 1; + Filter f, rf, xf, nf; + ID *stack; + AttributeAssertion aa_ref = ATTRIBUTEASSERTION_INIT; + Filter sf; + AttributeAssertion aa_subentry = ATTRIBUTEASSERTION_INIT; + + /* + * This routine takes as input a filter (user-filter) + * and rewrites it as follows: + * (&(scope=DN)[(objectClass=subentry)] + * (|[(objectClass=referral)(objectClass=alias)](user-filter)) + */ + + Debug(LDAP_DEBUG_TRACE, + "search_candidates: base=\"%s\" (0x%08lx) scope=%d\n", + e->e_nname.bv_val, (long) e->e_id, op->oq_search.rs_scope ); + + xf.f_or = op->oq_search.rs_filter; + xf.f_choice = LDAP_FILTER_OR; + xf.f_next = NULL; + + /* If the user's filter uses objectClass=*, + * these clauses are redundant. + */ + if (!oc_filter(op->oq_search.rs_filter, 1, &depth) + && !get_subentries_visibility(op)) { + if( !get_manageDSAit(op) && !get_domainScope(op) ) { + /* match referral objects */ + struct berval bv_ref = BER_BVC( "referral" ); + rf.f_choice = LDAP_FILTER_EQUALITY; + rf.f_ava = &aa_ref; + rf.f_av_desc = slap_schema.si_ad_objectClass; + rf.f_av_value = bv_ref; + rf.f_next = xf.f_or; + xf.f_or = &rf; + depth++; + } + } + + f.f_next = NULL; + f.f_choice = LDAP_FILTER_AND; + f.f_and = &nf; + /* Dummy; we compute scope separately now */ + nf.f_choice = SLAPD_FILTER_COMPUTED; + nf.f_result = LDAP_SUCCESS; + nf.f_next = ( xf.f_or == op->oq_search.rs_filter ) + ? op->oq_search.rs_filter : &xf ; + /* Filter depth increased again, adding dummy clause */ + depth++; + + if( get_subentries_visibility( op ) ) { + struct berval bv_subentry = BER_BVC( "subentry" ); + sf.f_choice = LDAP_FILTER_EQUALITY; + sf.f_ava = &aa_subentry; + sf.f_av_desc = slap_schema.si_ad_objectClass; + sf.f_av_value = bv_subentry; + sf.f_next = nf.f_next; + nf.f_next = &sf; + } + + /* Allocate IDL stack, plus 1 more for former tmp */ + if ( depth+1 > bdb->bi_search_stack_depth ) { + stack = ch_malloc( (depth + 1) * BDB_IDL_UM_SIZE * sizeof( ID ) ); + } else { + stack = search_stack( op ); + } + + if( op->ors_deref & LDAP_DEREF_SEARCHING ) { + rc = search_aliases( op, rs, e, txn, ids, scopes, stack ); + if ( BDB_IDL_IS_ZERO( ids ) && rc == LDAP_SUCCESS ) + rc = bdb_dn2idl( op, txn, &e->e_nname, BEI(e), ids, stack ); + } else { + rc = bdb_dn2idl( op, txn, &e->e_nname, BEI(e), ids, stack ); + } + + if ( rc == LDAP_SUCCESS ) { + rc = bdb_filter_candidates( op, txn, &f, ids, + stack, stack+BDB_IDL_UM_SIZE ); + } + + if ( depth+1 > bdb->bi_search_stack_depth ) { + ch_free( stack ); + } + + if( rc ) { + Debug(LDAP_DEBUG_TRACE, + "bdb_search_candidates: failed (rc=%d)\n", + rc, NULL, NULL ); + + } else { + Debug(LDAP_DEBUG_TRACE, + "bdb_search_candidates: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) BDB_IDL_FIRST(ids), + (long) BDB_IDL_LAST(ids) ); + } + + return rc; +} + +static int +parse_paged_cookie( Operation *op, SlapReply *rs ) +{ + int rc = LDAP_SUCCESS; + PagedResultsState *ps = op->o_pagedresults_state; + + /* this function must be invoked only if the pagedResults + * control has been detected, parsed and partially checked + * by the frontend */ + assert( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ); + + /* cookie decoding/checks deferred to backend... */ + if ( ps->ps_cookieval.bv_len ) { + PagedResultsCookie reqcookie; + if( ps->ps_cookieval.bv_len != sizeof( reqcookie ) ) { + /* bad cookie */ + rs->sr_text = "paged results cookie is invalid"; + rc = LDAP_PROTOCOL_ERROR; + goto done; + } + + AC_MEMCPY( &reqcookie, ps->ps_cookieval.bv_val, sizeof( reqcookie )); + + if ( reqcookie > ps->ps_cookie ) { + /* bad cookie */ + rs->sr_text = "paged results cookie is invalid"; + rc = LDAP_PROTOCOL_ERROR; + goto done; + + } else if ( reqcookie < ps->ps_cookie ) { + rs->sr_text = "paged results cookie is invalid or old"; + rc = LDAP_UNWILLING_TO_PERFORM; + goto done; + } + + } else { + /* we're going to use ps_cookie */ + op->o_conn->c_pagedresults_state.ps_cookie = 0; + } + +done:; + + return rc; +} + +static void +send_paged_response( + Operation *op, + SlapReply *rs, + ID *lastid, + int tentries ) +{ + LDAPControl *ctrls[2]; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + PagedResultsCookie respcookie; + struct berval cookie; + + Debug(LDAP_DEBUG_ARGS, + "send_paged_response: lastid=0x%08lx nentries=%d\n", + lastid ? *lastid : 0, rs->sr_nentries, NULL ); + + ctrls[1] = NULL; + + ber_init2( ber, NULL, LBER_USE_DER ); + + if ( lastid ) { + respcookie = ( PagedResultsCookie )(*lastid); + cookie.bv_len = sizeof( respcookie ); + cookie.bv_val = (char *)&respcookie; + + } else { + respcookie = ( PagedResultsCookie )0; + BER_BVSTR( &cookie, "" ); + } + + op->o_conn->c_pagedresults_state.ps_cookie = respcookie; + op->o_conn->c_pagedresults_state.ps_count = + ((PagedResultsState *)op->o_pagedresults_state)->ps_count + + rs->sr_nentries; + + /* return size of 0 -- no estimate */ + ber_printf( ber, "{iO}", 0, &cookie ); + + ctrls[0] = op->o_tmpalloc( sizeof(LDAPControl), op->o_tmpmemctx ); + if ( ber_flatten2( ber, &ctrls[0]->ldctl_value, 0 ) == -1 ) { + goto done; + } + + ctrls[0]->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS; + ctrls[0]->ldctl_iscritical = 0; + + slap_add_ctrls( op, rs, ctrls ); + rs->sr_err = LDAP_SUCCESS; + send_ldap_result( op, rs ); + +done: + (void) ber_free_buf( ber ); +} diff --git a/servers/slapd/back-bdb/tools.c b/servers/slapd/back-bdb/tools.c new file mode 100644 index 0000000..2345d65 --- /dev/null +++ b/servers/slapd/back-bdb/tools.c @@ -0,0 +1,1327 @@ +/* tools.c - tools for slap tools */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/errno.h> + +#define AVL_INTERNAL +#include "back-bdb.h" +#include "idl.h" + +static DBC *cursor = NULL; +static DBT key, data; +static EntryHeader eh; +static ID nid, previd = NOID; +static char ehbuf[16]; + +typedef struct dn_id { + ID id; + struct berval dn; +} dn_id; + +#define HOLE_SIZE 4096 +static dn_id hbuf[HOLE_SIZE], *holes = hbuf; +static unsigned nhmax = HOLE_SIZE; +static unsigned nholes; + +static int index_nattrs; + +static struct berval *tool_base; +static int tool_scope; +static Filter *tool_filter; +static Entry *tool_next_entry; + +#ifdef BDB_TOOL_IDL_CACHING +#define bdb_tool_idl_cmp BDB_SYMBOL(tool_idl_cmp) +#define bdb_tool_idl_flush_one BDB_SYMBOL(tool_idl_flush_one) +#define bdb_tool_idl_flush BDB_SYMBOL(tool_idl_flush) + +static int bdb_tool_idl_flush( BackendDB *be ); + +#define IDBLOCK 1024 + +typedef struct bdb_tool_idl_cache_entry { + struct bdb_tool_idl_cache_entry *next; + ID ids[IDBLOCK]; +} bdb_tool_idl_cache_entry; + +typedef struct bdb_tool_idl_cache { + struct berval kstr; + bdb_tool_idl_cache_entry *head, *tail; + ID first, last; + int count; +} bdb_tool_idl_cache; + +static bdb_tool_idl_cache_entry *bdb_tool_idl_free_list; +#endif /* BDB_TOOL_IDL_CACHING */ + +static ID bdb_tool_ix_id; +static Operation *bdb_tool_ix_op; +static int *bdb_tool_index_threads, bdb_tool_index_tcount; +static void *bdb_tool_index_rec; +static struct bdb_info *bdb_tool_info; +static ldap_pvt_thread_mutex_t bdb_tool_index_mutex; +static ldap_pvt_thread_cond_t bdb_tool_index_cond_main; +static ldap_pvt_thread_cond_t bdb_tool_index_cond_work; + +#if DB_VERSION_FULL >= 0x04060000 +#define USE_TRICKLE 1 +#else +/* Seems to slow things down too much in BDB 4.5 */ +#undef USE_TRICKLE +#endif + +#ifdef USE_TRICKLE +static ldap_pvt_thread_mutex_t bdb_tool_trickle_mutex; +static ldap_pvt_thread_cond_t bdb_tool_trickle_cond; +static ldap_pvt_thread_cond_t bdb_tool_trickle_cond_end; + +static void * bdb_tool_trickle_task( void *ctx, void *ptr ); +static int bdb_tool_trickle_active; +#endif + +static void * bdb_tool_index_task( void *ctx, void *ptr ); + +static int +bdb_tool_entry_get_int( BackendDB *be, ID id, Entry **ep ); + +static int bdb_tool_threads; + +int bdb_tool_entry_open( + BackendDB *be, int mode ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + + /* initialize key and data thangs */ + DBTzero( &key ); + DBTzero( &data ); + key.flags = DB_DBT_USERMEM; + key.data = &nid; + key.size = key.ulen = sizeof( nid ); + data.flags = DB_DBT_USERMEM; + + if (cursor == NULL) { + int rc = bdb->bi_id2entry->bdi_db->cursor( + bdb->bi_id2entry->bdi_db, bdb->bi_cache.c_txn, &cursor, + bdb->bi_db_opflags ); + if( rc != 0 ) { + return -1; + } + } + + /* Set up for threaded slapindex */ + if (( slapMode & (SLAP_TOOL_QUICK|SLAP_TOOL_READONLY)) == SLAP_TOOL_QUICK ) { + if ( !bdb_tool_info ) { +#ifdef USE_TRICKLE + ldap_pvt_thread_mutex_init( &bdb_tool_trickle_mutex ); + ldap_pvt_thread_cond_init( &bdb_tool_trickle_cond ); + ldap_pvt_thread_cond_init( &bdb_tool_trickle_cond_end ); + ldap_pvt_thread_pool_submit( &connection_pool, bdb_tool_trickle_task, bdb->bi_dbenv ); +#endif + + ldap_pvt_thread_mutex_init( &bdb_tool_index_mutex ); + ldap_pvt_thread_cond_init( &bdb_tool_index_cond_main ); + ldap_pvt_thread_cond_init( &bdb_tool_index_cond_work ); + if ( bdb->bi_nattrs ) { + int i; + bdb_tool_threads = slap_tool_thread_max - 1; + if ( bdb_tool_threads > 1 ) { + bdb_tool_index_threads = ch_malloc( bdb_tool_threads * sizeof( int )); + bdb_tool_index_rec = ch_malloc( bdb->bi_nattrs * sizeof( IndexRec )); + bdb_tool_index_tcount = bdb_tool_threads - 1; + for (i=1; i<bdb_tool_threads; i++) { + int *ptr = ch_malloc( sizeof( int )); + *ptr = i; + ldap_pvt_thread_pool_submit( &connection_pool, + bdb_tool_index_task, ptr ); + } + } + } + bdb_tool_info = bdb; + } + } + + return 0; +} + +int bdb_tool_entry_close( + BackendDB *be ) +{ + if ( bdb_tool_info ) { + slapd_shutdown = 1; +#ifdef USE_TRICKLE + ldap_pvt_thread_mutex_lock( &bdb_tool_trickle_mutex ); + + /* trickle thread may not have started yet */ + while ( !bdb_tool_trickle_active ) + ldap_pvt_thread_cond_wait( &bdb_tool_trickle_cond_end, + &bdb_tool_trickle_mutex ); + + ldap_pvt_thread_cond_signal( &bdb_tool_trickle_cond ); + while ( bdb_tool_trickle_active ) + ldap_pvt_thread_cond_wait( &bdb_tool_trickle_cond_end, + &bdb_tool_trickle_mutex ); + ldap_pvt_thread_mutex_unlock( &bdb_tool_trickle_mutex ); +#endif + if ( bdb_tool_threads > 1 ) { + ldap_pvt_thread_mutex_lock( &bdb_tool_index_mutex ); + + /* There might still be some threads starting */ + while ( bdb_tool_index_tcount > 0 ) { + ldap_pvt_thread_cond_wait( &bdb_tool_index_cond_main, + &bdb_tool_index_mutex ); + } + + bdb_tool_index_tcount = bdb_tool_threads - 1; + ldap_pvt_thread_cond_broadcast( &bdb_tool_index_cond_work ); + + /* Make sure all threads are stopped */ + while ( bdb_tool_index_tcount > 0 ) { + ldap_pvt_thread_cond_wait( &bdb_tool_index_cond_main, + &bdb_tool_index_mutex ); + } + ldap_pvt_thread_mutex_unlock( &bdb_tool_index_mutex ); + + ch_free( bdb_tool_index_threads ); + ch_free( bdb_tool_index_rec ); + bdb_tool_index_tcount = bdb_tool_threads - 1; + } + bdb_tool_info = NULL; + slapd_shutdown = 0; + } + + if( eh.bv.bv_val ) { + ch_free( eh.bv.bv_val ); + eh.bv.bv_val = NULL; + } + + if( cursor ) { + cursor->c_close( cursor ); + cursor = NULL; + } + +#ifdef BDB_TOOL_IDL_CACHING + bdb_tool_idl_flush( be ); +#endif + + if( nholes ) { + unsigned i; + fprintf( stderr, "Error, entries missing!\n"); + for (i=0; i<nholes; i++) { + fprintf(stderr, " entry %ld: %s\n", + holes[i].id, holes[i].dn.bv_val); + } + return -1; + } + + return 0; +} + +ID +bdb_tool_entry_first_x( + BackendDB *be, + struct berval *base, + int scope, + Filter *f ) +{ + tool_base = base; + tool_scope = scope; + tool_filter = f; + + return bdb_tool_entry_next( be ); +} + +ID bdb_tool_entry_next( + BackendDB *be ) +{ + int rc; + ID id; + struct bdb_info *bdb; + + assert( be != NULL ); + assert( slapMode & SLAP_TOOL_MODE ); + + bdb = (struct bdb_info *) be->be_private; + assert( bdb != NULL ); + +next:; + /* Get the header */ + data.ulen = data.dlen = sizeof( ehbuf ); + data.data = ehbuf; + data.flags |= DB_DBT_PARTIAL; + rc = cursor->c_get( cursor, &key, &data, DB_NEXT ); + + if( rc ) { + /* If we're doing linear indexing and there are more attrs to + * index, and we're at the end of the database, start over. + */ + if ( index_nattrs && rc == DB_NOTFOUND ) { + /* optional - do a checkpoint here? */ + bdb_attr_info_free( bdb->bi_attrs[0] ); + bdb->bi_attrs[0] = bdb->bi_attrs[index_nattrs]; + index_nattrs--; + rc = cursor->c_get( cursor, &key, &data, DB_FIRST ); + if ( rc ) { + return NOID; + } + } else { + return NOID; + } + } + + BDB_DISK2ID( key.data, &id ); + previd = id; + + if ( tool_filter || tool_base ) { + static Operation op = {0}; + static Opheader ohdr = {0}; + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + if ( tool_next_entry ) { + bdb_entry_release( &op, tool_next_entry, 0 ); + tool_next_entry = NULL; + } + + rc = bdb_tool_entry_get_int( be, id, &tool_next_entry ); + if ( rc == LDAP_NO_SUCH_OBJECT ) { + goto next; + } + + assert( tool_next_entry != NULL ); + +#ifdef BDB_HIER + /* TODO: needed until BDB_HIER is handled accordingly + * in bdb_tool_entry_get_int() */ + if ( tool_base && !dnIsSuffixScope( &tool_next_entry->e_nname, tool_base, tool_scope ) ) + { + bdb_entry_release( &op, tool_next_entry, 0 ); + tool_next_entry = NULL; + goto next; + } +#endif + + if ( tool_filter && test_filter( NULL, tool_next_entry, tool_filter ) != LDAP_COMPARE_TRUE ) + { + bdb_entry_release( &op, tool_next_entry, 0 ); + tool_next_entry = NULL; + goto next; + } + } + + return id; +} + +ID bdb_tool_dn2id_get( + Backend *be, + struct berval *dn +) +{ + Operation op = {0}; + Opheader ohdr = {0}; + EntryInfo *ei = NULL; + int rc; + + if ( BER_BVISEMPTY(dn) ) + return 0; + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + rc = bdb_cache_find_ndn( &op, 0, dn, &ei ); + if ( ei ) bdb_cache_entryinfo_unlock( ei ); + if ( rc == DB_NOTFOUND ) + return NOID; + + return ei->bei_id; +} + +static int +bdb_tool_entry_get_int( BackendDB *be, ID id, Entry **ep ) +{ + Entry *e = NULL; + char *dptr; + int rc, eoff; + + assert( be != NULL ); + assert( slapMode & SLAP_TOOL_MODE ); + + if ( ( tool_filter || tool_base ) && id == previd && tool_next_entry != NULL ) { + *ep = tool_next_entry; + tool_next_entry = NULL; + return LDAP_SUCCESS; + } + + if ( id != previd ) { + data.ulen = data.dlen = sizeof( ehbuf ); + data.data = ehbuf; + data.flags |= DB_DBT_PARTIAL; + + BDB_ID2DISK( id, &nid ); + rc = cursor->c_get( cursor, &key, &data, DB_SET ); + if ( rc ) { + rc = LDAP_OTHER; + goto done; + } + } + + /* Get the header */ + dptr = eh.bv.bv_val; + eh.bv.bv_val = ehbuf; + eh.bv.bv_len = data.size; + rc = entry_header( &eh ); + eoff = eh.data - eh.bv.bv_val; + eh.bv.bv_val = dptr; + if ( rc ) { + rc = LDAP_OTHER; + goto done; + } + + /* Get the size */ + data.flags &= ~DB_DBT_PARTIAL; + data.ulen = 0; + rc = cursor->c_get( cursor, &key, &data, DB_CURRENT ); + if ( rc != DB_BUFFER_SMALL ) { + rc = LDAP_OTHER; + goto done; + } + + /* Allocate a block and retrieve the data */ + eh.bv.bv_len = eh.nvals * sizeof( struct berval ) + data.size; + eh.bv.bv_val = ch_realloc( eh.bv.bv_val, eh.bv.bv_len ); + eh.data = eh.bv.bv_val + eh.nvals * sizeof( struct berval ); + data.data = eh.data; + data.ulen = data.size; + + /* Skip past already parsed nattr/nvals */ + eh.data += eoff; + + rc = cursor->c_get( cursor, &key, &data, DB_CURRENT ); + if ( rc ) { + rc = LDAP_OTHER; + goto done; + } + +#ifndef BDB_HIER + /* TODO: handle BDB_HIER accordingly */ + if ( tool_base != NULL ) { + struct berval ndn; + entry_decode_dn( &eh, NULL, &ndn ); + + if ( !dnIsSuffixScope( &ndn, tool_base, tool_scope ) ) { + return LDAP_NO_SUCH_OBJECT; + } + } +#endif + +#ifdef SLAP_ZONE_ALLOC + /* FIXME: will add ctx later */ + rc = entry_decode( &eh, &e, NULL ); +#else + rc = entry_decode( &eh, &e ); +#endif + + if( rc == LDAP_SUCCESS ) { + e->e_id = id; +#ifdef BDB_HIER + if ( slapMode & SLAP_TOOL_READONLY ) { + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + EntryInfo *ei = NULL; + Operation op = {0}; + Opheader ohdr = {0}; + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + rc = bdb_cache_find_parent( &op, bdb->bi_cache.c_txn, id, &ei ); + if ( rc == LDAP_SUCCESS ) { + bdb_cache_entryinfo_unlock( ei ); + e->e_private = ei; + ei->bei_e = e; + bdb_fix_dn( e, 0 ); + ei->bei_e = NULL; + e->e_private = NULL; + } + } +#endif + } +done: + if ( e != NULL ) { + *ep = e; + } + + return rc; +} + +Entry* +bdb_tool_entry_get( BackendDB *be, ID id ) +{ + Entry *e = NULL; + + (void)bdb_tool_entry_get_int( be, id, &e ); + return e; +} + +static int bdb_tool_next_id( + Operation *op, + DB_TXN *tid, + Entry *e, + struct berval *text, + int hole ) +{ + struct berval dn = e->e_name; + struct berval ndn = e->e_nname; + struct berval pdn, npdn; + EntryInfo *ei = NULL, eidummy; + int rc; + + if (ndn.bv_len == 0) { + e->e_id = 0; + return 0; + } + + rc = bdb_cache_find_ndn( op, tid, &ndn, &ei ); + if ( ei ) bdb_cache_entryinfo_unlock( ei ); + if ( rc == DB_NOTFOUND ) { + if ( !be_issuffix( op->o_bd, &ndn ) ) { + ID eid = e->e_id; + dnParent( &dn, &pdn ); + dnParent( &ndn, &npdn ); + e->e_name = pdn; + e->e_nname = npdn; + rc = bdb_tool_next_id( op, tid, e, text, 1 ); + e->e_name = dn; + e->e_nname = ndn; + if ( rc ) { + return rc; + } + /* If parent didn't exist, it was created just now + * and its ID is now in e->e_id. Make sure the current + * entry gets added under the new parent ID. + */ + if ( eid != e->e_id ) { + eidummy.bei_id = e->e_id; + ei = &eidummy; + } + } + rc = bdb_next_id( op->o_bd, &e->e_id ); + if ( rc ) { + snprintf( text->bv_val, text->bv_len, + "next_id failed: %s (%d)", + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> bdb_tool_next_id: %s\n", text->bv_val, 0, 0 ); + return rc; + } + rc = bdb_dn2id_add( op, tid, ei, e ); + if ( rc ) { + snprintf( text->bv_val, text->bv_len, + "dn2id_add failed: %s (%d)", + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> bdb_tool_next_id: %s\n", text->bv_val, 0, 0 ); + } else if ( hole ) { + if ( nholes == nhmax - 1 ) { + if ( holes == hbuf ) { + holes = ch_malloc( nhmax * sizeof(dn_id) * 2 ); + AC_MEMCPY( holes, hbuf, sizeof(hbuf) ); + } else { + holes = ch_realloc( holes, nhmax * sizeof(dn_id) * 2 ); + } + nhmax *= 2; + } + ber_dupbv( &holes[nholes].dn, &ndn ); + holes[nholes++].id = e->e_id; + } + } else if ( !hole ) { + unsigned i, j; + + e->e_id = ei->bei_id; + + for ( i=0; i<nholes; i++) { + if ( holes[i].id == e->e_id ) { + free(holes[i].dn.bv_val); + for (j=i;j<nholes;j++) holes[j] = holes[j+1]; + holes[j].id = 0; + nholes--; + break; + } else if ( holes[i].id > e->e_id ) { + break; + } + } + } + return rc; +} + +static int +bdb_tool_index_add( + Operation *op, + DB_TXN *txn, + Entry *e ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + + if ( !bdb->bi_nattrs ) + return 0; + + if ( bdb_tool_threads > 1 ) { + IndexRec *ir; + int i, rc; + Attribute *a; + + ir = bdb_tool_index_rec; + memset(ir, 0, bdb->bi_nattrs * sizeof( IndexRec )); + + for ( a = e->e_attrs; a != NULL; a = a->a_next ) { + rc = bdb_index_recset( bdb, a, a->a_desc->ad_type, + &a->a_desc->ad_tags, ir ); + if ( rc ) + return rc; + } + bdb_tool_ix_id = e->e_id; + bdb_tool_ix_op = op; + ldap_pvt_thread_mutex_lock( &bdb_tool_index_mutex ); + /* Wait for all threads to be ready */ + while ( bdb_tool_index_tcount > 0 ) { + ldap_pvt_thread_cond_wait( &bdb_tool_index_cond_main, + &bdb_tool_index_mutex ); + } + for ( i=1; i<bdb_tool_threads; i++ ) + bdb_tool_index_threads[i] = LDAP_BUSY; + bdb_tool_index_tcount = bdb_tool_threads - 1; + ldap_pvt_thread_cond_broadcast( &bdb_tool_index_cond_work ); + ldap_pvt_thread_mutex_unlock( &bdb_tool_index_mutex ); + rc = bdb_index_recrun( op, bdb, ir, e->e_id, 0 ); + if ( rc ) + return rc; + ldap_pvt_thread_mutex_lock( &bdb_tool_index_mutex ); + for ( i=1; i<bdb_tool_threads; i++ ) { + if ( bdb_tool_index_threads[i] == LDAP_BUSY ) { + ldap_pvt_thread_cond_wait( &bdb_tool_index_cond_main, + &bdb_tool_index_mutex ); + i--; + continue; + } + if ( bdb_tool_index_threads[i] ) { + rc = bdb_tool_index_threads[i]; + break; + } + } + ldap_pvt_thread_mutex_unlock( &bdb_tool_index_mutex ); + return rc; + } else { + return bdb_index_entry_add( op, txn, e ); + } +} + +ID bdb_tool_entry_put( + BackendDB *be, + Entry *e, + struct berval *text ) +{ + int rc; + struct bdb_info *bdb; + DB_TXN *tid = NULL; + Operation op = {0}; + Opheader ohdr = {0}; + + assert( be != NULL ); + assert( slapMode & SLAP_TOOL_MODE ); + + assert( text != NULL ); + assert( text->bv_val != NULL ); + assert( text->bv_val[0] == '\0' ); /* overconservative? */ + + Debug( LDAP_DEBUG_TRACE, "=> " LDAP_XSTRING(bdb_tool_entry_put) + "( %ld, \"%s\" )\n", (long) e->e_id, e->e_dn, 0 ); + + bdb = (struct bdb_info *) be->be_private; + + if (! (slapMode & SLAP_TOOL_QUICK)) { + rc = TXN_BEGIN( bdb->bi_dbenv, NULL, &tid, + bdb->bi_db_opflags ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "txn_begin failed: %s (%d)", + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + return NOID; + } + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_tool_entry_put) ": txn id: %x\n", + tid->id(tid), 0, 0 ); + } + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + /* add dn2id indices */ + rc = bdb_tool_next_id( &op, tid, e, text, 0 ); + if( rc != 0 ) { + goto done; + } + +#ifdef USE_TRICKLE + if (( slapMode & SLAP_TOOL_QUICK ) && (( e->e_id & 0xfff ) == 0xfff )) { + ldap_pvt_thread_cond_signal( &bdb_tool_trickle_cond ); + } +#endif + + if ( !bdb->bi_linear_index ) + rc = bdb_tool_index_add( &op, tid, e ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "index_entry_add failed: %s (%d)", + rc == LDAP_OTHER ? "Internal error" : + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + goto done; + } + + /* id2entry index */ + rc = bdb_id2entry_add( be, tid, e ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "id2entry_add failed: %s (%d)", + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + goto done; + } + +done: + if( rc == 0 ) { + if ( !( slapMode & SLAP_TOOL_QUICK )) { + rc = TXN_COMMIT( tid, 0 ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "txn_commit failed: %s (%d)", + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + e->e_id = NOID; + } + } + + } else { + if ( !( slapMode & SLAP_TOOL_QUICK )) { + TXN_ABORT( tid ); + snprintf( text->bv_val, text->bv_len, + "txn_aborted! %s (%d)", + rc == LDAP_OTHER ? "Internal error" : + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + } + e->e_id = NOID; + } + + return e->e_id; +} + +int bdb_tool_entry_reindex( + BackendDB *be, + ID id, + AttributeDescription **adv ) +{ + struct bdb_info *bi = (struct bdb_info *) be->be_private; + int rc; + Entry *e; + DB_TXN *tid = NULL; + Operation op = {0}; + Opheader ohdr = {0}; + + Debug( LDAP_DEBUG_ARGS, + "=> " LDAP_XSTRING(bdb_tool_entry_reindex) "( %ld )\n", + (long) id, 0, 0 ); + assert( tool_base == NULL ); + assert( tool_filter == NULL ); + + /* No indexes configured, nothing to do. Could return an + * error here to shortcut things. + */ + if (!bi->bi_attrs) { + return 0; + } + + /* Check for explicit list of attrs to index */ + if ( adv ) { + int i, j, n; + + if ( bi->bi_attrs[0]->ai_desc != adv[0] ) { + /* count */ + for ( n = 0; adv[n]; n++ ) ; + + /* insertion sort */ + for ( i = 0; i < n; i++ ) { + AttributeDescription *ad = adv[i]; + for ( j = i-1; j>=0; j--) { + if ( SLAP_PTRCMP( adv[j], ad ) <= 0 ) break; + adv[j+1] = adv[j]; + } + adv[j+1] = ad; + } + } + + for ( i = 0; adv[i]; i++ ) { + if ( bi->bi_attrs[i]->ai_desc != adv[i] ) { + for ( j = i+1; j < bi->bi_nattrs; j++ ) { + if ( bi->bi_attrs[j]->ai_desc == adv[i] ) { + AttrInfo *ai = bi->bi_attrs[i]; + bi->bi_attrs[i] = bi->bi_attrs[j]; + bi->bi_attrs[j] = ai; + break; + } + } + if ( j == bi->bi_nattrs ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_tool_entry_reindex) + ": no index configured for %s\n", + adv[i]->ad_cname.bv_val, 0, 0 ); + return -1; + } + } + } + bi->bi_nattrs = i; + } + + /* Get the first attribute to index */ + if (bi->bi_linear_index && !index_nattrs) { + index_nattrs = bi->bi_nattrs - 1; + bi->bi_nattrs = 1; + } + + e = bdb_tool_entry_get( be, id ); + + if( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_tool_entry_reindex) + ": could not locate id=%ld\n", + (long) id, 0, 0 ); + return -1; + } + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + if (! (slapMode & SLAP_TOOL_QUICK)) { + rc = TXN_BEGIN( bi->bi_dbenv, NULL, &tid, bi->bi_db_opflags ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_reindex) ": " + "txn_begin failed: %s (%d)\n", + db_strerror(rc), rc, 0 ); + goto done; + } + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_tool_entry_reindex) ": txn id: %x\n", + tid->id(tid), 0, 0 ); + } + + /* + * just (re)add them for now + * assume that some other routine (not yet implemented) + * will zap index databases + * + */ + + Debug( LDAP_DEBUG_TRACE, + "=> " LDAP_XSTRING(bdb_tool_entry_reindex) "( %ld, \"%s\" )\n", + (long) id, e->e_dn, 0 ); + + rc = bdb_tool_index_add( &op, tid, e ); + +done: + if( rc == 0 ) { + if (! (slapMode & SLAP_TOOL_QUICK)) { + rc = TXN_COMMIT( tid, 0 ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_reindex) + ": txn_commit failed: %s (%d)\n", + db_strerror(rc), rc, 0 ); + e->e_id = NOID; + } + } + + } else { + if (! (slapMode & SLAP_TOOL_QUICK)) { + TXN_ABORT( tid ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_reindex) + ": txn_aborted! %s (%d)\n", + db_strerror(rc), rc, 0 ); + } + e->e_id = NOID; + } + bdb_entry_release( &op, e, 0 ); + + return rc; +} + +ID bdb_tool_entry_modify( + BackendDB *be, + Entry *e, + struct berval *text ) +{ + int rc; + struct bdb_info *bdb; + DB_TXN *tid = NULL; + Operation op = {0}; + Opheader ohdr = {0}; + + assert( be != NULL ); + assert( slapMode & SLAP_TOOL_MODE ); + + assert( text != NULL ); + assert( text->bv_val != NULL ); + assert( text->bv_val[0] == '\0' ); /* overconservative? */ + + assert ( e->e_id != NOID ); + + Debug( LDAP_DEBUG_TRACE, + "=> " LDAP_XSTRING(bdb_tool_entry_modify) "( %ld, \"%s\" )\n", + (long) e->e_id, e->e_dn, 0 ); + + bdb = (struct bdb_info *) be->be_private; + + if (! (slapMode & SLAP_TOOL_QUICK)) { + if( cursor ) { + cursor->c_close( cursor ); + cursor = NULL; + } + rc = TXN_BEGIN( bdb->bi_dbenv, NULL, &tid, + bdb->bi_db_opflags ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "txn_begin failed: %s (%d)", + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_modify) ": %s\n", + text->bv_val, 0, 0 ); + return NOID; + } + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_tool_entry_modify) ": txn id: %x\n", + tid->id(tid), 0, 0 ); + } + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + /* id2entry index */ + rc = bdb_id2entry_update( be, tid, e ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "id2entry_add failed: %s (%d)", + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_modify) ": %s\n", + text->bv_val, 0, 0 ); + goto done; + } + +done: + if( rc == 0 ) { + if (! (slapMode & SLAP_TOOL_QUICK)) { + rc = TXN_COMMIT( tid, 0 ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "txn_commit failed: %s (%d)", + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_modify) ": " + "%s\n", text->bv_val, 0, 0 ); + e->e_id = NOID; + } + } + + } else { + if (! (slapMode & SLAP_TOOL_QUICK)) { + TXN_ABORT( tid ); + snprintf( text->bv_val, text->bv_len, + "txn_aborted! %s (%d)", + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_modify) ": %s\n", + text->bv_val, 0, 0 ); + } + e->e_id = NOID; + } + + return e->e_id; +} + +#ifdef BDB_TOOL_IDL_CACHING +static int +bdb_tool_idl_cmp( const void *v1, const void *v2 ) +{ + const bdb_tool_idl_cache *c1 = v1, *c2 = v2; + int rc; + + if (( rc = c1->kstr.bv_len - c2->kstr.bv_len )) return rc; + return memcmp( c1->kstr.bv_val, c2->kstr.bv_val, c1->kstr.bv_len ); +} + +static int +bdb_tool_idl_flush_one( void *v1, void *arg ) +{ + bdb_tool_idl_cache *ic = v1; + DB *db = arg; + struct bdb_info *bdb = bdb_tool_info; + bdb_tool_idl_cache_entry *ice; + DBC *curs; + DBT key, data; + int i, rc; + ID id, nid; + + /* Freshly allocated, ignore it */ + if ( !ic->head && ic->count <= BDB_IDL_DB_SIZE ) { + return 0; + } + + rc = db->cursor( db, NULL, &curs, 0 ); + if ( rc ) + return -1; + + DBTzero( &key ); + DBTzero( &data ); + + bv2DBT( &ic->kstr, &key ); + + data.size = data.ulen = sizeof( ID ); + data.flags = DB_DBT_USERMEM; + data.data = &nid; + + rc = curs->c_get( curs, &key, &data, DB_SET ); + /* If key already exists and we're writing a range... */ + if ( rc == 0 && ic->count > BDB_IDL_DB_SIZE ) { + /* If it's not currently a range, must delete old info */ + if ( nid ) { + /* Skip lo */ + while ( curs->c_get( curs, &key, &data, DB_NEXT_DUP ) == 0 ) + curs->c_del( curs, 0 ); + + nid = 0; + /* Store range marker */ + curs->c_put( curs, &key, &data, DB_KEYFIRST ); + } else { + + /* Skip lo */ + rc = curs->c_get( curs, &key, &data, DB_NEXT_DUP ); + + /* Get hi */ + rc = curs->c_get( curs, &key, &data, DB_NEXT_DUP ); + + /* Delete hi */ + curs->c_del( curs, 0 ); + } + BDB_ID2DISK( ic->last, &nid ); + curs->c_put( curs, &key, &data, DB_KEYLAST ); + rc = 0; + } else if ( rc && rc != DB_NOTFOUND ) { + rc = -1; + } else if ( ic->count > BDB_IDL_DB_SIZE ) { + /* range, didn't exist before */ + nid = 0; + rc = curs->c_put( curs, &key, &data, DB_KEYLAST ); + if ( rc == 0 ) { + BDB_ID2DISK( ic->first, &nid ); + rc = curs->c_put( curs, &key, &data, DB_KEYLAST ); + if ( rc == 0 ) { + BDB_ID2DISK( ic->last, &nid ); + rc = curs->c_put( curs, &key, &data, DB_KEYLAST ); + } + } + if ( rc ) { + rc = -1; + } + } else { + int n; + + /* Just a normal write */ + rc = 0; + for ( ice = ic->head, n=0; ice; ice = ice->next, n++ ) { + int end; + if ( ice->next ) { + end = IDBLOCK; + } else { + end = ic->count & (IDBLOCK-1); + if ( !end ) + end = IDBLOCK; + } + for ( i=0; i<end; i++ ) { + if ( !ice->ids[i] ) continue; + BDB_ID2DISK( ice->ids[i], &nid ); + rc = curs->c_put( curs, &key, &data, DB_NODUPDATA ); + if ( rc ) { + if ( rc == DB_KEYEXIST ) { + rc = 0; + continue; + } + rc = -1; + break; + } + } + if ( rc ) { + rc = -1; + break; + } + } + if ( ic->head ) { + ldap_pvt_thread_mutex_lock( &bdb->bi_idl_tree_lrulock ); + ic->tail->next = bdb_tool_idl_free_list; + bdb_tool_idl_free_list = ic->head; + bdb->bi_idl_cache_size -= n; + ldap_pvt_thread_mutex_unlock( &bdb->bi_idl_tree_lrulock ); + } + } + if ( ic != db->app_private ) { + ch_free( ic ); + } else { + ic->head = ic->tail = NULL; + } + curs->c_close( curs ); + return rc; +} + +static int +bdb_tool_idl_flush_db( DB *db, bdb_tool_idl_cache *ic ) +{ + Avlnode *root = db->app_private; + int rc; + + db->app_private = ic; + rc = avl_apply( root, bdb_tool_idl_flush_one, db, -1, AVL_INORDER ); + avl_free( root, NULL ); + db->app_private = NULL; + if ( rc != -1 ) + rc = 0; + return rc; +} + +static int +bdb_tool_idl_flush( BackendDB *be ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + DB *db; + Avlnode *root; + int i, rc = 0; + + for ( i=BDB_NDB; i < bdb->bi_ndatabases; i++ ) { + db = bdb->bi_databases[i]->bdi_db; + if ( !db->app_private ) continue; + rc = bdb_tool_idl_flush_db( db, NULL ); + if ( rc ) + break; + } + if ( !rc ) { + bdb->bi_idl_cache_size = 0; + } + return rc; +} + +int bdb_tool_idl_add( + BackendDB *be, + DB *db, + DB_TXN *txn, + DBT *key, + ID id ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + bdb_tool_idl_cache *ic, itmp; + bdb_tool_idl_cache_entry *ice; + int rc; + + if ( !bdb->bi_idl_cache_max_size ) + return bdb_idl_insert_key( be, db, txn, key, id ); + + DBT2bv( key, &itmp.kstr ); + + ic = avl_find( (Avlnode *)db->app_private, &itmp, bdb_tool_idl_cmp ); + + /* No entry yet, create one */ + if ( !ic ) { + DBC *curs; + DBT data; + ID nid; + int rc; + + ic = ch_malloc( sizeof( bdb_tool_idl_cache ) + itmp.kstr.bv_len ); + ic->kstr.bv_len = itmp.kstr.bv_len; + ic->kstr.bv_val = (char *)(ic+1); + AC_MEMCPY( ic->kstr.bv_val, itmp.kstr.bv_val, ic->kstr.bv_len ); + ic->head = ic->tail = NULL; + ic->last = 0; + ic->count = 0; + avl_insert( (Avlnode **)&db->app_private, ic, bdb_tool_idl_cmp, + avl_dup_error ); + + /* load existing key count here */ + rc = db->cursor( db, NULL, &curs, 0 ); + if ( rc ) return rc; + + data.ulen = sizeof( ID ); + data.flags = DB_DBT_USERMEM; + data.data = &nid; + rc = curs->c_get( curs, key, &data, DB_SET ); + if ( rc == 0 ) { + if ( nid == 0 ) { + ic->count = BDB_IDL_DB_SIZE+1; + } else { + db_recno_t count; + + curs->c_count( curs, &count, 0 ); + ic->count = count; + BDB_DISK2ID( &nid, &ic->first ); + } + } + curs->c_close( curs ); + } + /* are we a range already? */ + if ( ic->count > BDB_IDL_DB_SIZE ) { + ic->last = id; + return 0; + /* Are we at the limit, and converting to a range? */ + } else if ( ic->count == BDB_IDL_DB_SIZE ) { + int n; + for ( ice = ic->head, n=0; ice; ice = ice->next, n++ ) + /* counting */ ; + if ( n ) { + ldap_pvt_thread_mutex_lock( &bdb->bi_idl_tree_lrulock ); + ic->tail->next = bdb_tool_idl_free_list; + bdb_tool_idl_free_list = ic->head; + bdb->bi_idl_cache_size -= n; + ldap_pvt_thread_mutex_unlock( &bdb->bi_idl_tree_lrulock ); + } + ic->head = ic->tail = NULL; + ic->last = id; + ic->count++; + return 0; + } + /* No free block, create that too */ + if ( !ic->tail || ( ic->count & (IDBLOCK-1)) == 0) { + ice = NULL; + ldap_pvt_thread_mutex_lock( &bdb->bi_idl_tree_lrulock ); + if ( bdb->bi_idl_cache_size >= bdb->bi_idl_cache_max_size ) { + ldap_pvt_thread_mutex_unlock( &bdb->bi_idl_tree_lrulock ); + rc = bdb_tool_idl_flush_db( db, ic ); + if ( rc ) + return rc; + avl_insert( (Avlnode **)&db->app_private, ic, bdb_tool_idl_cmp, + avl_dup_error ); + ldap_pvt_thread_mutex_lock( &bdb->bi_idl_tree_lrulock ); + } + bdb->bi_idl_cache_size++; + if ( bdb_tool_idl_free_list ) { + ice = bdb_tool_idl_free_list; + bdb_tool_idl_free_list = ice->next; + } + ldap_pvt_thread_mutex_unlock( &bdb->bi_idl_tree_lrulock ); + if ( !ice ) { + ice = ch_malloc( sizeof( bdb_tool_idl_cache_entry )); + } + memset( ice, 0, sizeof( *ice )); + if ( !ic->head ) { + ic->head = ice; + } else { + ic->tail->next = ice; + } + ic->tail = ice; + if ( !ic->count ) + ic->first = id; + } + ice = ic->tail; + ice->ids[ ic->count & (IDBLOCK-1) ] = id; + ic->count++; + + return 0; +} +#endif + +#ifdef USE_TRICKLE +static void * +bdb_tool_trickle_task( void *ctx, void *ptr ) +{ + DB_ENV *env = ptr; + int wrote; + + ldap_pvt_thread_mutex_lock( &bdb_tool_trickle_mutex ); + bdb_tool_trickle_active = 1; + ldap_pvt_thread_cond_signal( &bdb_tool_trickle_cond_end ); + while ( 1 ) { + ldap_pvt_thread_cond_wait( &bdb_tool_trickle_cond, + &bdb_tool_trickle_mutex ); + if ( slapd_shutdown ) + break; + env->memp_trickle( env, 30, &wrote ); + } + bdb_tool_trickle_active = 0; + ldap_pvt_thread_cond_signal( &bdb_tool_trickle_cond_end ); + ldap_pvt_thread_mutex_unlock( &bdb_tool_trickle_mutex ); + + return NULL; +} +#endif + +static void * +bdb_tool_index_task( void *ctx, void *ptr ) +{ + int base = *(int *)ptr; + + free( ptr ); + while ( 1 ) { + ldap_pvt_thread_mutex_lock( &bdb_tool_index_mutex ); + bdb_tool_index_tcount--; + if ( !bdb_tool_index_tcount ) + ldap_pvt_thread_cond_signal( &bdb_tool_index_cond_main ); + ldap_pvt_thread_cond_wait( &bdb_tool_index_cond_work, + &bdb_tool_index_mutex ); + if ( slapd_shutdown ) { + bdb_tool_index_tcount--; + if ( !bdb_tool_index_tcount ) + ldap_pvt_thread_cond_signal( &bdb_tool_index_cond_main ); + ldap_pvt_thread_mutex_unlock( &bdb_tool_index_mutex ); + break; + } + ldap_pvt_thread_mutex_unlock( &bdb_tool_index_mutex ); + + bdb_tool_index_threads[base] = bdb_index_recrun( bdb_tool_ix_op, + bdb_tool_info, bdb_tool_index_rec, bdb_tool_ix_id, base ); + } + + return NULL; +} diff --git a/servers/slapd/back-bdb/trans.c b/servers/slapd/back-bdb/trans.c new file mode 100644 index 0000000..92da8be --- /dev/null +++ b/servers/slapd/back-bdb/trans.c @@ -0,0 +1,56 @@ +/* trans.c - bdb backend transaction routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-bdb.h" +#include "lber_pvt.h" +#include "lutil.h" + + +/* Congestion avoidance code + * for Deadlock Rollback + */ + +void +bdb_trans_backoff( int num_retries ) +{ + int i; + int delay = 0; + int pow_retries = 1; + unsigned long key = 0; + unsigned long max_key = -1; + struct timeval timeout; + + lutil_entropy( (unsigned char *) &key, sizeof( unsigned long )); + + for ( i = 0; i < num_retries; i++ ) { + if ( i >= 5 ) break; + pow_retries *= 4; + } + + delay = 16384 * (key * (double) pow_retries / (double) max_key); + delay = delay ? delay : 1; + + Debug( LDAP_DEBUG_TRACE, "delay = %d, num_retries = %d\n", delay, num_retries, 0 ); + + timeout.tv_sec = delay / 1000000; + timeout.tv_usec = delay % 1000000; + select( 0, NULL, NULL, NULL, &timeout ); +} diff --git a/servers/slapd/back-dnssrv/Makefile.in b/servers/slapd/back-dnssrv/Makefile.in new file mode 100644 index 0000000..cf7b18d --- /dev/null +++ b/servers/slapd/back-dnssrv/Makefile.in @@ -0,0 +1,46 @@ +# Makefile.in for back-dnssrv +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## Portions Copyright 1998-2003 Kurt D. Zeilenga. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +# +# ACKNOWLEDGEMENTS: +# The DNSSRV backend was written by Kurt D. Zeilenga. +# + +SRCS = init.c bind.c search.c config.c referral.c +OBJS = init.lo bind.lo search.lo config.lo referral.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-dnssrv" +BUILD_MOD = @BUILD_DNSSRV@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_DNSSRV@_DEFS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_R_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBBASE = back_dnssrv + +XINCPATH = -I.. -I$(srcdir)/.. +XDEFS = $(MODULES_CPPFLAGS) + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ + diff --git a/servers/slapd/back-dnssrv/bind.c b/servers/slapd/back-dnssrv/bind.c new file mode 100644 index 0000000..6005994 --- /dev/null +++ b/servers/slapd/back-dnssrv/bind.c @@ -0,0 +1,79 @@ +/* bind.c - DNS SRV backend bind function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * Portions Copyright 2000-2003 Kurt D. Zeilenga. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by Kurt D. Zeilenga for inclusion + * in OpenLDAP Software. + */ + + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "proto-dnssrv.h" + +int +dnssrv_back_bind( + Operation *op, + SlapReply *rs ) +{ + Debug( LDAP_DEBUG_TRACE, "DNSSRV: bind dn=\"%s\" (%d)\n", + BER_BVISNULL( &op->o_req_dn ) ? "" : op->o_req_dn.bv_val, + op->orb_method, 0 ); + + /* allow rootdn as a means to auth without the need to actually + * contact the proxied DSA */ + switch ( be_rootdn_bind( op, NULL ) ) { + case LDAP_SUCCESS: + /* frontend will send result */ + return rs->sr_err; + + default: + /* treat failure and like any other bind, otherwise + * it could reveal the DN of the rootdn */ + break; + } + + if ( !BER_BVISNULL( &op->orb_cred ) && + !BER_BVISEMPTY( &op->orb_cred ) ) + { + /* simple bind */ + Statslog( LDAP_DEBUG_STATS, + "%s DNSSRV BIND dn=\"%s\" provided cleartext passwd\n", + op->o_log_prefix, + BER_BVISNULL( &op->o_req_dn ) ? "" : op->o_req_dn.bv_val , 0, 0, 0 ); + + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "you shouldn't send strangers your password" ); + + } else { + /* unauthenticated bind */ + /* NOTE: we're not going to get here anyway: + * unauthenticated bind is dealt with by the frontend */ + Debug( LDAP_DEBUG_TRACE, "DNSSRV: BIND dn=\"%s\"\n", + BER_BVISNULL( &op->o_req_dn ) ? "" : op->o_req_dn.bv_val, 0, 0 ); + + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "anonymous bind expected" ); + } + + return 1; +} diff --git a/servers/slapd/back-dnssrv/compare.c b/servers/slapd/back-dnssrv/compare.c new file mode 100644 index 0000000..e5a0d45 --- /dev/null +++ b/servers/slapd/back-dnssrv/compare.c @@ -0,0 +1,46 @@ +/* compare.c - DNS SRV backend compare function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * Portions Copyright 2000-2003 Kurt D. Zeilenga. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by Kurt D. Zeilenga for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "proto-dnssrv.h" + +int +dnssrv_back_compare( + Operation *op, + SlapReply *rs +) +{ +#if 0 + assert( get_manageDSAit( op ) ); +#endif + send_ldap_error( op, rs, LDAP_OTHER, + "Operation not supported within naming context" ); + + /* not implemented */ + return 1; +} diff --git a/servers/slapd/back-dnssrv/config.c b/servers/slapd/back-dnssrv/config.c new file mode 100644 index 0000000..b04e76f --- /dev/null +++ b/servers/slapd/back-dnssrv/config.c @@ -0,0 +1,54 @@ +/* config.c - DNS SRV backend configuration file routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * Portions Copyright 2000-2003 Kurt D. Zeilenga. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by Kurt D. Zeilenga for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "proto-dnssrv.h" + +#if 0 +int +dnssrv_back_db_config( + BackendDB *be, + const char *fname, + int lineno, + int argc, + char **argv ) +{ +#if 0 + struct ldapinfo *li = (struct ldapinfo *) be->be_private; + + if ( li == NULL ) { + fprintf( stderr, "%s: line %d: DNSSRV backend info is null!\n", + fname, lineno ); + return( 1 ); + } +#endif + + /* no configuration options (yet) */ + return SLAP_CONF_UNKNOWN; +} +#endif diff --git a/servers/slapd/back-dnssrv/init.c b/servers/slapd/back-dnssrv/init.c new file mode 100644 index 0000000..e9d4dc7 --- /dev/null +++ b/servers/slapd/back-dnssrv/init.c @@ -0,0 +1,115 @@ +/* init.c - initialize ldap backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * Portions Copyright 2000-2003 Kurt D. Zeilenga. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by Kurt D. Zeilenga for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/param.h> +#include <ac/string.h> + +#include "slap.h" +#include "config.h" +#include "proto-dnssrv.h" + +int +dnssrv_back_initialize( + BackendInfo *bi ) +{ + static char *controls[] = { + LDAP_CONTROL_MANAGEDSAIT, + NULL + }; + + bi->bi_controls = controls; + + bi->bi_open = dnssrv_back_open; + bi->bi_config = 0; + bi->bi_close = 0; + bi->bi_destroy = 0; + + bi->bi_db_init = 0; + bi->bi_db_destroy = 0; + bi->bi_db_config = 0 /* dnssrv_back_db_config */; + bi->bi_db_open = 0; + bi->bi_db_close = 0; + + bi->bi_chk_referrals = dnssrv_back_referrals; + + bi->bi_op_bind = dnssrv_back_bind; + bi->bi_op_search = dnssrv_back_search; + bi->bi_op_compare = 0 /* dnssrv_back_compare */; + bi->bi_op_modify = 0; + bi->bi_op_modrdn = 0; + bi->bi_op_add = 0; + bi->bi_op_delete = 0; + bi->bi_op_abandon = 0; + bi->bi_op_unbind = 0; + + bi->bi_extended = 0; + + bi->bi_connection_init = 0; + bi->bi_connection_destroy = 0; + + bi->bi_access_allowed = slap_access_always_allowed; + + return 0; +} + +AttributeDescription *ad_dc; +AttributeDescription *ad_associatedDomain; + +int +dnssrv_back_open( + BackendInfo *bi ) +{ + const char *text; + + (void)slap_str2ad( "dc", &ad_dc, &text ); + (void)slap_str2ad( "associatedDomain", &ad_associatedDomain, &text ); + + return 0; +} + +int +dnssrv_back_db_init( + Backend *be, + ConfigReply *cr) +{ + return 0; +} + +int +dnssrv_back_db_destroy( + Backend *be, + ConfigReply *cr ) +{ + return 0; +} + +#if SLAPD_DNSSRV == SLAPD_MOD_DYNAMIC + +/* conditionally define the init_module() function */ +SLAP_BACKEND_INIT_MODULE( dnssrv ) + +#endif /* SLAPD_DNSSRV == SLAPD_MOD_DYNAMIC */ + diff --git a/servers/slapd/back-dnssrv/proto-dnssrv.h b/servers/slapd/back-dnssrv/proto-dnssrv.h new file mode 100644 index 0000000..11754a7 --- /dev/null +++ b/servers/slapd/back-dnssrv/proto-dnssrv.h @@ -0,0 +1,46 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by Kurt D. Zeilenga for inclusion + * in OpenLDAP Software. + */ + +#ifndef PROTO_DNSSRV_H +#define PROTO_DNSSRV_H + +LDAP_BEGIN_DECL + +extern BI_init dnssrv_back_initialize; + +extern BI_open dnssrv_back_open; +extern BI_close dnssrv_back_close; +extern BI_destroy dnssrv_back_destroy; + +extern BI_db_init dnssrv_back_db_init; +extern BI_db_destroy dnssrv_back_db_destroy; +extern BI_db_config dnssrv_back_db_config; + +extern BI_op_bind dnssrv_back_bind; +extern BI_op_search dnssrv_back_search; +extern BI_op_compare dnssrv_back_compare; + +extern BI_chk_referrals dnssrv_back_referrals; + +extern AttributeDescription *ad_dc; +extern AttributeDescription *ad_associatedDomain; + +LDAP_END_DECL + +#endif /* PROTO_DNSSRV_H */ diff --git a/servers/slapd/back-dnssrv/referral.c b/servers/slapd/back-dnssrv/referral.c new file mode 100644 index 0000000..baa9a41 --- /dev/null +++ b/servers/slapd/back-dnssrv/referral.c @@ -0,0 +1,129 @@ +/* referral.c - DNS SRV backend referral handler */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * Portions Copyright 2000-2003 Kurt D. Zeilenga. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by Kurt D. Zeilenga for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "proto-dnssrv.h" + +int +dnssrv_back_referrals( + Operation *op, + SlapReply *rs ) +{ + int i; + int rc = LDAP_OTHER; + char *domain = NULL; + char *hostlist = NULL; + char **hosts = NULL; + BerVarray urls = NULL; + + if ( BER_BVISEMPTY( &op->o_req_dn ) ) { + /* FIXME: need some means to determine whether the database + * is a glue instance */ + if ( SLAP_GLUE_INSTANCE( op->o_bd ) ) { + return LDAP_SUCCESS; + } + + rs->sr_text = "DNS SRV operation upon null (empty) DN disallowed"; + return LDAP_UNWILLING_TO_PERFORM; + } + + if( get_manageDSAit( op ) ) { + if( op->o_tag == LDAP_REQ_SEARCH ) { + return LDAP_SUCCESS; + } + + rs->sr_text = "DNS SRV problem processing manageDSAit control"; + return LDAP_OTHER; + } + + if( ldap_dn2domain( op->o_req_dn.bv_val, &domain ) || domain == NULL ) { + rs->sr_err = LDAP_REFERRAL; + rs->sr_ref = default_referral; + send_ldap_result( op, rs ); + rs->sr_ref = NULL; + return LDAP_REFERRAL; + } + + Debug( LDAP_DEBUG_TRACE, "DNSSRV: dn=\"%s\" -> domain=\"%s\"\n", + op->o_req_dn.bv_val, domain, 0 ); + + i = ldap_domain2hostlist( domain, &hostlist ); + if ( i ) { + Debug( LDAP_DEBUG_TRACE, + "DNSSRV: domain2hostlist(%s) returned %d\n", + domain, i, 0 ); + rs->sr_text = "no DNS SRV RR available for DN"; + rc = LDAP_NO_SUCH_OBJECT; + goto done; + } + + hosts = ldap_str2charray( hostlist, " " ); + + if( hosts == NULL ) { + Debug( LDAP_DEBUG_TRACE, "DNSSRV: str2charrary error\n", 0, 0, 0 ); + rs->sr_text = "problem processing DNS SRV records for DN"; + goto done; + } + + for( i=0; hosts[i] != NULL; i++) { + struct berval url; + + url.bv_len = STRLENOF( "ldap://" ) + strlen( hosts[i] ); + url.bv_val = ch_malloc( url.bv_len + 1 ); + + strcpy( url.bv_val, "ldap://" ); + strcpy( &url.bv_val[STRLENOF( "ldap://" )], hosts[i] ); + + if ( ber_bvarray_add( &urls, &url ) < 0 ) { + free( url.bv_val ); + rs->sr_text = "problem processing DNS SRV records for DN"; + goto done; + } + } + + Statslog( LDAP_DEBUG_STATS, + "%s DNSSRV p=%d dn=\"%s\" url=\"%s\"\n", + op->o_log_prefix, op->o_protocol, + op->o_req_dn.bv_val, urls[0].bv_val, 0 ); + + Debug( LDAP_DEBUG_TRACE, "DNSSRV: dn=\"%s\" -> url=\"%s\"\n", + op->o_req_dn.bv_val, urls[0].bv_val, 0 ); + + rs->sr_ref = urls; + send_ldap_error( op, rs, LDAP_REFERRAL, + "DNS SRV generated referrals" ); + rs->sr_ref = NULL; + rc = LDAP_REFERRAL; + +done: + if( domain != NULL ) ch_free( domain ); + if( hostlist != NULL ) ch_free( hostlist ); + if( hosts != NULL ) ldap_charray_free( hosts ); + ber_bvarray_free( urls ); + return rc; +} diff --git a/servers/slapd/back-dnssrv/search.c b/servers/slapd/back-dnssrv/search.c new file mode 100644 index 0000000..aa1d204 --- /dev/null +++ b/servers/slapd/back-dnssrv/search.c @@ -0,0 +1,240 @@ +/* search.c - DNS SRV backend search function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * Portions Copyright 2000-2003 Kurt D. Zeilenga. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by Kurt D. Zeilenga for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "slap.h" +#include "proto-dnssrv.h" + +int +dnssrv_back_search( + Operation *op, + SlapReply *rs ) +{ + int i; + int rc; + char *domain = NULL; + char *hostlist = NULL; + char **hosts = NULL; + char *refdn; + struct berval nrefdn = BER_BVNULL; + BerVarray urls = NULL; + int manageDSAit; + + rs->sr_ref = NULL; + + if ( BER_BVISEMPTY( &op->o_req_ndn ) ) { + /* FIXME: need some means to determine whether the database + * is a glue instance; if we got here with empty DN, then + * we passed this same test in dnssrv_back_referrals() */ + if ( !SLAP_GLUE_INSTANCE( op->o_bd ) ) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "DNS SRV operation upon null (empty) DN disallowed"; + + } else { + rs->sr_err = LDAP_SUCCESS; + } + goto done; + } + + manageDSAit = get_manageDSAit( op ); + /* + * FIXME: we may return a referral if manageDSAit is not set + */ + if ( !manageDSAit ) { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "manageDSAit must be set" ); + goto done; + } + + if( ldap_dn2domain( op->o_req_dn.bv_val, &domain ) || domain == NULL ) { + rs->sr_err = LDAP_REFERRAL; + rs->sr_ref = default_referral; + send_ldap_result( op, rs ); + rs->sr_ref = NULL; + goto done; + } + + Debug( LDAP_DEBUG_TRACE, "DNSSRV: dn=\"%s\" -> domain=\"%s\"\n", + op->o_req_dn.bv_len ? op->o_req_dn.bv_val : "", domain, 0 ); + + if( ( rc = ldap_domain2hostlist( domain, &hostlist ) ) ) { + Debug( LDAP_DEBUG_TRACE, "DNSSRV: domain2hostlist returned %d\n", + rc, 0, 0 ); + send_ldap_error( op, rs, LDAP_NO_SUCH_OBJECT, + "no DNS SRV RR available for DN" ); + goto done; + } + + hosts = ldap_str2charray( hostlist, " " ); + + if( hosts == NULL ) { + Debug( LDAP_DEBUG_TRACE, "DNSSRV: str2charrary error\n", 0, 0, 0 ); + send_ldap_error( op, rs, LDAP_OTHER, + "problem processing DNS SRV records for DN" ); + goto done; + } + + for( i=0; hosts[i] != NULL; i++) { + struct berval url; + + url.bv_len = STRLENOF( "ldap://" ) + strlen(hosts[i]); + url.bv_val = ch_malloc( url.bv_len + 1 ); + + strcpy( url.bv_val, "ldap://" ); + strcpy( &url.bv_val[STRLENOF( "ldap://" )], hosts[i] ); + + if( ber_bvarray_add( &urls, &url ) < 0 ) { + free( url.bv_val ); + send_ldap_error( op, rs, LDAP_OTHER, + "problem processing DNS SRV records for DN" ); + goto done; + } + } + + Statslog( LDAP_DEBUG_STATS, + "%s DNSSRV p=%d dn=\"%s\" url=\"%s\"\n", + op->o_log_prefix, op->o_protocol, + op->o_req_dn.bv_len ? op->o_req_dn.bv_val : "", urls[0].bv_val, 0 ); + + Debug( LDAP_DEBUG_TRACE, + "DNSSRV: ManageDSAit scope=%d dn=\"%s\" -> url=\"%s\"\n", + op->oq_search.rs_scope, + op->o_req_dn.bv_len ? op->o_req_dn.bv_val : "", + urls[0].bv_val ); + + rc = ldap_domain2dn(domain, &refdn); + + if( rc != LDAP_SUCCESS ) { + send_ldap_error( op, rs, LDAP_OTHER, + "DNS SRV problem processing manageDSAit control" ); + goto done; + + } else { + struct berval bv; + bv.bv_val = refdn; + bv.bv_len = strlen( refdn ); + + rc = dnNormalize( 0, NULL, NULL, &bv, &nrefdn, op->o_tmpmemctx ); + if( rc != LDAP_SUCCESS ) { + send_ldap_error( op, rs, LDAP_OTHER, + "DNS SRV problem processing manageDSAit control" ); + goto done; + } + } + + if( !dn_match( &nrefdn, &op->o_req_ndn ) ) { + /* requested dn is subordinate */ + + Debug( LDAP_DEBUG_TRACE, + "DNSSRV: dn=\"%s\" subordinate to refdn=\"%s\"\n", + op->o_req_dn.bv_len ? op->o_req_dn.bv_val : "", + refdn == NULL ? "" : refdn, + NULL ); + + rs->sr_matched = refdn; + rs->sr_err = LDAP_NO_SUCH_OBJECT; + send_ldap_result( op, rs ); + rs->sr_matched = NULL; + + } else if ( op->oq_search.rs_scope == LDAP_SCOPE_ONELEVEL ) { + send_ldap_error( op, rs, LDAP_SUCCESS, NULL ); + + } else { + Entry e = { 0 }; + AttributeDescription *ad_objectClass + = slap_schema.si_ad_objectClass; + AttributeDescription *ad_ref = slap_schema.si_ad_ref; + e.e_name.bv_val = ch_strdup( op->o_req_dn.bv_val ); + e.e_name.bv_len = op->o_req_dn.bv_len; + e.e_nname.bv_val = ch_strdup( op->o_req_ndn.bv_val ); + e.e_nname.bv_len = op->o_req_ndn.bv_len; + + e.e_attrs = NULL; + e.e_private = NULL; + + attr_merge_one( &e, ad_objectClass, &slap_schema.si_oc_referral->soc_cname, NULL ); + attr_merge_one( &e, ad_objectClass, &slap_schema.si_oc_extensibleObject->soc_cname, NULL ); + + if ( ad_dc ) { + char *p; + struct berval bv; + + bv.bv_val = domain; + + p = strchr( bv.bv_val, '.' ); + + if ( p == bv.bv_val ) { + bv.bv_len = 1; + + } else if ( p != NULL ) { + bv.bv_len = p - bv.bv_val; + + } else { + bv.bv_len = strlen( bv.bv_val ); + } + + attr_merge_normalize_one( &e, ad_dc, &bv, NULL ); + } + + if ( ad_associatedDomain ) { + struct berval bv; + + ber_str2bv( domain, 0, 0, &bv ); + attr_merge_normalize_one( &e, ad_associatedDomain, &bv, NULL ); + } + + attr_merge_normalize_one( &e, ad_ref, urls, NULL ); + + rc = test_filter( op, &e, op->oq_search.rs_filter ); + + if( rc == LDAP_COMPARE_TRUE ) { + rs->sr_entry = &e; + rs->sr_attrs = op->oq_search.rs_attrs; + rs->sr_flags = REP_ENTRY_MODIFIABLE; + send_search_entry( op, rs ); + rs->sr_entry = NULL; + rs->sr_attrs = NULL; + rs->sr_flags = 0; + } + + entry_clean( &e ); + + rs->sr_err = LDAP_SUCCESS; + send_ldap_result( op, rs ); + } + + free( refdn ); + if ( nrefdn.bv_val ) free( nrefdn.bv_val ); + +done: + if( domain != NULL ) ch_free( domain ); + if( hostlist != NULL ) ch_free( hostlist ); + if( hosts != NULL ) ldap_charray_free( hosts ); + if( urls != NULL ) ber_bvarray_free( urls ); + return 0; +} diff --git a/servers/slapd/back-hdb/Makefile.in b/servers/slapd/back-hdb/Makefile.in new file mode 100644 index 0000000..1ba7ae8 --- /dev/null +++ b/servers/slapd/back-hdb/Makefile.in @@ -0,0 +1,70 @@ +# Makefile for back-hdb +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +# +## Copyright 2003 Howard Chu @ Symas Corp. See master COPYRIGHT file for terms. + +XXDIR = $(srcdir)/../back-bdb + +XXSRCS = init.c tools.c config.c \ + add.c bind.c compare.c delete.c modify.c modrdn.c search.c \ + extended.c referral.c operational.c \ + attr.c index.c key.c dbcache.c filterindex.c trans.c \ + dn2entry.c dn2id.c error.c id2entry.c idl.c nextid.c cache.c \ + monitor.c +SRCS = $(XXSRCS) +OBJS = init.lo tools.lo config.lo \ + add.lo bind.lo compare.lo delete.lo modify.lo modrdn.lo search.lo \ + extended.lo referral.lo operational.lo \ + attr.lo index.lo key.lo dbcache.lo filterindex.lo trans.lo \ + dn2entry.lo dn2id.lo error.lo id2entry.lo idl.lo nextid.lo cache.lo \ + monitor.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-hdb" +BUILD_MOD = @BUILD_HDB@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_HDB@_DEFS) +MOD_LIBS = $(BDB_LIBS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_R_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +.links : Makefile + @for i in $(XXSRCS); do \ + $(RM) $$i; \ + $(LN_S) $(XXDIR)/$$i . ; \ + done + touch .links + +$(XXSRCS) : .links + +LIBBASE = back_hdb + +XINCPATH = -I.. -I$(srcdir)/.. -I$(srcdir) -I$(XXDIR) +XDEFS = $(MODULES_CPPFLAGS) + +depend-local-lib: .links + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ + +veryclean-local: FORCE + $(RM) $(XXSRCS) .links diff --git a/servers/slapd/back-hdb/back-bdb.h b/servers/slapd/back-hdb/back-bdb.h new file mode 100644 index 0000000..23b852d --- /dev/null +++ b/servers/slapd/back-hdb/back-bdb.h @@ -0,0 +1,31 @@ +/* back-bdb.h - hdb back-end header file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * Portions Copyright 2003 Howard Chu @ Symas Corp. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by Howard Chu for inclusion + * in OpenLDAP Software. + */ + +#ifndef _BACK_HDB_H_ +#define _BACK_HDB_H_ + +#ifndef BDB_HIER +#define BDB_HIER 1 +#endif + +#include "../back-bdb/back-bdb.h" + +#endif /* _BACK_HDB_H_ */ diff --git a/servers/slapd/back-ldap/Makefile.in b/servers/slapd/back-ldap/Makefile.in new file mode 100644 index 0000000..5521f8b --- /dev/null +++ b/servers/slapd/back-ldap/Makefile.in @@ -0,0 +1,45 @@ +# Makefile.in for back-ldap +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +SRCS = init.c config.c search.c bind.c unbind.c add.c compare.c \ + delete.c modify.c modrdn.c extended.c chain.c \ + distproc.c monitor.c pbind.c +OBJS = init.lo config.lo search.lo bind.lo unbind.lo add.lo compare.lo \ + delete.lo modify.lo modrdn.lo extended.lo chain.lo \ + distproc.lo monitor.lo pbind.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-ldap" +BUILD_MOD = @BUILD_LDAP@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_LDAP@_DEFS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_R_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBBASE = back_ldap + +XINCPATH = -I.. -I$(srcdir)/.. +XDEFS = $(MODULES_CPPFLAGS) + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ + diff --git a/servers/slapd/back-ldap/TODO.proxy b/servers/slapd/back-ldap/TODO.proxy new file mode 100644 index 0000000..4e61095 --- /dev/null +++ b/servers/slapd/back-ldap/TODO.proxy @@ -0,0 +1,101 @@ +back-proxy + +A proxy that handles a pool of URI associated to a unique suffix. +Each request is spread over the different URIs and results are +masqueraded to appear as coming from a unique server. + +Suppose a company has two branches, whose existing DS have URIs + +"ldap://ldap.branch1.com/o=Branch 1, c=US" +"ldap://ldap.branch2.it/o=Branch 2, c=IT" + +and it wants to propose to the outer world as a unique URI + +"ldap://ldap.company.net/dc=company, dc=net" + +It could do some rewriting to map everything that comes in with a base DN +of "o=Branch 1, dc=company, dc=net" as the URI of the Branch 1, and +everything that comes in with a base DN of "o=Branch 2, dc=company, dc=net" +as the URI of Branch 2, and by rewriting all the DNs back to the new, uniform +base. Everything that comes in with a base DN of "dc=company, dc=net" should +be handled locally and propagated to the two branch URIs if a subtree +(or at least onelevel) search is required. + +Operations: + +- bind +- unbind +- search +- compare +- add +- modify +- modrdn +- delete +- abandon + +The input of each operation may be related to: + + exact DN exact parent ancestor +------------------------------------------------------------- +bind x +unbind +search x x x +compare x +add x +modify x +modrdn x +delete x +abandon + +The backend must rely on a DN fetching mechanism. Each operation requires +to determine as early as possible which URI will be able to satisfy it. +Apart from searches, which by definition are usually allowed to return +multiple results, and apart from unbind and abandon, which do not return any +result, all the remaining operations require the related entry to be unique. + +A major problem isposed by the uniqueness of the DNs. As far as the suffixes +are masqueraded by a common suffix, the DNs are no longer guaranteed to be +unique. This backend relies on the assumption that the uniqueness of the +DNs is guaranteed. + +Two layers of depth in DN fetching are envisaged. +The first layer is provided by a backend-side cache made of previously +retrieved entries. The cache relates each RDN (i.e. the DN apart from the +common suffix) to the pool of URIs that are expected to contain a subset +of its children. + +The second layer is provided by a fetching function that spawns a search for +each URI in the pool determined by the cache if the correct URI has not been +directly determined. + +Note that, as the remote servers may have been updated by some direct +operation, this mechanism does not guarantee the uniqueness of the result. +So write operations will require to skip the cache search and to perform +the exaustive search of all the URIs unless some hint mechanism is provided +to the backend (e.g. a server is read-only). + +Again, the lag between the fetching of the required DN and the actual +read/write may result in a failure; however, this applies to any LDAP +operation AFAIK. + +- bind +if updates are to be strictly honored, a bind operation is performed against +each URI; otherwise, it is performed against the URIs resulting from a +cache-level DN fetch. + +- unbind +nothing to say; all the open handles related to the connection are reset. + +- search +if updates are to be strictly honored, a search operation is performed agaist +each URI. Note that this needs be performed also when the backend suffix +is used as base. In case the base is stricter, the URI pool may be restricted +by performing a cache DN fetch of the base first. + +- compare +the same applies to the compare DN. + +- add +this operation is delicate. Unless the DN up to the top-level part excluded +can be uniquely associated to a URI, and unless its uniqueness can be trusted, +no add operation should be allowed. diff --git a/servers/slapd/back-ldap/add.c b/servers/slapd/back-ldap/add.c new file mode 100644 index 0000000..a2e533a --- /dev/null +++ b/servers/slapd/back-ldap/add.c @@ -0,0 +1,139 @@ +/* add.c - ldap backend add function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-ldap.h" + +int +ldap_back_add( + Operation *op, + SlapReply *rs ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + ldapconn_t *lc = NULL; + int i = 0, + j = 0; + Attribute *a; + LDAPMod **attrs = NULL, + *attrs2 = NULL; + ber_int_t msgid; + int isupdate; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + LDAPControl **ctrls = NULL; + + rs->sr_err = LDAP_SUCCESS; + + Debug( LDAP_DEBUG_ARGS, "==> ldap_back_add(\"%s\")\n", + op->o_req_dn.bv_val, 0, 0 ); + + if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + lc = NULL; + goto cleanup; + } + + /* Count number of attributes in entry */ + for ( i = 1, a = op->oq_add.rs_e->e_attrs; a; i++, a = a->a_next ) + /* just count attrs */ ; + + /* Create array of LDAPMods for ldap_add() */ + attrs = (LDAPMod **)ch_malloc( sizeof( LDAPMod * )*i + + sizeof( LDAPMod )*( i - 1 ) ); + attrs2 = ( LDAPMod * )&attrs[ i ]; + + isupdate = be_shadow_update( op ); + for ( i = 0, a = op->oq_add.rs_e->e_attrs; a; a = a->a_next ) { + if ( !isupdate && !get_relax( op ) && a->a_desc->ad_type->sat_no_user_mod ) + { + continue; + } + + attrs[ i ] = &attrs2[ i ]; + attrs[ i ]->mod_op = LDAP_MOD_BVALUES; + attrs[ i ]->mod_type = a->a_desc->ad_cname.bv_val; + + for ( j = 0; a->a_vals[ j ].bv_val; j++ ) + /* just count vals */ ; + attrs[i]->mod_vals.modv_bvals = + ch_malloc( ( j + 1 )*sizeof( struct berval * ) ); + for ( j = 0; a->a_vals[ j ].bv_val; j++ ) { + attrs[ i ]->mod_vals.modv_bvals[ j ] = &a->a_vals[ j ]; + } + attrs[ i ]->mod_vals.modv_bvals[ j ] = NULL; + i++; + } + attrs[ i ] = NULL; + +retry: + ctrls = op->o_ctrls; + rs->sr_err = ldap_back_controls_add( op, rs, lc, &ctrls ); + if ( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = ldap_add_ext( lc->lc_ld, op->o_req_dn.bv_val, attrs, + ctrls, NULL, &msgid ); + rs->sr_err = ldap_back_op_result( lc, op, rs, msgid, + li->li_timeout[ SLAP_OP_ADD ], + ( LDAP_BACK_SENDRESULT | retrying ) ); + if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)ldap_back_controls_free( op, rs, &ctrls ); + goto retry; + } + } + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_ADD ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + +cleanup: + (void)ldap_back_controls_free( op, rs, &ctrls ); + + if ( attrs ) { + for ( --i; i >= 0; --i ) { + ch_free( attrs[ i ]->mod_vals.modv_bvals ); + } + ch_free( attrs ); + } + + if ( lc ) { + ldap_back_release_conn( li, lc ); + } + + Debug( LDAP_DEBUG_ARGS, "<== ldap_back_add(\"%s\"): %d\n", + op->o_req_dn.bv_val, rs->sr_err, 0 ); + + return rs->sr_err; +} + diff --git a/servers/slapd/back-ldap/back-ldap.h b/servers/slapd/back-ldap/back-ldap.h new file mode 100644 index 0000000..1e2f230 --- /dev/null +++ b/servers/slapd/back-ldap/back-ldap.h @@ -0,0 +1,475 @@ +/* back-ldap.h - ldap backend header file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#ifndef SLAPD_LDAP_H +#define SLAPD_LDAP_H + +#include "../back-monitor/back-monitor.h" + +LDAP_BEGIN_DECL + +struct ldapinfo_t; + +/* stuff required for monitoring */ +typedef struct ldap_monitor_info_t { + monitor_subsys_t lmi_mss[2]; + + struct berval lmi_ndn; + struct berval lmi_conn_rdn; + struct berval lmi_ops_rdn; +} ldap_monitor_info_t; + +enum { + /* even numbers are connection types */ + LDAP_BACK_PCONN_FIRST = 0, + LDAP_BACK_PCONN_ROOTDN = LDAP_BACK_PCONN_FIRST, + LDAP_BACK_PCONN_ANON = 2, + LDAP_BACK_PCONN_BIND = 4, + + /* add the TLS bit */ + LDAP_BACK_PCONN_TLS = 0x1U, + + LDAP_BACK_PCONN_ROOTDN_TLS = (LDAP_BACK_PCONN_ROOTDN|LDAP_BACK_PCONN_TLS), + LDAP_BACK_PCONN_ANON_TLS = (LDAP_BACK_PCONN_ANON|LDAP_BACK_PCONN_TLS), + LDAP_BACK_PCONN_BIND_TLS = (LDAP_BACK_PCONN_BIND|LDAP_BACK_PCONN_TLS), + + LDAP_BACK_PCONN_LAST +}; + +typedef struct ldapconn_base_t { + Connection *lcb_conn; +#define LDAP_BACK_CONN2PRIV(lc) ((unsigned long)(lc)->lc_conn) +#define LDAP_BACK_PCONN_ISPRIV(lc) (((void *)(lc)->lc_conn) >= ((void *)LDAP_BACK_PCONN_FIRST) \ + && ((void *)(lc)->lc_conn) < ((void *)LDAP_BACK_PCONN_LAST)) +#define LDAP_BACK_PCONN_ISROOTDN(lc) (LDAP_BACK_PCONN_ISPRIV((lc)) \ + && (LDAP_BACK_CONN2PRIV((lc)) < LDAP_BACK_PCONN_ANON)) +#define LDAP_BACK_PCONN_ISANON(lc) (LDAP_BACK_PCONN_ISPRIV((lc)) \ + && (LDAP_BACK_CONN2PRIV((lc)) < LDAP_BACK_PCONN_BIND) \ + && (LDAP_BACK_CONN2PRIV((lc)) >= LDAP_BACK_PCONN_ANON)) +#define LDAP_BACK_PCONN_ISBIND(lc) (LDAP_BACK_PCONN_ISPRIV((lc)) \ + && (LDAP_BACK_CONN2PRIV((lc)) >= LDAP_BACK_PCONN_BIND)) +#define LDAP_BACK_PCONN_ISTLS(lc) (LDAP_BACK_PCONN_ISPRIV((lc)) \ + && (LDAP_BACK_CONN2PRIV((lc)) & LDAP_BACK_PCONN_TLS)) +#ifdef HAVE_TLS +#define LDAP_BACK_PCONN_ROOTDN_SET(lc, op) \ + ((lc)->lc_conn = (void *)((op)->o_conn->c_is_tls ? (void *) LDAP_BACK_PCONN_ROOTDN_TLS : (void *) LDAP_BACK_PCONN_ROOTDN)) +#define LDAP_BACK_PCONN_ANON_SET(lc, op) \ + ((lc)->lc_conn = (void *)((op)->o_conn->c_is_tls ? (void *) LDAP_BACK_PCONN_ANON_TLS : (void *) LDAP_BACK_PCONN_ANON)) +#define LDAP_BACK_PCONN_BIND_SET(lc, op) \ + ((lc)->lc_conn = (void *)((op)->o_conn->c_is_tls ? (void *) LDAP_BACK_PCONN_BIND_TLS : (void *) LDAP_BACK_PCONN_BIND)) +#else /* ! HAVE_TLS */ +#define LDAP_BACK_PCONN_ROOTDN_SET(lc, op) \ + ((lc)->lc_conn = (void *)LDAP_BACK_PCONN_ROOTDN) +#define LDAP_BACK_PCONN_ANON_SET(lc, op) \ + ((lc)->lc_conn = (void *)LDAP_BACK_PCONN_ANON) +#define LDAP_BACK_PCONN_BIND_SET(lc, op) \ + ((lc)->lc_conn = (void *)LDAP_BACK_PCONN_BIND) +#endif /* ! HAVE_TLS */ +#define LDAP_BACK_PCONN_SET(lc, op) \ + (BER_BVISEMPTY(&(op)->o_ndn) ? \ + LDAP_BACK_PCONN_ANON_SET((lc), (op)) : LDAP_BACK_PCONN_ROOTDN_SET((lc), (op))) + + struct ldapinfo_t *lcb_ldapinfo; + struct berval lcb_local_ndn; + unsigned lcb_refcnt; + time_t lcb_create_time; + time_t lcb_time; +} ldapconn_base_t; + +typedef struct ldapconn_t { + ldapconn_base_t lc_base; +#define lc_conn lc_base.lcb_conn +#define lc_ldapinfo lc_base.lcb_ldapinfo +#define lc_local_ndn lc_base.lcb_local_ndn +#define lc_refcnt lc_base.lcb_refcnt +#define lc_create_time lc_base.lcb_create_time +#define lc_time lc_base.lcb_time + + LDAP_TAILQ_ENTRY(ldapconn_t) lc_q; + + unsigned lc_lcflags; +#define LDAP_BACK_CONN_ISSET_F(fp,f) (*(fp) & (f)) +#define LDAP_BACK_CONN_SET_F(fp,f) (*(fp) |= (f)) +#define LDAP_BACK_CONN_CLEAR_F(fp,f) (*(fp) &= ~(f)) +#define LDAP_BACK_CONN_CPY_F(fp,f,mfp) \ + do { \ + if ( ((f) & *(mfp)) == (f) ) { \ + *(fp) |= (f); \ + } else { \ + *(fp) &= ~(f); \ + } \ + } while ( 0 ) + +#define LDAP_BACK_CONN_ISSET(lc,f) LDAP_BACK_CONN_ISSET_F(&(lc)->lc_lcflags, (f)) +#define LDAP_BACK_CONN_SET(lc,f) LDAP_BACK_CONN_SET_F(&(lc)->lc_lcflags, (f)) +#define LDAP_BACK_CONN_CLEAR(lc,f) LDAP_BACK_CONN_CLEAR_F(&(lc)->lc_lcflags, (f)) +#define LDAP_BACK_CONN_CPY(lc,f,mlc) LDAP_BACK_CONN_CPY_F(&(lc)->lc_lcflags, (f), &(mlc)->lc_lcflags) + +/* 0xFFF00000U are reserved for back-meta */ + +#define LDAP_BACK_FCONN_ISBOUND (0x00000001U) +#define LDAP_BACK_FCONN_ISANON (0x00000002U) +#define LDAP_BACK_FCONN_ISBMASK (LDAP_BACK_FCONN_ISBOUND|LDAP_BACK_FCONN_ISANON) +#define LDAP_BACK_FCONN_ISPRIV (0x00000004U) +#define LDAP_BACK_FCONN_ISTLS (0x00000008U) +#define LDAP_BACK_FCONN_BINDING (0x00000010U) +#define LDAP_BACK_FCONN_TAINTED (0x00000020U) +#define LDAP_BACK_FCONN_ABANDON (0x00000040U) +#define LDAP_BACK_FCONN_ISIDASR (0x00000080U) +#define LDAP_BACK_FCONN_CACHED (0x00000100U) + +#define LDAP_BACK_CONN_ISBOUND(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_ISBOUND) +#define LDAP_BACK_CONN_ISBOUND_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_ISBOUND) +#define LDAP_BACK_CONN_ISBOUND_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_ISBMASK) +#define LDAP_BACK_CONN_ISBOUND_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), LDAP_BACK_FCONN_ISBOUND, (mlc)) +#define LDAP_BACK_CONN_ISANON(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_ISANON) +#define LDAP_BACK_CONN_ISANON_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_ISANON) +#define LDAP_BACK_CONN_ISANON_CLEAR(lc) LDAP_BACK_CONN_ISBOUND_CLEAR((lc)) +#define LDAP_BACK_CONN_ISANON_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), LDAP_BACK_FCONN_ISANON, (mlc)) +#define LDAP_BACK_CONN_ISPRIV(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_ISPRIV) +#define LDAP_BACK_CONN_ISPRIV_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_ISPRIV) +#define LDAP_BACK_CONN_ISPRIV_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_ISPRIV) +#define LDAP_BACK_CONN_ISPRIV_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), LDAP_BACK_FCONN_ISPRIV, (mlc)) +#define LDAP_BACK_CONN_ISTLS(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_ISTLS) +#define LDAP_BACK_CONN_ISTLS_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_ISTLS) +#define LDAP_BACK_CONN_ISTLS_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_ISTLS) +#define LDAP_BACK_CONN_ISTLS_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), LDAP_BACK_FCONN_ISTLS, (mlc)) +#define LDAP_BACK_CONN_BINDING(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_BINDING) +#define LDAP_BACK_CONN_BINDING_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_BINDING) +#define LDAP_BACK_CONN_BINDING_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_BINDING) +#define LDAP_BACK_CONN_TAINTED(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_TAINTED) +#define LDAP_BACK_CONN_TAINTED_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_TAINTED) +#define LDAP_BACK_CONN_TAINTED_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_TAINTED) +#define LDAP_BACK_CONN_ABANDON(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_ABANDON) +#define LDAP_BACK_CONN_ABANDON_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_ABANDON) +#define LDAP_BACK_CONN_ABANDON_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_ABANDON) +#define LDAP_BACK_CONN_ISIDASSERT(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_ISIDASR) +#define LDAP_BACK_CONN_ISIDASSERT_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_ISIDASR) +#define LDAP_BACK_CONN_ISIDASSERT_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_ISIDASR) +#define LDAP_BACK_CONN_ISIDASSERT_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), LDAP_BACK_FCONN_ISIDASR, (mlc)) +#define LDAP_BACK_CONN_CACHED(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_CACHED) +#define LDAP_BACK_CONN_CACHED_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_CACHED) +#define LDAP_BACK_CONN_CACHED_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_CACHED) + + LDAP *lc_ld; + unsigned long lc_connid; + struct berval lc_cred; + struct berval lc_bound_ndn; + unsigned lc_flags; +} ldapconn_t; + +typedef struct ldap_avl_info_t { + ldap_pvt_thread_mutex_t lai_mutex; + Avlnode *lai_tree; +} ldap_avl_info_t; + +typedef struct slap_retry_info_t { + time_t *ri_interval; + int *ri_num; + int ri_idx; + int ri_count; + time_t ri_last; + +#define SLAP_RETRYNUM_FOREVER (-1) /* retry forever */ +#define SLAP_RETRYNUM_TAIL (-2) /* end of retrynum array */ +#define SLAP_RETRYNUM_VALID(n) ((n) >= SLAP_RETRYNUM_FOREVER) /* valid retrynum */ +#define SLAP_RETRYNUM_FINITE(n) ((n) > SLAP_RETRYNUM_FOREVER) /* not forever */ +} slap_retry_info_t; + +/* + * identity assertion modes + */ +typedef enum { + LDAP_BACK_IDASSERT_LEGACY = 1, + LDAP_BACK_IDASSERT_NOASSERT, + LDAP_BACK_IDASSERT_ANONYMOUS, + LDAP_BACK_IDASSERT_SELF, + LDAP_BACK_IDASSERT_OTHERDN, + LDAP_BACK_IDASSERT_OTHERID +} slap_idassert_mode_t; + +/* ID assert stuff */ +typedef struct slap_idassert_t { + slap_idassert_mode_t si_mode; +#define li_idassert_mode li_idassert.si_mode + + slap_bindconf si_bc; +#define li_idassert_authcID li_idassert.si_bc.sb_authcId +#define li_idassert_authcDN li_idassert.si_bc.sb_binddn +#define li_idassert_passwd li_idassert.si_bc.sb_cred +#define li_idassert_authzID li_idassert.si_bc.sb_authzId +#define li_idassert_authmethod li_idassert.si_bc.sb_method +#define li_idassert_sasl_mech li_idassert.si_bc.sb_saslmech +#define li_idassert_sasl_realm li_idassert.si_bc.sb_realm +#define li_idassert_secprops li_idassert.si_bc.sb_secprops +#define li_idassert_tls li_idassert.si_bc.sb_tls + + unsigned si_flags; +#define LDAP_BACK_AUTH_NONE (0x00U) +#define LDAP_BACK_AUTH_NATIVE_AUTHZ (0x01U) +#define LDAP_BACK_AUTH_OVERRIDE (0x02U) +#define LDAP_BACK_AUTH_PRESCRIPTIVE (0x04U) +#define LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ (0x08U) +#define LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND (0x10U) +#define LDAP_BACK_AUTH_AUTHZ_ALL (0x20U) +#define LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL (0x40U) +#define li_idassert_flags li_idassert.si_flags + + BerVarray si_authz; +#define li_idassert_authz li_idassert.si_authz + + BerVarray si_passthru; +#define li_idassert_passthru li_idassert.si_passthru +} slap_idassert_t; + +/* + * Hook to allow mucking with ldapinfo_t when quarantine is over + */ +typedef int (*ldap_back_quarantine_f)( struct ldapinfo_t *, void * ); + +typedef struct ldapinfo_t { + /* li_uri: the string that goes into ldap_initialize() + * TODO: use li_acl.sb_uri instead */ + char *li_uri; + /* li_bvuri: an array of each single URI that is equivalent; + * to be checked for the presence of a certain item */ + BerVarray li_bvuri; + ldap_pvt_thread_mutex_t li_uri_mutex; + /* hack because when TLS is used we need to lock and let + * the li_urllist_f function to know it's locked */ + int li_uri_mutex_do_not_lock; + + LDAP_REBIND_PROC *li_rebind_f; + LDAP_URLLIST_PROC *li_urllist_f; + void *li_urllist_p; + + /* we only care about the TLS options here */ + slap_bindconf li_tls; + + slap_bindconf li_acl; +#define li_acl_authcID li_acl.sb_authcId +#define li_acl_authcDN li_acl.sb_binddn +#define li_acl_passwd li_acl.sb_cred +#define li_acl_authzID li_acl.sb_authzId +#define li_acl_authmethod li_acl.sb_method +#define li_acl_sasl_mech li_acl.sb_saslmech +#define li_acl_sasl_realm li_acl.sb_realm +#define li_acl_secprops li_acl.sb_secprops + + /* ID assert stuff */ + slap_idassert_t li_idassert; + /* end of ID assert stuff */ + + int li_nretries; +#define LDAP_BACK_RETRY_UNDEFINED (-2) +#define LDAP_BACK_RETRY_FOREVER (-1) +#define LDAP_BACK_RETRY_NEVER (0) +#define LDAP_BACK_RETRY_DEFAULT (3) + + unsigned li_flags; + +/* 0xFF000000U are reserved for back-meta */ + +#define LDAP_BACK_F_NONE (0x00000000U) +#define LDAP_BACK_F_SAVECRED (0x00000001U) +#define LDAP_BACK_F_USE_TLS (0x00000002U) +#define LDAP_BACK_F_PROPAGATE_TLS (0x00000004U) +#define LDAP_BACK_F_TLS_CRITICAL (0x00000008U) +#define LDAP_BACK_F_TLS_LDAPS (0x00000010U) + +#define LDAP_BACK_F_TLS_USE_MASK (LDAP_BACK_F_USE_TLS|LDAP_BACK_F_TLS_CRITICAL) +#define LDAP_BACK_F_TLS_PROPAGATE_MASK (LDAP_BACK_F_PROPAGATE_TLS|LDAP_BACK_F_TLS_CRITICAL) +#define LDAP_BACK_F_TLS_MASK (LDAP_BACK_F_TLS_USE_MASK|LDAP_BACK_F_TLS_PROPAGATE_MASK|LDAP_BACK_F_TLS_LDAPS) +#define LDAP_BACK_F_CHASE_REFERRALS (0x00000020U) +#define LDAP_BACK_F_PROXY_WHOAMI (0x00000040U) + +#define LDAP_BACK_F_T_F (0x00000080U) +#define LDAP_BACK_F_T_F_DISCOVER (0x00000100U) +#define LDAP_BACK_F_T_F_MASK (LDAP_BACK_F_T_F) +#define LDAP_BACK_F_T_F_MASK2 (LDAP_BACK_F_T_F_MASK|LDAP_BACK_F_T_F_DISCOVER) + +#define LDAP_BACK_F_MONITOR (0x00000200U) +#define LDAP_BACK_F_SINGLECONN (0x00000400U) +#define LDAP_BACK_F_USE_TEMPORARIES (0x00000800U) + +#define LDAP_BACK_F_ISOPEN (0x00001000U) + +#define LDAP_BACK_F_CANCEL_ABANDON (0x00000000U) +#define LDAP_BACK_F_CANCEL_IGNORE (0x00002000U) +#define LDAP_BACK_F_CANCEL_EXOP (0x00004000U) +#define LDAP_BACK_F_CANCEL_EXOP_DISCOVER (0x00008000U) +#define LDAP_BACK_F_CANCEL_MASK (LDAP_BACK_F_CANCEL_IGNORE|LDAP_BACK_F_CANCEL_EXOP) +#define LDAP_BACK_F_CANCEL_MASK2 (LDAP_BACK_F_CANCEL_MASK|LDAP_BACK_F_CANCEL_EXOP_DISCOVER) + +#define LDAP_BACK_F_QUARANTINE (0x00010000U) + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING +#define LDAP_BACK_F_ST_REQUEST (0x00020000U) +#define LDAP_BACK_F_ST_RESPONSE (0x00040000U) +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + +#define LDAP_BACK_F_NOREFS (0x00080000U) +#define LDAP_BACK_F_NOUNDEFFILTER (0x00100000U) +#define LDAP_BACK_F_OMIT_UNKNOWN_SCHEMA (0x00200000U) + +#define LDAP_BACK_F_ONERR_STOP (0x00400000U) + +#define LDAP_BACK_ISSET_F(ff,f) ( ( (ff) & (f) ) == (f) ) +#define LDAP_BACK_ISMASK_F(ff,m,f) ( ( (ff) & (m) ) == (f) ) + +#define LDAP_BACK_ISSET(li,f) LDAP_BACK_ISSET_F( (li)->li_flags, (f) ) +#define LDAP_BACK_ISMASK(li,m,f) LDAP_BACK_ISMASK_F( (li)->li_flags, (m), (f) ) + +#define LDAP_BACK_SAVECRED(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_SAVECRED ) +#define LDAP_BACK_USE_TLS(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_USE_TLS ) +#define LDAP_BACK_PROPAGATE_TLS(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_PROPAGATE_TLS ) +#define LDAP_BACK_TLS_CRITICAL(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_TLS_CRITICAL ) +#define LDAP_BACK_CHASE_REFERRALS(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_CHASE_REFERRALS ) +#define LDAP_BACK_PROXY_WHOAMI(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_PROXY_WHOAMI ) + +#define LDAP_BACK_USE_TLS_F(ff) LDAP_BACK_ISSET_F( (ff), LDAP_BACK_F_USE_TLS ) +#define LDAP_BACK_PROPAGATE_TLS_F(ff) LDAP_BACK_ISSET_F( (ff), LDAP_BACK_F_PROPAGATE_TLS ) +#define LDAP_BACK_TLS_CRITICAL_F(ff) LDAP_BACK_ISSET_F( (ff), LDAP_BACK_F_TLS_CRITICAL ) + +#define LDAP_BACK_T_F(li) LDAP_BACK_ISMASK( (li), LDAP_BACK_F_T_F_MASK, LDAP_BACK_F_T_F ) +#define LDAP_BACK_T_F_DISCOVER(li) LDAP_BACK_ISMASK( (li), LDAP_BACK_F_T_F_MASK2, LDAP_BACK_F_T_F_DISCOVER ) + +#define LDAP_BACK_MONITOR(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_MONITOR ) +#define LDAP_BACK_SINGLECONN(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_SINGLECONN ) +#define LDAP_BACK_USE_TEMPORARIES(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_USE_TEMPORARIES) + +#define LDAP_BACK_ISOPEN(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_ISOPEN ) + +#define LDAP_BACK_ABANDON(li) LDAP_BACK_ISMASK( (li), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_ABANDON ) +#define LDAP_BACK_IGNORE(li) LDAP_BACK_ISMASK( (li), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_IGNORE ) +#define LDAP_BACK_CANCEL(li) LDAP_BACK_ISMASK( (li), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_EXOP ) +#define LDAP_BACK_CANCEL_DISCOVER(li) LDAP_BACK_ISMASK( (li), LDAP_BACK_F_CANCEL_MASK2, LDAP_BACK_F_CANCEL_EXOP_DISCOVER ) + +#define LDAP_BACK_QUARANTINE(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_QUARANTINE ) + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING +#define LDAP_BACK_ST_REQUEST(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_ST_REQUEST) +#define LDAP_BACK_ST_RESPONSE(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_ST_RESPONSE) +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + +#define LDAP_BACK_NOREFS(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_NOREFS) +#define LDAP_BACK_NOUNDEFFILTER(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_NOUNDEFFILTER) +#define LDAP_BACK_OMIT_UNKNOWN_SCHEMA(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_OMIT_UNKNOWN_SCHEMA) +#define LDAP_BACK_ONERR_STOP(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_ONERR_STOP) + + int li_version; + + unsigned long li_conn_nextid; + + /* cached connections; + * special conns are in tailq rather than in tree */ + ldap_avl_info_t li_conninfo; + struct { + int lic_num; + LDAP_TAILQ_HEAD(lc_conn_priv_q, ldapconn_t) lic_priv; + } li_conn_priv[ LDAP_BACK_PCONN_LAST ]; + int li_conn_priv_max; +#define LDAP_BACK_CONN_PRIV_MIN (1) +#define LDAP_BACK_CONN_PRIV_MAX (256) + /* must be between LDAP_BACK_CONN_PRIV_MIN + * and LDAP_BACK_CONN_PRIV_MAX ! */ +#define LDAP_BACK_CONN_PRIV_DEFAULT (16) + + ldap_monitor_info_t li_monitor_info; + + sig_atomic_t li_isquarantined; +#define LDAP_BACK_FQ_NO (0) +#define LDAP_BACK_FQ_YES (1) +#define LDAP_BACK_FQ_RETRYING (2) + + slap_retry_info_t li_quarantine; + ldap_pvt_thread_mutex_t li_quarantine_mutex; + ldap_back_quarantine_f li_quarantine_f; + void *li_quarantine_p; + + time_t li_network_timeout; + time_t li_conn_ttl; + time_t li_idle_timeout; + time_t li_timeout[ SLAP_OP_LAST ]; + + ldap_pvt_thread_mutex_t li_counter_mutex; + ldap_pvt_mp_t li_ops_completed[SLAP_OP_LAST]; +} ldapinfo_t; + +#define LDAP_ERR_OK(err) ((err) == LDAP_SUCCESS || (err) == LDAP_COMPARE_FALSE || (err) == LDAP_COMPARE_TRUE) + +typedef enum ldap_back_send_t { + LDAP_BACK_DONTSEND = 0x00, + LDAP_BACK_SENDOK = 0x01, + LDAP_BACK_SENDERR = 0x02, + LDAP_BACK_SENDRESULT = (LDAP_BACK_SENDOK|LDAP_BACK_SENDERR), + LDAP_BACK_BINDING = 0x04, + + LDAP_BACK_BIND_DONTSEND = (LDAP_BACK_BINDING), + LDAP_BACK_BIND_SOK = (LDAP_BACK_BINDING|LDAP_BACK_SENDOK), + LDAP_BACK_BIND_SERR = (LDAP_BACK_BINDING|LDAP_BACK_SENDERR), + LDAP_BACK_BIND_SRES = (LDAP_BACK_BINDING|LDAP_BACK_SENDRESULT), + + LDAP_BACK_RETRYING = 0x08, + LDAP_BACK_RETRY_DONTSEND = (LDAP_BACK_RETRYING), + LDAP_BACK_RETRY_SOK = (LDAP_BACK_RETRYING|LDAP_BACK_SENDOK), + LDAP_BACK_RETRY_SERR = (LDAP_BACK_RETRYING|LDAP_BACK_SENDERR), + LDAP_BACK_RETRY_SRES = (LDAP_BACK_RETRYING|LDAP_BACK_SENDRESULT), + + LDAP_BACK_GETCONN = 0x10 +} ldap_back_send_t; + +/* define to use asynchronous StartTLS */ +#define SLAP_STARTTLS_ASYNCHRONOUS + +/* timeout to use when calling ldap_result() */ +#define LDAP_BACK_RESULT_TIMEOUT (0) +#define LDAP_BACK_RESULT_UTIMEOUT (100000) +#define LDAP_BACK_TV_SET(tv) \ + do { \ + (tv)->tv_sec = LDAP_BACK_RESULT_TIMEOUT; \ + (tv)->tv_usec = LDAP_BACK_RESULT_UTIMEOUT; \ + } while ( 0 ) + +#ifndef LDAP_BACK_PRINT_CONNTREE +#define LDAP_BACK_PRINT_CONNTREE 0 +#endif /* !LDAP_BACK_PRINT_CONNTREE */ + +typedef struct ldap_extra_t { + int (*proxy_authz_ctrl)( Operation *op, SlapReply *rs, struct berval *bound_ndn, + int version, slap_idassert_t *si, LDAPControl *ctrl ); + int (*controls_free)( Operation *op, SlapReply *rs, LDAPControl ***pctrls ); + int (*idassert_authzfrom_parse)( struct config_args_s *ca, slap_idassert_t *si ); + int (*idassert_passthru_parse_cf)( const char *fname, int lineno, const char *arg, slap_idassert_t *si ); + int (*idassert_parse)( struct config_args_s *ca, slap_idassert_t *si ); + void (*retry_info_destroy)( slap_retry_info_t *ri ); + int (*retry_info_parse)( char *in, slap_retry_info_t *ri, char *buf, ber_len_t buflen ); + int (*retry_info_unparse)( slap_retry_info_t *ri, struct berval *bvout ); + int (*connid2str)( const ldapconn_base_t *lc, char *buf, ber_len_t buflen ); +} ldap_extra_t; + +LDAP_END_DECL + +#include "proto-ldap.h" + +#endif /* SLAPD_LDAP_H */ diff --git a/servers/slapd/back-ldap/bind.c b/servers/slapd/back-ldap/bind.c new file mode 100644 index 0000000..91c62cd --- /dev/null +++ b/servers/slapd/back-ldap/bind.c @@ -0,0 +1,3068 @@ +/* bind.c - ldap backend bind function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> + +#define AVL_INTERNAL +#include "slap.h" +#include "back-ldap.h" +#include "lutil.h" +#include "lutil_ldap.h" + +#define LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ "2.16.840.1.113730.3.4.12" + +#ifdef LDAP_DEVEL +#define SLAP_AUTH_DN 1 +#endif + +#if LDAP_BACK_PRINT_CONNTREE > 0 + +static const struct { + slap_mask_t f; + char c; +} flagsmap[] = { + { LDAP_BACK_FCONN_ISBOUND, 'B' }, + { LDAP_BACK_FCONN_ISANON, 'A' }, + { LDAP_BACK_FCONN_ISPRIV, 'P' }, + { LDAP_BACK_FCONN_ISTLS, 'T' }, + { LDAP_BACK_FCONN_BINDING, 'X' }, + { LDAP_BACK_FCONN_TAINTED, 'E' }, + { LDAP_BACK_FCONN_ABANDON, 'N' }, + { LDAP_BACK_FCONN_ISIDASR, 'S' }, + { LDAP_BACK_FCONN_CACHED, 'C' }, + { 0, '\0' } +}; + +static void +ldap_back_conn_print( ldapconn_t *lc, const char *avlstr ) +{ + char buf[ SLAP_TEXT_BUFLEN ]; + char fbuf[ sizeof("BAPTIENSC") ]; + int i; + + ldap_back_conn2str( &lc->lc_base, buf, sizeof( buf ) ); + for ( i = 0; flagsmap[ i ].c != '\0'; i++ ) { + if ( lc->lc_lcflags & flagsmap[i].f ) { + fbuf[i] = flagsmap[i].c; + + } else { + fbuf[i] = '.'; + } + } + fbuf[i] = '\0'; + + fprintf( stderr, "lc=%p %s %s flags=0x%08x (%s)\n", + (void *)lc, buf, avlstr, lc->lc_lcflags, fbuf ); +} + +static void +ldap_back_ravl_print( Avlnode *root, int depth ) +{ + int i; + ldapconn_t *lc; + + if ( root == 0 ) { + return; + } + + ldap_back_ravl_print( root->avl_right, depth+1 ); + + for ( i = 0; i < depth; i++ ) { + fprintf( stderr, "-" ); + } + + lc = root->avl_data; + ldap_back_conn_print( lc, avl_bf2str( root->avl_bf ) ); + + ldap_back_ravl_print( root->avl_left, depth + 1 ); +} + +static char* priv2str[] = { + "privileged", + "privileged/TLS", + "anonymous", + "anonymous/TLS", + "bind", + "bind/TLS", + NULL +}; + +void +ldap_back_print_conntree( ldapinfo_t *li, char *msg ) +{ + int c; + + fprintf( stderr, "========> %s\n", msg ); + + for ( c = LDAP_BACK_PCONN_FIRST; c < LDAP_BACK_PCONN_LAST; c++ ) { + int i = 0; + ldapconn_t *lc; + + fprintf( stderr, " %s[%d]\n", priv2str[ c ], li->li_conn_priv[ c ].lic_num ); + + LDAP_TAILQ_FOREACH( lc, &li->li_conn_priv[ c ].lic_priv, lc_q ) + { + fprintf( stderr, " [%d] ", i ); + ldap_back_conn_print( lc, "" ); + i++; + } + } + + if ( li->li_conninfo.lai_tree == 0 ) { + fprintf( stderr, "\t(empty)\n" ); + + } else { + ldap_back_ravl_print( li->li_conninfo.lai_tree, 0 ); + } + + fprintf( stderr, "<======== %s\n", msg ); +} +#endif /* LDAP_BACK_PRINT_CONNTREE */ + +static int +ldap_back_freeconn( ldapinfo_t *li, ldapconn_t *lc, int dolock ); + +static ldapconn_t * +ldap_back_getconn( Operation *op, SlapReply *rs, ldap_back_send_t sendok, + struct berval *binddn, struct berval *bindcred ); + +static int +ldap_back_is_proxy_authz( Operation *op, SlapReply *rs, ldap_back_send_t sendok, + struct berval *binddn, struct berval *bindcred ); + +static int +ldap_back_proxy_authz_bind( ldapconn_t *lc, Operation *op, SlapReply *rs, + ldap_back_send_t sendok, struct berval *binddn, struct berval *bindcred ); + +static int +ldap_back_prepare_conn( ldapconn_t *lc, Operation *op, SlapReply *rs, + ldap_back_send_t sendok ); + +static int +ldap_back_conndnlc_cmp( const void *c1, const void *c2 ); + +ldapconn_t * +ldap_back_conn_delete( ldapinfo_t *li, ldapconn_t *lc ) +{ + if ( LDAP_BACK_PCONN_ISPRIV( lc ) ) { + if ( LDAP_BACK_CONN_CACHED( lc ) ) { + assert( lc->lc_q.tqe_prev != NULL ); + assert( li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_num > 0 ); + li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_num--; + LDAP_TAILQ_REMOVE( &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_priv, lc, lc_q ); + LDAP_TAILQ_ENTRY_INIT( lc, lc_q ); + LDAP_BACK_CONN_CACHED_CLEAR( lc ); + + } else { + assert( LDAP_BACK_CONN_TAINTED( lc ) ); + assert( lc->lc_q.tqe_prev == NULL ); + } + + } else { + ldapconn_t *tmplc = NULL; + + if ( LDAP_BACK_CONN_CACHED( lc ) ) { + assert( !LDAP_BACK_CONN_TAINTED( lc ) ); + tmplc = avl_delete( &li->li_conninfo.lai_tree, (caddr_t)lc, + ldap_back_conndnlc_cmp ); + assert( tmplc == lc ); + LDAP_BACK_CONN_CACHED_CLEAR( lc ); + } + + assert( LDAP_BACK_CONN_TAINTED( lc ) || tmplc == lc ); + } + + return lc; +} + +int +ldap_back_bind( Operation *op, SlapReply *rs ) +{ + ldapinfo_t *li = (ldapinfo_t *) op->o_bd->be_private; + ldapconn_t *lc; + + LDAPControl **ctrls = NULL; + struct berval save_o_dn; + int save_o_do_not_cache, + rc = 0; + ber_int_t msgid; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + + /* allow rootdn as a means to auth without the need to actually + * contact the proxied DSA */ + switch ( be_rootdn_bind( op, rs ) ) { + case SLAP_CB_CONTINUE: + break; + + default: + return rs->sr_err; + } + + lc = ldap_back_getconn( op, rs, LDAP_BACK_BIND_SERR, NULL, NULL ); + if ( !lc ) { + return rs->sr_err; + } + + /* we can do (almost) whatever we want with this conn, + * because either it's temporary, or it's marked as binding */ + if ( !BER_BVISNULL( &lc->lc_bound_ndn ) ) { + ch_free( lc->lc_bound_ndn.bv_val ); + BER_BVZERO( &lc->lc_bound_ndn ); + } + if ( !BER_BVISNULL( &lc->lc_cred ) ) { + memset( lc->lc_cred.bv_val, 0, lc->lc_cred.bv_len ); + ch_free( lc->lc_cred.bv_val ); + BER_BVZERO( &lc->lc_cred ); + } + LDAP_BACK_CONN_ISBOUND_CLEAR( lc ); + + /* don't add proxyAuthz; set the bindDN */ + save_o_dn = op->o_dn; + save_o_do_not_cache = op->o_do_not_cache; + op->o_dn = op->o_req_dn; + op->o_do_not_cache = 1; + + ctrls = op->o_ctrls; + rc = ldap_back_controls_add( op, rs, lc, &ctrls ); + op->o_dn = save_o_dn; + op->o_do_not_cache = save_o_do_not_cache; + if ( rc != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + ldap_back_release_conn( li, lc ); + return( rc ); + } + +retry:; + /* method is always LDAP_AUTH_SIMPLE if we got here */ + rs->sr_err = ldap_sasl_bind( lc->lc_ld, op->o_req_dn.bv_val, + LDAP_SASL_SIMPLE, + &op->orb_cred, ctrls, NULL, &msgid ); + /* FIXME: should we always retry, or only when piping the bind + * in the "override" connection pool? */ + rc = ldap_back_op_result( lc, op, rs, msgid, + li->li_timeout[ SLAP_OP_BIND ], + LDAP_BACK_BIND_SERR | retrying ); + if ( rc == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_BIND_SERR ) ) { + goto retry; + } + if ( !lc ) + return( rc ); + } + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_BIND ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + + ldap_back_controls_free( op, rs, &ctrls ); + + if ( rc == LDAP_SUCCESS ) { + op->o_conn->c_authz_cookie = op->o_bd->be_private; + + /* If defined, proxyAuthz will be used also when + * back-ldap is the authorizing backend; for this + * purpose, after a successful bind the connection + * is left for further binds, and further operations + * on this client connection will use a default + * connection with identity assertion */ + /* NOTE: use with care */ + if ( li->li_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) { + ldap_back_release_conn( li, lc ); + return( rc ); + } + + /* rebind is now done inside ldap_back_proxy_authz_bind() + * in case of success */ + LDAP_BACK_CONN_ISBOUND_SET( lc ); + ber_dupbv( &lc->lc_bound_ndn, &op->o_req_ndn ); + + if ( !BER_BVISNULL( &lc->lc_cred ) ) { + memset( lc->lc_cred.bv_val, 0, + lc->lc_cred.bv_len ); + } + + if ( LDAP_BACK_SAVECRED( li ) ) { + ber_bvreplace( &lc->lc_cred, &op->orb_cred ); + ldap_set_rebind_proc( lc->lc_ld, li->li_rebind_f, lc ); + + } else { + lc->lc_cred.bv_len = 0; + } + } + + /* must re-insert if local DN changed as result of bind */ + if ( !LDAP_BACK_CONN_ISBOUND( lc ) + || ( !dn_match( &op->o_req_ndn, &lc->lc_local_ndn ) + && !LDAP_BACK_PCONN_ISPRIV( lc ) ) ) + { + int lerr = -1; + ldapconn_t *tmplc; + + /* wait for all other ops to release the connection */ +retry_lock:; + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + if ( lc->lc_refcnt > 1 ) { + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + ldap_pvt_thread_yield(); + goto retry_lock; + } + +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, ">>> ldap_back_bind" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + + assert( lc->lc_refcnt == 1 ); + ldap_back_conn_delete( li, lc ); + + /* delete all cached connections with the current connection */ + if ( LDAP_BACK_SINGLECONN( li ) ) { + while ( ( tmplc = avl_delete( &li->li_conninfo.lai_tree, (caddr_t)lc, ldap_back_conn_cmp ) ) != NULL ) + { + assert( !LDAP_BACK_PCONN_ISPRIV( lc ) ); + Debug( LDAP_DEBUG_TRACE, + "=>ldap_back_bind: destroying conn %lu (refcnt=%u)\n", + lc->lc_conn->c_connid, lc->lc_refcnt, 0 ); + + if ( tmplc->lc_refcnt != 0 ) { + /* taint it */ + LDAP_BACK_CONN_TAINTED_SET( tmplc ); + LDAP_BACK_CONN_CACHED_CLEAR( tmplc ); + + } else { + /* + * Needs a test because the handler may be corrupted, + * and calling ldap_unbind on a corrupted header results + * in a segmentation fault + */ + ldap_back_conn_free( tmplc ); + } + } + } + + if ( LDAP_BACK_CONN_ISBOUND( lc ) ) { + ber_bvreplace( &lc->lc_local_ndn, &op->o_req_ndn ); + if ( be_isroot_dn( op->o_bd, &op->o_req_ndn ) ) { + LDAP_BACK_PCONN_ROOTDN_SET( lc, op ); + } + lerr = avl_insert( &li->li_conninfo.lai_tree, (caddr_t)lc, + ldap_back_conndn_cmp, ldap_back_conndn_dup ); + } + +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, "<<< ldap_back_bind" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + switch ( lerr ) { + case 0: + LDAP_BACK_CONN_CACHED_SET( lc ); + break; + + case -1: + /* duplicate; someone else successfully bound + * on the same connection with the same identity; + * we can do this because lc_refcnt == 1 */ + ldap_back_conn_free( lc ); + lc = NULL; + } + } + + if ( lc != NULL ) { + ldap_back_release_conn( li, lc ); + } + + return( rc ); +} + +/* + * ldap_back_conndn_cmp + * + * compares two ldapconn_t based on the value of the conn pointer + * and of the local DN; used by avl stuff for insert, lookup + * and direct delete + */ +int +ldap_back_conndn_cmp( const void *c1, const void *c2 ) +{ + const ldapconn_t *lc1 = (const ldapconn_t *)c1; + const ldapconn_t *lc2 = (const ldapconn_t *)c2; + int rc; + + /* If local DNs don't match, it is definitely not a match */ + /* For shared sessions, conn is NULL. Only explicitly + * bound sessions will have non-NULL conn. + */ + rc = SLAP_PTRCMP( lc1->lc_conn, lc2->lc_conn ); + if ( rc == 0 ) { + rc = ber_bvcmp( &lc1->lc_local_ndn, &lc2->lc_local_ndn ); + } + + return rc; +} + +/* + * ldap_back_conndnlc_cmp + * + * compares two ldapconn_t based on the value of the conn pointer, + * the local DN and the lc pointer; used by avl stuff for insert, lookup + * and direct delete + */ +static int +ldap_back_conndnlc_cmp( const void *c1, const void *c2 ) +{ + const ldapconn_t *lc1 = (const ldapconn_t *)c1; + const ldapconn_t *lc2 = (const ldapconn_t *)c2; + int rc; + + /* If local DNs don't match, it is definitely not a match */ + /* For shared sessions, conn is NULL. Only explicitly + * bound sessions will have non-NULL conn. + */ + rc = SLAP_PTRCMP( lc1->lc_conn, lc2->lc_conn ); + if ( rc == 0 ) { + rc = ber_bvcmp( &lc1->lc_local_ndn, &lc2->lc_local_ndn ); + if ( rc == 0 ) { + rc = SLAP_PTRCMP( lc1, lc2 ); + } + } + + return rc; +} + +/* + * ldap_back_conn_cmp + * + * compares two ldapconn_t based on the value of the conn pointer; + * used by avl stuff for delete of all conns with the same connid + */ +int +ldap_back_conn_cmp( const void *c1, const void *c2 ) +{ + const ldapconn_t *lc1 = (const ldapconn_t *)c1; + const ldapconn_t *lc2 = (const ldapconn_t *)c2; + + /* For shared sessions, conn is NULL. Only explicitly + * bound sessions will have non-NULL conn. + */ + return SLAP_PTRCMP( lc1->lc_conn, lc2->lc_conn ); +} + +/* + * ldap_back_conndn_dup + * + * returns -1 in case a duplicate ldapconn_t has been inserted; + * used by avl stuff + */ +int +ldap_back_conndn_dup( void *c1, void *c2 ) +{ + ldapconn_t *lc1 = (ldapconn_t *)c1; + ldapconn_t *lc2 = (ldapconn_t *)c2; + + /* Cannot have more than one shared session with same DN */ + if ( lc1->lc_conn == lc2->lc_conn && + dn_match( &lc1->lc_local_ndn, &lc2->lc_local_ndn ) ) + { + return -1; + } + + return 0; +} + +static int +ldap_back_freeconn( ldapinfo_t *li, ldapconn_t *lc, int dolock ) +{ + if ( dolock ) { + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + } + +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, ">>> ldap_back_freeconn" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + + (void)ldap_back_conn_delete( li, lc ); + + if ( lc->lc_refcnt == 0 ) { + ldap_back_conn_free( (void *)lc ); + } + +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, "<<< ldap_back_freeconn" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + } + + return 0; +} + +#ifdef HAVE_TLS +static int +ldap_back_start_tls( + LDAP *ld, + int protocol, + int *is_tls, + const char *url, + unsigned flags, + int timeout, + const char **text ) +{ + int rc = LDAP_SUCCESS; + + /* start TLS ("tls-[try-]{start,propagate}" statements) */ + if ( ( LDAP_BACK_USE_TLS_F( flags ) || ( *is_tls && LDAP_BACK_PROPAGATE_TLS_F( flags ) ) ) + && !ldap_is_ldaps_url( url ) ) + { +#ifdef SLAP_STARTTLS_ASYNCHRONOUS + /* + * use asynchronous StartTLS + * in case, chase referral (not implemented yet) + */ + int msgid; + + if ( protocol == 0 ) { + ldap_get_option( ld, LDAP_OPT_PROTOCOL_VERSION, + (void *)&protocol ); + } + + if ( protocol < LDAP_VERSION3 ) { + /* we should rather bail out... */ + rc = LDAP_UNWILLING_TO_PERFORM; + *text = "invalid protocol version"; + } + + if ( rc == LDAP_SUCCESS ) { + rc = ldap_start_tls( ld, NULL, NULL, &msgid ); + } + + if ( rc == LDAP_SUCCESS ) { + LDAPMessage *res = NULL; + struct timeval tv; + + if ( timeout ) { + tv.tv_sec = timeout; + tv.tv_usec = 0; + } else { + LDAP_BACK_TV_SET( &tv ); + } + rc = ldap_result( ld, msgid, LDAP_MSG_ALL, &tv, &res ); + if ( rc <= 0 ) { + rc = LDAP_UNAVAILABLE; + + } else if ( rc == LDAP_RES_EXTENDED ) { + struct berval *data = NULL; + + rc = ldap_parse_extended_result( ld, res, + NULL, &data, 0 ); + if ( rc == LDAP_SUCCESS ) { + SlapReply rs; + rc = ldap_parse_result( ld, res, &rs.sr_err, + NULL, NULL, NULL, NULL, 1 ); + if ( rc != LDAP_SUCCESS ) { + rs.sr_err = rc; + } + rc = slap_map_api2result( &rs ); + res = NULL; + + /* FIXME: in case a referral + * is returned, should we try + * using it instead of the + * configured URI? */ + if ( rc == LDAP_SUCCESS ) { + rc = ldap_install_tls( ld ); + + } else if ( rc == LDAP_REFERRAL ) { + rc = LDAP_UNWILLING_TO_PERFORM; + *text = "unwilling to chase referral returned by Start TLS exop"; + } + + if ( data ) { + if ( data->bv_val ) { + ber_memfree( data->bv_val ); + } + ber_memfree( data ); + } + } + + } else { + rc = LDAP_OTHER; + } + + if ( res != NULL ) { + ldap_msgfree( res ); + } + } +#else /* ! SLAP_STARTTLS_ASYNCHRONOUS */ + /* + * use synchronous StartTLS + */ + rc = ldap_start_tls_s( ld, NULL, NULL ); +#endif /* ! SLAP_STARTTLS_ASYNCHRONOUS */ + + /* if StartTLS is requested, only attempt it if the URL + * is not "ldaps://"; this may occur not only in case + * of misconfiguration, but also when used in the chain + * overlay, where the "uri" can be parsed out of a referral */ + switch ( rc ) { + case LDAP_SUCCESS: + *is_tls = 1; + break; + + case LDAP_SERVER_DOWN: + break; + + default: + if ( LDAP_BACK_TLS_CRITICAL_F( flags ) ) { + *text = "could not start TLS"; + break; + } + + /* in case Start TLS is not critical */ + *is_tls = 0; + rc = LDAP_SUCCESS; + break; + } + + } else { + *is_tls = 0; + } + + return rc; +} +#endif /* HAVE_TLS */ + +static int +ldap_back_prepare_conn( ldapconn_t *lc, Operation *op, SlapReply *rs, ldap_back_send_t sendok ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + int version; + LDAP *ld = NULL; +#ifdef HAVE_TLS + int is_tls = op->o_conn->c_is_tls; + int flags = li->li_flags; + time_t lctime = (time_t)(-1); + slap_bindconf *sb; +#endif /* HAVE_TLS */ + + ldap_pvt_thread_mutex_lock( &li->li_uri_mutex ); + rs->sr_err = ldap_initialize( &ld, li->li_uri ); + ldap_pvt_thread_mutex_unlock( &li->li_uri_mutex ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto error_return; + } + + if ( li->li_urllist_f ) { + ldap_set_urllist_proc( ld, li->li_urllist_f, li->li_urllist_p ); + } + + /* Set LDAP version. This will always succeed: If the client + * bound with a particular version, then so can we. + */ + if ( li->li_version != 0 ) { + version = li->li_version; + + } else if ( op->o_protocol != 0 ) { + version = op->o_protocol; + + } else { + /* assume it's an internal op; set to LDAPv3 */ + version = LDAP_VERSION3; + } + ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, (const void *)&version ); + + /* automatically chase referrals ("chase-referrals [{yes|no}]" statement) */ + ldap_set_option( ld, LDAP_OPT_REFERRALS, + LDAP_BACK_CHASE_REFERRALS( li ) ? LDAP_OPT_ON : LDAP_OPT_OFF ); + + if ( li->li_network_timeout > 0 ) { + struct timeval tv; + + tv.tv_sec = li->li_network_timeout; + tv.tv_usec = 0; + ldap_set_option( ld, LDAP_OPT_NETWORK_TIMEOUT, (const void *)&tv ); + } + + /* turn on network keepalive, if configured so */ + slap_client_keepalive(ld, &li->li_tls.sb_keepalive); + +#ifdef HAVE_TLS + if ( LDAP_BACK_CONN_ISPRIV( lc ) ) { + /* See "rationale" comment in ldap_back_getconn() */ + if ( li->li_acl_authmethod == LDAP_AUTH_NONE && + li->li_idassert_authmethod != LDAP_AUTH_NONE ) + sb = &li->li_idassert.si_bc; + else + sb = &li->li_acl; + + } else if ( LDAP_BACK_CONN_ISIDASSERT( lc ) ) { + sb = &li->li_idassert.si_bc; + + } else { + sb = &li->li_tls; + } + + if ( sb->sb_tls_do_init ) { + bindconf_tls_set( sb, ld ); + } else if ( sb->sb_tls_ctx ) { + ldap_set_option( ld, LDAP_OPT_X_TLS_CTX, sb->sb_tls_ctx ); + } + + /* if required by the bindconf configuration, force TLS */ + if ( ( sb == &li->li_acl || sb == &li->li_idassert.si_bc ) && + sb->sb_tls_ctx ) + { + flags |= LDAP_BACK_F_USE_TLS; + } + + ldap_pvt_thread_mutex_lock( &li->li_uri_mutex ); + assert( li->li_uri_mutex_do_not_lock == 0 ); + li->li_uri_mutex_do_not_lock = 1; + rs->sr_err = ldap_back_start_tls( ld, op->o_protocol, &is_tls, + li->li_uri, flags, li->li_timeout[ SLAP_OP_BIND ], &rs->sr_text ); + li->li_uri_mutex_do_not_lock = 0; + ldap_pvt_thread_mutex_unlock( &li->li_uri_mutex ); + if ( rs->sr_err != LDAP_SUCCESS ) { + ldap_unbind_ext( ld, NULL, NULL ); + rs->sr_text = "Start TLS failed"; + goto error_return; + + } else if ( li->li_idle_timeout ) { + /* only touch when activity actually took place... */ + lctime = op->o_time; + } +#endif /* HAVE_TLS */ + + lc->lc_ld = ld; + lc->lc_refcnt = 1; +#ifdef HAVE_TLS + if ( is_tls ) { + LDAP_BACK_CONN_ISTLS_SET( lc ); + } else { + LDAP_BACK_CONN_ISTLS_CLEAR( lc ); + } + if ( lctime != (time_t)(-1) ) { + lc->lc_time = lctime; + } +#endif /* HAVE_TLS */ + +error_return:; + if ( rs->sr_err != LDAP_SUCCESS ) { + rs->sr_err = slap_map_api2result( rs ); + if ( sendok & LDAP_BACK_SENDERR ) { + if ( rs->sr_text == NULL ) { + rs->sr_text = "Proxy connection initialization failed"; + } + send_ldap_result( op, rs ); + } + + } else { + if ( li->li_conn_ttl > 0 ) { + lc->lc_create_time = op->o_time; + } + } + + return rs->sr_err; +} + +static ldapconn_t * +ldap_back_getconn( + Operation *op, + SlapReply *rs, + ldap_back_send_t sendok, + struct berval *binddn, + struct berval *bindcred ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + ldapconn_t *lc = NULL, + lc_curr = {{ 0 }}; + int refcnt = 1, + lookupconn = !( sendok & LDAP_BACK_BINDING ); + + /* if the server is quarantined, and + * - the current interval did not expire yet, or + * - no more retries should occur, + * don't return the connection */ + if ( li->li_isquarantined ) { + slap_retry_info_t *ri = &li->li_quarantine; + int dont_retry = 1; + + if ( li->li_quarantine.ri_interval ) { + ldap_pvt_thread_mutex_lock( &li->li_quarantine_mutex ); + if ( li->li_isquarantined == LDAP_BACK_FQ_YES ) { + dont_retry = ( ri->ri_num[ ri->ri_idx ] == SLAP_RETRYNUM_TAIL + || slap_get_time() < ri->ri_last + ri->ri_interval[ ri->ri_idx ] ); + if ( !dont_retry ) { + Debug( LDAP_DEBUG_ANY, + "%s: ldap_back_getconn quarantine " + "retry block #%d try #%d.\n", + op->o_log_prefix, ri->ri_idx, ri->ri_count ); + li->li_isquarantined = LDAP_BACK_FQ_RETRYING; + } + } + ldap_pvt_thread_mutex_unlock( &li->li_quarantine_mutex ); + } + + if ( dont_retry ) { + rs->sr_err = LDAP_UNAVAILABLE; + if ( op->o_conn && ( sendok & LDAP_BACK_SENDERR ) ) { + rs->sr_text = "Target is quarantined"; + send_ldap_result( op, rs ); + } + return NULL; + } + } + + /* Internal searches are privileged and shared. So is root. */ + if ( op->o_do_not_cache || be_isroot( op ) ) { + LDAP_BACK_CONN_ISPRIV_SET( &lc_curr ); + lc_curr.lc_local_ndn = op->o_bd->be_rootndn; + LDAP_BACK_PCONN_ROOTDN_SET( &lc_curr, op ); + + } else { + struct berval tmpbinddn, + tmpbindcred, + save_o_dn, + save_o_ndn; + int isproxyauthz; + + /* need cleanup */ + if ( binddn == NULL ) { + binddn = &tmpbinddn; + } + if ( bindcred == NULL ) { + bindcred = &tmpbindcred; + } + if ( op->o_tag == LDAP_REQ_BIND ) { + save_o_dn = op->o_dn; + save_o_ndn = op->o_ndn; + op->o_dn = op->o_req_dn; + op->o_ndn = op->o_req_ndn; + } + isproxyauthz = ldap_back_is_proxy_authz( op, rs, sendok, binddn, bindcred ); + if ( op->o_tag == LDAP_REQ_BIND ) { + op->o_dn = save_o_dn; + op->o_ndn = save_o_ndn; + } + if ( isproxyauthz == -1 ) { + return NULL; + } + + lc_curr.lc_local_ndn = op->o_ndn; + /* Explicit binds must not be shared; + * however, explicit binds are piped in a special connection + * when idassert is to occur with "override" set */ + if ( op->o_tag == LDAP_REQ_BIND && !isproxyauthz ) { + lc_curr.lc_conn = op->o_conn; + + } else { + if ( isproxyauthz && !( sendok & LDAP_BACK_BINDING ) ) { + lc_curr.lc_local_ndn = *binddn; + LDAP_BACK_PCONN_ROOTDN_SET( &lc_curr, op ); + LDAP_BACK_CONN_ISIDASSERT_SET( &lc_curr ); + + } else if ( isproxyauthz && ( li->li_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) ) { + lc_curr.lc_local_ndn = slap_empty_bv; + LDAP_BACK_PCONN_BIND_SET( &lc_curr, op ); + LDAP_BACK_CONN_ISIDASSERT_SET( &lc_curr ); + lookupconn = 1; + + } else if ( SLAP_IS_AUTHZ_BACKEND( op ) ) { + lc_curr.lc_conn = op->o_conn; + + } else { + LDAP_BACK_PCONN_ANON_SET( &lc_curr, op ); + } + } + } + + /* Explicit Bind requests always get their own conn */ + if ( lookupconn ) { +retry_lock: + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + if ( LDAP_BACK_PCONN_ISPRIV( &lc_curr ) ) { + /* lookup a conn that's not binding */ + LDAP_TAILQ_FOREACH( lc, + &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( &lc_curr ) ].lic_priv, + lc_q ) + { + if ( !LDAP_BACK_CONN_BINDING( lc ) && lc->lc_refcnt == 0 ) { + break; + } + } + + if ( lc != NULL ) { + if ( lc != LDAP_TAILQ_LAST( &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_priv, + lc_conn_priv_q ) ) + { + LDAP_TAILQ_REMOVE( &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_priv, + lc, lc_q ); + LDAP_TAILQ_ENTRY_INIT( lc, lc_q ); + LDAP_TAILQ_INSERT_TAIL( &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_priv, + lc, lc_q ); + } + + } else if ( !LDAP_BACK_USE_TEMPORARIES( li ) + && li->li_conn_priv[ LDAP_BACK_CONN2PRIV( &lc_curr ) ].lic_num == li->li_conn_priv_max ) + { + lc = LDAP_TAILQ_FIRST( &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( &lc_curr ) ].lic_priv ); + } + + } else { + + /* Searches for a ldapconn in the avl tree */ + lc = (ldapconn_t *)avl_find( li->li_conninfo.lai_tree, + (caddr_t)&lc_curr, ldap_back_conndn_cmp ); + } + + if ( lc != NULL ) { + /* Don't reuse connections while they're still binding */ + if ( LDAP_BACK_CONN_BINDING( lc ) ) { + if ( !LDAP_BACK_USE_TEMPORARIES( li ) ) { + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + + ldap_pvt_thread_yield(); + goto retry_lock; + } + lc = NULL; + } + + if ( lc != NULL ) { + if ( op->o_tag == LDAP_REQ_BIND ) { + /* right now, this is the only possible case */ + assert( ( li->li_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) ); + LDAP_BACK_CONN_BINDING_SET( lc ); + } + + refcnt = ++lc->lc_refcnt; + } + } + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + } + + /* Looks like we didn't get a bind. Open a new session... */ + if ( lc == NULL ) { + lc = (ldapconn_t *)ch_calloc( 1, sizeof( ldapconn_t ) ); + lc->lc_flags = li->li_flags; + lc->lc_lcflags = lc_curr.lc_lcflags; + lc->lc_ldapinfo = li; + if ( ldap_back_prepare_conn( lc, op, rs, sendok ) != LDAP_SUCCESS ) { + ch_free( lc ); + return NULL; + } + + if ( sendok & LDAP_BACK_BINDING ) { + LDAP_BACK_CONN_BINDING_SET( lc ); + } + + lc->lc_conn = lc_curr.lc_conn; + ber_dupbv( &lc->lc_local_ndn, &lc_curr.lc_local_ndn ); + + /* + * the rationale is: connections as the rootdn are privileged, + * so li_acl is to be used; however, in some cases + * one already configured identity assertion with a highly + * privileged idassert_authcDN, so if li_acl is not configured + * and idassert is, use idassert instead. + * + * might change in the future, because it's preferable + * to make clear what identity is being used, since + * the only drawback is that one risks to configure + * the same identity twice... + */ + if ( LDAP_BACK_CONN_ISPRIV( &lc_curr ) ) { + if ( li->li_acl_authmethod == LDAP_AUTH_NONE && + li->li_idassert_authmethod != LDAP_AUTH_NONE ) { + ber_dupbv( &lc->lc_bound_ndn, &li->li_idassert_authcDN ); + ber_dupbv( &lc->lc_cred, &li->li_idassert_passwd ); + + } else { + ber_dupbv( &lc->lc_bound_ndn, &li->li_acl_authcDN ); + ber_dupbv( &lc->lc_cred, &li->li_acl_passwd ); + } + LDAP_BACK_CONN_ISPRIV_SET( lc ); + + } else if ( LDAP_BACK_CONN_ISIDASSERT( &lc_curr ) ) { + if ( !LDAP_BACK_PCONN_ISBIND( &lc_curr ) ) { + ber_dupbv( &lc->lc_bound_ndn, &li->li_idassert_authcDN ); + ber_dupbv( &lc->lc_cred, &li->li_idassert_passwd ); + } + LDAP_BACK_CONN_ISIDASSERT_SET( lc ); + + } else { + BER_BVZERO( &lc->lc_cred ); + BER_BVZERO( &lc->lc_bound_ndn ); + if ( !BER_BVISEMPTY( &op->o_ndn ) + && SLAP_IS_AUTHZ_BACKEND( op ) ) + { + ber_dupbv( &lc->lc_bound_ndn, &op->o_ndn ); + } + } + +#ifdef HAVE_TLS + /* if start TLS failed but it was not mandatory, + * check if the non-TLS connection was already + * in cache; in case, destroy the newly created + * connection and use the existing one */ + if ( LDAP_BACK_PCONN_ISTLS( lc ) + && !ldap_tls_inplace( lc->lc_ld ) ) + { + ldapconn_t *tmplc = NULL; + int idx = LDAP_BACK_CONN2PRIV( &lc_curr ) - 1; + + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + LDAP_TAILQ_FOREACH( tmplc, + &li->li_conn_priv[ idx ].lic_priv, + lc_q ) + { + if ( !LDAP_BACK_CONN_BINDING( tmplc ) ) { + break; + } + } + + if ( tmplc != NULL ) { + refcnt = ++tmplc->lc_refcnt; + ldap_back_conn_free( lc ); + lc = tmplc; + } + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + + if ( tmplc != NULL ) { + goto done; + } + } +#endif /* HAVE_TLS */ + + /* Inserts the newly created ldapconn in the avl tree */ + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + + LDAP_BACK_CONN_ISBOUND_CLEAR( lc ); + lc->lc_connid = li->li_conn_nextid++; + + assert( lc->lc_refcnt == 1 ); + +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, ">>> ldap_back_getconn(insert)" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + + if ( LDAP_BACK_PCONN_ISPRIV( lc ) ) { + if ( li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_num < li->li_conn_priv_max ) { + LDAP_TAILQ_INSERT_TAIL( &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_priv, lc, lc_q ); + li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_num++; + LDAP_BACK_CONN_CACHED_SET( lc ); + + } else { + LDAP_BACK_CONN_TAINTED_SET( lc ); + } + rs->sr_err = 0; + + } else { + rs->sr_err = avl_insert( &li->li_conninfo.lai_tree, (caddr_t)lc, + ldap_back_conndn_cmp, ldap_back_conndn_dup ); + LDAP_BACK_CONN_CACHED_SET( lc ); + } + +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, "<<< ldap_back_getconn(insert)" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + + if ( LogTest( LDAP_DEBUG_TRACE ) ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "lc=%p inserted refcnt=%u rc=%d", + (void *)lc, refcnt, rs->sr_err ); + + Debug( LDAP_DEBUG_TRACE, + "=>ldap_back_getconn: %s: %s\n", + op->o_log_prefix, buf, 0 ); + } + + if ( !LDAP_BACK_PCONN_ISPRIV( lc ) ) { + /* Err could be -1 in case a duplicate ldapconn is inserted */ + switch ( rs->sr_err ) { + case 0: + break; + + case -1: + LDAP_BACK_CONN_CACHED_CLEAR( lc ); + if ( !( sendok & LDAP_BACK_BINDING ) && !LDAP_BACK_USE_TEMPORARIES( li ) ) { + /* duplicate: free and try to get the newly created one */ + ldap_back_conn_free( lc ); + lc = NULL; + goto retry_lock; + } + + /* taint connection, so that it'll be freed when released */ + LDAP_BACK_CONN_TAINTED_SET( lc ); + break; + + default: + LDAP_BACK_CONN_CACHED_CLEAR( lc ); + ldap_back_conn_free( lc ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "Proxy bind collision"; + if ( op->o_conn && ( sendok & LDAP_BACK_SENDERR ) ) { + send_ldap_result( op, rs ); + } + return NULL; + } + } + + } else { + int expiring = 0; + + if ( ( li->li_idle_timeout != 0 && op->o_time > lc->lc_time + li->li_idle_timeout ) + || ( li->li_conn_ttl != 0 && op->o_time > lc->lc_create_time + li->li_conn_ttl ) ) + { + expiring = 1; + + /* let it be used, but taint/delete it so that + * no-one else can look it up any further */ + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, ">>> ldap_back_getconn(timeout)" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + + (void)ldap_back_conn_delete( li, lc ); + LDAP_BACK_CONN_TAINTED_SET( lc ); + +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, "<<< ldap_back_getconn(timeout)" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + } + + if ( LogTest( LDAP_DEBUG_TRACE ) ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "conn %p fetched refcnt=%u%s", + (void *)lc, refcnt, + expiring ? " expiring" : "" ); + Debug( LDAP_DEBUG_TRACE, + "=>ldap_back_getconn: %s.\n", buf, 0, 0 ); + } + } + +#ifdef HAVE_TLS +done:; +#endif /* HAVE_TLS */ + + return lc; +} + +void +ldap_back_release_conn_lock( + ldapinfo_t *li, + ldapconn_t **lcp, + int dolock ) +{ + + ldapconn_t *lc = *lcp; + + if ( dolock ) { + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + } + assert( lc->lc_refcnt > 0 ); + LDAP_BACK_CONN_BINDING_CLEAR( lc ); + lc->lc_refcnt--; + if ( LDAP_BACK_CONN_TAINTED( lc ) ) { + ldap_back_freeconn( li, lc, 0 ); + *lcp = NULL; + } + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + } +} + +void +ldap_back_quarantine( + Operation *op, + SlapReply *rs ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + slap_retry_info_t *ri = &li->li_quarantine; + + ldap_pvt_thread_mutex_lock( &li->li_quarantine_mutex ); + + if ( rs->sr_err == LDAP_UNAVAILABLE ) { + time_t new_last = slap_get_time(); + + switch ( li->li_isquarantined ) { + case LDAP_BACK_FQ_NO: + if ( ri->ri_last == new_last ) { + goto done; + } + + Debug( LDAP_DEBUG_ANY, + "%s: ldap_back_quarantine enter.\n", + op->o_log_prefix, 0, 0 ); + + ri->ri_idx = 0; + ri->ri_count = 0; + break; + + case LDAP_BACK_FQ_RETRYING: + Debug( LDAP_DEBUG_ANY, + "%s: ldap_back_quarantine block #%d try #%d failed.\n", + op->o_log_prefix, ri->ri_idx, ri->ri_count ); + + ++ri->ri_count; + if ( ri->ri_num[ ri->ri_idx ] != SLAP_RETRYNUM_FOREVER + && ri->ri_count == ri->ri_num[ ri->ri_idx ] ) + { + ri->ri_count = 0; + ++ri->ri_idx; + } + break; + + default: + break; + } + + li->li_isquarantined = LDAP_BACK_FQ_YES; + ri->ri_last = new_last; + + } else if ( li->li_isquarantined != LDAP_BACK_FQ_NO ) { + if ( ri->ri_last == slap_get_time() ) { + goto done; + } + + Debug( LDAP_DEBUG_ANY, + "%s: ldap_back_quarantine exit (%d) err=%d.\n", + op->o_log_prefix, li->li_isquarantined, rs->sr_err ); + + if ( li->li_quarantine_f ) { + (void)li->li_quarantine_f( li, li->li_quarantine_p ); + } + + ri->ri_count = 0; + ri->ri_idx = 0; + li->li_isquarantined = LDAP_BACK_FQ_NO; + } + +done:; + ldap_pvt_thread_mutex_unlock( &li->li_quarantine_mutex ); +} + +static int +ldap_back_dobind_cb( + Operation *op, + SlapReply *rs +) +{ + ber_tag_t *tptr = op->o_callback->sc_private; + op->o_tag = *tptr; + rs->sr_tag = slap_req2res( op->o_tag ); + + return SLAP_CB_CONTINUE; +} + +/* + * ldap_back_dobind_int + * + * Note: dolock indicates whether li->li_conninfo.lai_mutex must be locked or not + */ +static int +ldap_back_dobind_int( + ldapconn_t **lcp, + Operation *op, + SlapReply *rs, + ldap_back_send_t sendok, + int retries, + int dolock ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + ldapconn_t *lc; + struct berval binddn = slap_empty_bv, + bindcred = slap_empty_bv; + + int rc = 0, + isbound, + binding = 0; + ber_int_t msgid; + ber_tag_t o_tag = op->o_tag; + slap_callback cb = {0}; + char *tmp_dn; + + assert( lcp != NULL ); + assert( retries >= 0 ); + + if ( sendok & LDAP_BACK_GETCONN ) { + assert( *lcp == NULL ); + + lc = ldap_back_getconn( op, rs, sendok, &binddn, &bindcred ); + if ( lc == NULL ) { + return 0; + } + *lcp = lc; + + } else { + lc = *lcp; + } + + assert( lc != NULL ); + +retry_lock:; + if ( dolock ) { + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + } + + if ( binding == 0 ) { + /* check if already bound */ + rc = isbound = LDAP_BACK_CONN_ISBOUND( lc ); + if ( isbound ) { + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + } + return rc; + } + + if ( LDAP_BACK_CONN_BINDING( lc ) ) { + /* if someone else is about to bind it, give up and retry */ + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + } + ldap_pvt_thread_yield(); + goto retry_lock; + + } else { + /* otherwise this thread will bind it */ + LDAP_BACK_CONN_BINDING_SET( lc ); + binding = 1; + } + } + + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + } + + /* + * FIXME: we need to let clients use proxyAuthz + * otherwise we cannot do symmetric pools of servers; + * we have to live with the fact that a user can + * authorize itself as any ID that is allowed + * by the authzTo directive of the "proxyauthzdn". + */ + /* + * NOTE: current Proxy Authorization specification + * and implementation do not allow proxy authorization + * control to be provided with Bind requests + */ + /* + * if no bind took place yet, but the connection is bound + * and the "idassert-authcDN" (or other ID) is set, + * then bind as the asserting identity and explicitly + * add the proxyAuthz control to every operation with the + * dn bound to the connection as control value. + * This is done also if this is the authorizing backend, + * but the "override" flag is given to idassert. + * It allows to use SASL bind and yet proxyAuthz users + */ + op->o_tag = LDAP_REQ_BIND; + cb.sc_next = op->o_callback; + cb.sc_private = &o_tag; + cb.sc_response = ldap_back_dobind_cb; + op->o_callback = &cb; + + if ( LDAP_BACK_CONN_ISIDASSERT( lc ) ) { + if ( BER_BVISEMPTY( &binddn ) && BER_BVISEMPTY( &bindcred ) ) { + /* if we got here, it shouldn't return result */ + rc = ldap_back_is_proxy_authz( op, rs, + LDAP_BACK_DONTSEND, &binddn, &bindcred ); + if ( rc != 1 ) { + Debug( LDAP_DEBUG_ANY, "Error: ldap_back_is_proxy_authz " + "returned %d, misconfigured URI?\n", rc, 0, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "misconfigured URI?"; + LDAP_BACK_CONN_ISBOUND_CLEAR( lc ); + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + goto done; + } + } + rc = ldap_back_proxy_authz_bind( lc, op, rs, sendok, &binddn, &bindcred ); + goto done; + } + +#ifdef HAVE_CYRUS_SASL + if ( LDAP_BACK_CONN_ISPRIV( lc )) { + slap_bindconf *sb; + if ( li->li_acl_authmethod != LDAP_AUTH_NONE ) + sb = &li->li_acl; + else + sb = &li->li_idassert.si_bc; + + if ( sb->sb_method == LDAP_AUTH_SASL ) { + void *defaults = NULL; + + if ( sb->sb_secprops != NULL ) { + rc = ldap_set_option( lc->lc_ld, + LDAP_OPT_X_SASL_SECPROPS, sb->sb_secprops ); + + if ( rc != LDAP_OPT_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "Error: ldap_set_option " + "(SECPROPS,\"%s\") failed!\n", + sb->sb_secprops, 0, 0 ); + goto done; + } + } + + defaults = lutil_sasl_defaults( lc->lc_ld, + sb->sb_saslmech.bv_val, + sb->sb_realm.bv_val, + sb->sb_authcId.bv_val, + sb->sb_cred.bv_val, + NULL ); + if ( defaults == NULL ) { + rs->sr_err = LDAP_OTHER; + LDAP_BACK_CONN_ISBOUND_CLEAR( lc ); + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + goto done; + } + + rs->sr_err = ldap_sasl_interactive_bind_s( lc->lc_ld, + sb->sb_binddn.bv_val, + sb->sb_saslmech.bv_val, NULL, NULL, + LDAP_SASL_QUIET, lutil_sasl_interact, + defaults ); + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_BIND ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + + lutil_sasl_freedefs( defaults ); + + switch ( rs->sr_err ) { + case LDAP_SUCCESS: + LDAP_BACK_CONN_ISBOUND_SET( lc ); + break; + + case LDAP_LOCAL_ERROR: + /* list client API error codes that require + * to taint the connection */ + /* FIXME: should actually retry? */ + LDAP_BACK_CONN_TAINTED_SET( lc ); + + /* fallthru */ + + default: + LDAP_BACK_CONN_ISBOUND_CLEAR( lc ); + rs->sr_err = slap_map_api2result( rs ); + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + break; + } + + if ( LDAP_BACK_QUARANTINE( li ) ) { + ldap_back_quarantine( op, rs ); + } + + goto done; + } + } +#endif /* HAVE_CYRUS_SASL */ + +retry:; + if ( BER_BVISNULL( &lc->lc_cred ) ) { + tmp_dn = ""; + if ( !BER_BVISNULL( &lc->lc_bound_ndn ) && !BER_BVISEMPTY( &lc->lc_bound_ndn ) ) { + Debug( LDAP_DEBUG_ANY, "%s ldap_back_dobind_int: DN=\"%s\" without creds, binding anonymously", + op->o_log_prefix, lc->lc_bound_ndn.bv_val, 0 ); + } + + } else { + tmp_dn = lc->lc_bound_ndn.bv_val; + } + rs->sr_err = ldap_sasl_bind( lc->lc_ld, + tmp_dn, + LDAP_SASL_SIMPLE, &lc->lc_cred, + NULL, NULL, &msgid ); + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_BIND ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + + if ( rs->sr_err == LDAP_SERVER_DOWN ) { + if ( retries != LDAP_BACK_RETRY_NEVER ) { + if ( dolock ) { + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + } + + assert( lc->lc_refcnt > 0 ); + if ( lc->lc_refcnt == 1 ) { + ldap_unbind_ext( lc->lc_ld, NULL, NULL ); + lc->lc_ld = NULL; + + /* lc here must be the regular lc, reset and ready for init */ + rs->sr_err = ldap_back_prepare_conn( lc, op, rs, sendok ); + if ( rs->sr_err != LDAP_SUCCESS ) { + sendok &= ~LDAP_BACK_SENDERR; + lc->lc_refcnt = 0; + } + } + + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + } + + if ( rs->sr_err == LDAP_SUCCESS ) { + if ( retries > 0 ) { + retries--; + } + goto retry; + } + } + + assert( lc->lc_refcnt == 1 ); + lc->lc_refcnt = 0; + ldap_back_freeconn( li, lc, dolock ); + *lcp = NULL; + rs->sr_err = slap_map_api2result( rs ); + + if ( LDAP_BACK_QUARANTINE( li ) ) { + ldap_back_quarantine( op, rs ); + } + + if ( rs->sr_err != LDAP_SUCCESS && + ( sendok & LDAP_BACK_SENDERR ) ) + { + if ( op->o_callback == &cb ) + op->o_callback = cb.sc_next; + op->o_tag = o_tag; + rs->sr_text = "Proxy can't contact remote server"; + send_ldap_result( op, rs ); + /* if we originally bound and wanted rebind-as-user, must drop + * the connection now because we just discarded the credentials. + * ITS#7464, #8142 + */ + if ( LDAP_BACK_SAVECRED( li ) && SLAP_IS_AUTHZ_BACKEND( op ) ) + rs->sr_err = SLAPD_DISCONNECT; + } + + rc = 0; + goto func_leave; + } + + rc = ldap_back_op_result( lc, op, rs, msgid, + -1, ( sendok | LDAP_BACK_BINDING ) ); + if ( rc == LDAP_SUCCESS ) { + LDAP_BACK_CONN_ISBOUND_SET( lc ); + } + +done:; + LDAP_BACK_CONN_BINDING_CLEAR( lc ); + rc = LDAP_BACK_CONN_ISBOUND( lc ); + if ( !rc ) { + ldap_back_release_conn_lock( li, lcp, dolock ); + + } else if ( LDAP_BACK_SAVECRED( li ) ) { + ldap_set_rebind_proc( lc->lc_ld, li->li_rebind_f, lc ); + } + +func_leave:; + if ( op->o_callback == &cb ) + op->o_callback = cb.sc_next; + op->o_tag = o_tag; + + return rc; +} + +/* + * ldap_back_dobind + * + * Note: dolock indicates whether li->li_conninfo.lai_mutex must be locked or not + */ +int +ldap_back_dobind( ldapconn_t **lcp, Operation *op, SlapReply *rs, ldap_back_send_t sendok ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + return ldap_back_dobind_int( lcp, op, rs, + ( sendok | LDAP_BACK_GETCONN ), li->li_nretries, 1 ); +} + +/* + * ldap_back_default_rebind + * + * This is a callback used for chasing referrals using the same + * credentials as the original user on this session. + */ +int +ldap_back_default_rebind( LDAP *ld, LDAP_CONST char *url, ber_tag_t request, + ber_int_t msgid, void *params ) +{ + ldapconn_t *lc = (ldapconn_t *)params; + +#ifdef HAVE_TLS + /* ... otherwise we couldn't get here */ + assert( lc != NULL ); + + if ( !ldap_tls_inplace( ld ) ) { + int is_tls = LDAP_BACK_CONN_ISTLS( lc ), + rc; + const char *text = NULL; + + rc = ldap_back_start_tls( ld, 0, &is_tls, url, lc->lc_flags, + lc->lc_ldapinfo->li_timeout[ SLAP_OP_BIND ], &text ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + } +#endif /* HAVE_TLS */ + + /* FIXME: add checks on the URL/identity? */ + /* TODO: would like to count this bind operation for monitoring + * too, but where do we get the ldapinfo_t? */ + + return ldap_sasl_bind_s( ld, + BER_BVISNULL( &lc->lc_cred ) ? "" : lc->lc_bound_ndn.bv_val, + LDAP_SASL_SIMPLE, &lc->lc_cred, NULL, NULL, NULL ); +} + +/* + * ldap_back_default_urllist + */ +int +ldap_back_default_urllist( + LDAP *ld, + LDAPURLDesc **urllist, + LDAPURLDesc **url, + void *params ) +{ + ldapinfo_t *li = (ldapinfo_t *)params; + LDAPURLDesc **urltail; + + if ( urllist == url ) { + return LDAP_SUCCESS; + } + + for ( urltail = &(*url)->lud_next; *urltail; urltail = &(*urltail)->lud_next ) + /* count */ ; + + *urltail = *urllist; + *urllist = *url; + *url = NULL; + + if ( !li->li_uri_mutex_do_not_lock ) { + ldap_pvt_thread_mutex_lock( &li->li_uri_mutex ); + } + + if ( li->li_uri ) { + ch_free( li->li_uri ); + } + + ldap_get_option( ld, LDAP_OPT_URI, (void *)&li->li_uri ); + + if ( !li->li_uri_mutex_do_not_lock ) { + ldap_pvt_thread_mutex_unlock( &li->li_uri_mutex ); + } + + return LDAP_SUCCESS; +} + +int +ldap_back_cancel( + ldapconn_t *lc, + Operation *op, + SlapReply *rs, + ber_int_t msgid, + ldap_back_send_t sendok ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + /* default behavior */ + if ( LDAP_BACK_ABANDON( li ) ) { + return ldap_abandon_ext( lc->lc_ld, msgid, NULL, NULL ); + } + + if ( LDAP_BACK_IGNORE( li ) ) { + return ldap_pvt_discard( lc->lc_ld, msgid ); + } + + if ( LDAP_BACK_CANCEL( li ) ) { + /* FIXME: asynchronous? */ + return ldap_cancel_s( lc->lc_ld, msgid, NULL, NULL ); + } + + assert( 0 ); + + return LDAP_OTHER; +} + +int +ldap_back_op_result( + ldapconn_t *lc, + Operation *op, + SlapReply *rs, + ber_int_t msgid, + time_t timeout, + ldap_back_send_t sendok ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + char *match = NULL; + char *text = NULL; + char **refs = NULL; + LDAPControl **ctrls = NULL; + + rs->sr_text = NULL; + rs->sr_matched = NULL; + rs->sr_ref = NULL; + rs->sr_ctrls = NULL; + + /* if the error recorded in the reply corresponds + * to a successful state, get the error from the + * remote server response */ + if ( LDAP_ERR_OK( rs->sr_err ) ) { + int rc; + struct timeval tv; + LDAPMessage *res = NULL; + time_t stoptime = (time_t)(-1); + int timeout_err = op->o_protocol >= LDAP_VERSION3 ? + LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER; + const char *timeout_text = "Operation timed out"; + + /* if timeout is not specified, compute and use + * the one specific to the ongoing operation */ + if ( timeout == (time_t)(-1) ) { + slap_op_t opidx = slap_req2op( op->o_tag ); + + if ( opidx == SLAP_OP_SEARCH ) { + if ( op->ors_tlimit <= 0 ) { + timeout = 0; + + } else { + timeout = op->ors_tlimit; + timeout_err = LDAP_TIMELIMIT_EXCEEDED; + timeout_text = NULL; + } + + } else { + timeout = li->li_timeout[ opidx ]; + } + } + + /* better than nothing :) */ + if ( timeout == 0 ) { + if ( li->li_idle_timeout ) { + timeout = li->li_idle_timeout; + + } else if ( li->li_conn_ttl ) { + timeout = li->li_conn_ttl; + } + } + + if ( timeout ) { + stoptime = op->o_time + timeout; + } + + LDAP_BACK_TV_SET( &tv ); + +retry:; + /* if result parsing fails, note the failure reason */ + rc = ldap_result( lc->lc_ld, msgid, LDAP_MSG_ALL, &tv, &res ); + switch ( rc ) { + case 0: + if ( timeout && slap_get_time() > stoptime ) { + if ( sendok & LDAP_BACK_BINDING ) { + ldap_unbind_ext( lc->lc_ld, NULL, NULL ); + lc->lc_ld = NULL; + + /* let it be used, but taint/delete it so that + * no-one else can look it up any further */ + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, ">>> ldap_back_getconn(timeout)" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + + (void)ldap_back_conn_delete( li, lc ); + LDAP_BACK_CONN_TAINTED_SET( lc ); + +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, "<<< ldap_back_getconn(timeout)" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + + } else { + (void)ldap_back_cancel( lc, op, rs, msgid, sendok ); + } + rs->sr_err = timeout_err; + rs->sr_text = timeout_text; + break; + } + + /* timeout == 0 */ + LDAP_BACK_TV_SET( &tv ); + ldap_pvt_thread_yield(); + goto retry; + + case -1: + ldap_get_option( lc->lc_ld, LDAP_OPT_ERROR_NUMBER, + &rs->sr_err ); + break; + + + /* otherwise get the result; if it is not + * LDAP_SUCCESS, record it in the reply + * structure (this includes + * LDAP_COMPARE_{TRUE|FALSE}) */ + default: + /* only touch when activity actually took place... */ + if ( li->li_idle_timeout ) { + lc->lc_time = op->o_time; + } + + rc = ldap_parse_result( lc->lc_ld, res, &rs->sr_err, + &match, &text, &refs, &ctrls, 1 ); + if ( rc == LDAP_SUCCESS ) { + rs->sr_text = text; + } else { + rs->sr_err = rc; + } + rs->sr_err = slap_map_api2result( rs ); + + /* RFC 4511: referrals can only appear + * if result code is LDAP_REFERRAL */ + if ( refs != NULL + && refs[ 0 ] != NULL + && refs[ 0 ][ 0 ] != '\0' ) + { + if ( rs->sr_err != LDAP_REFERRAL ) { + Debug( LDAP_DEBUG_ANY, + "%s ldap_back_op_result: " + "got referrals with err=%d\n", + op->o_log_prefix, + rs->sr_err, 0 ); + + } else { + int i; + + for ( i = 0; refs[ i ] != NULL; i++ ) + /* count */ ; + rs->sr_ref = op->o_tmpalloc( sizeof( struct berval ) * ( i + 1 ), + op->o_tmpmemctx ); + for ( i = 0; refs[ i ] != NULL; i++ ) { + ber_str2bv( refs[ i ], 0, 0, &rs->sr_ref[ i ] ); + } + BER_BVZERO( &rs->sr_ref[ i ] ); + } + + } else if ( rs->sr_err == LDAP_REFERRAL ) { + Debug( LDAP_DEBUG_ANY, + "%s ldap_back_op_result: " + "got err=%d with null " + "or empty referrals\n", + op->o_log_prefix, + rs->sr_err, 0 ); + + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } + + if ( ctrls != NULL ) { + rs->sr_ctrls = ctrls; + } + } + } + + /* if the error in the reply structure is not + * LDAP_SUCCESS, try to map it from client + * to server error */ + if ( !LDAP_ERR_OK( rs->sr_err ) ) { + rs->sr_err = slap_map_api2result( rs ); + + /* internal ops ( op->o_conn == NULL ) + * must not reply to client */ + if ( op->o_conn && !op->o_do_not_cache && match ) { + + /* record the (massaged) matched + * DN into the reply structure */ + rs->sr_matched = match; + } + } + + if ( rs->sr_err == LDAP_UNAVAILABLE ) { + if ( !( sendok & LDAP_BACK_RETRYING ) ) { + if ( LDAP_BACK_QUARANTINE( li ) ) { + ldap_back_quarantine( op, rs ); + } + if ( op->o_conn && ( sendok & LDAP_BACK_SENDERR ) ) { + if ( rs->sr_text == NULL ) rs->sr_text = "Proxy operation retry failed"; + send_ldap_result( op, rs ); + } + } + + } else if ( op->o_conn && + ( ( ( sendok & LDAP_BACK_SENDOK ) && LDAP_ERR_OK( rs->sr_err ) ) + || ( ( sendok & LDAP_BACK_SENDERR ) && !LDAP_ERR_OK( rs->sr_err ) ) ) ) + { + send_ldap_result( op, rs ); + } + + if ( text ) { + ldap_memfree( text ); + } + rs->sr_text = NULL; + + /* there can't be refs with a (successful) bind */ + if ( rs->sr_ref ) { + op->o_tmpfree( rs->sr_ref, op->o_tmpmemctx ); + rs->sr_ref = NULL; + } + + if ( refs ) { + ber_memvfree( (void **)refs ); + } + + /* match should not be possible with a successful bind */ + if ( match ) { + if ( rs->sr_matched != match ) { + free( (char *)rs->sr_matched ); + } + rs->sr_matched = NULL; + ldap_memfree( match ); + } + + if ( ctrls != NULL ) { + if ( op->o_tag == LDAP_REQ_BIND && rs->sr_err == LDAP_SUCCESS ) { + int i; + + for ( i = 0; ctrls[i] != NULL; i++ ); + + rs->sr_ctrls = op->o_tmpalloc( sizeof( LDAPControl * )*( i + 1 ), + op->o_tmpmemctx ); + for ( i = 0; ctrls[ i ] != NULL; i++ ) { + char *ptr; + ber_len_t oidlen = strlen( ctrls[i]->ldctl_oid ); + ber_len_t size = sizeof( LDAPControl ) + + oidlen + 1 + + ctrls[i]->ldctl_value.bv_len + 1; + + rs->sr_ctrls[ i ] = op->o_tmpalloc( size, op->o_tmpmemctx ); + rs->sr_ctrls[ i ]->ldctl_oid = (char *)&rs->sr_ctrls[ i ][ 1 ]; + lutil_strcopy( rs->sr_ctrls[ i ]->ldctl_oid, ctrls[i]->ldctl_oid ); + rs->sr_ctrls[ i ]->ldctl_value.bv_val + = (char *)&rs->sr_ctrls[ i ]->ldctl_oid[oidlen + 1]; + rs->sr_ctrls[ i ]->ldctl_value.bv_len + = ctrls[i]->ldctl_value.bv_len; + ptr = lutil_memcopy( rs->sr_ctrls[ i ]->ldctl_value.bv_val, + ctrls[i]->ldctl_value.bv_val, ctrls[i]->ldctl_value.bv_len ); + *ptr = '\0'; + } + rs->sr_ctrls[ i ] = NULL; + rs->sr_flags |= REP_CTRLS_MUSTBEFREED; + + } else { + assert( rs->sr_ctrls != NULL ); + rs->sr_ctrls = NULL; + } + + ldap_controls_free( ctrls ); + } + + return( LDAP_ERR_OK( rs->sr_err ) ? LDAP_SUCCESS : rs->sr_err ); +} + +/* return true if bound, false if failed */ +int +ldap_back_retry( ldapconn_t **lcp, Operation *op, SlapReply *rs, ldap_back_send_t sendok ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + int rc = 0; + + assert( lcp != NULL ); + assert( *lcp != NULL ); + + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + + if ( (*lcp)->lc_refcnt == 1 ) { + int binding = LDAP_BACK_CONN_BINDING( *lcp ); + + ldap_pvt_thread_mutex_lock( &li->li_uri_mutex ); + Debug( LDAP_DEBUG_ANY, + "%s ldap_back_retry: retrying URI=\"%s\" DN=\"%s\"\n", + op->o_log_prefix, li->li_uri, + BER_BVISNULL( &(*lcp)->lc_bound_ndn ) ? + "" : (*lcp)->lc_bound_ndn.bv_val ); + ldap_pvt_thread_mutex_unlock( &li->li_uri_mutex ); + + ldap_unbind_ext( (*lcp)->lc_ld, NULL, NULL ); + (*lcp)->lc_ld = NULL; + LDAP_BACK_CONN_ISBOUND_CLEAR( (*lcp) ); + + /* lc here must be the regular lc, reset and ready for init */ + rc = ldap_back_prepare_conn( *lcp, op, rs, sendok ); + if ( rc != LDAP_SUCCESS ) { + /* freeit, because lc_refcnt == 1 */ + (*lcp)->lc_refcnt = 0; + (void)ldap_back_freeconn( li, *lcp, 0 ); + *lcp = NULL; + rc = 0; + + } else if ( ( sendok & LDAP_BACK_BINDING ) ) { + if ( binding ) { + LDAP_BACK_CONN_BINDING_SET( *lcp ); + } + rc = 1; + + } else { + rc = ldap_back_dobind_int( lcp, op, rs, sendok, 0, 0 ); + if ( rc == 0 && *lcp != NULL ) { + /* freeit, because lc_refcnt == 1 */ + (*lcp)->lc_refcnt = 0; + LDAP_BACK_CONN_TAINTED_SET( *lcp ); + (void)ldap_back_freeconn( li, *lcp, 0 ); + *lcp = NULL; + } + } + + } else { + Debug( LDAP_DEBUG_TRACE, + "ldap_back_retry: conn %p refcnt=%u unable to retry.\n", + (void *)(*lcp), (*lcp)->lc_refcnt, 0 ); + + LDAP_BACK_CONN_TAINTED_SET( *lcp ); + ldap_back_release_conn_lock( li, lcp, 0 ); + assert( *lcp == NULL ); + + if ( sendok & LDAP_BACK_SENDERR ) { + rs->sr_err = LDAP_UNAVAILABLE; + rs->sr_text = "Unable to retry"; + send_ldap_result( op, rs ); + } + } + + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + + return rc; +} + +static int +ldap_back_is_proxy_authz( Operation *op, SlapReply *rs, ldap_back_send_t sendok, + struct berval *binddn, struct berval *bindcred ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + struct berval ndn; + int dobind = 0; + + if ( op->o_conn == NULL || op->o_do_not_cache ) { + goto done; + } + + /* don't proxyAuthz if protocol is not LDAPv3 */ + switch ( li->li_version ) { + case LDAP_VERSION3: + break; + + case 0: + if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) { + break; + } + /* fall thru */ + + default: + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + dobind = -1; + } + goto done; + } + + /* safe default */ + *binddn = slap_empty_bv; + *bindcred = slap_empty_bv; + + if ( !BER_BVISNULL( &op->o_conn->c_ndn ) ) { + ndn = op->o_conn->c_ndn; + + } else { + ndn = op->o_ndn; + } + + if ( !( li->li_idassert_flags & LDAP_BACK_AUTH_OVERRIDE )) { + if ( op->o_tag == LDAP_REQ_BIND && ( sendok & LDAP_BACK_SENDERR )) { + if ( !BER_BVISEMPTY( &ndn )) { + dobind = 0; + goto done; + } + } else if ( SLAP_IS_AUTHZ_BACKEND( op )) { + dobind = 0; + goto done; + } + } + + switch ( li->li_idassert_mode ) { + case LDAP_BACK_IDASSERT_LEGACY: + if ( !BER_BVISNULL( &ndn ) && !BER_BVISEMPTY( &ndn ) ) { + if ( !BER_BVISNULL( &li->li_idassert_authcDN ) && !BER_BVISEMPTY( &li->li_idassert_authcDN ) ) + { + *binddn = li->li_idassert_authcDN; + *bindcred = li->li_idassert_passwd; + dobind = 1; + } + } + break; + + default: + /* NOTE: rootdn can always idassert */ + if ( BER_BVISNULL( &ndn ) + && li->li_idassert_authz == NULL + && !( li->li_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) ) + { + if ( li->li_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) { + rs->sr_err = LDAP_INAPPROPRIATE_AUTH; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + dobind = -1; + } + + } else { + rs->sr_err = LDAP_SUCCESS; + *binddn = slap_empty_bv; + *bindcred = slap_empty_bv; + break; + } + + goto done; + + } else if ( !be_isroot( op ) ) { + if ( li->li_idassert_passthru ) { + struct berval authcDN; + + if ( BER_BVISNULL( &ndn ) ) { + authcDN = slap_empty_bv; + + } else { + authcDN = ndn; + } + rs->sr_err = slap_sasl_matches( op, li->li_idassert_passthru, + &authcDN, &authcDN ); + if ( rs->sr_err == LDAP_SUCCESS ) { + dobind = 0; + break; + } + } + + if ( li->li_idassert_authz ) { + struct berval authcDN; + + if ( BER_BVISNULL( &ndn ) ) { + authcDN = slap_empty_bv; + + } else { + authcDN = ndn; + } + rs->sr_err = slap_sasl_matches( op, li->li_idassert_authz, + &authcDN, &authcDN ); + if ( rs->sr_err != LDAP_SUCCESS ) { + if ( li->li_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) { + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + dobind = -1; + } + + } else { + rs->sr_err = LDAP_SUCCESS; + *binddn = slap_empty_bv; + *bindcred = slap_empty_bv; + break; + } + + goto done; + } + } + } + + *binddn = li->li_idassert_authcDN; + *bindcred = li->li_idassert_passwd; + dobind = 1; + break; + } + +done:; + return dobind; +} + +static int +ldap_back_proxy_authz_bind( + ldapconn_t *lc, + Operation *op, + SlapReply *rs, + ldap_back_send_t sendok, + struct berval *binddn, + struct berval *bindcred ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + struct berval ndn; + int msgid; + int rc; + + if ( !BER_BVISNULL( &op->o_conn->c_ndn ) ) { + ndn = op->o_conn->c_ndn; + + } else { + ndn = op->o_ndn; + } + + if ( li->li_idassert_authmethod == LDAP_AUTH_SASL ) { +#ifdef HAVE_CYRUS_SASL + void *defaults = NULL; + struct berval authzID = BER_BVNULL; + int freeauthz = 0; + LDAPControl **ctrlsp = NULL; + LDAPMessage *result = NULL; + const char *rmech = NULL; + const char *save_text = rs->sr_text; + +#ifdef SLAP_AUTH_DN + LDAPControl ctrl, *ctrls[2]; + int msgid; +#endif /* SLAP_AUTH_DN */ + + /* if SASL supports native authz, prepare for it */ + if ( ( !op->o_do_not_cache || !op->o_is_auth_check ) && + ( li->li_idassert_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) ) + { + switch ( li->li_idassert_mode ) { + case LDAP_BACK_IDASSERT_OTHERID: + case LDAP_BACK_IDASSERT_OTHERDN: + authzID = li->li_idassert_authzID; + break; + + case LDAP_BACK_IDASSERT_ANONYMOUS: + BER_BVSTR( &authzID, "dn:" ); + break; + + case LDAP_BACK_IDASSERT_SELF: + if ( BER_BVISNULL( &ndn ) ) { + /* connection is not authc'd, so don't idassert */ + BER_BVSTR( &authzID, "dn:" ); + break; + } + authzID.bv_len = STRLENOF( "dn:" ) + ndn.bv_len; + authzID.bv_val = slap_sl_malloc( authzID.bv_len + 1, op->o_tmpmemctx ); + AC_MEMCPY( authzID.bv_val, "dn:", STRLENOF( "dn:" ) ); + AC_MEMCPY( authzID.bv_val + STRLENOF( "dn:" ), + ndn.bv_val, ndn.bv_len + 1 ); + freeauthz = 1; + break; + + default: + break; + } + } + + if ( li->li_idassert_secprops != NULL ) { + rs->sr_err = ldap_set_option( lc->lc_ld, + LDAP_OPT_X_SASL_SECPROPS, + (void *)li->li_idassert_secprops ); + + if ( rs->sr_err != LDAP_OPT_SUCCESS ) { + rs->sr_err = LDAP_OTHER; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + LDAP_BACK_CONN_ISBOUND_CLEAR( lc ); + goto done; + } + } + + defaults = lutil_sasl_defaults( lc->lc_ld, + li->li_idassert_sasl_mech.bv_val, + li->li_idassert_sasl_realm.bv_val, + li->li_idassert_authcID.bv_val, + li->li_idassert_passwd.bv_val, + authzID.bv_val ); + if ( defaults == NULL ) { + rs->sr_err = LDAP_OTHER; + LDAP_BACK_CONN_ISBOUND_CLEAR( lc ); + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + goto done; + } + +#ifdef SLAP_AUTH_DN + if ( li->li_idassert_flags & LDAP_BACK_AUTH_DN_AUTHZID ) { + assert( BER_BVISNULL( binddn ) ); + + ctrl.ldctl_oid = LDAP_CONTROL_AUTHZID_REQUEST; + ctrl.ldctl_iscritical = 0; + BER_BVZERO( &ctrl.ldctl_value ); + ctrls[0] = &ctrl; + ctrls[1] = NULL; + ctrlsp = ctrls; + } +#endif /* SLAP_AUTH_DN */ + + do { + rs->sr_err = ldap_sasl_interactive_bind( lc->lc_ld, binddn->bv_val, + li->li_idassert_sasl_mech.bv_val, + ctrlsp, NULL, LDAP_SASL_QUIET, lutil_sasl_interact, defaults, + result, &rmech, &msgid ); + + if ( rs->sr_err != LDAP_SASL_BIND_IN_PROGRESS ) + break; + + ldap_msgfree( result ); + + if ( ldap_result( lc->lc_ld, msgid, LDAP_MSG_ALL, NULL, &result ) == -1 || !result ) { + ldap_get_option( lc->lc_ld, LDAP_OPT_RESULT_CODE, (void*)&rs->sr_err ); + ldap_get_option( lc->lc_ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void*)&rs->sr_text ); + break; + } + } while ( rs->sr_err == LDAP_SASL_BIND_IN_PROGRESS ); + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_BIND ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + + switch ( rs->sr_err ) { + case LDAP_SUCCESS: +#ifdef SLAP_AUTH_DN + /* FIXME: right now, the only reason to check + * response controls is RFC 3829 authzid */ + if ( li->li_idassert_flags & LDAP_BACK_AUTH_DN_AUTHZID ) { + ctrlsp = NULL; + rc = ldap_parse_result( lc->lc_ld, result, NULL, NULL, NULL, NULL, + &ctrlsp, 0 ); + if ( rc == LDAP_SUCCESS && ctrlsp ) { + LDAPControl *ctrl; + + ctrl = ldap_control_find( LDAP_CONTROL_AUTHZID_RESPONSE, + ctrlsp, NULL ); + if ( ctrl ) { + Debug( LDAP_DEBUG_TRACE, "%s: ldap_back_proxy_authz_bind: authzID=\"%s\" (authzid)\n", + op->o_log_prefix, ctrl->ldctl_value.bv_val, 0 ); + if ( ctrl->ldctl_value.bv_len > STRLENOF("dn:") && + strncasecmp( ctrl->ldctl_value.bv_val, "dn:", STRLENOF("dn:") ) == 0 ) + { + struct berval bv; + bv.bv_val = &ctrl->ldctl_value.bv_val[STRLENOF("dn:")]; + bv.bv_len = ctrl->ldctl_value.bv_len - STRLENOF("dn:"); + ber_bvreplace( &lc->lc_bound_ndn, &bv ); + } + } + } + + ldap_controls_free( ctrlsp ); + + } else if ( li->li_idassert_flags & LDAP_BACK_AUTH_DN_WHOAMI ) { + struct berval *val = NULL; + rc = ldap_whoami_s( lc->lc_ld, &val, NULL, NULL ); + if ( rc == LDAP_SUCCESS && val != NULL ) { + Debug( LDAP_DEBUG_TRACE, "%s: ldap_back_proxy_authz_bind: authzID=\"%s\" (whoami)\n", + op->o_log_prefix, val->bv_val, 0 ); + if ( val->bv_len > STRLENOF("dn:") && + strncasecmp( val->bv_val, "dn:", STRLENOF("dn:") ) == 0 ) + { + struct berval bv; + bv.bv_val = &val->bv_val[STRLENOF("dn:")]; + bv.bv_len = val->bv_len - STRLENOF("dn:"); + ber_bvreplace( &lc->lc_bound_ndn, &bv ); + } + ber_bvfree( val ); + } + } + + if ( ( li->li_idassert_flags & LDAP_BACK_AUTH_DN_MASK ) && + BER_BVISNULL( &lc->lc_bound_ndn ) ) + { + /* all in all, we only need it to be non-null */ + /* FIXME: should this be configurable? */ + static struct berval bv = BER_BVC("cn=authzdn"); + ber_bvreplace( &lc->lc_bound_ndn, &bv ); + } +#endif /* SLAP_AUTH_DN */ + LDAP_BACK_CONN_ISBOUND_SET( lc ); + break; + + case LDAP_LOCAL_ERROR: + /* list client API error codes that require + * to taint the connection */ + /* FIXME: should actually retry? */ + LDAP_BACK_CONN_TAINTED_SET( lc ); + + /* fallthru */ + + default: + LDAP_BACK_CONN_ISBOUND_CLEAR( lc ); + rs->sr_err = slap_map_api2result( rs ); + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + break; + } + + if ( save_text != rs->sr_text ) { + ldap_memfree( (char *)rs->sr_text ); + rs->sr_text = save_text; + } + + ldap_msgfree( result ); + + lutil_sasl_freedefs( defaults ); + if ( freeauthz ) { + slap_sl_free( authzID.bv_val, op->o_tmpmemctx ); + } + + goto done; +#endif /* HAVE_CYRUS_SASL */ + } + + switch ( li->li_idassert_authmethod ) { + case LDAP_AUTH_NONE: + /* FIXME: do we really need this? */ + BER_BVSTR( binddn, "" ); + BER_BVSTR( bindcred, "" ); + /* fallthru */ + + case LDAP_AUTH_SIMPLE: + rs->sr_err = ldap_sasl_bind( lc->lc_ld, + binddn->bv_val, LDAP_SASL_SIMPLE, + bindcred, NULL, NULL, &msgid ); + rc = ldap_back_op_result( lc, op, rs, msgid, + -1, ( sendok | LDAP_BACK_BINDING ) ); + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_BIND ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + break; + + default: + /* unsupported! */ + LDAP_BACK_CONN_ISBOUND_CLEAR( lc ); + rs->sr_err = LDAP_AUTH_METHOD_NOT_SUPPORTED; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + goto done; + } + + if ( rc == LDAP_SUCCESS ) { + /* set rebind stuff in case of successful proxyAuthz bind, + * so that referral chasing is attempted using the right + * identity */ + LDAP_BACK_CONN_ISBOUND_SET( lc ); + if ( !BER_BVISNULL( binddn ) ) { + ber_bvreplace( &lc->lc_bound_ndn, binddn ); + } + + if ( !BER_BVISNULL( &lc->lc_cred ) ) { + memset( lc->lc_cred.bv_val, 0, + lc->lc_cred.bv_len ); + } + + if ( LDAP_BACK_SAVECRED( li ) ) { + if ( !BER_BVISNULL( bindcred ) ) { + ber_bvreplace( &lc->lc_cred, bindcred ); + ldap_set_rebind_proc( lc->lc_ld, li->li_rebind_f, lc ); + } + + } else { + lc->lc_cred.bv_len = 0; + } + } + +done:; + return LDAP_BACK_CONN_ISBOUND( lc ); +} + +/* + * ldap_back_proxy_authz_ctrl() prepends a proxyAuthz control + * to existing server-side controls if required; if not, + * the existing server-side controls are placed in *pctrls. + * The caller, after using the controls in client API + * operations, if ( *pctrls != op->o_ctrls ), should + * free( (*pctrls)[ 0 ] ) and free( *pctrls ). + * The function returns success if the control could + * be added if required, or if it did nothing; in the future, + * it might return some error if it failed. + * + * if no bind took place yet, but the connection is bound + * and the "proxyauthzdn" is set, then bind as "proxyauthzdn" + * and explicitly add proxyAuthz the control to every operation + * with the dn bound to the connection as control value. + * + * If no server-side controls are defined for the operation, + * simply add the proxyAuthz control; otherwise, if the + * proxyAuthz control is not already set, add it as + * the first one + * + * FIXME: is controls order significant for security? + * ANSWER: controls ordering and interoperability + * must be indicated by the specs of each control; if none + * is specified, the order is irrelevant. + */ +int +ldap_back_proxy_authz_ctrl( + Operation *op, + SlapReply *rs, + struct berval *bound_ndn, + int version, + slap_idassert_t *si, + LDAPControl *ctrl ) +{ + slap_idassert_mode_t mode; + struct berval assertedID, + ndn; + int isroot = 0; + + rs->sr_err = SLAP_CB_CONTINUE; + + /* FIXME: SASL/EXTERNAL over ldapi:// doesn't honor the authcID, + * but if it is not set this test fails. We need a different + * means to detect if idassert is enabled */ + if ( ( BER_BVISNULL( &si->si_bc.sb_authcId ) || BER_BVISEMPTY( &si->si_bc.sb_authcId ) ) + && ( BER_BVISNULL( &si->si_bc.sb_binddn ) || BER_BVISEMPTY( &si->si_bc.sb_binddn ) ) + && BER_BVISNULL( &si->si_bc.sb_saslmech ) ) + { + goto done; + } + + if ( !op->o_conn || op->o_do_not_cache || ( isroot = be_isroot( op ) ) ) { + goto done; + } + + if ( op->o_tag == LDAP_REQ_BIND ) { + ndn = op->o_req_ndn; + + } else if ( !BER_BVISNULL( &op->o_conn->c_ndn ) ) { + ndn = op->o_conn->c_ndn; + + } else { + ndn = op->o_ndn; + } + + if ( si->si_mode == LDAP_BACK_IDASSERT_LEGACY ) { + if ( op->o_proxy_authz ) { + /* + * FIXME: we do not want to perform proxyAuthz + * on behalf of the client, because this would + * be performed with "proxyauthzdn" privileges. + * + * This might actually be too strict, since + * the "proxyauthzdn" authzTo, and each entry's + * authzFrom attributes may be crafted + * to avoid unwanted proxyAuthz to take place. + */ +#if 0 + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "proxyAuthz not allowed within namingContext"; +#endif + goto done; + } + + if ( !BER_BVISNULL( bound_ndn ) ) { + goto done; + } + + if ( BER_BVISNULL( &ndn ) ) { + goto done; + } + + if ( BER_BVISNULL( &si->si_bc.sb_binddn ) ) { + goto done; + } + + } else if ( si->si_bc.sb_method == LDAP_AUTH_SASL ) { + if ( ( si->si_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) ) + { + /* already asserted in SASL via native authz */ + goto done; + } + + } else if ( si->si_authz && !isroot ) { + int rc; + struct berval authcDN; + + if ( BER_BVISNULL( &ndn ) ) { + authcDN = slap_empty_bv; + } else { + authcDN = ndn; + } + rc = slap_sasl_matches( op, si->si_authz, + &authcDN, &authcDN ); + if ( rc != LDAP_SUCCESS ) { + if ( si->si_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) { + /* ndn is not authorized + * to use idassert */ + rs->sr_err = rc; + } + goto done; + } + } + + if ( op->o_proxy_authz ) { + /* + * FIXME: we can: + * 1) ignore the already set proxyAuthz control + * 2) leave it in place, and don't set ours + * 3) add both + * 4) reject the operation + * + * option (4) is very drastic + * option (3) will make the remote server reject + * the operation, thus being equivalent to (4) + * option (2) will likely break the idassert + * assumptions, so we cannot accept it; + * option (1) means that we are contradicting + * the client's reques. + * + * I think (4) is the only correct choice. + */ + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "proxyAuthz not allowed within namingContext"; + } + + if ( op->o_is_auth_check ) { + mode = LDAP_BACK_IDASSERT_NOASSERT; + + } else { + mode = si->si_mode; + } + + switch ( mode ) { + case LDAP_BACK_IDASSERT_LEGACY: + /* original behavior: + * assert the client's identity */ + case LDAP_BACK_IDASSERT_SELF: + assertedID = ndn; + break; + + case LDAP_BACK_IDASSERT_ANONYMOUS: + /* assert "anonymous" */ + assertedID = slap_empty_bv; + break; + + case LDAP_BACK_IDASSERT_NOASSERT: + /* don't assert; bind as proxyauthzdn */ + goto done; + + case LDAP_BACK_IDASSERT_OTHERID: + case LDAP_BACK_IDASSERT_OTHERDN: + /* assert idassert DN */ + assertedID = si->si_bc.sb_authzId; + break; + + default: + assert( 0 ); + } + + /* if we got here, "" is allowed to proxyAuthz */ + if ( BER_BVISNULL( &assertedID ) ) { + assertedID = slap_empty_bv; + } + + /* don't idassert the bound DN (ITS#4497) */ + if ( dn_match( &assertedID, bound_ndn ) ) { + goto done; + } + + ctrl->ldctl_oid = LDAP_CONTROL_PROXY_AUTHZ; + ctrl->ldctl_iscritical = ( ( si->si_flags & LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL ) == LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL ); + + switch ( si->si_mode ) { + /* already in u:ID or dn:DN form */ + case LDAP_BACK_IDASSERT_OTHERID: + case LDAP_BACK_IDASSERT_OTHERDN: + ber_dupbv_x( &ctrl->ldctl_value, &assertedID, op->o_tmpmemctx ); + rs->sr_err = LDAP_SUCCESS; + break; + + /* needs the dn: prefix */ + default: + ctrl->ldctl_value.bv_len = assertedID.bv_len + STRLENOF( "dn:" ); + ctrl->ldctl_value.bv_val = op->o_tmpalloc( ctrl->ldctl_value.bv_len + 1, + op->o_tmpmemctx ); + AC_MEMCPY( ctrl->ldctl_value.bv_val, "dn:", STRLENOF( "dn:" ) ); + AC_MEMCPY( &ctrl->ldctl_value.bv_val[ STRLENOF( "dn:" ) ], + assertedID.bv_val, assertedID.bv_len + 1 ); + rs->sr_err = LDAP_SUCCESS; + break; + } + + /* Older versions of <draft-weltman-ldapv3-proxy> required + * to encode the value of the authzID (and called it proxyDN); + * this hack provides compatibility with those DSAs that + * implement it this way */ + if ( si->si_flags & LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND ) { + struct berval authzID = ctrl->ldctl_value; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_tag_t tag; + + ber_init2( ber, 0, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + tag = ber_printf( ber, "O", &authzID ); + if ( tag == LBER_ERROR ) { + rs->sr_err = LDAP_OTHER; + goto free_ber; + } + + if ( ber_flatten2( ber, &ctrl->ldctl_value, 1 ) == -1 ) { + rs->sr_err = LDAP_OTHER; + goto free_ber; + } + + rs->sr_err = LDAP_SUCCESS; + +free_ber:; + op->o_tmpfree( authzID.bv_val, op->o_tmpmemctx ); + ber_free_buf( ber ); + + if ( rs->sr_err != LDAP_SUCCESS ) { + goto done; + } + + } else if ( si->si_flags & LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ ) { + struct berval authzID = ctrl->ldctl_value, + tmp; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_tag_t tag; + + if ( strncasecmp( authzID.bv_val, "dn:", STRLENOF( "dn:" ) ) != 0 ) { + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + + tmp = authzID; + tmp.bv_val += STRLENOF( "dn:" ); + tmp.bv_len -= STRLENOF( "dn:" ); + + ber_init2( ber, 0, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + /* apparently, Mozilla API encodes this + * as "SEQUENCE { LDAPDN }" */ + tag = ber_printf( ber, "{O}", &tmp ); + if ( tag == LBER_ERROR ) { + rs->sr_err = LDAP_OTHER; + goto free_ber2; + } + + if ( ber_flatten2( ber, &ctrl->ldctl_value, 1 ) == -1 ) { + rs->sr_err = LDAP_OTHER; + goto free_ber2; + } + + ctrl->ldctl_oid = LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ; + rs->sr_err = LDAP_SUCCESS; + +free_ber2:; + op->o_tmpfree( authzID.bv_val, op->o_tmpmemctx ); + ber_free_buf( ber ); + + if ( rs->sr_err != LDAP_SUCCESS ) { + goto done; + } + } + +done:; + + return rs->sr_err; +} + +/* + * Add controls; + * + * if any needs to be added, it is prepended to existing ones, + * in a newly allocated array. The companion function + * ldap_back_controls_free() must be used to restore the original + * status of op->o_ctrls. + */ +int +ldap_back_controls_add( + Operation *op, + SlapReply *rs, + ldapconn_t *lc, + LDAPControl ***pctrls ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + LDAPControl **ctrls = NULL; + /* set to the maximum number of controls this backend can add */ + LDAPControl c[ 2 ] = { { 0 } }; + int n = 0, i, j1 = 0, j2 = 0; + + *pctrls = NULL; + + rs->sr_err = LDAP_SUCCESS; + + /* don't add controls if protocol is not LDAPv3 */ + switch ( li->li_version ) { + case LDAP_VERSION3: + break; + + case 0: + if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) { + break; + } + /* fall thru */ + + default: + goto done; + } + + /* put controls that go __before__ existing ones here */ + + /* proxyAuthz for identity assertion */ + switch ( ldap_back_proxy_authz_ctrl( op, rs, &lc->lc_bound_ndn, + li->li_version, &li->li_idassert, &c[ j1 ] ) ) + { + case SLAP_CB_CONTINUE: + break; + + case LDAP_SUCCESS: + j1++; + break; + + default: + goto done; + } + + /* put controls that go __after__ existing ones here */ + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + /* FIXME: according to <draft-wahl-ldap-session>, + * the server should check if the control can be added + * based on the identity of the client and so */ + + /* session tracking */ + if ( LDAP_BACK_ST_REQUEST( li ) ) { + switch ( slap_ctrl_session_tracking_request_add( op, rs, &c[ j1 + j2 ] ) ) { + case SLAP_CB_CONTINUE: + break; + + case LDAP_SUCCESS: + j2++; + break; + + default: + goto done; + } + } +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + + if ( rs->sr_err == SLAP_CB_CONTINUE ) { + rs->sr_err = LDAP_SUCCESS; + } + + /* if nothing to do, just bail out */ + if ( j1 == 0 && j2 == 0 ) { + goto done; + } + + assert( j1 + j2 <= (int) (sizeof( c )/sizeof( c[0] )) ); + + if ( op->o_ctrls ) { + for ( n = 0; op->o_ctrls[ n ]; n++ ) + /* just count ctrls */ ; + } + + ctrls = op->o_tmpalloc( (n + j1 + j2 + 1) * sizeof( LDAPControl * ) + ( j1 + j2 ) * sizeof( LDAPControl ), + op->o_tmpmemctx ); + if ( j1 ) { + ctrls[ 0 ] = (LDAPControl *)&ctrls[ n + j1 + j2 + 1 ]; + *ctrls[ 0 ] = c[ 0 ]; + for ( i = 1; i < j1; i++ ) { + ctrls[ i ] = &ctrls[ 0 ][ i ]; + *ctrls[ i ] = c[ i ]; + } + } + + i = 0; + if ( op->o_ctrls ) { + for ( i = 0; op->o_ctrls[ i ]; i++ ) { + ctrls[ i + j1 ] = op->o_ctrls[ i ]; + } + } + + n += j1; + if ( j2 ) { + ctrls[ n ] = (LDAPControl *)&ctrls[ n + j2 + 1 ] + j1; + *ctrls[ n ] = c[ j1 ]; + for ( i = 1; i < j2; i++ ) { + ctrls[ n + i ] = &ctrls[ n ][ i ]; + *ctrls[ n + i ] = c[ i ]; + } + } + + ctrls[ n + j2 ] = NULL; + +done:; + if ( ctrls == NULL ) { + ctrls = op->o_ctrls; + } + + *pctrls = ctrls; + + return rs->sr_err; +} + +int +ldap_back_controls_free( Operation *op, SlapReply *rs, LDAPControl ***pctrls ) +{ + LDAPControl **ctrls = *pctrls; + + /* we assume that the controls added by the proxy come first, + * so as soon as we find op->o_ctrls[ 0 ] we can stop */ + if ( ctrls && ctrls != op->o_ctrls ) { + int i = 0, n = 0, n_added; + LDAPControl *lower, *upper; + + assert( ctrls[ 0 ] != NULL ); + + for ( n = 0; ctrls[ n ] != NULL; n++ ) + /* count 'em */ ; + + if ( op->o_ctrls ) { + for ( i = 0; op->o_ctrls[ i ] != NULL; i++ ) + /* count 'em */ ; + } + + n_added = n - i; + lower = (LDAPControl *)&ctrls[ n ]; + upper = &lower[ n_added ]; + + for ( i = 0; ctrls[ i ] != NULL; i++ ) { + if ( ctrls[ i ] < lower || ctrls[ i ] >= upper ) { + /* original; don't touch */ + continue; + } + + if ( !BER_BVISNULL( &ctrls[ i ]->ldctl_value ) ) { + op->o_tmpfree( ctrls[ i ]->ldctl_value.bv_val, op->o_tmpmemctx ); + } + } + + op->o_tmpfree( ctrls, op->o_tmpmemctx ); + } + + *pctrls = NULL; + + return 0; +} + +int +ldap_back_conn2str( const ldapconn_base_t *lc, char *buf, ber_len_t buflen ) +{ + char tbuf[ SLAP_TEXT_BUFLEN ]; + char *ptr = buf, *end = buf + buflen; + int len; + + if ( ptr + sizeof("conn=") > end ) return -1; + ptr = lutil_strcopy( ptr, "conn=" ); + + len = ldap_back_connid2str( lc, ptr, (ber_len_t)(end - ptr) ); + ptr += len; + if ( ptr >= end ) return -1; + + if ( !BER_BVISNULL( &lc->lcb_local_ndn ) ) { + if ( ptr + sizeof(" DN=\"\"") + lc->lcb_local_ndn.bv_len > end ) return -1; + ptr = lutil_strcopy( ptr, " DN=\"" ); + ptr = lutil_strncopy( ptr, lc->lcb_local_ndn.bv_val, lc->lcb_local_ndn.bv_len ); + *ptr++ = '"'; + } + + if ( lc->lcb_create_time != 0 ) { + len = snprintf( tbuf, sizeof(tbuf), "%ld", lc->lcb_create_time ); + if ( ptr + sizeof(" created=") + len >= end ) return -1; + ptr = lutil_strcopy( ptr, " created=" ); + ptr = lutil_strcopy( ptr, tbuf ); + } + + if ( lc->lcb_time != 0 ) { + len = snprintf( tbuf, sizeof(tbuf), "%ld", lc->lcb_time ); + if ( ptr + sizeof(" modified=") + len >= end ) return -1; + ptr = lutil_strcopy( ptr, " modified=" ); + ptr = lutil_strcopy( ptr, tbuf ); + } + + len = snprintf( tbuf, sizeof(tbuf), "%u", lc->lcb_refcnt ); + if ( ptr + sizeof(" refcnt=") + len >= end ) return -1; + ptr = lutil_strcopy( ptr, " refcnt=" ); + ptr = lutil_strcopy( ptr, tbuf ); + + return ptr - buf; +} + +int +ldap_back_connid2str( const ldapconn_base_t *lc, char *buf, ber_len_t buflen ) +{ + static struct berval conns[] = { + BER_BVC("ROOTDN"), + BER_BVC("ROOTDN-TLS"), + BER_BVC("ANON"), + BER_BVC("ANON-TLS"), + BER_BVC("BIND"), + BER_BVC("BIND-TLS"), + BER_BVNULL + }; + + int len = 0; + + if ( LDAP_BACK_PCONN_ISPRIV( (const ldapconn_t *)lc ) ) { + long cid; + struct berval *bv; + + cid = (long)lc->lcb_conn; + assert( cid >= LDAP_BACK_PCONN_FIRST && cid < LDAP_BACK_PCONN_LAST ); + + bv = &conns[ cid ]; + + if ( bv->bv_len >= buflen ) { + return bv->bv_len + 1; + } + + len = bv->bv_len; + lutil_strncopy( buf, bv->bv_val, bv->bv_len + 1 ); + + } else { + len = snprintf( buf, buflen, "%lu", lc->lcb_conn->c_connid ); + } + + return len; +} diff --git a/servers/slapd/back-ldap/chain.c b/servers/slapd/back-ldap/chain.c new file mode 100644 index 0000000..f1b3af6 --- /dev/null +++ b/servers/slapd/back-ldap/chain.c @@ -0,0 +1,2358 @@ +/* chain.c - chain LDAP operations */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * Portions Copyright 2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software. + * This work was subsequently modified by Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "lutil.h" +#include "slap.h" +#include "back-ldap.h" +#include "config.h" + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR +#define SLAP_CHAINING_DEFAULT LDAP_CHAINING_PREFERRED +#define SLAP_CH_RESOLVE_SHIFT SLAP_CONTROL_SHIFT +#define SLAP_CH_RESOLVE_MASK (0x3 << SLAP_CH_RESOLVE_SHIFT) +#define SLAP_CH_RESOLVE_CHAINING_PREFERRED (LDAP_CHAINING_PREFERRED << SLAP_CH_RESOLVE_SHIFT) +#define SLAP_CH_RESOLVE_CHAINING_REQUIRED (LDAP_CHAINING_REQUIRED << SLAP_CH_RESOLVE_SHIFT) +#define SLAP_CH_RESOLVE_REFERRALS_PREFERRED (LDAP_REFERRALS_PREFERRED << SLAP_CH_RESOLVE_SHIFT) +#define SLAP_CH_RESOLVE_REFERRALS_REQUIRED (LDAP_REFERRALS_REQUIRED << SLAP_CH_RESOLVE_SHIFT) +#define SLAP_CH_RESOLVE_DEFAULT (SLAP_CHAINING_DEFAULT << SLAP_CH_RESOLVE_SHIFT) +#define SLAP_CH_CONTINUATION_SHIFT (SLAP_CH_RESOLVE_SHIFT + 2) +#define SLAP_CH_CONTINUATION_MASK (0x3 << SLAP_CH_CONTINUATION_SHIFT) +#define SLAP_CH_CONTINUATION_CHAINING_PREFERRED (LDAP_CHAINING_PREFERRED << SLAP_CH_CONTINUATION_SHIFT) +#define SLAP_CH_CONTINUATION_CHAINING_REQUIRED (LDAP_CHAINING_REQUIRED << SLAP_CH_CONTINUATION_SHIFT) +#define SLAP_CH_CONTINUATION_REFERRALS_PREFERRED (LDAP_REFERRALS_PREFERRED << SLAP_CH_CONTINUATION_SHIFT) +#define SLAP_CH_CONTINUATION_REFERRALS_REQUIRED (LDAP_REFERRALS_REQUIRED << SLAP_CH_CONTINUATION_SHIFT) +#define SLAP_CH_CONTINUATION_DEFAULT (SLAP_CHAINING_DEFAULT << SLAP_CH_CONTINUATION_SHIFT) + +#define o_chaining o_ctrlflag[sc_chainingBehavior] +#define get_chaining(op) ((op)->o_chaining & SLAP_CONTROL_MASK) +#define get_chainingBehavior(op) ((op)->o_chaining & (SLAP_CH_RESOLVE_MASK|SLAP_CH_CONTINUATION_MASK)) +#define get_resolveBehavior(op) ((op)->o_chaining & SLAP_CH_RESOLVE_MASK) +#define get_continuationBehavior(op) ((op)->o_chaining & SLAP_CH_CONTINUATION_MASK) + +static int sc_chainingBehavior; +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + +typedef enum { + LDAP_CH_NONE = 0, + LDAP_CH_RES, + LDAP_CH_ERR +} ldap_chain_status_t; + +static BackendInfo *lback; + +typedef struct ldap_chain_t { + /* + * A "template" ldapinfo_t gets all common configuration items; + * then, for each configured URI, an entry is created in the tree; + * all the specific configuration items get in the current URI + * structure. + * + * Then, for each referral, extract the URI and lookup the + * related structure. If configured to do so, allow URIs + * not found in the structure to create a temporary one + * that chains anonymously; maybe it can also be added to + * the tree? Should be all configurable. + */ + + /* "common" configuration info (anything occurring before an "uri") */ + ldapinfo_t *lc_common_li; + + /* current configuration info */ + ldapinfo_t *lc_cfg_li; + + /* tree of configured[/generated?] "uri" info */ + ldap_avl_info_t lc_lai; + + /* max depth in nested referrals chaining */ + int lc_max_depth; + + unsigned lc_flags; +#define LDAP_CHAIN_F_NONE (0x00U) +#define LDAP_CHAIN_F_CHAINING (0x01U) +#define LDAP_CHAIN_F_CACHE_URI (0x02U) +#define LDAP_CHAIN_F_RETURN_ERR (0x04U) + +#define LDAP_CHAIN_ISSET(lc, f) ( ( (lc)->lc_flags & (f) ) == (f) ) +#define LDAP_CHAIN_CHAINING( lc ) LDAP_CHAIN_ISSET( (lc), LDAP_CHAIN_F_CHAINING ) +#define LDAP_CHAIN_CACHE_URI( lc ) LDAP_CHAIN_ISSET( (lc), LDAP_CHAIN_F_CACHE_URI ) +#define LDAP_CHAIN_RETURN_ERR( lc ) LDAP_CHAIN_ISSET( (lc), LDAP_CHAIN_F_RETURN_ERR ) + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + LDAPControl lc_chaining_ctrl; + char lc_chaining_ctrlflag; +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ +} ldap_chain_t; + +static int ldap_chain_db_init_common( BackendDB *be ); +static int ldap_chain_db_init_one( BackendDB *be ); +static int ldap_chain_db_open_one( BackendDB *be ); +#define ldap_chain_db_close_one(be) (0) +#define ldap_chain_db_destroy_one(be, rs) (lback)->bi_db_destroy( (be), (rs) ) + +typedef struct ldap_chain_cb_t { + ldap_chain_status_t lb_status; + ldap_chain_t *lb_lc; + BI_op_func *lb_op_f; + int lb_depth; +} ldap_chain_cb_t; + +static int +ldap_chain_op( + Operation *op, + SlapReply *rs, + BI_op_func *op_f, + BerVarray ref, + int depth ); + +static int +ldap_chain_search( + Operation *op, + SlapReply *rs, + BerVarray ref, + int depth ); + +static slap_overinst ldapchain; + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR +static int +chaining_control_add( + ldap_chain_t *lc, + Operation *op, + LDAPControl ***oldctrlsp ) +{ + LDAPControl **ctrls = NULL; + int c = 0; + + *oldctrlsp = op->o_ctrls; + + /* default chaining control not defined */ + if ( !LDAP_CHAIN_CHAINING( lc ) ) { + return 0; + } + + /* already present */ + if ( get_chaining( op ) > SLAP_CONTROL_IGNORED ) { + return 0; + } + + /* FIXME: check other incompatibilities */ + + /* add to other controls */ + if ( op->o_ctrls ) { + for ( c = 0; op->o_ctrls[ c ]; c++ ) + /* count them */ ; + } + + ctrls = ch_calloc( sizeof( LDAPControl *), c + 2 ); + ctrls[ 0 ] = &lc->lc_chaining_ctrl; + if ( op->o_ctrls ) { + for ( c = 0; op->o_ctrls[ c ]; c++ ) { + ctrls[ c + 1 ] = op->o_ctrls[ c ]; + } + } + ctrls[ c + 1 ] = NULL; + + op->o_ctrls = ctrls; + + op->o_chaining = lc->lc_chaining_ctrlflag; + + return 0; +} + +static int +chaining_control_remove( + Operation *op, + LDAPControl ***oldctrlsp ) +{ + LDAPControl **oldctrls = *oldctrlsp; + + /* we assume that the first control is the chaining control + * added by the chain overlay, so it's the only one we explicitly + * free */ + if ( op->o_ctrls != oldctrls ) { + if ( op->o_ctrls != NULL ) { + assert( op->o_ctrls[ 0 ] != NULL ); + + free( op->o_ctrls ); + + op->o_chaining = 0; + } + op->o_ctrls = oldctrls; + } + + *oldctrlsp = NULL; + + return 0; +} +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + +static int +ldap_chain_uri_cmp( const void *c1, const void *c2 ) +{ + const ldapinfo_t *li1 = (const ldapinfo_t *)c1; + const ldapinfo_t *li2 = (const ldapinfo_t *)c2; + + assert( li1->li_bvuri != NULL ); + assert( !BER_BVISNULL( &li1->li_bvuri[ 0 ] ) ); + assert( BER_BVISNULL( &li1->li_bvuri[ 1 ] ) ); + + assert( li2->li_bvuri != NULL ); + assert( !BER_BVISNULL( &li2->li_bvuri[ 0 ] ) ); + assert( BER_BVISNULL( &li2->li_bvuri[ 1 ] ) ); + + return ber_bvcmp( &li1->li_bvuri[ 0 ], &li2->li_bvuri[ 0 ] ); +} + +static int +ldap_chain_uri_dup( void *c1, void *c2 ) +{ + ldapinfo_t *li1 = (ldapinfo_t *)c1; + ldapinfo_t *li2 = (ldapinfo_t *)c2; + + assert( li1->li_bvuri != NULL ); + assert( !BER_BVISNULL( &li1->li_bvuri[ 0 ] ) ); + assert( BER_BVISNULL( &li1->li_bvuri[ 1 ] ) ); + + assert( li2->li_bvuri != NULL ); + assert( !BER_BVISNULL( &li2->li_bvuri[ 0 ] ) ); + assert( BER_BVISNULL( &li2->li_bvuri[ 1 ] ) ); + + if ( ber_bvcmp( &li1->li_bvuri[ 0 ], &li2->li_bvuri[ 0 ] ) == 0 ) { + return -1; + } + + return 0; +} + +/* + * Search specific response that strips entryDN from entries + */ +static int +ldap_chain_cb_search_response( Operation *op, SlapReply *rs ) +{ + ldap_chain_cb_t *lb = (ldap_chain_cb_t *)op->o_callback->sc_private; + + assert( op->o_tag == LDAP_REQ_SEARCH ); + + /* if in error, don't proceed any further */ + if ( lb->lb_status == LDAP_CH_ERR ) { + return 0; + } + + if ( rs->sr_type == REP_SEARCH ) { + Attribute **ap = &rs->sr_entry->e_attrs; + + for ( ; *ap != NULL; ap = &(*ap)->a_next ) { + /* will be generated later by frontend + * (a cleaner solution would be that + * the frontend checks if it already exists */ + if ( ad_cmp( (*ap)->a_desc, slap_schema.si_ad_entryDN ) == 0 ) + { + Attribute *a = *ap; + + *ap = (*ap)->a_next; + attr_free( a ); + + /* there SHOULD be one only! */ + break; + } + } + + /* tell the frontend not to add generated + * operational attributes */ + rs->sr_flags |= REP_NO_OPERATIONALS; + + return SLAP_CB_CONTINUE; + + } else if ( rs->sr_type == REP_SEARCHREF ) { + /* if we get it here, it means the library was unable + * to chase the referral... */ + if ( lb->lb_depth < lb->lb_lc->lc_max_depth && rs->sr_ref != NULL ) { + rs->sr_err = ldap_chain_search( op, rs, rs->sr_ref, lb->lb_depth ); + } + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + if ( rs->sr_err == LDAP_REFERRAL && get_chaining( op ) > SLAP_CONTROL_IGNORED ) { + switch ( get_continuationBehavior( op ) ) { + case SLAP_CH_RESOLVE_CHAINING_REQUIRED: + lb->lb_status = LDAP_CH_ERR; + return rs->sr_err = LDAP_X_CANNOT_CHAIN; + + default: + break; + } + } +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + return SLAP_CB_CONTINUE; + + } else if ( rs->sr_type == REP_RESULT ) { + if ( rs->sr_err == LDAP_REFERRAL + && lb->lb_depth < lb->lb_lc->lc_max_depth + && rs->sr_ref != NULL ) + { + rs->sr_err = ldap_chain_op( op, rs, lb->lb_op_f, rs->sr_ref, lb->lb_depth ); + } + + /* back-ldap tried to send result */ + lb->lb_status = LDAP_CH_RES; + /* don't let other callbacks run, this isn't + * the real result for this op. + */ + op->o_callback->sc_next = NULL; + } + + return 0; +} + +/* + * Dummy response that simply traces if back-ldap tried to send + * anything to the client + */ +static int +ldap_chain_cb_response( Operation *op, SlapReply *rs ) +{ + ldap_chain_cb_t *lb = (ldap_chain_cb_t *)op->o_callback->sc_private; + + /* if in error, don't proceed any further */ + if ( lb->lb_status == LDAP_CH_ERR ) { + return 0; + } + + if ( rs->sr_type == REP_RESULT ) { +retry:; + switch ( rs->sr_err ) { + case LDAP_COMPARE_TRUE: + case LDAP_COMPARE_FALSE: + if ( op->o_tag != LDAP_REQ_COMPARE ) { + return rs->sr_err; + } + /* fallthru */ + + case LDAP_SUCCESS: + lb->lb_status = LDAP_CH_RES; + break; + + case LDAP_REFERRAL: + if ( lb->lb_depth < lb->lb_lc->lc_max_depth && rs->sr_ref != NULL ) { + rs->sr_err = ldap_chain_op( op, rs, lb->lb_op_f, rs->sr_ref, lb->lb_depth ); + goto retry; + } + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + if ( get_chaining( op ) > SLAP_CONTROL_IGNORED ) { + switch ( get_continuationBehavior( op ) ) { + case SLAP_CH_RESOLVE_CHAINING_REQUIRED: + lb->lb_status = LDAP_CH_ERR; + return rs->sr_err = LDAP_X_CANNOT_CHAIN; + + default: + break; + } + } +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + break; + + default: + return rs->sr_err; + } + + } else if ( op->o_tag == LDAP_REQ_SEARCH && rs->sr_type == REP_SEARCH ) + { + /* strip the entryDN attribute, but keep returning results */ + (void)ldap_chain_cb_search_response( op, rs ); + } + + return SLAP_CB_CONTINUE; +} + +static int +ldap_chain_op( + Operation *op, + SlapReply *rs, + BI_op_func *op_f, + BerVarray ref, + int depth ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + ldap_chain_cb_t *lb = (ldap_chain_cb_t *)op->o_callback->sc_private; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + struct berval odn = op->o_req_dn, + ondn = op->o_req_ndn; + ldapinfo_t li = { 0 }, *lip = NULL; + struct berval bvuri[ 2 ] = { { 0 } }; + + /* NOTE: returned if ref is empty... */ + int rc = LDAP_OTHER, + first_rc; + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + LDAPControl **ctrls = NULL; + + (void)chaining_control_add( lc, op, &ctrls ); +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + + li.li_bvuri = bvuri; + first_rc = -1; + for ( ; !BER_BVISNULL( ref ); ref++ ) { + SlapReply rs2 = { 0 }; + LDAPURLDesc *srv = NULL; + req_search_s save_oq_search = op->oq_search, + tmp_oq_search = { 0 }; + struct berval dn = BER_BVNULL, + pdn = odn, + ndn = ondn; + char *filter = NULL; + int temporary = 0; + int free_dn = 0; + + /* We're setting the URI of the first referral; + * what if there are more? + +Document: RFC 4511 + +4.1.10. Referral + ... + If the client wishes to progress the operation, it MUST follow the + referral by contacting one of the supported services. If multiple + URIs are present, the client assumes that any supported URI may be + used to progress the operation. + + * so we actually need to follow exactly one, + * and we can assume any is fine. + */ + + /* parse reference and use + * proto://[host][:port]/ only */ + rc = ldap_url_parse_ext( ref->bv_val, &srv, LDAP_PVT_URL_PARSE_NONE ); + if ( rc != LDAP_URL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: unable to parse ref=\"%s\"\n", + op->o_log_prefix, ref->bv_val, 0 ); + + /* try next */ + rc = LDAP_OTHER; + continue; + } + + if ( op->o_tag == LDAP_REQ_SEARCH ) { + if ( srv->lud_scope != LDAP_SCOPE_DEFAULT ) { + /* RFC 4511: if scope is present, use it */ + tmp_oq_search.rs_scope = srv->lud_scope; + + } else { + /* RFC 4511: if scope is absent, use original */ + tmp_oq_search.rs_scope = op->ors_scope; + } + } + + rc = LDAP_SUCCESS; + srv->lud_scope = LDAP_SCOPE_DEFAULT; + dn.bv_val = srv->lud_dn; + filter = srv->lud_filter; + + /* normalize DN */ + if ( srv->lud_dn == NULL || srv->lud_dn[0] == '\0' ) { + if ( srv->lud_dn == NULL ) { + srv->lud_dn = ""; + } + + } else { + ber_str2bv( srv->lud_dn, 0, 0, &dn ); + rc = dnPrettyNormal( NULL, &dn, &pdn, &ndn, op->o_tmpmemctx ); + if ( rc == LDAP_SUCCESS ) { + /* remove DN essentially because later on + * ldap_initialize() will parse the URL + * as a comma-separated URL list */ + srv->lud_dn = ""; + free_dn = 1; + } + } + + /* prepare filter */ + if ( rc == LDAP_SUCCESS && op->o_tag == LDAP_REQ_SEARCH ) { + /* filter */ + if ( srv->lud_filter != NULL + && srv->lud_filter[0] != '\0' + && strcasecmp( srv->lud_filter, "(objectClass=*)" ) != 0 ) + { + /* RFC 4511: if filter is present, use it; + * otherwise, use original */ + tmp_oq_search.rs_filter = str2filter_x( op, srv->lud_filter ); + if ( tmp_oq_search.rs_filter != NULL ) { + filter2bv_x( op, tmp_oq_search.rs_filter, &tmp_oq_search.rs_filterstr ); + + } else { + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\": unable to parse filter=\"%s\"\n", + op->o_log_prefix, ref->bv_val, srv->lud_filter ); + rc = LDAP_OTHER; + } + } + } + srv->lud_filter = NULL; + + if ( rc == LDAP_SUCCESS ) { + li.li_uri = ldap_url_desc2str( srv ); + } + + srv->lud_dn = dn.bv_val; + srv->lud_filter = filter; + ldap_free_urldesc( srv ); + + if ( rc != LDAP_SUCCESS ) { + /* try next */ + rc = LDAP_OTHER; + continue; + } + + if ( li.li_uri == NULL ) { + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\" unable to reconstruct URI\n", + op->o_log_prefix, ref->bv_val, 0 ); + + /* try next */ + rc = LDAP_OTHER; + goto further_cleanup; + } + + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\" -> \"%s\"\n", + op->o_log_prefix, ref->bv_val, li.li_uri ); + + op->o_req_dn = pdn; + op->o_req_ndn = ndn; + + if ( op->o_tag == LDAP_REQ_SEARCH ) { + op->ors_scope = tmp_oq_search.rs_scope; + if ( tmp_oq_search.rs_filter != NULL ) { + op->ors_filter = tmp_oq_search.rs_filter; + op->ors_filterstr = tmp_oq_search.rs_filterstr; + } + } + + ber_str2bv( li.li_uri, 0, 0, &li.li_bvuri[ 0 ] ); + + /* Searches for a ldapinfo in the avl tree */ + ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex ); + lip = (ldapinfo_t *)avl_find( lc->lc_lai.lai_tree, + (caddr_t)&li, ldap_chain_uri_cmp ); + ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex ); + + if ( lip != NULL ) { + op->o_bd->be_private = (void *)lip; + + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\": URI=\"%s\" found in cache\n", + op->o_log_prefix, ref->bv_val, li.li_uri ); + + } else { + rc = ldap_chain_db_init_one( op->o_bd ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\" unable to init back-ldap for URI=\"%s\"\n", + op->o_log_prefix, ref->bv_val, li.li_uri ); + goto cleanup; + } + lip = (ldapinfo_t *)op->o_bd->be_private; + lip->li_uri = li.li_uri; + lip->li_bvuri = bvuri; + rc = ldap_chain_db_open_one( op->o_bd ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\" unable to open back-ldap for URI=\"%s\"\n", + op->o_log_prefix, ref->bv_val, li.li_uri ); + lip->li_uri = NULL; + lip->li_bvuri = NULL; + (void)ldap_chain_db_destroy_one( op->o_bd, NULL); + goto cleanup; + } + + if ( LDAP_CHAIN_CACHE_URI( lc ) ) { + ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex ); + if ( avl_insert( &lc->lc_lai.lai_tree, + (caddr_t)lip, ldap_chain_uri_cmp, ldap_chain_uri_dup ) ) + { + /* someone just inserted another; + * don't bother, use this and then + * just free it */ + temporary = 1; + } + ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex ); + + } else { + temporary = 1; + } + + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\" %s\n", + op->o_log_prefix, ref->bv_val, temporary ? "temporary" : "caching" ); + } + + lb->lb_op_f = op_f; + lb->lb_depth = depth + 1; + + rc = op_f( op, &rs2 ); + + /* note the first error */ + if ( first_rc == -1 ) { + first_rc = rc; + } + +cleanup:; + ldap_memfree( li.li_uri ); + li.li_uri = NULL; + + if ( temporary ) { + lip->li_uri = NULL; + lip->li_bvuri = NULL; + (void)ldap_chain_db_close_one( op->o_bd ); + (void)ldap_chain_db_destroy_one( op->o_bd, NULL ); + } + +further_cleanup:; + if ( op->o_req_dn.bv_val == pdn.bv_val ) { + op->o_req_dn = odn; + op->o_req_ndn = ondn; + } + + if ( free_dn ) { + op->o_tmpfree( pdn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( ndn.bv_val, op->o_tmpmemctx ); + } + + if ( op->o_tag == LDAP_REQ_SEARCH ) { + if ( tmp_oq_search.rs_filter != NULL ) { + filter_free_x( op, tmp_oq_search.rs_filter, 1 ); + } + + if ( !BER_BVISNULL( &tmp_oq_search.rs_filterstr ) ) { + slap_sl_free( tmp_oq_search.rs_filterstr.bv_val, op->o_tmpmemctx ); + } + + op->oq_search = save_oq_search; + } + + if ( rc == LDAP_SUCCESS && rs2.sr_err == LDAP_SUCCESS ) { + *rs = rs2; + break; + } + + rc = rs2.sr_err; + } + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + (void)chaining_control_remove( op, &ctrls ); +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + + if ( rc != LDAP_SUCCESS && first_rc > 0 ) { + rc = first_rc; + } + + return rc; +} + +static int +ldap_chain_search( + Operation *op, + SlapReply *rs, + BerVarray ref, + int depth ) + +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + ldap_chain_cb_t *lb = (ldap_chain_cb_t *)op->o_callback->sc_private; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + ldapinfo_t li = { 0 }, *lip = NULL; + struct berval bvuri[ 2 ] = { { 0 } }; + + struct berval odn = op->o_req_dn, + ondn = op->o_req_ndn; + Entry *save_entry = rs->sr_entry; + slap_mask_t save_flags = rs->sr_flags; + + int rc = LDAP_OTHER, + first_rc = -1; + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + LDAPControl **ctrls = NULL; + + (void)chaining_control_add( lc, op, &ctrls ); +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + + assert( rs->sr_type == REP_SEARCHREF ); + + rs->sr_type = REP_SEARCH; + + /* if we parse the URI then by no means + * we can cache stuff or reuse connections, + * because in back-ldap there's no caching + * based on the URI value, which is supposed + * to be set once for all (correct?) */ + li.li_bvuri = bvuri; + for ( ; !BER_BVISNULL( &ref[0] ); ref++ ) { + SlapReply rs2 = { REP_RESULT }; + LDAPURLDesc *srv; + req_search_s save_oq_search = op->oq_search, + tmp_oq_search = { 0 }; + struct berval dn, + pdn = op->o_req_dn, + ndn = op->o_req_ndn; + char *filter = NULL; + int temporary = 0; + int free_dn = 0; + + /* parse reference and use + * proto://[host][:port]/ only */ + rc = ldap_url_parse_ext( ref[0].bv_val, &srv, LDAP_PVT_URL_PARSE_NONE ); + if ( rc != LDAP_URL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: unable to parse ref=\"%s\"\n", + op->o_log_prefix, ref->bv_val, 0 ); + + /* try next */ + rs->sr_err = LDAP_OTHER; + continue; + } + + if ( srv->lud_scope != LDAP_SCOPE_DEFAULT ) { + /* RFC 4511: if scope is present, use it */ + tmp_oq_search.rs_scope = srv->lud_scope; + + } else { + /* RFC 4511: if scope is absent, use original */ + /* Section 4.5.3: if scope is onelevel, use base */ + if ( op->ors_scope == LDAP_SCOPE_ONELEVEL ) + tmp_oq_search.rs_scope = LDAP_SCOPE_BASE; + else + tmp_oq_search.rs_scope = op->ors_scope; + } + + rc = LDAP_SUCCESS; + srv->lud_scope = LDAP_SCOPE_DEFAULT; + dn.bv_val = srv->lud_dn; + filter = srv->lud_filter; + + /* normalize DN */ + if ( srv->lud_dn == NULL || srv->lud_dn[0] == '\0' ) { + if ( srv->lud_dn == NULL ) { + srv->lud_dn = ""; + } + + if ( save_entry != NULL ) { + /* use the "right" DN, if available */ + pdn = save_entry->e_name; + ndn = save_entry->e_nname; + } /* else leave the original req DN in place, if any RFC 4511 */ + + } else { + /* RFC 4511: if DN is present, use it */ + ber_str2bv( srv->lud_dn, 0, 0, &dn ); + rc = dnPrettyNormal( NULL, &dn, &pdn, &ndn, op->o_tmpmemctx ); + if ( rc == LDAP_SUCCESS ) { + /* remove DN essentially because later on + * ldap_initialize() will parse the URL + * as a comma-separated URL list */ + srv->lud_dn = ""; + free_dn = 1; + } + } + + /* prepare filter */ + if ( rc == LDAP_SUCCESS ) { + /* filter */ + if ( srv->lud_filter != NULL + && srv->lud_filter[0] != '\0' + && strcasecmp( srv->lud_filter, "(objectClass=*)" ) != 0 ) + { + /* RFC 4511: if filter is present, use it; + * otherwise, use original */ + tmp_oq_search.rs_filter = str2filter_x( op, srv->lud_filter ); + if ( tmp_oq_search.rs_filter != NULL ) { + filter2bv_x( op, tmp_oq_search.rs_filter, &tmp_oq_search.rs_filterstr ); + + } else { + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\": unable to parse filter=\"%s\"\n", + op->o_log_prefix, ref->bv_val, srv->lud_filter ); + rc = LDAP_OTHER; + } + } + } + srv->lud_filter = NULL; + + if ( rc == LDAP_SUCCESS ) { + li.li_uri = ldap_url_desc2str( srv ); + } + + srv->lud_dn = dn.bv_val; + srv->lud_filter = filter; + ldap_free_urldesc( srv ); + + if ( rc != LDAP_SUCCESS || li.li_uri == NULL ) { + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\" unable to reconstruct URI\n", + op->o_log_prefix, ref->bv_val, 0 ); + + /* try next */ + rc = LDAP_OTHER; + goto further_cleanup; + } + + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\" -> \"%s\"\n", + op->o_log_prefix, ref->bv_val, li.li_uri ); + + op->o_req_dn = pdn; + op->o_req_ndn = ndn; + op->ors_scope = tmp_oq_search.rs_scope; + if ( tmp_oq_search.rs_filter != NULL ) { + op->ors_filter = tmp_oq_search.rs_filter; + op->ors_filterstr = tmp_oq_search.rs_filterstr; + } + + ber_str2bv( li.li_uri, 0, 0, &li.li_bvuri[ 0 ] ); + + /* Searches for a ldapinfo in the avl tree */ + ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex ); + lip = (ldapinfo_t *)avl_find( lc->lc_lai.lai_tree, + (caddr_t)&li, ldap_chain_uri_cmp ); + ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex ); + + if ( lip != NULL ) { + op->o_bd->be_private = (void *)lip; + + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\": URI=\"%s\" found in cache\n", + op->o_log_prefix, ref->bv_val, li.li_uri ); + + } else { + /* if none is found, create a temporary... */ + rc = ldap_chain_db_init_one( op->o_bd ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\" unable to init back-ldap for URI=\"%s\"\n", + op->o_log_prefix, ref->bv_val, li.li_uri ); + goto cleanup; + } + lip = (ldapinfo_t *)op->o_bd->be_private; + lip->li_uri = li.li_uri; + lip->li_bvuri = bvuri; + rc = ldap_chain_db_open_one( op->o_bd ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\" unable to open back-ldap for URI=\"%s\"\n", + op->o_log_prefix, ref->bv_val, li.li_uri ); + lip->li_uri = NULL; + lip->li_bvuri = NULL; + (void)ldap_chain_db_destroy_one( op->o_bd, NULL ); + goto cleanup; + } + + if ( LDAP_CHAIN_CACHE_URI( lc ) ) { + ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex ); + if ( avl_insert( &lc->lc_lai.lai_tree, + (caddr_t)lip, ldap_chain_uri_cmp, ldap_chain_uri_dup ) ) + { + /* someone just inserted another; + * don't bother, use this and then + * just free it */ + temporary = 1; + } + ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex ); + + } else { + temporary = 1; + } + + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\" %s\n", + op->o_log_prefix, ref->bv_val, temporary ? "temporary" : "caching" ); + } + + lb->lb_op_f = lback->bi_op_search; + lb->lb_depth = depth + 1; + + /* FIXME: should we also copy filter and scope? + * according to RFC3296, no */ + rc = lback->bi_op_search( op, &rs2 ); + if ( first_rc == -1 ) { + first_rc = rc; + } + +cleanup:; + ldap_memfree( li.li_uri ); + li.li_uri = NULL; + + if ( temporary ) { + lip->li_uri = NULL; + lip->li_bvuri = NULL; + (void)ldap_chain_db_close_one( op->o_bd ); + (void)ldap_chain_db_destroy_one( op->o_bd, NULL ); + } + +further_cleanup:; + if ( op->o_req_dn.bv_val == pdn.bv_val ) { + op->o_req_dn = odn; + op->o_req_ndn = ondn; + } + + if ( free_dn ) { + op->o_tmpfree( pdn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( ndn.bv_val, op->o_tmpmemctx ); + } + + if ( tmp_oq_search.rs_filter != NULL ) { + filter_free_x( op, tmp_oq_search.rs_filter, 1 ); + } + + if ( !BER_BVISNULL( &tmp_oq_search.rs_filterstr ) ) { + slap_sl_free( tmp_oq_search.rs_filterstr.bv_val, op->o_tmpmemctx ); + } + + op->oq_search = save_oq_search; + + if ( rc == LDAP_SUCCESS && rs2.sr_err == LDAP_SUCCESS ) { + *rs = rs2; + break; + } + + rc = rs2.sr_err; + } + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + (void)chaining_control_remove( op, &ctrls ); +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + + rs->sr_type = REP_SEARCHREF; + rs->sr_entry = save_entry; + rs->sr_flags = save_flags; + + if ( rc != LDAP_SUCCESS ) { + /* couldn't chase any of the referrals */ + if ( first_rc != -1 ) { + rc = first_rc; + + } else { + rc = SLAP_CB_CONTINUE; + } + } + + return rc; +} + +static int +ldap_chain_response( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + BackendDB db, *bd = op->o_bd; + ldap_chain_cb_t lb = { 0 }; + slap_callback *sc = op->o_callback, + sc2 = { 0 }; + int rc = 0; + const char *text = NULL; + const char *matched; + BerVarray ref; + struct berval ndn = op->o_ndn; + + int sr_err = rs->sr_err; + slap_reply_t sr_type = rs->sr_type; +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + slap_mask_t chain_mask = 0; + ber_len_t chain_shift = 0; +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + + if ( rs->sr_err != LDAP_REFERRAL && rs->sr_type != REP_SEARCHREF ) { + return SLAP_CB_CONTINUE; + } + if ( !rs->sr_ref ) { + return SLAP_CB_CONTINUE; + } + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + if ( rs->sr_err == LDAP_REFERRAL && get_chaining( op ) > SLAP_CONTROL_IGNORED ) { + switch ( get_resolveBehavior( op ) ) { + case SLAP_CH_RESOLVE_REFERRALS_PREFERRED: + case SLAP_CH_RESOLVE_REFERRALS_REQUIRED: + return SLAP_CB_CONTINUE; + + default: + chain_mask = SLAP_CH_RESOLVE_MASK; + chain_shift = SLAP_CH_RESOLVE_SHIFT; + break; + } + + } else if ( rs->sr_type == REP_SEARCHREF && get_chaining( op ) > SLAP_CONTROL_IGNORED ) { + switch ( get_continuationBehavior( op ) ) { + case SLAP_CH_CONTINUATION_REFERRALS_PREFERRED: + case SLAP_CH_CONTINUATION_REFERRALS_REQUIRED: + return SLAP_CB_CONTINUE; + + default: + chain_mask = SLAP_CH_CONTINUATION_MASK; + chain_shift = SLAP_CH_CONTINUATION_SHIFT; + break; + } + } +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + + /* + * TODO: add checks on who/when chain operations; e.g.: + * a) what identities are authorized + * b) what request DN (e.g. only chain requests rooted at <DN>) + * c) what referral URIs + * d) what protocol scheme (e.g. only ldaps://) + * e) what ssf + */ + + db = *op->o_bd; + SLAP_DBFLAGS( &db ) &= ~SLAP_DBFLAG_MONITORING; + op->o_bd = &db; + + text = rs->sr_text; + rs->sr_text = NULL; + matched = rs->sr_matched; + rs->sr_matched = NULL; + ref = rs->sr_ref; + rs->sr_ref = NULL; + + /* we need this to know if back-ldap returned any result */ + lb.lb_lc = lc; + sc2.sc_next = sc->sc_next; + sc2.sc_private = &lb; + sc2.sc_response = ldap_chain_cb_response; + op->o_callback = &sc2; + + /* Chaining can be performed by a privileged user on behalf + * of normal users, using the ProxyAuthz control, by exploiting + * the identity assertion feature of back-ldap; see idassert-* + * directives in slapd-ldap(5). + * + * FIXME: the idassert-authcDN is one, will it be fine regardless + * of the URI we obtain from the referral? + */ + + switch ( op->o_tag ) { + case LDAP_REQ_BIND: { + struct berval rndn = op->o_req_ndn; + Connection *conn = op->o_conn; + + /* FIXME: can we really get a referral for binds? */ + op->o_req_ndn = slap_empty_bv; + op->o_conn = NULL; + rc = ldap_chain_op( op, rs, lback->bi_op_bind, ref, 0 ); + op->o_req_ndn = rndn; + op->o_conn = conn; + } + break; + + case LDAP_REQ_ADD: + rc = ldap_chain_op( op, rs, lback->bi_op_add, ref, 0 ); + break; + + case LDAP_REQ_DELETE: + rc = ldap_chain_op( op, rs, lback->bi_op_delete, ref, 0 ); + break; + + case LDAP_REQ_MODRDN: + rc = ldap_chain_op( op, rs, lback->bi_op_modrdn, ref, 0 ); + break; + + case LDAP_REQ_MODIFY: + rc = ldap_chain_op( op, rs, lback->bi_op_modify, ref, 0 ); + break; + + case LDAP_REQ_COMPARE: + rc = ldap_chain_op( op, rs, lback->bi_op_compare, ref, 0 ); + if ( rs->sr_err == LDAP_COMPARE_TRUE || rs->sr_err == LDAP_COMPARE_FALSE ) { + rc = LDAP_SUCCESS; + } + break; + + case LDAP_REQ_SEARCH: + if ( rs->sr_type == REP_SEARCHREF ) { + sc2.sc_response = ldap_chain_cb_search_response; + rc = ldap_chain_search( op, rs, ref, 0 ); + + } else { + /* we might get here before any database actually + * performed a search; in those cases, we need + * to check limits, to make sure safe defaults + * are in place */ + if ( op->ors_limit != NULL || limits_check( op, rs ) == 0 ) { + rc = ldap_chain_op( op, rs, lback->bi_op_search, ref, 0 ); + + } else { + rc = SLAP_CB_CONTINUE; + } + } + break; + + case LDAP_REQ_EXTENDED: + rc = ldap_chain_op( op, rs, lback->bi_extended, ref, 0 ); + /* FIXME: ldap_back_extended() by design + * doesn't send result; frontend is expected + * to send it... */ + /* FIXME: what about chaining? */ + if ( rc != SLAPD_ABANDON ) { + rs->sr_err = rc; + send_ldap_extended( op, rs ); + rc = LDAP_SUCCESS; + } + lb.lb_status = LDAP_CH_RES; + break; + + default: + rc = SLAP_CB_CONTINUE; + break; + } + + switch ( rc ) { + case SLAPD_ABANDON: + goto dont_chain; + + case LDAP_SUCCESS: + case LDAP_REFERRAL: + sr_err = rs->sr_err; + /* slapd-ldap sent response */ + if ( !op->o_abandon && lb.lb_status != LDAP_CH_RES ) { + /* FIXME: should we send response? */ + Debug( LDAP_DEBUG_ANY, + "%s: ldap_chain_response: " + "overlay should have sent result.\n", + op->o_log_prefix, 0, 0 ); + } + break; + + default: +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + if ( lb.lb_status == LDAP_CH_ERR && rs->sr_err == LDAP_X_CANNOT_CHAIN ) { + goto cannot_chain; + } + + switch ( ( get_chainingBehavior( op ) & chain_mask ) >> chain_shift ) { + case LDAP_CHAINING_REQUIRED: +cannot_chain:; + op->o_callback = NULL; + send_ldap_error( op, rs, LDAP_X_CANNOT_CHAIN, + "operation cannot be completed without chaining" ); + goto dont_chain; + + default: +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + if ( LDAP_CHAIN_RETURN_ERR( lc ) ) { + sr_err = rs->sr_err = rc; + rs->sr_type = sr_type; + + } else { + rc = SLAP_CB_CONTINUE; + rs->sr_err = sr_err; + rs->sr_type = sr_type; + rs->sr_text = text; + rs->sr_matched = matched; + rs->sr_ref = ref; + } +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + break; + } +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + } + + if ( lb.lb_status == LDAP_CH_NONE && rc != SLAPD_ABANDON ) { + /* give the remaining callbacks a chance */ + op->o_callback = sc->sc_next; + rc = rs->sr_err = slap_map_api2result( rs ); + send_ldap_result( op, rs ); + } + +dont_chain:; + rs->sr_err = sr_err; + rs->sr_type = sr_type; + rs->sr_text = text; + rs->sr_matched = matched; + rs->sr_ref = ref; + op->o_bd = bd; + op->o_callback = sc; + op->o_ndn = ndn; + + return rc; +} + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR +static int +ldap_chain_parse_ctrl( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ); + +static int +str2chain( const char *s ) +{ + if ( strcasecmp( s, "chainingPreferred" ) == 0 ) { + return LDAP_CHAINING_PREFERRED; + + } else if ( strcasecmp( s, "chainingRequired" ) == 0 ) { + return LDAP_CHAINING_REQUIRED; + + } else if ( strcasecmp( s, "referralsPreferred" ) == 0 ) { + return LDAP_REFERRALS_PREFERRED; + + } else if ( strcasecmp( s, "referralsRequired" ) == 0 ) { + return LDAP_REFERRALS_REQUIRED; + } + + return -1; +} +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + +/* + * configuration... + */ + +enum { + CH_CHAINING = 1, + CH_CACHE_URI, + CH_MAX_DEPTH, + CH_RETURN_ERR, + + CH_LAST +}; + +static ConfigDriver chain_cf_gen; +static ConfigCfAdd chain_cfadd; +static ConfigLDAPadd chain_ldadd; +#ifdef SLAP_CONFIG_DELETE +static ConfigLDAPdel chain_lddel; +#endif + +static ConfigTable chaincfg[] = { +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + { "chain-chaining", "args", + 2, 4, 0, ARG_MAGIC|ARG_BERVAL|CH_CHAINING, chain_cf_gen, + "( OLcfgOvAt:3.1 NAME 'olcChainingBehavior' " + "DESC 'Chaining behavior control parameters (draft-sermersheim-ldap-chaining)' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + { "chain-cache-uri", "TRUE/FALSE", + 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|CH_CACHE_URI, chain_cf_gen, + "( OLcfgOvAt:3.2 NAME 'olcChainCacheURI' " + "DESC 'Enables caching of URIs not present in configuration' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "chain-max-depth", "args", + 2, 2, 0, ARG_MAGIC|ARG_INT|CH_MAX_DEPTH, chain_cf_gen, + "( OLcfgOvAt:3.3 NAME 'olcChainMaxReferralDepth' " + "DESC 'max referral depth' " + "SYNTAX OMsInteger " + "EQUALITY integerMatch " + "SINGLE-VALUE )", NULL, NULL }, + { "chain-return-error", "TRUE/FALSE", + 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|CH_RETURN_ERR, chain_cf_gen, + "( OLcfgOvAt:3.4 NAME 'olcChainReturnError' " + "DESC 'Errors are returned instead of the original referral' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs chainocs[] = { + { "( OLcfgOvOc:3.1 " + "NAME 'olcChainConfig' " + "DESC 'Chain configuration' " + "SUP olcOverlayConfig " + "MAY ( " +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + "olcChainingBehavior $ " +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + "olcChainCacheURI $ " + "olcChainMaxReferralDepth $ " + "olcChainReturnError " + ") )", + Cft_Overlay, chaincfg, NULL, chain_cfadd }, + { "( OLcfgOvOc:3.2 " + "NAME 'olcChainDatabase' " + "DESC 'Chain remote server configuration' " + "AUXILIARY )", + Cft_Misc, NULL, chain_ldadd +#ifdef SLAP_CONFIG_DELETE + , NULL, chain_lddel +#endif + }, + { NULL, 0, NULL } +}; + +static int +chain_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca ) +{ + slap_overinst *on; + ldap_chain_t *lc; + + ldapinfo_t *li; + + AttributeDescription *ad = NULL; + Attribute *at; + const char *text; + + int rc; + + if ( p->ce_type != Cft_Overlay + || !p->ce_bi + || p->ce_bi->bi_cf_ocs != chainocs ) + { + return LDAP_CONSTRAINT_VIOLATION; + } + + on = (slap_overinst *)p->ce_bi; + lc = (ldap_chain_t *)on->on_bi.bi_private; + + assert( ca->be == NULL ); + ca->be = (BackendDB *)ch_calloc( 1, sizeof( BackendDB ) ); + + ca->be->bd_info = (BackendInfo *)on; + + rc = slap_str2ad( "olcDbURI", &ad, &text ); + assert( rc == LDAP_SUCCESS ); + + at = attr_find( e->e_attrs, ad ); +#if 0 + if ( lc->lc_common_li == NULL && at != NULL ) { + /* FIXME: we should generate an empty default entry + * if none is supplied */ + Debug( LDAP_DEBUG_ANY, "slapd-chain: " + "first underlying database \"%s\" " + "cannot contain attribute \"%s\".\n", + e->e_name.bv_val, ad->ad_cname.bv_val, 0 ); + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + + } else +#endif + if ( lc->lc_common_li != NULL && lc->lc_common_li != lc->lc_cfg_li && at == NULL ) { + /* FIXME: we should generate an empty default entry + * if none is supplied */ + Debug( LDAP_DEBUG_ANY, "slapd-chain: " + "subsequent underlying database \"%s\" " + "must contain attribute \"%s\".\n", + e->e_name.bv_val, ad->ad_cname.bv_val, 0 ); + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + + if ( lc->lc_common_li == NULL ) { + rc = ldap_chain_db_init_common( ca->be ); + if ( rc != 0 ) + goto fail; + li = ca->be->be_private; + lc->lc_common_li = lc->lc_cfg_li = li; + + } + rc = ldap_chain_db_init_one( ca->be ); + lc->lc_cfg_li = NULL; + + if ( rc != 0 ) { +fail: + Debug( LDAP_DEBUG_ANY, "slapd-chain: " + "unable to init %sunderlying database \"%s\".\n", + lc->lc_common_li == NULL ? "common " : "", e->e_name.bv_val, 0 ); + return LDAP_CONSTRAINT_VIOLATION; + } + + li = ca->be->be_private; + + if ( at ) { + char **urls; + + urls = ldap_str2charray( at->a_vals[ 0 ].bv_val, ", \t" ); + if ( !urls || !urls[0] || urls[1] ) { + ldap_charray_free( urls ); + Debug( LDAP_DEBUG_ANY, "slapd-chain: " + "olcDbURI must contain exactly one url, got %s\n", + at->a_vals[ 0 ].bv_val, 0, 0 ); + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + ldap_charray_free( urls ); + + li->li_uri = ch_strdup( at->a_vals[ 0 ].bv_val ); + value_add_one( &li->li_bvuri, &at->a_vals[ 0 ] ); + if ( avl_insert( &lc->lc_lai.lai_tree, (caddr_t)li, + ldap_chain_uri_cmp, ldap_chain_uri_dup ) ) + { + Debug( LDAP_DEBUG_ANY, "slapd-chain: " + "database \"%s\" insert failed.\n", + e->e_name.bv_val, 0, 0 ); + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + } + + ca->ca_private = on; + +done:; + if ( rc != LDAP_SUCCESS ) { + (void)ldap_chain_db_destroy_one( ca->be, NULL ); + ch_free( ca->be ); + ca->be = NULL; + } + + return rc; +} + +typedef struct ldap_chain_cfadd_apply_t { + Operation *op; + SlapReply *rs; + Entry *p; + ConfigArgs *ca; + int count; +} ldap_chain_cfadd_apply_t; + +static int +ldap_chain_cfadd_apply( void *datum, void *arg ) +{ + ldapinfo_t *li = (ldapinfo_t *)datum; + ldap_chain_cfadd_apply_t *lca = (ldap_chain_cfadd_apply_t *)arg; + + struct berval bv; + + /* FIXME: should not hardcode "olcDatabase" here */ + bv.bv_len = snprintf( lca->ca->cr_msg, sizeof( lca->ca->cr_msg ), + "olcDatabase={%d}%s", lca->count, lback->bi_type ); + bv.bv_val = lca->ca->cr_msg; + + lca->ca->be->be_private = (void *)li; + config_build_entry( lca->op, lca->rs, lca->p->e_private, lca->ca, + &bv, lback->bi_cf_ocs, &chainocs[1] ); + + lca->count++; + + return 0; +} + +static int +chain_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca ) +{ + CfEntryInfo *pe = p->e_private; + slap_overinst *on = (slap_overinst *)pe->ce_bi; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + void *priv = (void *)ca->be->be_private; + + if ( lback->bi_cf_ocs ) { + ldap_chain_cfadd_apply_t lca = { 0 }; + + lca.op = op; + lca.rs = rs; + lca.p = p; + lca.ca = ca; + lca.count = 0; + + (void)ldap_chain_cfadd_apply( (void *)lc->lc_common_li, (void *)&lca ); + + (void)avl_apply( lc->lc_lai.lai_tree, ldap_chain_cfadd_apply, + &lca, 1, AVL_INORDER ); + + ca->be->be_private = priv; + } + + lc->lc_cfg_li = NULL; + + return 0; +} + +#ifdef SLAP_CONFIG_DELETE +static int +chain_lddel( CfEntryInfo *ce, Operation *op ) +{ + CfEntryInfo *pe = ce->ce_parent; + slap_overinst *on = (slap_overinst *)pe->ce_bi; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + ldapinfo_t *li = (ldapinfo_t *) ce->ce_be->be_private; + + if ( li != lc->lc_common_li ) { + if (! avl_delete( &lc->lc_lai.lai_tree, li, ldap_chain_uri_cmp ) ) { + Debug( LDAP_DEBUG_ANY, "slapd-chain: avl_delete failed. " + "\"%s\" not found.\n", li->li_uri, 0, 0 ); + return -1; + } + } else if ( lc->lc_lai.lai_tree ) { + Debug( LDAP_DEBUG_ANY, "slapd-chain: cannot delete first underlying " + "LDAP database when other databases are still present.\n", 0, 0, 0 ); + return -1; + } else { + lc->lc_common_li = NULL; + } + + ce->ce_be->bd_info = lback; + + if ( ce->ce_be->bd_info->bi_db_close ) { + ce->ce_be->bd_info->bi_db_close( ce->ce_be, NULL ); + } + if ( ce->ce_be->bd_info->bi_db_destroy ) { + ce->ce_be->bd_info->bi_db_destroy( ce->ce_be, NULL ); + } + + ch_free(ce->ce_be); + ce->ce_be = NULL; + + return LDAP_SUCCESS; +} +#endif /* SLAP_CONFIG_DELETE */ + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR +static slap_verbmasks chaining_mode[] = { + { BER_BVC("referralsRequired"), LDAP_REFERRALS_REQUIRED }, + { BER_BVC("referralsPreferred"), LDAP_REFERRALS_PREFERRED }, + { BER_BVC("chainingRequired"), LDAP_CHAINING_REQUIRED }, + { BER_BVC("chainingPreferred"), LDAP_CHAINING_PREFERRED }, + { BER_BVNULL, 0 } +}; +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + +static int +chain_cf_gen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + + int rc = 0; + + if ( c->op == SLAP_CONFIG_EMIT ) { + switch( c->type ) { +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + case CH_CHAINING: { + struct berval resolve = BER_BVNULL, + continuation = BER_BVNULL; + + if ( !LDAP_CHAIN_CHAINING( lc ) ) { + return 1; + } + + enum_to_verb( chaining_mode, ( ( lc->lc_chaining_ctrlflag & SLAP_CH_RESOLVE_MASK ) >> SLAP_CH_RESOLVE_SHIFT ), &resolve ); + enum_to_verb( chaining_mode, ( ( lc->lc_chaining_ctrlflag & SLAP_CH_CONTINUATION_MASK ) >> SLAP_CH_CONTINUATION_SHIFT ), &continuation ); + + c->value_bv.bv_len = STRLENOF( "resolve=" ) + resolve.bv_len + + STRLENOF( " " ) + + STRLENOF( "continuation=" ) + continuation.bv_len; + c->value_bv.bv_val = ch_malloc( c->value_bv.bv_len + 1 ); + snprintf( c->value_bv.bv_val, c->value_bv.bv_len + 1, + "resolve=%s continuation=%s", + resolve.bv_val, continuation.bv_val ); + + if ( lc->lc_chaining_ctrl.ldctl_iscritical ) { + c->value_bv.bv_val = ch_realloc( c->value_bv.bv_val, + c->value_bv.bv_len + STRLENOF( " critical" ) + 1 ); + AC_MEMCPY( &c->value_bv.bv_val[ c->value_bv.bv_len ], + " critical", STRLENOF( " critical" ) + 1 ); + c->value_bv.bv_len += STRLENOF( " critical" ); + } + + break; + } +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + + case CH_CACHE_URI: + c->value_int = LDAP_CHAIN_CACHE_URI( lc ); + break; + + case CH_MAX_DEPTH: + c->value_int = lc->lc_max_depth; + break; + + case CH_RETURN_ERR: + c->value_int = LDAP_CHAIN_RETURN_ERR( lc ); + break; + + default: + assert( 0 ); + rc = 1; + } + return rc; + + } else if ( c->op == LDAP_MOD_DELETE ) { + switch( c->type ) { + case CH_CHAINING: + return 1; + + case CH_CACHE_URI: + lc->lc_flags &= ~LDAP_CHAIN_F_CACHE_URI; + break; + + case CH_MAX_DEPTH: + c->value_int = 0; + break; + + case CH_RETURN_ERR: + lc->lc_flags &= ~LDAP_CHAIN_F_RETURN_ERR; + break; + + default: + return 1; + } + return rc; + } + + switch( c->type ) { + case CH_CHAINING: { +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + char **argv = c->argv; + int argc = c->argc; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + int resolve = -1, + continuation = -1, + iscritical = 0; + Operation op = { 0 }; + SlapReply rs = { 0 }; + + lc->lc_chaining_ctrlflag = 0; + + for ( argc--, argv++; argc > 0; argc--, argv++ ) { + if ( strncasecmp( argv[ 0 ], "resolve=", STRLENOF( "resolve=" ) ) == 0 ) { + resolve = str2chain( argv[ 0 ] + STRLENOF( "resolve=" ) ); + if ( resolve == -1 ) { + Debug( LDAP_DEBUG_ANY, "%s: " + "illegal <resolve> value %s " + "in \"chain-chaining>\".\n", + c->log, argv[ 0 ], 0 ); + return 1; + } + + } else if ( strncasecmp( argv[ 0 ], "continuation=", STRLENOF( "continuation=" ) ) == 0 ) { + continuation = str2chain( argv[ 0 ] + STRLENOF( "continuation=" ) ); + if ( continuation == -1 ) { + Debug( LDAP_DEBUG_ANY, "%s: " + "illegal <continuation> value %s " + "in \"chain-chaining\".\n", + c->log, argv[ 0 ], 0 ); + return 1; + } + + } else if ( strcasecmp( argv[ 0 ], "critical" ) == 0 ) { + iscritical = 1; + + } else { + Debug( LDAP_DEBUG_ANY, "%s: " + "unknown option in \"chain-chaining\".\n", + c->log, 0, 0 ); + return 1; + } + } + + if ( resolve != -1 || continuation != -1 ) { + int err; + + if ( resolve == -1 ) { + /* default */ + resolve = SLAP_CHAINING_DEFAULT; + } + + ber_init2( ber, NULL, LBER_USE_DER ); + + err = ber_printf( ber, "{e" /* } */, resolve ); + if ( err == -1 ) { + ber_free( ber, 1 ); + Debug( LDAP_DEBUG_ANY, "%s: " + "chaining behavior control encoding error!\n", + c->log, 0, 0 ); + return 1; + } + + if ( continuation > -1 ) { + err = ber_printf( ber, "e", continuation ); + if ( err == -1 ) { + ber_free( ber, 1 ); + Debug( LDAP_DEBUG_ANY, "%s: " + "chaining behavior control encoding error!\n", + c->log, 0, 0 ); + return 1; + } + } + + err = ber_printf( ber, /* { */ "N}" ); + if ( err == -1 ) { + ber_free( ber, 1 ); + Debug( LDAP_DEBUG_ANY, "%s: " + "chaining behavior control encoding error!\n", + c->log, 0, 0 ); + return 1; + } + + if ( ber_flatten2( ber, &lc->lc_chaining_ctrl.ldctl_value, 0 ) == -1 ) { + exit( EXIT_FAILURE ); + } + + } else { + BER_BVZERO( &lc->lc_chaining_ctrl.ldctl_value ); + } + + lc->lc_chaining_ctrl.ldctl_oid = LDAP_CONTROL_X_CHAINING_BEHAVIOR; + lc->lc_chaining_ctrl.ldctl_iscritical = iscritical; + + if ( ldap_chain_parse_ctrl( &op, &rs, &lc->lc_chaining_ctrl ) != LDAP_SUCCESS ) + { + Debug( LDAP_DEBUG_ANY, "%s: " + "unable to parse chaining control%s%s.\n", + c->log, rs.sr_text ? ": " : "", + rs.sr_text ? rs.sr_text : "" ); + return 1; + } + + lc->lc_chaining_ctrlflag = op.o_chaining; + + lc->lc_flags |= LDAP_CHAIN_F_CHAINING; + + rc = 0; +#else /* ! LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + Debug( LDAP_DEBUG_ANY, "%s: " + "\"chaining\" control unsupported (ignored).\n", + c->log, 0, 0 ); +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + } break; + + case CH_CACHE_URI: + if ( c->value_int ) { + lc->lc_flags |= LDAP_CHAIN_F_CACHE_URI; + } else { + lc->lc_flags &= ~LDAP_CHAIN_F_CACHE_URI; + } + break; + + case CH_MAX_DEPTH: + if ( c->value_int < 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> invalid max referral depth %d", + c->argv[0], c->value_int ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + rc = 1; + break; + } + lc->lc_max_depth = c->value_int; + + case CH_RETURN_ERR: + if ( c->value_int ) { + lc->lc_flags |= LDAP_CHAIN_F_RETURN_ERR; + } else { + lc->lc_flags &= ~LDAP_CHAIN_F_RETURN_ERR; + } + break; + + default: + assert( 0 ); + return 1; + } + return rc; +} + +static int +ldap_chain_db_init( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ldap_chain_t *lc = NULL; + + if ( lback == NULL ) { + lback = backend_info( "ldap" ); + + if ( lback == NULL ) { + return 1; + } + } + + lc = ch_malloc( sizeof( ldap_chain_t ) ); + if ( lc == NULL ) { + return 1; + } + memset( lc, 0, sizeof( ldap_chain_t ) ); + lc->lc_max_depth = 1; + ldap_pvt_thread_mutex_init( &lc->lc_lai.lai_mutex ); + + on->on_bi.bi_private = (void *)lc; + + return 0; +} + +static int +ldap_chain_db_config( + BackendDB *be, + const char *fname, + int lineno, + int argc, + char **argv ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + + int rc = SLAP_CONF_UNKNOWN; + + if ( lc->lc_common_li == NULL ) { + BackendDB db = *be; + ldap_chain_db_init_common( &db ); + lc->lc_common_li = lc->lc_cfg_li = (ldapinfo_t *)db.be_private; + } + + /* Something for the chain database? */ + if ( strncasecmp( argv[ 0 ], "chain-", STRLENOF( "chain-" ) ) == 0 ) { + char *save_argv0 = argv[ 0 ]; + BackendDB db = *be; + static char *allowed_argv[] = { + /* special: put URI here, so in the meanwhile + * it detects whether a new URI is being provided */ + "uri", + "nretries", + "timeout", + /* flags */ + "tls", + /* FIXME: maybe rebind-as-user should be allowed + * only within known URIs... */ + "rebind-as-user", + "chase-referrals", + "t-f-support", + "proxy-whoami", + NULL + }; + int which_argv = -1; + + argv[ 0 ] += STRLENOF( "chain-" ); + + for ( which_argv = 0; allowed_argv[ which_argv ]; which_argv++ ) { + if ( strcasecmp( argv[ 0 ], allowed_argv[ which_argv ] ) == 0 ) { + break; + } + } + + if ( allowed_argv[ which_argv ] == NULL ) { + which_argv = -1; + + if ( lc->lc_cfg_li == lc->lc_common_li ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "\"%s\" only allowed within a URI directive.\n.", + fname, lineno, argv[ 0 ] ); + return 1; + } + } + + if ( which_argv == 0 ) { + rc = ldap_chain_db_init_one( &db ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "underlying slapd-ldap initialization failed.\n.", + fname, lineno, 0 ); + return 1; + } + lc->lc_cfg_li = db.be_private; + } + + /* TODO: add checks on what other slapd-ldap(5) args + * should be put in the template; this is not quite + * harmful, because attributes that shouldn't don't + * get actually used, but the user should at least + * be warned. + */ + + db.bd_info = lback; + db.be_private = (void *)lc->lc_cfg_li; + db.be_cf_ocs = lback->bi_cf_ocs; + + rc = config_generic_wrapper( &db, fname, lineno, argc, argv ); + + argv[ 0 ] = save_argv0; + + if ( which_argv == 0 ) { +private_destroy:; + if ( rc != 0 ) { + db.bd_info = lback; + db.be_private = (void *)lc->lc_cfg_li; + ldap_chain_db_destroy_one( &db, NULL ); + lc->lc_cfg_li = NULL; + } else { + if ( lc->lc_cfg_li->li_bvuri == NULL + || BER_BVISNULL( &lc->lc_cfg_li->li_bvuri[ 0 ] ) + || !BER_BVISNULL( &lc->lc_cfg_li->li_bvuri[ 1 ] ) ) + { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "no URI list allowed in slapo-chain.\n", + fname, lineno, 0 ); + rc = 1; + goto private_destroy; + } + + if ( avl_insert( &lc->lc_lai.lai_tree, + (caddr_t)lc->lc_cfg_li, + ldap_chain_uri_cmp, ldap_chain_uri_dup ) ) + { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "duplicate URI in slapo-chain.\n", + fname, lineno, 0 ); + rc = 1; + goto private_destroy; + } + } + } + } + + return rc; +} + +enum db_which { + db_open = 0, + db_close, + db_destroy, + + db_last +}; + +typedef struct ldap_chain_db_apply_t { + BackendDB *be; + BI_db_func *func; +} ldap_chain_db_apply_t; + +static int +ldap_chain_db_apply( void *datum, void *arg ) +{ + ldapinfo_t *li = (ldapinfo_t *)datum; + ldap_chain_db_apply_t *lca = (ldap_chain_db_apply_t *)arg; + + lca->be->be_private = (void *)li; + + return lca->func( lca->be, NULL ); +} + +static int +ldap_chain_db_func( + BackendDB *be, + enum db_which which +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + + int rc = 0; + + if ( lc ) { + BI_db_func *func = (&lback->bi_db_open)[ which ]; + + if ( func != NULL && lc->lc_common_li != NULL ) { + BackendDB db = *be; + + db.bd_info = lback; + db.be_private = lc->lc_common_li; + + rc = func( &db, NULL ); + + if ( rc != 0 ) { + return rc; + } + + if ( lc->lc_lai.lai_tree != NULL ) { + ldap_chain_db_apply_t lca; + + lca.be = &db; + lca.func = func; + + rc = avl_apply( lc->lc_lai.lai_tree, + ldap_chain_db_apply, (void *)&lca, + 1, AVL_INORDER ) != AVL_NOMORE; + } + } + } + + return rc; +} + +static int +ldap_chain_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + slap_mask_t monitoring; + int rc = 0; + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + rc = overlay_register_control( be, LDAP_CONTROL_X_CHAINING_BEHAVIOR ); + if ( rc != 0 ) { + return rc; + } +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + + if ( lc->lc_common_li == NULL ) { + void *be_private = be->be_private; + ldap_chain_db_init_common( be ); + lc->lc_common_li = lc->lc_cfg_li = (ldapinfo_t *)be->be_private; + be->be_private = be_private; + } + + /* filter out and restore monitoring */ + monitoring = ( SLAP_DBFLAGS( be ) & SLAP_DBFLAG_MONITORING ); + SLAP_DBFLAGS( be ) &= ~SLAP_DBFLAG_MONITORING; + rc = ldap_chain_db_func( be, db_open ); + SLAP_DBFLAGS( be ) |= monitoring; + + return rc; +} + +static int +ldap_chain_db_close( + BackendDB *be, + ConfigReply *cr ) +{ +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR +#ifdef SLAP_CONFIG_DELETE + overlay_unregister_control( be, LDAP_CONTROL_X_CHAINING_BEHAVIOR ); +#endif /* SLAP_CONFIG_DELETE */ +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + return ldap_chain_db_func( be, db_close ); +} + +static int +ldap_chain_db_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + + int rc; + + rc = ldap_chain_db_func( be, db_destroy ); + + if ( lc ) { + avl_free( lc->lc_lai.lai_tree, NULL ); + ldap_pvt_thread_mutex_destroy( &lc->lc_lai.lai_mutex ); + ch_free( lc ); + } + + return rc; +} + +/* + * inits one instance of the slapd-ldap backend, and stores + * the private info in be_private of the arg + */ +static int +ldap_chain_db_init_common( + BackendDB *be ) +{ + BackendInfo *bi = be->bd_info; + ldapinfo_t *li; + int rc; + + be->bd_info = lback; + be->be_private = NULL; + rc = lback->bi_db_init( be, NULL ); + if ( rc != 0 ) { + return rc; + } + li = (ldapinfo_t *)be->be_private; + li->li_urllist_f = NULL; + li->li_urllist_p = NULL; + + be->bd_info = bi; + + return 0; +} + +/* + * inits one instance of the slapd-ldap backend, stores + * the private info in be_private of the arg and fills + * selected fields with data from the template. + * + * NOTE: add checks about the other fields of the template, + * which are ignored and SHOULD NOT be configured by the user. + */ +static int +ldap_chain_db_init_one( + BackendDB *be ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + + BackendInfo *bi = be->bd_info; + ldapinfo_t *li; + + slap_op_t t; + + be->bd_info = lback; + be->be_private = NULL; + t = lback->bi_db_init( be, NULL ); + if ( t != 0 ) { + return t; + } + li = (ldapinfo_t *)be->be_private; + li->li_urllist_f = NULL; + li->li_urllist_p = NULL; + + /* copy common data */ + li->li_nretries = lc->lc_common_li->li_nretries; + li->li_flags = lc->lc_common_li->li_flags; + li->li_version = lc->lc_common_li->li_version; + for ( t = 0; t < SLAP_OP_LAST; t++ ) { + li->li_timeout[ t ] = lc->lc_common_li->li_timeout[ t ]; + } + be->bd_info = bi; + + return 0; +} + +static int +ldap_chain_db_open_one( + BackendDB *be ) +{ + if ( SLAP_DBMONITORING( be ) ) { + ldapinfo_t *li = (ldapinfo_t *)be->be_private; + + if ( li->li_uri == NULL ) { + ber_str2bv( "cn=Common Connections", 0, 1, + &li->li_monitor_info.lmi_conn_rdn ); + ber_str2bv( "cn=Operations on Common Connections", 0, 1, + &li->li_monitor_info.lmi_conn_rdn ); + + } else { + char *ptr; + + li->li_monitor_info.lmi_conn_rdn.bv_len + = STRLENOF( "cn=" ) + strlen( li->li_uri ); + ptr = li->li_monitor_info.lmi_conn_rdn.bv_val + = ch_malloc( li->li_monitor_info.lmi_conn_rdn.bv_len + 1 ); + ptr = lutil_strcopy( ptr, "cn=" ); + ptr = lutil_strcopy( ptr, li->li_uri ); + ptr[ 0 ] = '\0'; + + li->li_monitor_info.lmi_ops_rdn.bv_len + = STRLENOF( "cn=Operations on " ) + strlen( li->li_uri ); + ptr = li->li_monitor_info.lmi_ops_rdn.bv_val + = ch_malloc( li->li_monitor_info.lmi_ops_rdn.bv_len + 1 ); + ptr = lutil_strcopy( ptr, "cn=Operations on " ); + ptr = lutil_strcopy( ptr, li->li_uri ); + ptr[ 0 ] = '\0'; + } + } + + return lback->bi_db_open( be, NULL ); +} + +typedef struct ldap_chain_conn_apply_t { + BackendDB *be; + Connection *conn; +} ldap_chain_conn_apply_t; + +static int +ldap_chain_conn_apply( void *datum, void *arg ) +{ + ldapinfo_t *li = (ldapinfo_t *)datum; + ldap_chain_conn_apply_t *lca = (ldap_chain_conn_apply_t *)arg; + + lca->be->be_private = (void *)li; + + return lback->bi_connection_destroy( lca->be, lca->conn ); +} + +static int +ldap_chain_connection_destroy( + BackendDB *be, + Connection *conn +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + void *private = be->be_private; + ldap_chain_conn_apply_t lca; + int rc; + + be->be_private = NULL; + lca.be = be; + lca.conn = conn; + ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex ); + rc = avl_apply( lc->lc_lai.lai_tree, ldap_chain_conn_apply, + (void *)&lca, 1, AVL_INORDER ) != AVL_NOMORE; + ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex ); + be->be_private = private; + + return rc; +} + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR +static int +ldap_chain_parse_ctrl( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + ber_tag_t tag; + BerElement *ber; + ber_int_t mode, + behavior; + + if ( get_chaining( op ) != SLAP_CONTROL_NONE ) { + rs->sr_text = "Chaining behavior control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( op->o_pagedresults != SLAP_CONTROL_NONE ) { + rs->sr_text = "Chaining behavior control specified with pagedResults control"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) { + mode = (SLAP_CH_RESOLVE_DEFAULT|SLAP_CH_CONTINUATION_DEFAULT); + + } else { + ber_len_t len; + + /* Parse the control value + * ChainingBehavior ::= SEQUENCE { + * resolveBehavior Behavior OPTIONAL, + * continuationBehavior Behavior OPTIONAL } + * + * Behavior :: = ENUMERATED { + * chainingPreferred (0), + * chainingRequired (1), + * referralsPreferred (2), + * referralsRequired (3) } + */ + + ber = ber_init( &ctrl->ldctl_value ); + if( ber == NULL ) { + rs->sr_text = "internal error"; + return LDAP_OTHER; + } + + tag = ber_scanf( ber, "{e" /* } */, &behavior ); + /* FIXME: since the whole SEQUENCE is optional, + * should we accept no enumerations at all? */ + if ( tag != LBER_ENUMERATED ) { + rs->sr_text = "Chaining behavior control: resolveBehavior decoding error"; + return LDAP_PROTOCOL_ERROR; + } + + switch ( behavior ) { + case LDAP_CHAINING_PREFERRED: + mode = SLAP_CH_RESOLVE_CHAINING_PREFERRED; + break; + + case LDAP_CHAINING_REQUIRED: + mode = SLAP_CH_RESOLVE_CHAINING_REQUIRED; + break; + + case LDAP_REFERRALS_PREFERRED: + mode = SLAP_CH_RESOLVE_REFERRALS_PREFERRED; + break; + + case LDAP_REFERRALS_REQUIRED: + mode = SLAP_CH_RESOLVE_REFERRALS_REQUIRED; + break; + + default: + rs->sr_text = "Chaining behavior control: unknown resolveBehavior"; + return LDAP_PROTOCOL_ERROR; + } + + tag = ber_peek_tag( ber, &len ); + if ( tag == LBER_ENUMERATED ) { + tag = ber_scanf( ber, "e", &behavior ); + if ( tag == LBER_ERROR ) { + rs->sr_text = "Chaining behavior control: continuationBehavior decoding error"; + return LDAP_PROTOCOL_ERROR; + } + } + + if ( tag == LBER_DEFAULT ) { + mode |= SLAP_CH_CONTINUATION_DEFAULT; + + } else { + switch ( behavior ) { + case LDAP_CHAINING_PREFERRED: + mode |= SLAP_CH_CONTINUATION_CHAINING_PREFERRED; + break; + + case LDAP_CHAINING_REQUIRED: + mode |= SLAP_CH_CONTINUATION_CHAINING_REQUIRED; + break; + + case LDAP_REFERRALS_PREFERRED: + mode |= SLAP_CH_CONTINUATION_REFERRALS_PREFERRED; + break; + + case LDAP_REFERRALS_REQUIRED: + mode |= SLAP_CH_CONTINUATION_REFERRALS_REQUIRED; + break; + + default: + rs->sr_text = "Chaining behavior control: unknown continuationBehavior"; + return LDAP_PROTOCOL_ERROR; + } + } + + if ( ( ber_scanf( ber, /* { */ "}") ) == LBER_ERROR ) { + rs->sr_text = "Chaining behavior control: decoding error"; + return LDAP_PROTOCOL_ERROR; + } + + (void) ber_free( ber, 1 ); + } + + op->o_chaining = mode | ( ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL ); + + return LDAP_SUCCESS; +} +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + +int +chain_initialize( void ) +{ + int rc; + + /* Make sure we don't exceed the bits reserved for userland */ + config_check_userland( CH_LAST ); + + /* olcDatabaseDummy is defined in slapd, and Windows + will not let us initialize a struct element with a data pointer + from another library, so we have to initialize this element + "by hand". */ + chainocs[1].co_table = olcDatabaseDummy; + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + rc = register_supported_control( LDAP_CONTROL_X_CHAINING_BEHAVIOR, + /* SLAP_CTRL_GLOBAL| */ SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE, NULL, + ldap_chain_parse_ctrl, &sc_chainingBehavior ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "slapd-chain: " + "unable to register chaining behavior control: %d.\n", + rc, 0, 0 ); + return rc; + } +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + + ldapchain.on_bi.bi_type = "chain"; + ldapchain.on_bi.bi_db_init = ldap_chain_db_init; + ldapchain.on_bi.bi_db_config = ldap_chain_db_config; + ldapchain.on_bi.bi_db_open = ldap_chain_db_open; + ldapchain.on_bi.bi_db_close = ldap_chain_db_close; + ldapchain.on_bi.bi_db_destroy = ldap_chain_db_destroy; + + ldapchain.on_bi.bi_connection_destroy = ldap_chain_connection_destroy; + + ldapchain.on_response = ldap_chain_response; + + ldapchain.on_bi.bi_cf_ocs = chainocs; + + rc = config_register_schema( chaincfg, chainocs ); + if ( rc ) { + return rc; + } + + return overlay_register( &ldapchain ); +} + diff --git a/servers/slapd/back-ldap/compare.c b/servers/slapd/back-ldap/compare.c new file mode 100644 index 0000000..64c40cd --- /dev/null +++ b/servers/slapd/back-ldap/compare.c @@ -0,0 +1,88 @@ +/* compare.c - ldap backend compare function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-ldap.h" + +int +ldap_back_compare( + Operation *op, + SlapReply *rs ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + ldapconn_t *lc = NULL; + ber_int_t msgid; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + LDAPControl **ctrls = NULL; + int rc = LDAP_SUCCESS; + + if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + lc = NULL; + goto cleanup; + } + +retry: + ctrls = op->o_ctrls; + rc = ldap_back_controls_add( op, rs, lc, &ctrls ); + if ( rc != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = ldap_compare_ext( lc->lc_ld, op->o_req_dn.bv_val, + op->orc_ava->aa_desc->ad_cname.bv_val, + &op->orc_ava->aa_value, + ctrls, NULL, &msgid ); + rc = ldap_back_op_result( lc, op, rs, msgid, + li->li_timeout[ SLAP_OP_COMPARE ], + ( LDAP_BACK_SENDRESULT | retrying ) ); + if ( rc == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)ldap_back_controls_free( op, rs, &ctrls ); + goto retry; + } + } + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_COMPARE ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + +cleanup: + (void)ldap_back_controls_free( op, rs, &ctrls ); + + if ( lc != NULL ) { + ldap_back_release_conn( li, lc ); + } + + return rs->sr_err; +} diff --git a/servers/slapd/back-ldap/config.c b/servers/slapd/back-ldap/config.c new file mode 100644 index 0000000..1b2c758 --- /dev/null +++ b/servers/slapd/back-ldap/config.c @@ -0,0 +1,2461 @@ +/* config.c - ldap backend configuration file routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/ctype.h> +#include <ac/socket.h> + +#include "slap.h" +#include "config.h" +#include "back-ldap.h" +#include "lutil.h" +#include "ldif.h" + +static SLAP_EXTOP_MAIN_FN ldap_back_exop_whoami; + +static ConfigDriver ldap_back_cf_gen; +static ConfigDriver ldap_pbind_cf_gen; + +enum { + LDAP_BACK_CFG_URI = 1, + LDAP_BACK_CFG_TLS, + LDAP_BACK_CFG_ACL_AUTHCDN, + LDAP_BACK_CFG_ACL_PASSWD, + LDAP_BACK_CFG_ACL_METHOD, + LDAP_BACK_CFG_ACL_BIND, + LDAP_BACK_CFG_IDASSERT_MODE, + LDAP_BACK_CFG_IDASSERT_AUTHCDN, + LDAP_BACK_CFG_IDASSERT_PASSWD, + LDAP_BACK_CFG_IDASSERT_AUTHZFROM, + LDAP_BACK_CFG_IDASSERT_PASSTHRU, + LDAP_BACK_CFG_IDASSERT_METHOD, + LDAP_BACK_CFG_IDASSERT_BIND, + LDAP_BACK_CFG_REBIND, + LDAP_BACK_CFG_CHASE, + LDAP_BACK_CFG_T_F, + LDAP_BACK_CFG_WHOAMI, + LDAP_BACK_CFG_TIMEOUT, + LDAP_BACK_CFG_IDLE_TIMEOUT, + LDAP_BACK_CFG_CONN_TTL, + LDAP_BACK_CFG_NETWORK_TIMEOUT, + LDAP_BACK_CFG_VERSION, + LDAP_BACK_CFG_SINGLECONN, + LDAP_BACK_CFG_USETEMP, + LDAP_BACK_CFG_CONNPOOLMAX, + LDAP_BACK_CFG_CANCEL, + LDAP_BACK_CFG_QUARANTINE, + LDAP_BACK_CFG_ST_REQUEST, + LDAP_BACK_CFG_NOREFS, + LDAP_BACK_CFG_NOUNDEFFILTER, + LDAP_BACK_CFG_ONERR, + + LDAP_BACK_CFG_REWRITE, + LDAP_BACK_CFG_KEEPALIVE, + + LDAP_BACK_CFG_OMIT_UNKNOWN_SCHEMA, + + LDAP_BACK_CFG_LAST +}; + +static ConfigTable ldapcfg[] = { + { "uri", "uri", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_URI, + ldap_back_cf_gen, "( OLcfgDbAt:0.14 " + "NAME 'olcDbURI' " + "DESC 'URI (list) for remote DSA' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "tls", "what", 2, 0, 0, + ARG_MAGIC|LDAP_BACK_CFG_TLS, + ldap_back_cf_gen, "( OLcfgDbAt:3.1 " + "NAME 'olcDbStartTLS' " + "DESC 'StartTLS' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "acl-authcDN", "DN", 2, 2, 0, + ARG_DN|ARG_MAGIC|LDAP_BACK_CFG_ACL_AUTHCDN, + ldap_back_cf_gen, "( OLcfgDbAt:3.2 " + "NAME 'olcDbACLAuthcDn' " + "DESC 'Remote ACL administrative identity' " + "OBSOLETE " + "SYNTAX OMsDN " + "SINGLE-VALUE )", + NULL, NULL }, + /* deprecated, will be removed; aliases "acl-authcDN" */ + { "binddn", "DN", 2, 2, 0, + ARG_DN|ARG_MAGIC|LDAP_BACK_CFG_ACL_AUTHCDN, + ldap_back_cf_gen, NULL, NULL, NULL }, + { "acl-passwd", "cred", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_ACL_PASSWD, + ldap_back_cf_gen, "( OLcfgDbAt:3.3 " + "NAME 'olcDbACLPasswd' " + "DESC 'Remote ACL administrative identity credentials' " + "OBSOLETE " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + /* deprecated, will be removed; aliases "acl-passwd" */ + { "bindpw", "cred", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_ACL_PASSWD, + ldap_back_cf_gen, NULL, NULL, NULL }, + /* deprecated, will be removed; aliases "acl-bind" */ + { "acl-method", "args", 2, 0, 0, + ARG_MAGIC|LDAP_BACK_CFG_ACL_METHOD, + ldap_back_cf_gen, NULL, NULL, NULL }, + { "acl-bind", "args", 2, 0, 0, + ARG_MAGIC|LDAP_BACK_CFG_ACL_BIND, + ldap_back_cf_gen, "( OLcfgDbAt:3.4 " + "NAME 'olcDbACLBind' " + "DESC 'Remote ACL administrative identity auth bind configuration' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "idassert-authcDN", "DN", 2, 2, 0, + ARG_DN|ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_AUTHCDN, + ldap_back_cf_gen, "( OLcfgDbAt:3.5 " + "NAME 'olcDbIDAssertAuthcDn' " + "DESC 'Remote Identity Assertion administrative identity' " + "OBSOLETE " + "SYNTAX OMsDN " + "SINGLE-VALUE )", + NULL, NULL }, + /* deprecated, will be removed; partially aliases "idassert-authcDN" */ + { "proxyauthzdn", "DN", 2, 2, 0, + ARG_DN|ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_AUTHCDN, + ldap_back_cf_gen, NULL, NULL, NULL }, + { "idassert-passwd", "cred", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_PASSWD, + ldap_back_cf_gen, "( OLcfgDbAt:3.6 " + "NAME 'olcDbIDAssertPasswd' " + "DESC 'Remote Identity Assertion administrative identity credentials' " + "OBSOLETE " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + /* deprecated, will be removed; partially aliases "idassert-passwd" */ + { "proxyauthzpw", "cred", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_PASSWD, + ldap_back_cf_gen, NULL, NULL, NULL }, + { "idassert-bind", "args", 2, 0, 0, + ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_BIND, + ldap_back_cf_gen, "( OLcfgDbAt:3.7 " + "NAME 'olcDbIDAssertBind' " + "DESC 'Remote Identity Assertion administrative identity auth bind configuration' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "idassert-method", "args", 2, 0, 0, + ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_METHOD, + ldap_back_cf_gen, NULL, NULL, NULL }, + { "idassert-mode", "mode>|u:<user>|[dn:]<DN", 2, 0, 0, + ARG_STRING|ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_MODE, + ldap_back_cf_gen, "( OLcfgDbAt:3.8 " + "NAME 'olcDbIDAssertMode' " + "DESC 'Remote Identity Assertion mode' " + "OBSOLETE " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE)", + NULL, NULL }, + { "idassert-authzFrom", "authzRule", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_AUTHZFROM, + ldap_back_cf_gen, "( OLcfgDbAt:3.9 " + "NAME 'olcDbIDAssertAuthzFrom' " + "DESC 'Remote Identity Assertion authz rules' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "X-ORDERED 'VALUES' )", + NULL, NULL }, + { "rebind-as-user", "true|FALSE", 1, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_REBIND, + ldap_back_cf_gen, "( OLcfgDbAt:3.10 " + "NAME 'olcDbRebindAsUser' " + "DESC 'Rebind as user' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "chase-referrals", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_CHASE, + ldap_back_cf_gen, "( OLcfgDbAt:3.11 " + "NAME 'olcDbChaseReferrals' " + "DESC 'Chase referrals' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "t-f-support", "true|FALSE|discover", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_T_F, + ldap_back_cf_gen, "( OLcfgDbAt:3.12 " + "NAME 'olcDbTFSupport' " + "DESC 'Absolute filters support' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "proxy-whoami", "true|FALSE", 1, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_WHOAMI, + ldap_back_cf_gen, "( OLcfgDbAt:3.13 " + "NAME 'olcDbProxyWhoAmI' " + "DESC 'Proxy whoAmI exop' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "timeout", "timeout(list)", 2, 0, 0, + ARG_MAGIC|LDAP_BACK_CFG_TIMEOUT, + ldap_back_cf_gen, "( OLcfgDbAt:3.14 " + "NAME 'olcDbTimeout' " + "DESC 'Per-operation timeouts' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "idle-timeout", "timeout", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_IDLE_TIMEOUT, + ldap_back_cf_gen, "( OLcfgDbAt:3.15 " + "NAME 'olcDbIdleTimeout' " + "DESC 'connection idle timeout' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "conn-ttl", "ttl", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_CONN_TTL, + ldap_back_cf_gen, "( OLcfgDbAt:3.16 " + "NAME 'olcDbConnTtl' " + "DESC 'connection ttl' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "network-timeout", "timeout", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_NETWORK_TIMEOUT, + ldap_back_cf_gen, "( OLcfgDbAt:3.17 " + "NAME 'olcDbNetworkTimeout' " + "DESC 'connection network timeout' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "protocol-version", "version", 2, 2, 0, + ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_VERSION, + ldap_back_cf_gen, "( OLcfgDbAt:3.18 " + "NAME 'olcDbProtocolVersion' " + "DESC 'protocol version' " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", + NULL, NULL }, + { "single-conn", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_SINGLECONN, + ldap_back_cf_gen, "( OLcfgDbAt:3.19 " + "NAME 'olcDbSingleConn' " + "DESC 'cache a single connection per identity' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "cancel", "ABANDON|ignore|exop", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_CANCEL, + ldap_back_cf_gen, "( OLcfgDbAt:3.20 " + "NAME 'olcDbCancel' " + "DESC 'abandon/ignore/exop operations when appropriate' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "quarantine", "retrylist", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_QUARANTINE, + ldap_back_cf_gen, "( OLcfgDbAt:3.21 " + "NAME 'olcDbQuarantine' " + "DESC 'Quarantine database if connection fails and retry according to rule' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "use-temporary-conn", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_USETEMP, + ldap_back_cf_gen, "( OLcfgDbAt:3.22 " + "NAME 'olcDbUseTemporaryConn' " + "DESC 'Use temporary connections if the cached one is busy' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "conn-pool-max", "<n>", 2, 2, 0, + ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_CONNPOOLMAX, + ldap_back_cf_gen, "( OLcfgDbAt:3.23 " + "NAME 'olcDbConnectionPoolMax' " + "DESC 'Max size of privileged connections pool' " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", + NULL, NULL }, +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + { "session-tracking-request", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_ST_REQUEST, + ldap_back_cf_gen, "( OLcfgDbAt:3.24 " + "NAME 'olcDbSessionTrackingRequest' " + "DESC 'Add session tracking control to proxied requests' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + { "norefs", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_NOREFS, + ldap_back_cf_gen, "( OLcfgDbAt:3.25 " + "NAME 'olcDbNoRefs' " + "DESC 'Do not return search reference responses' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "noundeffilter", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_NOUNDEFFILTER, + ldap_back_cf_gen, "( OLcfgDbAt:3.26 " + "NAME 'olcDbNoUndefFilter' " + "DESC 'Do not propagate undefined search filters' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "onerr", "CONTINUE|report|stop", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_ONERR, + ldap_back_cf_gen, "( OLcfgDbAt:3.108 " + "NAME 'olcDbOnErr' " + "DESC 'error handling' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "idassert-passThru", "authzRule", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_PASSTHRU, + ldap_back_cf_gen, "( OLcfgDbAt:3.27 " + "NAME 'olcDbIDAssertPassThru' " + "DESC 'Remote Identity Assertion passthru rules' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "X-ORDERED 'VALUES' )", + NULL, NULL }, + + { "suffixmassage", "[virtual]> <real", 2, 3, 0, + ARG_STRING|ARG_MAGIC|LDAP_BACK_CFG_REWRITE, + ldap_back_cf_gen, NULL, NULL, NULL }, + { "map", "attribute|objectClass> [*|<local>] *|<remote", 3, 4, 0, + ARG_STRING|ARG_MAGIC|LDAP_BACK_CFG_REWRITE, + ldap_back_cf_gen, NULL, NULL, NULL }, + { "rewrite", "<arglist>", 2, 4, STRLENOF( "rewrite" ), + ARG_STRING|ARG_MAGIC|LDAP_BACK_CFG_REWRITE, + ldap_back_cf_gen, NULL, NULL, NULL }, + { "omit-unknown-schema", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_OMIT_UNKNOWN_SCHEMA, + ldap_back_cf_gen, "( OLcfgDbAt:3.28 " + "NAME 'olcDbRemoveUnknownSchema' " + "DESC 'Omit unknown schema when returning search results' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "keepalive", "keepalive", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_KEEPALIVE, + ldap_back_cf_gen, "( OLcfgDbAt:3.29 " + "NAME 'olcDbKeepalive' " + "DESC 'TCP keepalive' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED, + NULL, NULL, NULL, NULL } +}; + +static ConfigOCs ldapocs[] = { + { "( OLcfgDbOc:3.1 " + "NAME 'olcLDAPConfig' " + "DESC 'LDAP backend configuration' " + "SUP olcDatabaseConfig " + "MAY ( olcDbURI " + "$ olcDbStartTLS " + "$ olcDbACLAuthcDn " + "$ olcDbACLPasswd " + "$ olcDbACLBind " + "$ olcDbIDAssertAuthcDn " + "$ olcDbIDAssertPasswd " + "$ olcDbIDAssertBind " + "$ olcDbIDAssertMode " + "$ olcDbIDAssertAuthzFrom " + "$ olcDbIDAssertPassThru " + "$ olcDbRebindAsUser " + "$ olcDbChaseReferrals " + "$ olcDbTFSupport " + "$ olcDbProxyWhoAmI " + "$ olcDbTimeout " + "$ olcDbIdleTimeout " + "$ olcDbConnTtl " + "$ olcDbNetworkTimeout " + "$ olcDbProtocolVersion " + "$ olcDbSingleConn " + "$ olcDbCancel " + "$ olcDbQuarantine " + "$ olcDbUseTemporaryConn " + "$ olcDbConnectionPoolMax " +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + "$ olcDbSessionTrackingRequest " +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + "$ olcDbNoRefs " + "$ olcDbNoUndefFilter " + "$ olcDbOnErr " + "$ olcDbKeepalive " + ") )", + Cft_Database, ldapcfg}, + { NULL, 0, NULL } +}; + +static ConfigTable pbindcfg[] = { + { "uri", "uri", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_URI, + ldap_pbind_cf_gen, "( OLcfgDbAt:0.14 " + "NAME 'olcDbURI' " + "DESC 'URI (list) for remote DSA' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "tls", "what", 2, 0, 0, + ARG_MAGIC|LDAP_BACK_CFG_TLS, + ldap_pbind_cf_gen, "( OLcfgDbAt:3.1 " + "NAME 'olcDbStartTLS' " + "DESC 'StartTLS' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "network-timeout", "timeout", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_NETWORK_TIMEOUT, + ldap_pbind_cf_gen, "( OLcfgDbAt:3.17 " + "NAME 'olcDbNetworkTimeout' " + "DESC 'connection network timeout' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "quarantine", "retrylist", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_QUARANTINE, + ldap_pbind_cf_gen, "( OLcfgDbAt:3.21 " + "NAME 'olcDbQuarantine' " + "DESC 'Quarantine database if connection fails and retry according to rule' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED, + NULL, NULL, NULL, NULL } +}; + +static ConfigOCs pbindocs[] = { + { "( OLcfgOvOc:3.3 " + "NAME 'olcPBindConfig' " + "DESC 'Proxy Bind configuration' " + "SUP olcOverlayConfig " + "MUST olcDbURI " + "MAY ( olcDbStartTLS " + "$ olcDbNetworkTimeout " + "$ olcDbQuarantine " + ") )", + Cft_Overlay, pbindcfg}, + { NULL, 0, NULL } +}; + +static slap_verbmasks idassert_mode[] = { + { BER_BVC("self"), LDAP_BACK_IDASSERT_SELF }, + { BER_BVC("anonymous"), LDAP_BACK_IDASSERT_ANONYMOUS }, + { BER_BVC("none"), LDAP_BACK_IDASSERT_NOASSERT }, + { BER_BVC("legacy"), LDAP_BACK_IDASSERT_LEGACY }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks tls_mode[] = { + { BER_BVC( "propagate" ), LDAP_BACK_F_TLS_PROPAGATE_MASK }, + { BER_BVC( "try-propagate" ), LDAP_BACK_F_PROPAGATE_TLS }, + { BER_BVC( "start" ), LDAP_BACK_F_TLS_USE_MASK }, + { BER_BVC( "try-start" ), LDAP_BACK_F_USE_TLS }, + { BER_BVC( "ldaps" ), LDAP_BACK_F_TLS_LDAPS }, + { BER_BVC( "none" ), LDAP_BACK_F_NONE }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks t_f_mode[] = { + { BER_BVC( "yes" ), LDAP_BACK_F_T_F }, + { BER_BVC( "discover" ), LDAP_BACK_F_T_F_DISCOVER }, + { BER_BVC( "no" ), LDAP_BACK_F_NONE }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks cancel_mode[] = { + { BER_BVC( "ignore" ), LDAP_BACK_F_CANCEL_IGNORE }, + { BER_BVC( "exop" ), LDAP_BACK_F_CANCEL_EXOP }, + { BER_BVC( "exop-discover" ), LDAP_BACK_F_CANCEL_EXOP_DISCOVER }, + { BER_BVC( "abandon" ), LDAP_BACK_F_CANCEL_ABANDON }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks onerr_mode[] = { + { BER_BVC( "stop" ), LDAP_BACK_F_ONERR_STOP }, + { BER_BVC( "report" ), LDAP_BACK_F_ONERR_STOP }, /* same behavior */ + { BER_BVC( "continue" ), LDAP_BACK_F_NONE }, + { BER_BVNULL, 0 } +}; + +/* see enum in slap.h */ +static slap_cf_aux_table timeout_table[] = { + { BER_BVC("bind="), SLAP_OP_BIND * sizeof( time_t ), 'u', 0, NULL }, + /* unbind makes no sense */ + { BER_BVC("add="), SLAP_OP_ADD * sizeof( time_t ), 'u', 0, NULL }, + { BER_BVC("delete="), SLAP_OP_DELETE * sizeof( time_t ), 'u', 0, NULL }, + { BER_BVC("modrdn="), SLAP_OP_MODRDN * sizeof( time_t ), 'u', 0, NULL }, + { BER_BVC("modify="), SLAP_OP_MODIFY * sizeof( time_t ), 'u', 0, NULL }, + { BER_BVC("compare="), SLAP_OP_COMPARE * sizeof( time_t ), 'u', 0, NULL }, + { BER_BVC("search="), SLAP_OP_SEARCH * sizeof( time_t ), 'u', 0, NULL }, + /* abandon makes little sense */ +#if 0 /* not implemented yet */ + { BER_BVC("extended="), SLAP_OP_EXTENDED * sizeof( time_t ), 'u', 0, NULL }, +#endif + { BER_BVNULL, 0, 0, 0, NULL } +}; + +int +slap_retry_info_parse( + char *in, + slap_retry_info_t *ri, + char *buf, + ber_len_t buflen ) +{ + char **retrylist = NULL; + int rc = 0; + int i; + + slap_str2clist( &retrylist, in, " ;" ); + if ( retrylist == NULL ) { + return 1; + } + + for ( i = 0; retrylist[ i ] != NULL; i++ ) + /* count */ ; + + ri->ri_interval = ch_calloc( sizeof( time_t ), i + 1 ); + ri->ri_num = ch_calloc( sizeof( int ), i + 1 ); + + for ( i = 0; retrylist[ i ] != NULL; i++ ) { + unsigned long t; + char *sep = strchr( retrylist[ i ], ',' ); + + if ( sep == NULL ) { + snprintf( buf, buflen, + "missing comma in retry pattern #%d \"%s\"", + i, retrylist[ i ] ); + rc = 1; + goto done; + } + + *sep++ = '\0'; + + if ( lutil_parse_time( retrylist[ i ], &t ) ) { + snprintf( buf, buflen, + "unable to parse interval #%d \"%s\"", + i, retrylist[ i ] ); + rc = 1; + goto done; + } + ri->ri_interval[ i ] = (time_t)t; + + if ( strcmp( sep, "+" ) == 0 ) { + if ( retrylist[ i + 1 ] != NULL ) { + snprintf( buf, buflen, + "extra cruft after retry pattern " + "#%d \"%s,+\" with \"forever\" mark", + i, retrylist[ i ] ); + rc = 1; + goto done; + } + ri->ri_num[ i ] = SLAP_RETRYNUM_FOREVER; + + } else if ( lutil_atoi( &ri->ri_num[ i ], sep ) ) { + snprintf( buf, buflen, + "unable to parse retry num #%d \"%s\"", + i, sep ); + rc = 1; + goto done; + } + } + + ri->ri_num[ i ] = SLAP_RETRYNUM_TAIL; + + ri->ri_idx = 0; + ri->ri_count = 0; + ri->ri_last = (time_t)(-1); + +done:; + ldap_charray_free( retrylist ); + + if ( rc ) { + slap_retry_info_destroy( ri ); + } + + return rc; +} + +int +slap_retry_info_unparse( + slap_retry_info_t *ri, + struct berval *bvout ) +{ + char buf[ BUFSIZ * 2 ], + *ptr = buf; + int i, len, restlen = (int) sizeof( buf ); + struct berval bv; + + assert( ri != NULL ); + assert( bvout != NULL ); + + BER_BVZERO( bvout ); + + for ( i = 0; ri->ri_num[ i ] != SLAP_RETRYNUM_TAIL; i++ ) { + if ( i > 0 ) { + if ( --restlen <= 0 ) { + return 1; + } + *ptr++ = ';'; + } + + if ( lutil_unparse_time( ptr, restlen, ri->ri_interval[i] ) < 0 ) { + return 1; + } + len = (int) strlen( ptr ); + if ( (restlen -= len + 1) <= 0 ) { + return 1; + } + ptr += len; + *ptr++ = ','; + + if ( ri->ri_num[i] == SLAP_RETRYNUM_FOREVER ) { + if ( --restlen <= 0 ) { + return 1; + } + *ptr++ = '+'; + + } else { + len = snprintf( ptr, restlen, "%d", ri->ri_num[i] ); + if ( (restlen -= len) <= 0 || len < 0 ) { + return 1; + } + ptr += len; + } + } + + bv.bv_val = buf; + bv.bv_len = ptr - buf; + ber_dupbv( bvout, &bv ); + + return 0; +} + +void +slap_retry_info_destroy( + slap_retry_info_t *ri ) +{ + assert( ri != NULL ); + + assert( ri->ri_interval != NULL ); + ch_free( ri->ri_interval ); + ri->ri_interval = NULL; + + assert( ri->ri_num != NULL ); + ch_free( ri->ri_num ); + ri->ri_num = NULL; +} + +int +slap_idassert_authzfrom_parse( ConfigArgs *c, slap_idassert_t *si ) +{ + struct berval bv; + struct berval in; + int rc; + + if ( strcmp( c->argv[ 1 ], "*" ) == 0 + || strcmp( c->argv[ 1 ], "dn:*" ) == 0 + || strcasecmp( c->argv[ 1 ], "dn.regex:.*" ) == 0 ) + { + if ( si->si_authz != NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"idassert-authzFrom <authz>\": " + "\"%s\" conflicts with existing authz rules", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + si->si_flags |= LDAP_BACK_AUTH_AUTHZ_ALL; + + return 0; + + } else if ( ( si->si_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"idassert-authzFrom <authz>\": " + "\"<authz>\" conflicts with \"*\"" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + ber_str2bv( c->argv[ 1 ], 0, 0, &in ); + rc = authzNormalize( 0, NULL, NULL, &in, &bv, NULL ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"idassert-authzFrom <authz>\": " + "invalid syntax" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + if ( c->valx == -1 ) { + ber_bvarray_add( &si->si_authz, &bv ); + + } else { + int i = 0; + if ( si->si_authz != NULL ) { + for ( ; !BER_BVISNULL( &si->si_authz[ i ] ); i++ ) + ; + } + + if ( i <= c->valx ) { + ber_bvarray_add( &si->si_authz, &bv ); + + } else { + BerVarray tmp = ber_memrealloc( si->si_authz, + sizeof( struct berval )*( i + 2 ) ); + if ( tmp == NULL ) { + return -1; + } + si->si_authz = tmp; + for ( ; i > c->valx; i-- ) { + si->si_authz[ i ] = si->si_authz[ i - 1 ]; + } + si->si_authz[ c->valx ] = bv; + } + } + + return 0; +} + +static int +slap_idassert_passthru_parse( ConfigArgs *c, slap_idassert_t *si ) +{ + struct berval bv; + struct berval in; + int rc; + + ber_str2bv( c->argv[ 1 ], 0, 0, &in ); + rc = authzNormalize( 0, NULL, NULL, &in, &bv, NULL ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"idassert-passThru <authz>\": " + "invalid syntax" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + if ( c->valx == -1 ) { + ber_bvarray_add( &si->si_passthru, &bv ); + + } else { + int i = 0; + if ( si->si_passthru != NULL ) { + for ( ; !BER_BVISNULL( &si->si_passthru[ i ] ); i++ ) + ; + } + + if ( i <= c->valx ) { + ber_bvarray_add( &si->si_passthru, &bv ); + + } else { + BerVarray tmp = ber_memrealloc( si->si_passthru, + sizeof( struct berval )*( i + 2 ) ); + if ( tmp == NULL ) { + return -1; + } + si->si_passthru = tmp; + for ( ; i > c->valx; i-- ) { + si->si_passthru[ i ] = si->si_passthru[ i - 1 ]; + } + si->si_passthru[ c->valx ] = bv; + } + } + + return 0; +} + +int +slap_idassert_parse( ConfigArgs *c, slap_idassert_t *si ) +{ + int i; + + for ( i = 1; i < c->argc; i++ ) { + if ( strncasecmp( c->argv[ i ], "mode=", STRLENOF( "mode=" ) ) == 0 ) { + char *argvi = c->argv[ i ] + STRLENOF( "mode=" ); + int j; + + j = verb_to_mask( argvi, idassert_mode ); + if ( BER_BVISNULL( &idassert_mode[ j ].word ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"idassert-bind <args>\": " + "unknown mode \"%s\"", + argvi ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + si->si_mode = idassert_mode[ j ].mask; + + } else if ( strncasecmp( c->argv[ i ], "authz=", STRLENOF( "authz=" ) ) == 0 ) { + char *argvi = c->argv[ i ] + STRLENOF( "authz=" ); + + if ( strcasecmp( argvi, "native" ) == 0 ) { + if ( si->si_bc.sb_method != LDAP_AUTH_SASL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"idassert-bind <args>\": " + "authz=\"native\" incompatible " + "with auth method" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + si->si_flags |= LDAP_BACK_AUTH_NATIVE_AUTHZ; + + } else if ( strcasecmp( argvi, "proxyAuthz" ) == 0 ) { + si->si_flags &= ~LDAP_BACK_AUTH_NATIVE_AUTHZ; + + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"idassert-bind <args>\": " + "unknown authz \"%s\"", + argvi ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + } else if ( strncasecmp( c->argv[ i ], "flags=", STRLENOF( "flags=" ) ) == 0 ) { + char *argvi = c->argv[ i ] + STRLENOF( "flags=" ); + char **flags = ldap_str2charray( argvi, "," ); + int j, err = 0; + + if ( flags == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"idassert-bind <args>\": " + "unable to parse flags \"%s\"", + argvi ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + for ( j = 0; flags[ j ] != NULL; j++ ) { + + if ( strcasecmp( flags[ j ], "override" ) == 0 ) { + si->si_flags |= LDAP_BACK_AUTH_OVERRIDE; + + } else if ( strcasecmp( flags[ j ], "prescriptive" ) == 0 ) { + si->si_flags |= LDAP_BACK_AUTH_PRESCRIPTIVE; + + } else if ( strcasecmp( flags[ j ], "non-prescriptive" ) == 0 ) { + si->si_flags &= ( ~LDAP_BACK_AUTH_PRESCRIPTIVE ); + + } else if ( strcasecmp( flags[ j ], "obsolete-proxy-authz" ) == 0 ) { + if ( si->si_flags & LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND ) { + Debug( LDAP_DEBUG_ANY, + "%s: \"obsolete-proxy-authz\" flag " + "in \"idassert-mode <args>\" " + "incompatible with previously issued \"obsolete-encoding-workaround\" flag.\n", + c->log, 0, 0 ); + err = 1; + break; + + } else { + si->si_flags |= LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ; + } + + } else if ( strcasecmp( flags[ j ], "obsolete-encoding-workaround" ) == 0 ) { + if ( si->si_flags & LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ ) { + Debug( LDAP_DEBUG_ANY, + "%s: \"obsolete-encoding-workaround\" flag " + "in \"idassert-mode <args>\" " + "incompatible with previously issued \"obsolete-proxy-authz\" flag.\n", + c->log, 0, 0 ); + err = 1; + break; + + } else { + si->si_flags |= LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND; + } + + } else if ( strcasecmp( flags[ j ], "proxy-authz-critical" ) == 0 ) { + si->si_flags |= LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL; + + } else if ( strcasecmp( flags[ j ], "proxy-authz-non-critical" ) == 0 ) { + si->si_flags &= ~LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL; + + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"idassert-bind <args>\": " + "unknown flag \"%s\"", + flags[ j ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + err = 1; + break; + } + } + + ldap_charray_free( flags ); + if ( err ) { + return 1; + } + + } else if ( bindconf_parse( c->argv[ i ], &si->si_bc ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"idassert-bind <args>\": " + "unable to parse field \"%s\"", + c->argv[ i ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + } + + if ( si->si_bc.sb_method == LDAP_AUTH_SIMPLE ) { + if ( BER_BVISNULL( &si->si_bc.sb_binddn ) + || BER_BVISNULL( &si->si_bc.sb_cred ) ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"idassert-bind <args>\": " + "SIMPLE needs \"binddn\" and \"credentials\"" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + } + + bindconf_tls_defaults( &si->si_bc ); + + return 0; +} + +/* NOTE: temporary, until back-meta is ported to back-config */ +int +slap_idassert_passthru_parse_cf( const char *fname, int lineno, const char *arg, slap_idassert_t *si ) +{ + ConfigArgs c = { 0 }; + char *argv[ 3 ]; + + snprintf( c.log, sizeof( c.log ), "%s: line %d", fname, lineno ); + c.argc = 2; + c.argv = argv; + argv[ 0 ] = "idassert-passThru"; + argv[ 1 ] = (char *)arg; + argv[ 2 ] = NULL; + + return slap_idassert_passthru_parse( &c, si ); +} + +static int +ldap_back_cf_gen( ConfigArgs *c ) +{ + ldapinfo_t *li = ( ldapinfo_t * )c->be->be_private; + int rc = 0; + int i; + + if ( c->op == SLAP_CONFIG_EMIT ) { + struct berval bv = BER_BVNULL; + + if ( li == NULL ) { + return 1; + } + + switch( c->type ) { + case LDAP_BACK_CFG_URI: + if ( li->li_uri != NULL ) { + struct berval bv, bv2; + + ber_str2bv( li->li_uri, 0, 0, &bv ); + bv2.bv_len = bv.bv_len + STRLENOF( "\"\"" ); + bv2.bv_val = ch_malloc( bv2.bv_len + 1 ); + snprintf( bv2.bv_val, bv2.bv_len + 1, + "\"%s\"", bv.bv_val ); + ber_bvarray_add( &c->rvalue_vals, &bv2 ); + + } else { + rc = 1; + } + break; + + case LDAP_BACK_CFG_TLS: { + struct berval bc = BER_BVNULL, bv2; + enum_to_verb( tls_mode, ( li->li_flags & LDAP_BACK_F_TLS_MASK ), &bv ); + assert( !BER_BVISNULL( &bv ) ); + bindconf_tls_unparse( &li->li_tls, &bc ); + + if ( !BER_BVISEMPTY( &bc )) { + bv2.bv_len = bv.bv_len + bc.bv_len + 1; + bv2.bv_val = ch_malloc( bv2.bv_len + 1 ); + strcpy( bv2.bv_val, bv.bv_val ); + bv2.bv_val[bv.bv_len] = ' '; + strcpy( &bv2.bv_val[bv.bv_len + 1], bc.bv_val ); + ber_bvarray_add( &c->rvalue_vals, &bv2 ); + + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + ber_memfree( bc.bv_val ); + } + break; + + case LDAP_BACK_CFG_ACL_AUTHCDN: + case LDAP_BACK_CFG_ACL_PASSWD: + case LDAP_BACK_CFG_ACL_METHOD: + /* handled by LDAP_BACK_CFG_ACL_BIND */ + rc = 1; + break; + + case LDAP_BACK_CFG_ACL_BIND: { + int i; + + if ( li->li_acl_authmethod == LDAP_AUTH_NONE ) { + return 1; + } + + bindconf_unparse( &li->li_acl, &bv ); + + for ( i = 0; isspace( (unsigned char) bv.bv_val[ i ] ); i++ ) + /* count spaces */ ; + + if ( i ) { + bv.bv_len -= i; + AC_MEMCPY( bv.bv_val, &bv.bv_val[ i ], + bv.bv_len + 1 ); + } + + ber_bvarray_add( &c->rvalue_vals, &bv ); + break; + } + + case LDAP_BACK_CFG_IDASSERT_MODE: + case LDAP_BACK_CFG_IDASSERT_AUTHCDN: + case LDAP_BACK_CFG_IDASSERT_PASSWD: + case LDAP_BACK_CFG_IDASSERT_METHOD: + /* handled by LDAP_BACK_CFG_IDASSERT_BIND */ + rc = 1; + break; + + case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: + case LDAP_BACK_CFG_IDASSERT_PASSTHRU: { + BerVarray *bvp; + int i; + struct berval bv = BER_BVNULL; + char buf[SLAP_TEXT_BUFLEN]; + + switch ( c->type ) { + case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: bvp = &li->li_idassert_authz; break; + case LDAP_BACK_CFG_IDASSERT_PASSTHRU: bvp = &li->li_idassert_passthru; break; + default: assert( 0 ); break; + } + + if ( *bvp == NULL ) { + if ( bvp == &li->li_idassert_authz + && ( li->li_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) ) + { + BER_BVSTR( &bv, "*" ); + value_add_one( &c->rvalue_vals, &bv ); + + } else { + rc = 1; + } + break; + } + + for ( i = 0; !BER_BVISNULL( &((*bvp)[ i ]) ); i++ ) { + char *ptr; + int len = snprintf( buf, sizeof( buf ), SLAP_X_ORDERED_FMT, i ); + bv.bv_len = ((*bvp)[ i ]).bv_len + len; + bv.bv_val = ber_memrealloc( bv.bv_val, bv.bv_len + 1 ); + ptr = bv.bv_val; + ptr = lutil_strcopy( ptr, buf ); + ptr = lutil_strncopy( ptr, ((*bvp)[ i ]).bv_val, ((*bvp)[ i ]).bv_len ); + value_add_one( &c->rvalue_vals, &bv ); + } + if ( bv.bv_val ) { + ber_memfree( bv.bv_val ); + } + break; + } + + case LDAP_BACK_CFG_IDASSERT_BIND: { + int i; + struct berval bc = BER_BVNULL; + char *ptr; + + if ( li->li_idassert_authmethod == LDAP_AUTH_NONE ) { + return 1; + } + + if ( li->li_idassert_authmethod != LDAP_AUTH_NONE ) { + ber_len_t len; + + switch ( li->li_idassert_mode ) { + case LDAP_BACK_IDASSERT_OTHERID: + case LDAP_BACK_IDASSERT_OTHERDN: + break; + + default: { + struct berval mode = BER_BVNULL; + + enum_to_verb( idassert_mode, li->li_idassert_mode, &mode ); + if ( BER_BVISNULL( &mode ) ) { + /* there's something wrong... */ + assert( 0 ); + rc = 1; + + } else { + bv.bv_len = STRLENOF( "mode=" ) + mode.bv_len; + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + + ptr = lutil_strcopy( bv.bv_val, "mode=" ); + ptr = lutil_strcopy( ptr, mode.bv_val ); + } + break; + } + } + + if ( li->li_idassert_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) { + len = bv.bv_len + STRLENOF( "authz=native" ); + + if ( !BER_BVISEMPTY( &bv ) ) { + len += STRLENOF( " " ); + } + + bv.bv_val = ch_realloc( bv.bv_val, len + 1 ); + + ptr = &bv.bv_val[ bv.bv_len ]; + + if ( !BER_BVISEMPTY( &bv ) ) { + ptr = lutil_strcopy( ptr, " " ); + } + + (void)lutil_strcopy( ptr, "authz=native" ); + } + + len = bv.bv_len + STRLENOF( "flags=non-prescriptive,override,obsolete-encoding-workaround,proxy-authz-non-critical" ); + /* flags */ + if ( !BER_BVISEMPTY( &bv ) ) { + len += STRLENOF( " " ); + } + + bv.bv_val = ch_realloc( bv.bv_val, len + 1 ); + + ptr = &bv.bv_val[ bv.bv_len ]; + + if ( !BER_BVISEMPTY( &bv ) ) { + ptr = lutil_strcopy( ptr, " " ); + } + + ptr = lutil_strcopy( ptr, "flags=" ); + + if ( li->li_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) { + ptr = lutil_strcopy( ptr, "prescriptive" ); + } else { + ptr = lutil_strcopy( ptr, "non-prescriptive" ); + } + + if ( li->li_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) { + ptr = lutil_strcopy( ptr, ",override" ); + } + + if ( li->li_idassert_flags & LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ ) { + ptr = lutil_strcopy( ptr, ",obsolete-proxy-authz" ); + + } else if ( li->li_idassert_flags & LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND ) { + ptr = lutil_strcopy( ptr, ",obsolete-encoding-workaround" ); + } + + if ( li->li_idassert_flags & LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL ) { + ptr = lutil_strcopy( ptr, ",proxy-authz-critical" ); + + } else { + ptr = lutil_strcopy( ptr, ",proxy-authz-non-critical" ); + } + + bv.bv_len = ( ptr - bv.bv_val ); + /* end-of-flags */ + } + + bindconf_unparse( &li->li_idassert.si_bc, &bc ); + + if ( !BER_BVISNULL( &bv ) ) { + ber_len_t len = bv.bv_len + bc.bv_len; + + bv.bv_val = ch_realloc( bv.bv_val, len + 1 ); + + assert( bc.bv_val[ 0 ] == ' ' ); + + ptr = lutil_strcopy( &bv.bv_val[ bv.bv_len ], bc.bv_val ); + free( bc.bv_val ); + bv.bv_len = ptr - bv.bv_val; + + } else { + for ( i = 0; isspace( (unsigned char) bc.bv_val[ i ] ); i++ ) + /* count spaces */ ; + + if ( i ) { + bc.bv_len -= i; + AC_MEMCPY( bc.bv_val, &bc.bv_val[ i ], bc.bv_len + 1 ); + } + + bv = bc; + } + + ber_bvarray_add( &c->rvalue_vals, &bv ); + + break; + } + + case LDAP_BACK_CFG_REBIND: + c->value_int = LDAP_BACK_SAVECRED( li ); + break; + + case LDAP_BACK_CFG_CHASE: + c->value_int = LDAP_BACK_CHASE_REFERRALS( li ); + break; + + case LDAP_BACK_CFG_T_F: + enum_to_verb( t_f_mode, (li->li_flags & LDAP_BACK_F_T_F_MASK2), &bv ); + if ( BER_BVISNULL( &bv ) ) { + /* there's something wrong... */ + assert( 0 ); + rc = 1; + + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + break; + + case LDAP_BACK_CFG_WHOAMI: + c->value_int = LDAP_BACK_PROXY_WHOAMI( li ); + break; + + case LDAP_BACK_CFG_TIMEOUT: + BER_BVZERO( &bv ); + + for ( i = 0; i < SLAP_OP_LAST; i++ ) { + if ( li->li_timeout[ i ] != 0 ) { + break; + } + } + + if ( i == SLAP_OP_LAST ) { + return 1; + } + + slap_cf_aux_table_unparse( li->li_timeout, &bv, timeout_table ); + + if ( BER_BVISNULL( &bv ) ) { + return 1; + } + + for ( i = 0; isspace( (unsigned char) bv.bv_val[ i ] ); i++ ) + /* count spaces */ ; + + if ( i ) { + bv.bv_len -= i; + AC_MEMCPY( bv.bv_val, &bv.bv_val[ i ], + bv.bv_len + 1 ); + } + + ber_bvarray_add( &c->rvalue_vals, &bv ); + break; + + case LDAP_BACK_CFG_IDLE_TIMEOUT: { + char buf[ SLAP_TEXT_BUFLEN ]; + + if ( li->li_idle_timeout == 0 ) { + return 1; + } + + lutil_unparse_time( buf, sizeof( buf ), li->li_idle_timeout ); + ber_str2bv( buf, 0, 0, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + } break; + + case LDAP_BACK_CFG_CONN_TTL: { + char buf[ SLAP_TEXT_BUFLEN ]; + + if ( li->li_conn_ttl == 0 ) { + return 1; + } + + lutil_unparse_time( buf, sizeof( buf ), li->li_conn_ttl ); + ber_str2bv( buf, 0, 0, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + } break; + + case LDAP_BACK_CFG_NETWORK_TIMEOUT: { + char buf[ SLAP_TEXT_BUFLEN ]; + + if ( li->li_network_timeout == 0 ) { + return 1; + } + + snprintf( buf, sizeof( buf ), "%ld", + (long)li->li_network_timeout ); + ber_str2bv( buf, 0, 0, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + } break; + + case LDAP_BACK_CFG_VERSION: + if ( li->li_version == 0 ) { + return 1; + } + + c->value_int = li->li_version; + break; + + case LDAP_BACK_CFG_SINGLECONN: + c->value_int = LDAP_BACK_SINGLECONN( li ); + break; + + case LDAP_BACK_CFG_USETEMP: + c->value_int = LDAP_BACK_USE_TEMPORARIES( li ); + break; + + case LDAP_BACK_CFG_CONNPOOLMAX: + c->value_int = li->li_conn_priv_max; + break; + + case LDAP_BACK_CFG_CANCEL: { + slap_mask_t mask = LDAP_BACK_F_CANCEL_MASK2; + + if ( LDAP_BACK_CANCEL_DISCOVER( li ) ) { + mask &= ~LDAP_BACK_F_CANCEL_EXOP; + } + enum_to_verb( cancel_mode, (li->li_flags & mask), &bv ); + if ( BER_BVISNULL( &bv ) ) { + /* there's something wrong... */ + assert( 0 ); + rc = 1; + + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + } break; + + case LDAP_BACK_CFG_QUARANTINE: + if ( !LDAP_BACK_QUARANTINE( li ) ) { + rc = 1; + break; + } + + rc = slap_retry_info_unparse( &li->li_quarantine, &bv ); + if ( rc == 0 ) { + ber_bvarray_add( &c->rvalue_vals, &bv ); + } + break; + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + case LDAP_BACK_CFG_ST_REQUEST: + c->value_int = LDAP_BACK_ST_REQUEST( li ); + break; +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + + case LDAP_BACK_CFG_NOREFS: + c->value_int = LDAP_BACK_NOREFS( li ); + break; + + case LDAP_BACK_CFG_NOUNDEFFILTER: + c->value_int = LDAP_BACK_NOUNDEFFILTER( li ); + break; + + case LDAP_BACK_CFG_OMIT_UNKNOWN_SCHEMA: + c->value_int = LDAP_BACK_OMIT_UNKNOWN_SCHEMA( li ); + break; + + case LDAP_BACK_CFG_ONERR: + enum_to_verb( onerr_mode, li->li_flags & LDAP_BACK_F_ONERR_STOP, &bv ); + if ( BER_BVISNULL( &bv )) { + rc = 1; + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + break; + + case LDAP_BACK_CFG_KEEPALIVE: { + struct berval bv; + char buf[AC_LINE_MAX]; + bv.bv_len = AC_LINE_MAX; + bv.bv_val = &buf[0]; + slap_keepalive_parse(&bv, &li->li_tls.sb_keepalive, 0, 0, 1); + value_add_one( &c->rvalue_vals, &bv ); + break; + } + + default: + /* FIXME: we need to handle all... */ + assert( 0 ); + break; + } + return rc; + + } else if ( c->op == LDAP_MOD_DELETE ) { + switch( c->type ) { + case LDAP_BACK_CFG_URI: + if ( li->li_uri != NULL ) { + ch_free( li->li_uri ); + li->li_uri = NULL; + + assert( li->li_bvuri != NULL ); + ber_bvarray_free( li->li_bvuri ); + li->li_bvuri = NULL; + } + + /* better cleanup the cached connections... */ + /* NOTE: don't worry about locking: if we got here, + * other threads are suspended. */ + if ( li->li_conninfo.lai_tree != NULL ) { + avl_free( li->li_conninfo.lai_tree, ldap_back_conn_free ); + li->li_conninfo.lai_tree = NULL; + } + + break; + + case LDAP_BACK_CFG_TLS: + rc = 1; + break; + + case LDAP_BACK_CFG_ACL_AUTHCDN: + case LDAP_BACK_CFG_ACL_PASSWD: + case LDAP_BACK_CFG_ACL_METHOD: + /* handled by LDAP_BACK_CFG_ACL_BIND */ + rc = 1; + break; + + case LDAP_BACK_CFG_ACL_BIND: + bindconf_free( &li->li_acl ); + break; + + case LDAP_BACK_CFG_IDASSERT_MODE: + case LDAP_BACK_CFG_IDASSERT_AUTHCDN: + case LDAP_BACK_CFG_IDASSERT_PASSWD: + case LDAP_BACK_CFG_IDASSERT_METHOD: + /* handled by LDAP_BACK_CFG_IDASSERT_BIND */ + rc = 1; + break; + + case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: + case LDAP_BACK_CFG_IDASSERT_PASSTHRU: { + BerVarray *bvp; + + switch ( c->type ) { + case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: bvp = &li->li_idassert_authz; break; + case LDAP_BACK_CFG_IDASSERT_PASSTHRU: bvp = &li->li_idassert_passthru; break; + default: assert( 0 ); break; + } + + if ( c->valx < 0 ) { + if ( *bvp != NULL ) { + ber_bvarray_free( *bvp ); + *bvp = NULL; + } + + } else { + int i; + + if ( *bvp == NULL ) { + rc = 1; + break; + } + + for ( i = 0; !BER_BVISNULL( &((*bvp)[ i ]) ); i++ ) + ; + + if ( i >= c->valx ) { + rc = 1; + break; + } + ber_memfree( ((*bvp)[ c->valx ]).bv_val ); + for ( i = c->valx; !BER_BVISNULL( &((*bvp)[ i + 1 ]) ); i++ ) { + (*bvp)[ i ] = (*bvp)[ i + 1 ]; + } + BER_BVZERO( &((*bvp)[ i ]) ); + } + } break; + + case LDAP_BACK_CFG_IDASSERT_BIND: + bindconf_free( &li->li_idassert.si_bc ); + memset( &li->li_idassert, 0, sizeof( slap_idassert_t ) ); + break; + + case LDAP_BACK_CFG_REBIND: + case LDAP_BACK_CFG_CHASE: + case LDAP_BACK_CFG_T_F: + case LDAP_BACK_CFG_WHOAMI: + case LDAP_BACK_CFG_CANCEL: + rc = 1; + break; + + case LDAP_BACK_CFG_TIMEOUT: + for ( i = 0; i < SLAP_OP_LAST; i++ ) { + li->li_timeout[ i ] = 0; + } + break; + + case LDAP_BACK_CFG_IDLE_TIMEOUT: + li->li_idle_timeout = 0; + break; + + case LDAP_BACK_CFG_CONN_TTL: + li->li_conn_ttl = 0; + break; + + case LDAP_BACK_CFG_NETWORK_TIMEOUT: + li->li_network_timeout = 0; + break; + + case LDAP_BACK_CFG_VERSION: + li->li_version = 0; + break; + + case LDAP_BACK_CFG_SINGLECONN: + li->li_flags &= ~LDAP_BACK_F_SINGLECONN; + break; + + case LDAP_BACK_CFG_USETEMP: + li->li_flags &= ~LDAP_BACK_F_USE_TEMPORARIES; + break; + + case LDAP_BACK_CFG_CONNPOOLMAX: + li->li_conn_priv_max = LDAP_BACK_CONN_PRIV_MIN; + break; + + case LDAP_BACK_CFG_QUARANTINE: + if ( !LDAP_BACK_QUARANTINE( li ) ) { + break; + } + + slap_retry_info_destroy( &li->li_quarantine ); + ldap_pvt_thread_mutex_destroy( &li->li_quarantine_mutex ); + li->li_isquarantined = 0; + li->li_flags &= ~LDAP_BACK_F_QUARANTINE; + break; + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + case LDAP_BACK_CFG_ST_REQUEST: + li->li_flags &= ~LDAP_BACK_F_ST_REQUEST; + break; +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + + case LDAP_BACK_CFG_NOREFS: + li->li_flags &= ~LDAP_BACK_F_NOREFS; + break; + + case LDAP_BACK_CFG_NOUNDEFFILTER: + li->li_flags &= ~LDAP_BACK_F_NOUNDEFFILTER; + break; + + case LDAP_BACK_CFG_OMIT_UNKNOWN_SCHEMA: + li->li_flags &= ~LDAP_BACK_F_OMIT_UNKNOWN_SCHEMA; + break; + + case LDAP_BACK_CFG_ONERR: + li->li_flags &= ~LDAP_BACK_F_ONERR_STOP; + break; + + case LDAP_BACK_CFG_KEEPALIVE: + li->li_tls.sb_keepalive.sk_idle = 0; + li->li_tls.sb_keepalive.sk_probes = 0; + li->li_tls.sb_keepalive.sk_interval = 0; + break; + + default: + /* FIXME: we need to handle all... */ + assert( 0 ); + break; + } + return rc; + + } + + switch( c->type ) { + case LDAP_BACK_CFG_URI: { + LDAPURLDesc *tmpludp, *lud; + char **urllist = NULL; + int urlrc = LDAP_URL_SUCCESS, i; + + if ( li->li_uri != NULL ) { + ch_free( li->li_uri ); + li->li_uri = NULL; + + assert( li->li_bvuri != NULL ); + ber_bvarray_free( li->li_bvuri ); + li->li_bvuri = NULL; + } + + /* PARANOID: DN and more are not required nor allowed */ + urlrc = ldap_url_parselist_ext( &lud, c->argv[ 1 ], ", \t", LDAP_PVT_URL_PARSE_NONE ); + if ( urlrc != LDAP_URL_SUCCESS ) { + char *why; + + switch ( urlrc ) { + case LDAP_URL_ERR_MEM: + why = "no memory"; + break; + case LDAP_URL_ERR_PARAM: + why = "parameter is bad"; + break; + case LDAP_URL_ERR_BADSCHEME: + why = "URL doesn't begin with \"[c]ldap[si]://\""; + break; + case LDAP_URL_ERR_BADENCLOSURE: + why = "URL is missing trailing \">\""; + break; + case LDAP_URL_ERR_BADURL: + why = "URL is bad"; + break; + case LDAP_URL_ERR_BADHOST: + why = "host/port is bad"; + break; + case LDAP_URL_ERR_BADATTRS: + why = "bad (or missing) attributes"; + break; + case LDAP_URL_ERR_BADSCOPE: + why = "scope string is invalid (or missing)"; + break; + case LDAP_URL_ERR_BADFILTER: + why = "bad or missing filter"; + break; + case LDAP_URL_ERR_BADEXTS: + why = "bad or missing extensions"; + break; + default: + why = "unknown reason"; + break; + } + snprintf( c->cr_msg, sizeof( c->cr_msg), + "unable to parse uri \"%s\" " + "in \"uri <uri>\" line: %s", + c->value_string, why ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + urlrc = 1; + goto done_url; + } + + for ( i = 0, tmpludp = lud; + tmpludp; + i++, tmpludp = tmpludp->lud_next ) + { + if ( ( tmpludp->lud_dn != NULL + && tmpludp->lud_dn[0] != '\0' ) + || tmpludp->lud_attrs != NULL + /* || tmpludp->lud_scope != LDAP_SCOPE_DEFAULT */ + || tmpludp->lud_filter != NULL + || tmpludp->lud_exts != NULL ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "warning, only protocol, " + "host and port allowed " + "in \"uri <uri>\" statement " + "for uri #%d of \"%s\"", + i, c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + } + } + + for ( i = 0, tmpludp = lud; + tmpludp; + i++, tmpludp = tmpludp->lud_next ) + /* just count */ + ; + urllist = ch_calloc( sizeof( char * ), i + 1 ); + + for ( i = 0, tmpludp = lud; + tmpludp; + i++, tmpludp = tmpludp->lud_next ) + { + LDAPURLDesc tmplud; + + tmplud = *tmpludp; + tmplud.lud_dn = ""; + tmplud.lud_attrs = NULL; + tmplud.lud_filter = NULL; + if ( !ldap_is_ldapi_url( tmplud.lud_scheme ) ) { + tmplud.lud_exts = NULL; + tmplud.lud_crit_exts = 0; + } + + urllist[ i ] = ldap_url_desc2str( &tmplud ); + + if ( urllist[ i ] == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "unable to rebuild uri " + "in \"uri <uri>\" statement " + "for \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + urlrc = 1; + goto done_url; + } + } + + li->li_uri = ldap_charray2str( urllist, " " ); + for ( i = 0; urllist[ i ] != NULL; i++ ) { + struct berval bv; + + ber_str2bv( urllist[ i ], 0, 0, &bv ); + ber_bvarray_add( &li->li_bvuri, &bv ); + urllist[ i ] = NULL; + } + ldap_memfree( urllist ); + urllist = NULL; + +done_url:; + if ( urllist ) { + ldap_charray_free( urllist ); + } + if ( lud ) { + ldap_free_urllist( lud ); + } + if ( urlrc != LDAP_URL_SUCCESS ) { + return 1; + } + break; + } + + case LDAP_BACK_CFG_TLS: + i = verb_to_mask( c->argv[1], tls_mode ); + if ( BER_BVISNULL( &tls_mode[i].word ) ) { + return 1; + } + li->li_flags &= ~LDAP_BACK_F_TLS_MASK; + li->li_flags |= tls_mode[i].mask; + if ( c->argc > 2 ) { + for ( i=2; i<c->argc; i++ ) { + if ( bindconf_tls_parse( c->argv[i], &li->li_tls )) + return 1; + } + bindconf_tls_defaults( &li->li_tls ); + } + break; + + case LDAP_BACK_CFG_ACL_AUTHCDN: + switch ( li->li_acl_authmethod ) { + case LDAP_AUTH_NONE: + li->li_acl_authmethod = LDAP_AUTH_SIMPLE; + break; + + case LDAP_AUTH_SIMPLE: + break; + + default: + snprintf( c->cr_msg, sizeof( c->cr_msg), + "\"acl-authcDN <DN>\" incompatible " + "with auth method %d", + li->li_acl_authmethod ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + if ( !BER_BVISNULL( &li->li_acl_authcDN ) ) { + free( li->li_acl_authcDN.bv_val ); + } + ber_memfree_x( c->value_dn.bv_val, NULL ); + li->li_acl_authcDN = c->value_ndn; + BER_BVZERO( &c->value_dn ); + BER_BVZERO( &c->value_ndn ); + break; + + case LDAP_BACK_CFG_ACL_PASSWD: + switch ( li->li_acl_authmethod ) { + case LDAP_AUTH_NONE: + li->li_acl_authmethod = LDAP_AUTH_SIMPLE; + break; + + case LDAP_AUTH_SIMPLE: + break; + + default: + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"acl-passwd <cred>\" incompatible " + "with auth method %d", + li->li_acl_authmethod ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + if ( !BER_BVISNULL( &li->li_acl_passwd ) ) { + free( li->li_acl_passwd.bv_val ); + } + ber_str2bv( c->argv[ 1 ], 0, 1, &li->li_acl_passwd ); + break; + + case LDAP_BACK_CFG_ACL_METHOD: + case LDAP_BACK_CFG_ACL_BIND: + for ( i = 1; i < c->argc; i++ ) { + if ( bindconf_parse( c->argv[ i ], &li->li_acl ) ) { + return 1; + } + } + bindconf_tls_defaults( &li->li_acl ); + break; + + case LDAP_BACK_CFG_IDASSERT_MODE: + i = verb_to_mask( c->argv[1], idassert_mode ); + if ( BER_BVISNULL( &idassert_mode[i].word ) ) { + if ( strncasecmp( c->argv[1], "u:", STRLENOF( "u:" ) ) == 0 ) { + li->li_idassert_mode = LDAP_BACK_IDASSERT_OTHERID; + ber_str2bv( c->argv[1], 0, 1, &li->li_idassert_authzID ); + li->li_idassert_authzID.bv_val[ 0 ] = 'u'; + + } else { + struct berval id, ndn; + + ber_str2bv( c->argv[1], 0, 0, &id ); + + if ( strncasecmp( c->argv[1], "dn:", STRLENOF( "dn:" ) ) == 0 ) { + id.bv_val += STRLENOF( "dn:" ); + id.bv_len -= STRLENOF( "dn:" ); + } + + rc = dnNormalize( 0, NULL, NULL, &id, &ndn, NULL ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: idassert ID \"%s\" is not a valid DN\n", + c->fname, c->lineno, c->argv[1] ); + return 1; + } + + li->li_idassert_authzID.bv_len = STRLENOF( "dn:" ) + ndn.bv_len; + li->li_idassert_authzID.bv_val = ch_malloc( li->li_idassert_authzID.bv_len + 1 ); + AC_MEMCPY( li->li_idassert_authzID.bv_val, "dn:", STRLENOF( "dn:" ) ); + AC_MEMCPY( &li->li_idassert_authzID.bv_val[ STRLENOF( "dn:" ) ], ndn.bv_val, ndn.bv_len + 1 ); + ch_free( ndn.bv_val ); + + li->li_idassert_mode = LDAP_BACK_IDASSERT_OTHERDN; + } + + } else { + li->li_idassert_mode = idassert_mode[i].mask; + } + + if ( c->argc > 2 ) { + int i; + + for ( i = 2; i < c->argc; i++ ) { + if ( strcasecmp( c->argv[ i ], "override" ) == 0 ) { + li->li_idassert_flags |= LDAP_BACK_AUTH_OVERRIDE; + + } else if ( strcasecmp( c->argv[ i ], "prescriptive" ) == 0 ) { + li->li_idassert_flags |= LDAP_BACK_AUTH_PRESCRIPTIVE; + + } else if ( strcasecmp( c->argv[ i ], "non-prescriptive" ) == 0 ) { + li->li_idassert_flags &= ( ~LDAP_BACK_AUTH_PRESCRIPTIVE ); + + } else if ( strcasecmp( c->argv[ i ], "obsolete-proxy-authz" ) == 0 ) { + if ( li->li_idassert_flags & LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: \"obsolete-proxy-authz\" flag " + "in \"idassert-mode <args>\" " + "incompatible with previously issued \"obsolete-encoding-workaround\" flag.\n", + c->fname, c->lineno, 0 ); + return 1; + } + li->li_idassert_flags |= LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ; + + } else if ( strcasecmp( c->argv[ i ], "obsolete-encoding-workaround" ) == 0 ) { + if ( li->li_idassert_flags & LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: \"obsolete-encoding-workaround\" flag " + "in \"idassert-mode <args>\" " + "incompatible with previously issued \"obsolete-proxy-authz\" flag.\n", + c->fname, c->lineno, 0 ); + return 1; + } + li->li_idassert_flags |= LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND; + + } else { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: unknown flag #%d " + "in \"idassert-mode <args> " + "[<flags>]\" line.\n", + c->fname, c->lineno, i - 2 ); + return 1; + } + } + } + break; + + case LDAP_BACK_CFG_IDASSERT_AUTHCDN: + switch ( li->li_idassert_authmethod ) { + case LDAP_AUTH_NONE: + li->li_idassert_authmethod = LDAP_AUTH_SIMPLE; + break; + + case LDAP_AUTH_SIMPLE: + break; + + default: + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"idassert-authcDN <DN>\" incompatible " + "with auth method %d", + li->li_idassert_authmethod ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + if ( !BER_BVISNULL( &li->li_idassert_authcDN ) ) { + free( li->li_idassert_authcDN.bv_val ); + } + ber_memfree_x( c->value_dn.bv_val, NULL ); + li->li_idassert_authcDN = c->value_ndn; + BER_BVZERO( &c->value_dn ); + BER_BVZERO( &c->value_ndn ); + break; + + case LDAP_BACK_CFG_IDASSERT_PASSWD: + switch ( li->li_idassert_authmethod ) { + case LDAP_AUTH_NONE: + li->li_idassert_authmethod = LDAP_AUTH_SIMPLE; + break; + + case LDAP_AUTH_SIMPLE: + break; + + default: + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"idassert-passwd <cred>\" incompatible " + "with auth method %d", + li->li_idassert_authmethod ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + if ( !BER_BVISNULL( &li->li_idassert_passwd ) ) { + free( li->li_idassert_passwd.bv_val ); + } + ber_str2bv( c->argv[ 1 ], 0, 1, &li->li_idassert_passwd ); + break; + + case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: + rc = slap_idassert_authzfrom_parse( c, &li->li_idassert ); + break; + + case LDAP_BACK_CFG_IDASSERT_PASSTHRU: + rc = slap_idassert_passthru_parse( c, &li->li_idassert ); + break; + + case LDAP_BACK_CFG_IDASSERT_METHOD: + /* no longer supported */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"idassert-method <args>\": " + "no longer supported; use \"idassert-bind\"" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + + case LDAP_BACK_CFG_IDASSERT_BIND: + rc = slap_idassert_parse( c, &li->li_idassert ); + break; + + case LDAP_BACK_CFG_REBIND: + if ( c->argc == 1 || c->value_int ) { + li->li_flags |= LDAP_BACK_F_SAVECRED; + + } else { + li->li_flags &= ~LDAP_BACK_F_SAVECRED; + } + break; + + case LDAP_BACK_CFG_CHASE: + if ( c->argc == 1 || c->value_int ) { + li->li_flags |= LDAP_BACK_F_CHASE_REFERRALS; + + } else { + li->li_flags &= ~LDAP_BACK_F_CHASE_REFERRALS; + } + break; + + case LDAP_BACK_CFG_T_F: { + slap_mask_t mask; + + i = verb_to_mask( c->argv[1], t_f_mode ); + if ( BER_BVISNULL( &t_f_mode[i].word ) ) { + return 1; + } + + mask = t_f_mode[i].mask; + + if ( LDAP_BACK_ISOPEN( li ) + && mask == LDAP_BACK_F_T_F_DISCOVER + && !LDAP_BACK_T_F( li ) ) + { + slap_bindconf sb = { BER_BVNULL }; + int rc; + + if ( li->li_uri == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "need URI to discover absolute filters support " + "in \"t-f-support discover\"" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + ber_str2bv( li->li_uri, 0, 0, &sb.sb_uri ); + sb.sb_version = li->li_version; + sb.sb_method = LDAP_AUTH_SIMPLE; + BER_BVSTR( &sb.sb_binddn, "" ); + + rc = slap_discover_feature( &sb, + slap_schema.si_ad_supportedFeatures->ad_cname.bv_val, + LDAP_FEATURE_ABSOLUTE_FILTERS ); + if ( rc == LDAP_COMPARE_TRUE ) { + mask |= LDAP_BACK_F_T_F; + } + } + + li->li_flags &= ~LDAP_BACK_F_T_F_MASK2; + li->li_flags |= mask; + } break; + + case LDAP_BACK_CFG_WHOAMI: + if ( c->argc == 1 || c->value_int ) { + li->li_flags |= LDAP_BACK_F_PROXY_WHOAMI; + load_extop( (struct berval *)&slap_EXOP_WHOAMI, + 0, ldap_back_exop_whoami ); + + } else { + li->li_flags &= ~LDAP_BACK_F_PROXY_WHOAMI; + } + break; + + case LDAP_BACK_CFG_TIMEOUT: + for ( i = 1; i < c->argc; i++ ) { + if ( isdigit( (unsigned char) c->argv[ i ][ 0 ] ) ) { + int j; + unsigned u; + + if ( lutil_atoux( &u, c->argv[ i ], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "unable to parse timeout \"%s\"", + c->argv[ i ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + for ( j = 0; j < SLAP_OP_LAST; j++ ) { + li->li_timeout[ j ] = u; + } + + continue; + } + + if ( slap_cf_aux_table_parse( c->argv[ i ], li->li_timeout, timeout_table, "slapd-ldap timeout" ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "unable to parse timeout \"%s\"", + c->argv[ i ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + } + break; + + case LDAP_BACK_CFG_IDLE_TIMEOUT: { + unsigned long t; + + if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "unable to parse idle timeout \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + li->li_idle_timeout = (time_t)t; + } break; + + case LDAP_BACK_CFG_CONN_TTL: { + unsigned long t; + + if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "unable to parse conn ttl\"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + li->li_conn_ttl = (time_t)t; + } break; + + case LDAP_BACK_CFG_NETWORK_TIMEOUT: { + unsigned long t; + + if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "unable to parse network timeout \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + li->li_network_timeout = (time_t)t; + } break; + + case LDAP_BACK_CFG_VERSION: + if ( c->value_int != 0 && ( c->value_int < LDAP_VERSION_MIN || c->value_int > LDAP_VERSION_MAX ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unsupported version \"%s\" " + "in \"protocol-version <version>\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + li->li_version = c->value_int; + break; + + case LDAP_BACK_CFG_SINGLECONN: + if ( c->value_int ) { + li->li_flags |= LDAP_BACK_F_SINGLECONN; + + } else { + li->li_flags &= ~LDAP_BACK_F_SINGLECONN; + } + break; + + case LDAP_BACK_CFG_USETEMP: + if ( c->value_int ) { + li->li_flags |= LDAP_BACK_F_USE_TEMPORARIES; + + } else { + li->li_flags &= ~LDAP_BACK_F_USE_TEMPORARIES; + } + break; + + case LDAP_BACK_CFG_CONNPOOLMAX: + if ( c->value_int < LDAP_BACK_CONN_PRIV_MIN + || c->value_int > LDAP_BACK_CONN_PRIV_MAX ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "invalid max size " "of privileged " + "connections pool \"%s\" " + "in \"conn-pool-max <n> " + "(must be between %d and %d)\"", + c->argv[ 1 ], + LDAP_BACK_CONN_PRIV_MIN, + LDAP_BACK_CONN_PRIV_MAX ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + li->li_conn_priv_max = c->value_int; + break; + + case LDAP_BACK_CFG_CANCEL: { + slap_mask_t mask; + + i = verb_to_mask( c->argv[1], cancel_mode ); + if ( BER_BVISNULL( &cancel_mode[i].word ) ) { + return 1; + } + + mask = cancel_mode[i].mask; + + if ( LDAP_BACK_ISOPEN( li ) + && mask == LDAP_BACK_F_CANCEL_EXOP_DISCOVER + && !LDAP_BACK_CANCEL( li ) ) + { + slap_bindconf sb = { BER_BVNULL }; + int rc; + + if ( li->li_uri == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "need URI to discover \"cancel\" support " + "in \"cancel exop-discover\"" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + ber_str2bv( li->li_uri, 0, 0, &sb.sb_uri ); + sb.sb_version = li->li_version; + sb.sb_method = LDAP_AUTH_SIMPLE; + BER_BVSTR( &sb.sb_binddn, "" ); + + rc = slap_discover_feature( &sb, + slap_schema.si_ad_supportedExtension->ad_cname.bv_val, + LDAP_EXOP_CANCEL ); + if ( rc == LDAP_COMPARE_TRUE ) { + mask |= LDAP_BACK_F_CANCEL_EXOP; + } + } + + li->li_flags &= ~LDAP_BACK_F_CANCEL_MASK2; + li->li_flags |= mask; + } break; + + case LDAP_BACK_CFG_QUARANTINE: + if ( LDAP_BACK_QUARANTINE( li ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "quarantine already defined" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + rc = slap_retry_info_parse( c->argv[1], &li->li_quarantine, + c->cr_msg, sizeof( c->cr_msg ) ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + + } else { + ldap_pvt_thread_mutex_init( &li->li_quarantine_mutex ); + /* give it a chance to retry if the pattern gets reset + * via back-config */ + li->li_isquarantined = 0; + li->li_flags |= LDAP_BACK_F_QUARANTINE; + } + break; + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + case LDAP_BACK_CFG_ST_REQUEST: + if ( c->value_int ) { + li->li_flags |= LDAP_BACK_F_ST_REQUEST; + + } else { + li->li_flags &= ~LDAP_BACK_F_ST_REQUEST; + } + break; +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + + case LDAP_BACK_CFG_NOREFS: + if ( c->value_int ) { + li->li_flags |= LDAP_BACK_F_NOREFS; + + } else { + li->li_flags &= ~LDAP_BACK_F_NOREFS; + } + break; + + case LDAP_BACK_CFG_NOUNDEFFILTER: + if ( c->value_int ) { + li->li_flags |= LDAP_BACK_F_NOUNDEFFILTER; + + } else { + li->li_flags &= ~LDAP_BACK_F_NOUNDEFFILTER; + } + break; + + case LDAP_BACK_CFG_ONERR: + /* onerr? */ + i = verb_to_mask( c->argv[1], onerr_mode ); + if ( BER_BVISNULL( &onerr_mode[i].word ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s unknown argument \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + li->li_flags &= ~LDAP_BACK_F_ONERR_STOP; + li->li_flags |= onerr_mode[i].mask; + break; + + case LDAP_BACK_CFG_REWRITE: + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "rewrite/remap capabilities have been moved " + "to the \"rwm\" overlay; see slapo-rwm(5) " + "for details (hint: add \"overlay rwm\" " + "and prefix all directives with \"rwm-\")" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + + case LDAP_BACK_CFG_OMIT_UNKNOWN_SCHEMA: + if ( c->value_int ) { + li->li_flags |= LDAP_BACK_F_OMIT_UNKNOWN_SCHEMA; + + } else { + li->li_flags &= ~LDAP_BACK_F_OMIT_UNKNOWN_SCHEMA; + } + break; + + case LDAP_BACK_CFG_KEEPALIVE: + slap_keepalive_parse( ber_bvstrdup(c->argv[1]), + &li->li_tls.sb_keepalive, 0, 0, 0); + break; + + default: + /* FIXME: try to catch inconsistencies */ + assert( 0 ); + break; + } + + return rc; +} + +int +ldap_back_init_cf( BackendInfo *bi ) +{ + int rc; + AttributeDescription *ad = NULL; + const char *text; + + /* Make sure we don't exceed the bits reserved for userland */ + config_check_userland( LDAP_BACK_CFG_LAST ); + + bi->bi_cf_ocs = ldapocs; + + rc = config_register_schema( ldapcfg, ldapocs ); + if ( rc ) { + return rc; + } + + /* setup olcDbAclPasswd and olcDbIDAssertPasswd + * to be base64-encoded when written in LDIF form; + * basically, we don't care if it fails */ + rc = slap_str2ad( "olcDbACLPasswd", &ad, &text ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, "config_back_initialize: " + "warning, unable to get \"olcDbACLPasswd\" " + "attribute description: %d: %s\n", + rc, text, 0 ); + } else { + (void)ldif_must_b64_encode_register( ad->ad_cname.bv_val, + ad->ad_type->sat_oid ); + } + + ad = NULL; + rc = slap_str2ad( "olcDbIDAssertPasswd", &ad, &text ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, "config_back_initialize: " + "warning, unable to get \"olcDbIDAssertPasswd\" " + "attribute description: %d: %s\n", + rc, text, 0 ); + } else { + (void)ldif_must_b64_encode_register( ad->ad_cname.bv_val, + ad->ad_type->sat_oid ); + } + + return 0; +} + +static int +ldap_pbind_cf_gen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + void *private = c->be->be_private; + int rc; + + c->be->be_private = on->on_bi.bi_private; + rc = ldap_back_cf_gen( c ); + c->be->be_private = private; + return rc; +} + +int +ldap_pbind_init_cf( BackendInfo *bi ) +{ + bi->bi_cf_ocs = pbindocs; + + return config_register_schema( pbindcfg, pbindocs ); +} + +static int +ldap_back_exop_whoami( + Operation *op, + SlapReply *rs ) +{ + struct berval *bv = NULL; + + if ( op->oq_extended.rs_reqdata != NULL ) { + /* no request data should be provided */ + rs->sr_text = "no request data expected"; + return rs->sr_err = LDAP_PROTOCOL_ERROR; + } + + Statslog( LDAP_DEBUG_STATS, "%s WHOAMI\n", + op->o_log_prefix, 0, 0, 0, 0 ); + + rs->sr_err = backend_check_restrictions( op, rs, + (struct berval *)&slap_EXOP_WHOAMI ); + if( rs->sr_err != LDAP_SUCCESS ) return rs->sr_err; + + /* if auth'd by back-ldap and request is proxied, forward it */ + if ( op->o_conn->c_authz_backend + && !strcmp( op->o_conn->c_authz_backend->be_type, "ldap" ) + && !dn_match( &op->o_ndn, &op->o_conn->c_ndn ) ) + { + ldapconn_t *lc = NULL; + LDAPControl c, *ctrls[2] = {NULL, NULL}; + LDAPMessage *res; + Operation op2 = *op; + ber_int_t msgid; + int doretry = 1; + char *ptr; + + ctrls[0] = &c; + op2.o_ndn = op->o_conn->c_ndn; + if ( !ldap_back_dobind( &lc, &op2, rs, LDAP_BACK_SENDERR ) ) { + return -1; + } + c.ldctl_oid = LDAP_CONTROL_PROXY_AUTHZ; + c.ldctl_iscritical = 1; + c.ldctl_value.bv_val = op->o_tmpalloc( + op->o_ndn.bv_len + STRLENOF( "dn:" ) + 1, + op->o_tmpmemctx ); + c.ldctl_value.bv_len = op->o_ndn.bv_len + 3; + ptr = c.ldctl_value.bv_val; + ptr = lutil_strcopy( ptr, "dn:" ); + ptr = lutil_strncopy( ptr, op->o_ndn.bv_val, op->o_ndn.bv_len ); + ptr[ 0 ] = '\0'; + +retry: + rs->sr_err = ldap_whoami( lc->lc_ld, ctrls, NULL, &msgid ); + if ( rs->sr_err == LDAP_SUCCESS ) { + /* by now, make sure no timeout is used (ITS#6282) */ + struct timeval tv = { -1, 0 }; + if ( ldap_result( lc->lc_ld, msgid, LDAP_MSG_ALL, &tv, &res ) == -1 ) { + ldap_get_option( lc->lc_ld, LDAP_OPT_ERROR_NUMBER, + &rs->sr_err ); + if ( rs->sr_err == LDAP_SERVER_DOWN && doretry ) { + doretry = 0; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + goto retry; + } + } + + } else { + /* NOTE: are we sure "bv" will be malloc'ed + * with the appropriate memory? */ + rs->sr_err = ldap_parse_whoami( lc->lc_ld, res, &bv ); + ldap_msgfree(res); + } + } + op->o_tmpfree( c.ldctl_value.bv_val, op->o_tmpmemctx ); + if ( rs->sr_err != LDAP_SUCCESS ) { + rs->sr_err = slap_map_api2result( rs ); + } + + if ( lc != NULL ) { + ldap_back_release_conn( (ldapinfo_t *)op2.o_bd->be_private, lc ); + } + + } else { + /* else just do the same as before */ + bv = (struct berval *) ch_malloc( sizeof( struct berval ) ); + if ( !BER_BVISEMPTY( &op->o_dn ) ) { + bv->bv_len = op->o_dn.bv_len + STRLENOF( "dn:" ); + bv->bv_val = ch_malloc( bv->bv_len + 1 ); + AC_MEMCPY( bv->bv_val, "dn:", STRLENOF( "dn:" ) ); + AC_MEMCPY( &bv->bv_val[ STRLENOF( "dn:" ) ], op->o_dn.bv_val, + op->o_dn.bv_len ); + bv->bv_val[ bv->bv_len ] = '\0'; + + } else { + bv->bv_len = 0; + bv->bv_val = NULL; + } + } + + rs->sr_rspdata = bv; + return rs->sr_err; +} + + diff --git a/servers/slapd/back-ldap/delete.c b/servers/slapd/back-ldap/delete.c new file mode 100644 index 0000000..d5b5d33 --- /dev/null +++ b/servers/slapd/back-ldap/delete.c @@ -0,0 +1,85 @@ +/* delete.c - ldap backend delete function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-ldap.h" + +int +ldap_back_delete( + Operation *op, + SlapReply *rs ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + ldapconn_t *lc = NULL; + ber_int_t msgid; + LDAPControl **ctrls = NULL; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + int rc = LDAP_SUCCESS; + + if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + return rs->sr_err; + } + +retry: + ctrls = op->o_ctrls; + rc = ldap_back_controls_add( op, rs, lc, &ctrls ); + if ( rc != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = ldap_delete_ext( lc->lc_ld, op->o_req_dn.bv_val, + ctrls, NULL, &msgid ); + rc = ldap_back_op_result( lc, op, rs, msgid, + li->li_timeout[ SLAP_OP_DELETE ], + ( LDAP_BACK_SENDRESULT | retrying ) ); + if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)ldap_back_controls_free( op, rs, &ctrls ); + goto retry; + } + } + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_DELETE ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + +cleanup: + (void)ldap_back_controls_free( op, rs, &ctrls ); + + if ( lc != NULL ) { + ldap_back_release_conn( li, lc ); + } + + return rs->sr_err; +} diff --git a/servers/slapd/back-ldap/distproc.c b/servers/slapd/back-ldap/distproc.c new file mode 100644 index 0000000..a84b024 --- /dev/null +++ b/servers/slapd/back-ldap/distproc.c @@ -0,0 +1,1017 @@ +/* distproc.c - implement distributed procedures */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2021 The OpenLDAP Foundation. + * Portions Copyright 2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + * Based on back-ldap and slapo-chain, developed by Howard Chu + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" + +#ifdef SLAP_DISTPROC + +#include "back-ldap.h" + +#include "config.h" + +/* + * From <draft-sermersheim-ldap-distproc> + * + + ContinuationReference ::= SET { + referralURI [0] SET SIZE (1..MAX) OF URI, + localReference [2] LDAPDN, + referenceType [3] ReferenceType, + remainingName [4] RelativeLDAPDN OPTIONAL, + searchScope [5] SearchScope OPTIONAL, + searchedSubtrees [6] SearchedSubtrees OPTIONAL, + failedName [7] LDAPDN OPTIONAL, + ... } + + ReferenceType ::= ENUMERATED { + superior (0), + subordinate (1), + cross (2), + nonSpecificSubordinate (3), + supplier (4), + master (5), + immediateSuperior (6), + self (7), + ... } + + SearchScope ::= ENUMERATED { + baseObject (0), + singleLevel (1), + wholeSubtree (2), + subordinateSubtree (3), + ... } + + SearchedSubtrees ::= SET OF RelativeLDAPDN + + LDAPDN, RelativeLDAPDN, and LDAPString, are defined in [RFC2251]. + + */ + +typedef enum ReferenceType_t { + LDAP_DP_RT_UNKNOWN = -1, + LDAP_DP_RT_SUPERIOR = 0, + LDAP_DP_RT_SUBORDINATE = 1, + LDAP_DP_RT_CROSS = 2, + LDAP_DP_RT_NONSPECIFICSUBORDINATE = 3, + LDAP_DP_RT_SUPPLIER = 4, + LDAP_DP_RT_MASTER = 5, + LDAP_DP_RT_IMMEDIATESUPERIOR = 6, + LDAP_DP_RT_SELF = 7, + LDAP_DP_RT_LAST +} ReferenceType_t; + +typedef enum SearchScope_t { + LDAP_DP_SS_UNKNOWN = -1, + LDAP_DP_SS_BASEOBJECT = 0, + LDAP_DP_SS_SINGLELEVEL = 1, + LDAP_DP_SS_WHOLESUBTREE = 2, + LDAP_DP_SS_SUBORDINATESUBTREE = 3, + LDAP_DP_SS_LAST +} SearchScope_t; + +typedef struct ContinuationReference_t { + BerVarray cr_referralURI; + /* ? [1] ? */ + struct berval cr_localReference; + ReferenceType_t cr_referenceType; + struct berval cr_remainingName; + SearchScope_t cr_searchScope; + BerVarray cr_searchedSubtrees; + struct berval cr_failedName; +} ContinuationReference_t; +#define CR_INIT { NULL, BER_BVNULL, LDAP_DP_RT_UNKNOWN, BER_BVNULL, LDAP_DP_SS_UNKNOWN, NULL, BER_BVNULL } + +#ifdef unused +static struct berval bv2rt[] = { + BER_BVC( "superior" ), + BER_BVC( "subordinate" ), + BER_BVC( "cross" ), + BER_BVC( "nonSpecificSubordinate" ), + BER_BVC( "supplier" ), + BER_BVC( "master" ), + BER_BVC( "immediateSuperior" ), + BER_BVC( "self" ), + BER_BVNULL +}; + +static struct berval bv2ss[] = { + BER_BVC( "baseObject" ), + BER_BVC( "singleLevel" ), + BER_BVC( "wholeSubtree" ), + BER_BVC( "subordinateSubtree" ), + BER_BVNULL +}; + +static struct berval * +ldap_distproc_rt2bv( ReferenceType_t rt ) +{ + return &bv2rt[ rt ]; +} + +static const char * +ldap_distproc_rt2str( ReferenceType_t rt ) +{ + return bv2rt[ rt ].bv_val; +} + +static ReferenceType_t +ldap_distproc_bv2rt( struct berval *bv ) +{ + ReferenceType_t rt; + + for ( rt = 0; !BER_BVISNULL( &bv2rt[ rt ] ); rt++ ) { + if ( ber_bvstrcasecmp( bv, &bv2rt[ rt ] ) == 0 ) { + return rt; + } + } + + return LDAP_DP_RT_UNKNOWN; +} + +static ReferenceType_t +ldap_distproc_str2rt( const char *s ) +{ + struct berval bv; + + ber_str2bv( s, 0, 0, &bv ); + return ldap_distproc_bv2rt( &bv ); +} + +static struct berval * +ldap_distproc_ss2bv( SearchScope_t ss ) +{ + return &bv2ss[ ss ]; +} + +static const char * +ldap_distproc_ss2str( SearchScope_t ss ) +{ + return bv2ss[ ss ].bv_val; +} + +static SearchScope_t +ldap_distproc_bv2ss( struct berval *bv ) +{ + ReferenceType_t ss; + + for ( ss = 0; !BER_BVISNULL( &bv2ss[ ss ] ); ss++ ) { + if ( ber_bvstrcasecmp( bv, &bv2ss[ ss ] ) == 0 ) { + return ss; + } + } + + return LDAP_DP_SS_UNKNOWN; +} + +static SearchScope_t +ldap_distproc_str2ss( const char *s ) +{ + struct berval bv; + + ber_str2bv( s, 0, 0, &bv ); + return ldap_distproc_bv2ss( &bv ); +} +#endif /* unused */ + +/* + * NOTE: this overlay assumes that the chainingBehavior control + * is registered by the chain overlay; it may move here some time. + * This overlay provides support for that control as well. + */ + + +static int sc_returnContRef; +#define o_returnContRef o_ctrlflag[sc_returnContRef] +#define get_returnContRef(op) ((op)->o_returnContRef & SLAP_CONTROL_MASK) + +static struct berval slap_EXOP_CHAINEDREQUEST = BER_BVC( LDAP_EXOP_X_CHAINEDREQUEST ); +#ifdef LDAP_DEVEL +static struct berval slap_FEATURE_CANCHAINOPS = BER_BVC( LDAP_FEATURE_X_CANCHAINOPS ); +#endif + + +static BackendInfo *lback; + +typedef struct ldap_distproc_t { + /* "common" configuration info (anything occurring before an "uri") */ + ldapinfo_t *lc_common_li; + + /* current configuration info */ + ldapinfo_t *lc_cfg_li; + + /* tree of configured[/generated?] "uri" info */ + ldap_avl_info_t lc_lai; + + unsigned lc_flags; +#define LDAP_DISTPROC_F_NONE (0x00U) +#define LDAP_DISTPROC_F_CHAINING (0x01U) +#define LDAP_DISTPROC_F_CACHE_URI (0x10U) + +#define LDAP_DISTPROC_CHAINING( lc ) ( ( (lc)->lc_flags & LDAP_DISTPROC_F_CHAINING ) == LDAP_DISTPROC_F_CHAINING ) +#define LDAP_DISTPROC_CACHE_URI( lc ) ( ( (lc)->lc_flags & LDAP_DISTPROC_F_CACHE_URI ) == LDAP_DISTPROC_F_CACHE_URI ) + +} ldap_distproc_t; + +static int ldap_distproc_db_init_common( BackendDB *be ); +static int ldap_distproc_db_init_one( BackendDB *be ); +#define ldap_distproc_db_open_one(be) (lback)->bi_db_open( (be) ) +#define ldap_distproc_db_close_one(be) (0) +#define ldap_distproc_db_destroy_one(be, ca) (lback)->bi_db_destroy( (be), (ca) ) + +static int +ldap_distproc_uri_cmp( const void *c1, const void *c2 ) +{ + const ldapinfo_t *li1 = (const ldapinfo_t *)c1; + const ldapinfo_t *li2 = (const ldapinfo_t *)c2; + + assert( li1->li_bvuri != NULL ); + assert( !BER_BVISNULL( &li1->li_bvuri[ 0 ] ) ); + assert( BER_BVISNULL( &li1->li_bvuri[ 1 ] ) ); + + assert( li2->li_bvuri != NULL ); + assert( !BER_BVISNULL( &li2->li_bvuri[ 0 ] ) ); + assert( BER_BVISNULL( &li2->li_bvuri[ 1 ] ) ); + + /* If local DNs don't match, it is definitely not a match */ + return ber_bvcmp( &li1->li_bvuri[ 0 ], &li2->li_bvuri[ 0 ] ); +} + +static int +ldap_distproc_uri_dup( void *c1, void *c2 ) +{ + ldapinfo_t *li1 = (ldapinfo_t *)c1; + ldapinfo_t *li2 = (ldapinfo_t *)c2; + + assert( li1->li_bvuri != NULL ); + assert( !BER_BVISNULL( &li1->li_bvuri[ 0 ] ) ); + assert( BER_BVISNULL( &li1->li_bvuri[ 1 ] ) ); + + assert( li2->li_bvuri != NULL ); + assert( !BER_BVISNULL( &li2->li_bvuri[ 0 ] ) ); + assert( BER_BVISNULL( &li2->li_bvuri[ 1 ] ) ); + + /* Cannot have more than one shared session with same DN */ + if ( ber_bvcmp( &li1->li_bvuri[ 0 ], &li2->li_bvuri[ 0 ] ) == 0 ) { + return -1; + } + + return 0; +} + +static int +ldap_distproc_operational( Operation *op, SlapReply *rs ) +{ + /* Trap entries generated by back-ldap. + * + * FIXME: we need a better way to recognize them; a cleaner + * solution would be to be able to intercept the response + * of be_operational(), so that we can divert only those + * calls that fail because operational attributes were + * requested for entries that do not belong to the underlying + * database. This fix is likely to intercept also entries + * generated by back-perl and so. */ + if ( rs->sr_entry->e_private == NULL ) { + return LDAP_SUCCESS; + } + + return SLAP_CB_CONTINUE; +} + +static int +ldap_distproc_response( Operation *op, SlapReply *rs ) +{ + return SLAP_CB_CONTINUE; +} + +/* + * configuration... + */ + +enum { + /* NOTE: the chaining behavior control is registered + * by the chain overlay; it may move here some time */ + DP_CHAINING = 1, + DP_CACHE_URI, + + DP_LAST +}; + +static ConfigDriver distproc_cfgen; +static ConfigCfAdd distproc_cfadd; +static ConfigLDAPadd distproc_ldadd; + +static ConfigTable distproc_cfg[] = { + { "distproc-chaining", "args", + 2, 4, 0, ARG_MAGIC|ARG_BERVAL|DP_CHAINING, distproc_cfgen, + /* NOTE: using the same attributeTypes defined + * for the "chain" overlay */ + "( OLcfgOvAt:3.1 NAME 'olcChainingBehavior' " + "DESC 'Chaining behavior control parameters (draft-sermersheim-ldap-chaining)' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "distproc-cache-uri", "TRUE/FALSE", + 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|DP_CACHE_URI, distproc_cfgen, + "( OLcfgOvAt:3.2 NAME 'olcChainCacheURI' " + "DESC 'Enables caching of URIs not present in configuration' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs distproc_ocs[] = { + { "( OLcfgOvOc:7.1 " + "NAME 'olcDistProcConfig' " + "DESC 'Distributed procedures <draft-sermersheim-ldap-distproc> configuration' " + "SUP olcOverlayConfig " + "MAY ( " + "olcChainingBehavior $ " + "olcChainCacheURI " + ") )", + Cft_Overlay, distproc_cfg, NULL, distproc_cfadd }, + { "( OLcfgOvOc:7.2 " + "NAME 'olcDistProcDatabase' " + "DESC 'Distributed procedure remote server configuration' " + "AUXILIARY )", + Cft_Misc, distproc_cfg, distproc_ldadd }, + { NULL, 0, NULL } +}; + +static int +distproc_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca ) +{ + slap_overinst *on; + ldap_distproc_t *lc; + + ldapinfo_t *li; + + AttributeDescription *ad = NULL; + Attribute *at; + const char *text; + + int rc; + + if ( p->ce_type != Cft_Overlay + || !p->ce_bi + || p->ce_bi->bi_cf_ocs != distproc_ocs ) + { + return LDAP_CONSTRAINT_VIOLATION; + } + + on = (slap_overinst *)p->ce_bi; + lc = (ldap_distproc_t *)on->on_bi.bi_private; + + assert( ca->be == NULL ); + ca->be = (BackendDB *)ch_calloc( 1, sizeof( BackendDB ) ); + + ca->be->bd_info = (BackendInfo *)on; + + rc = slap_str2ad( "olcDbURI", &ad, &text ); + assert( rc == LDAP_SUCCESS ); + + at = attr_find( e->e_attrs, ad ); + if ( lc->lc_common_li == NULL && at != NULL ) { + /* FIXME: we should generate an empty default entry + * if none is supplied */ + Debug( LDAP_DEBUG_ANY, "slapd-distproc: " + "first underlying database \"%s\" " + "cannot contain attribute \"%s\".\n", + e->e_name.bv_val, ad->ad_cname.bv_val, 0 ); + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + + } else if ( lc->lc_common_li != NULL && at == NULL ) { + /* FIXME: we should generate an empty default entry + * if none is supplied */ + Debug( LDAP_DEBUG_ANY, "slapd-distproc: " + "subsequent underlying database \"%s\" " + "must contain attribute \"%s\".\n", + e->e_name.bv_val, ad->ad_cname.bv_val, 0 ); + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + + if ( lc->lc_common_li == NULL ) { + rc = ldap_distproc_db_init_common( ca->be ); + + } else { + rc = ldap_distproc_db_init_one( ca->be ); + } + + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "slapd-distproc: " + "unable to init %sunderlying database \"%s\".\n", + lc->lc_common_li == NULL ? "common " : "", e->e_name.bv_val, 0 ); + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + + li = ca->be->be_private; + + if ( lc->lc_common_li == NULL ) { + lc->lc_common_li = li; + + } else if ( avl_insert( &lc->lc_lai.lai_tree, (caddr_t)li, + ldap_distproc_uri_cmp, ldap_distproc_uri_dup ) ) + { + Debug( LDAP_DEBUG_ANY, "slapd-distproc: " + "database \"%s\" insert failed.\n", + e->e_name.bv_val, 0, 0 ); + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + +done:; + if ( rc != LDAP_SUCCESS ) { + (void)ldap_distproc_db_destroy_one( ca->be, NULL ); + ch_free( ca->be ); + ca->be = NULL; + } + + return rc; +} + +typedef struct ldap_distproc_cfadd_apply_t { + Operation *op; + SlapReply *rs; + Entry *p; + ConfigArgs *ca; + int count; +} ldap_distproc_cfadd_apply_t; + +static int +ldap_distproc_cfadd_apply( void *datum, void *arg ) +{ + ldapinfo_t *li = (ldapinfo_t *)datum; + ldap_distproc_cfadd_apply_t *lca = (ldap_distproc_cfadd_apply_t *)arg; + + struct berval bv; + + /* FIXME: should not hardcode "olcDatabase" here */ + bv.bv_len = snprintf( lca->ca->cr_msg, sizeof( lca->ca->cr_msg ), + "olcDatabase={%d}%s", lca->count, lback->bi_type ); + bv.bv_val = lca->ca->cr_msg; + + lca->ca->be->be_private = (void *)li; + config_build_entry( lca->op, lca->rs, lca->p->e_private, lca->ca, + &bv, lback->bi_cf_ocs, &distproc_ocs[ 1 ] ); + + lca->count++; + + return 0; +} + +static int +distproc_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca ) +{ + CfEntryInfo *pe = p->e_private; + slap_overinst *on = (slap_overinst *)pe->ce_bi; + ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private; + void *priv = (void *)ca->be->be_private; + + if ( lback->bi_cf_ocs ) { + ldap_distproc_cfadd_apply_t lca = { 0 }; + + lca.op = op; + lca.rs = rs; + lca.p = p; + lca.ca = ca; + lca.count = 0; + + (void)ldap_distproc_cfadd_apply( (void *)lc->lc_common_li, (void *)&lca ); + + (void)avl_apply( lc->lc_lai.lai_tree, ldap_distproc_cfadd_apply, + &lca, 1, AVL_INORDER ); + + ca->be->be_private = priv; + } + + return 0; +} + +static int +distproc_cfgen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private; + + int rc = 0; + + if ( c->op == SLAP_CONFIG_EMIT ) { + switch( c->type ) { + case DP_CACHE_URI: + c->value_int = LDAP_DISTPROC_CACHE_URI( lc ); + break; + + default: + assert( 0 ); + rc = 1; + } + return rc; + + } else if ( c->op == LDAP_MOD_DELETE ) { + switch( c->type ) { + case DP_CHAINING: + return 1; + + case DP_CACHE_URI: + lc->lc_flags &= ~LDAP_DISTPROC_F_CACHE_URI; + break; + + default: + return 1; + } + return rc; + } + + switch( c->type ) { + case DP_CACHE_URI: + if ( c->value_int ) { + lc->lc_flags |= LDAP_DISTPROC_F_CACHE_URI; + } else { + lc->lc_flags &= ~LDAP_DISTPROC_F_CACHE_URI; + } + break; + + default: + assert( 0 ); + return 1; + } + + return rc; +} + +static int +ldap_distproc_db_init( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ldap_distproc_t *lc = NULL; + + if ( lback == NULL ) { + lback = backend_info( "ldap" ); + + if ( lback == NULL ) { + return 1; + } + } + + lc = ch_malloc( sizeof( ldap_distproc_t ) ); + if ( lc == NULL ) { + return 1; + } + memset( lc, 0, sizeof( ldap_distproc_t ) ); + ldap_pvt_thread_mutex_init( &lc->lc_lai.lai_mutex ); + + on->on_bi.bi_private = (void *)lc; + + return 0; +} + +static int +ldap_distproc_db_config( + BackendDB *be, + const char *fname, + int lineno, + int argc, + char **argv ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private; + + int rc = SLAP_CONF_UNKNOWN; + + if ( lc->lc_common_li == NULL ) { + void *be_private = be->be_private; + ldap_distproc_db_init_common( be ); + lc->lc_common_li = lc->lc_cfg_li = (ldapinfo_t *)be->be_private; + be->be_private = be_private; + } + + /* Something for the distproc database? */ + if ( strncasecmp( argv[ 0 ], "distproc-", STRLENOF( "distproc-" ) ) == 0 ) { + char *save_argv0 = argv[ 0 ]; + BackendInfo *bd_info = be->bd_info; + void *be_private = be->be_private; + ConfigOCs *be_cf_ocs = be->be_cf_ocs; + int is_uri = 0; + + argv[ 0 ] += STRLENOF( "distproc-" ); + + if ( strcasecmp( argv[ 0 ], "uri" ) == 0 ) { + rc = ldap_distproc_db_init_one( be ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "underlying slapd-ldap initialization failed.\n.", + fname, lineno, 0 ); + return 1; + } + lc->lc_cfg_li = be->be_private; + is_uri = 1; + } + + /* TODO: add checks on what other slapd-ldap(5) args + * should be put in the template; this is not quite + * harmful, because attributes that shouldn't don't + * get actually used, but the user should at least + * be warned. + */ + + be->bd_info = lback; + be->be_private = (void *)lc->lc_cfg_li; + be->be_cf_ocs = lback->bi_cf_ocs; + + rc = config_generic_wrapper( be, fname, lineno, argc, argv ); + + argv[ 0 ] = save_argv0; + be->be_cf_ocs = be_cf_ocs; + be->be_private = be_private; + be->bd_info = bd_info; + + if ( is_uri ) { +private_destroy:; + if ( rc != 0 ) { + BackendDB db = *be; + + db.bd_info = lback; + db.be_private = (void *)lc->lc_cfg_li; + ldap_distproc_db_destroy_one( &db, NULL ); + lc->lc_cfg_li = NULL; + + } else { + if ( lc->lc_cfg_li->li_bvuri == NULL + || BER_BVISNULL( &lc->lc_cfg_li->li_bvuri[ 0 ] ) + || !BER_BVISNULL( &lc->lc_cfg_li->li_bvuri[ 1 ] ) ) + { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "no URI list allowed in slapo-distproc.\n", + fname, lineno, 0 ); + rc = 1; + goto private_destroy; + } + + if ( avl_insert( &lc->lc_lai.lai_tree, + (caddr_t)lc->lc_cfg_li, + ldap_distproc_uri_cmp, ldap_distproc_uri_dup ) ) + { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "duplicate URI in slapo-distproc.\n", + fname, lineno, 0 ); + rc = 1; + goto private_destroy; + } + } + } + } + + return rc; +} + +enum db_which { + db_open = 0, + db_close, + db_destroy, + + db_last +}; + +typedef struct ldap_distproc_db_apply_t { + BackendDB *be; + BI_db_func *func; +} ldap_distproc_db_apply_t; + +static int +ldap_distproc_db_apply( void *datum, void *arg ) +{ + ldapinfo_t *li = (ldapinfo_t *)datum; + ldap_distproc_db_apply_t *lca = (ldap_distproc_db_apply_t *)arg; + + lca->be->be_private = (void *)li; + + return lca->func( lca->be, NULL ); +} + +static int +ldap_distproc_db_func( + BackendDB *be, + enum db_which which +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private; + + int rc = 0; + + if ( lc ) { + BI_db_func *func = (&lback->bi_db_open)[ which ]; + + if ( func != NULL && lc->lc_common_li != NULL ) { + BackendDB db = *be; + + db.bd_info = lback; + db.be_private = lc->lc_common_li; + + rc = func( &db, NULL ); + + if ( rc != 0 ) { + return rc; + } + + if ( lc->lc_lai.lai_tree != NULL ) { + ldap_distproc_db_apply_t lca; + + lca.be = &db; + lca.func = func; + + rc = avl_apply( lc->lc_lai.lai_tree, + ldap_distproc_db_apply, (void *)&lca, + 1, AVL_INORDER ) != AVL_NOMORE; + } + } + } + + return rc; +} + +static int +ldap_distproc_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + return ldap_distproc_db_func( be, db_open ); +} + +static int +ldap_distproc_db_close( + BackendDB *be, + ConfigReply *cr ) +{ + return ldap_distproc_db_func( be, db_close ); +} + +static int +ldap_distproc_db_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private; + + int rc; + + rc = ldap_distproc_db_func( be, db_destroy ); + + if ( lc ) { + avl_free( lc->lc_lai.lai_tree, NULL ); + ldap_pvt_thread_mutex_destroy( &lc->lc_lai.lai_mutex ); + ch_free( lc ); + } + + return rc; +} + +/* + * inits one instance of the slapd-ldap backend, and stores + * the private info in be_private of the arg + */ +static int +ldap_distproc_db_init_common( + BackendDB *be ) +{ + BackendInfo *bi = be->bd_info; + int t; + + be->bd_info = lback; + be->be_private = NULL; + t = lback->bi_db_init( be, NULL ); + if ( t != 0 ) { + return t; + } + be->bd_info = bi; + + return 0; +} + +/* + * inits one instance of the slapd-ldap backend, stores + * the private info in be_private of the arg and fills + * selected fields with data from the template. + * + * NOTE: add checks about the other fields of the template, + * which are ignored and SHOULD NOT be configured by the user. + */ +static int +ldap_distproc_db_init_one( + BackendDB *be ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private; + + BackendInfo *bi = be->bd_info; + ldapinfo_t *li; + + slap_op_t t; + + be->bd_info = lback; + be->be_private = NULL; + t = lback->bi_db_init( be, NULL ); + if ( t != 0 ) { + return t; + } + li = (ldapinfo_t *)be->be_private; + + /* copy common data */ + li->li_nretries = lc->lc_common_li->li_nretries; + li->li_flags = lc->lc_common_li->li_flags; + li->li_version = lc->lc_common_li->li_version; + for ( t = 0; t < SLAP_OP_LAST; t++ ) { + li->li_timeout[ t ] = lc->lc_common_li->li_timeout[ t ]; + } + be->bd_info = bi; + + return 0; +} + +typedef struct ldap_distproc_conn_apply_t { + BackendDB *be; + Connection *conn; +} ldap_distproc_conn_apply_t; + +static int +ldap_distproc_conn_apply( void *datum, void *arg ) +{ + ldapinfo_t *li = (ldapinfo_t *)datum; + ldap_distproc_conn_apply_t *lca = (ldap_distproc_conn_apply_t *)arg; + + lca->be->be_private = (void *)li; + + return lback->bi_connection_destroy( lca->be, lca->conn ); +} + +static int +ldap_distproc_connection_destroy( + BackendDB *be, + Connection *conn +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private; + void *private = be->be_private; + ldap_distproc_conn_apply_t lca; + int rc; + + be->be_private = NULL; + lca.be = be; + lca.conn = conn; + ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex ); + rc = avl_apply( lc->lc_lai.lai_tree, ldap_distproc_conn_apply, + (void *)&lca, 1, AVL_INORDER ) != AVL_NOMORE; + ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex ); + be->be_private = private; + + return rc; +} + +static int +ldap_distproc_parse_returnContRef_ctrl( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + if ( get_returnContRef( op ) != SLAP_CONTROL_NONE ) { + rs->sr_text = "returnContinuationReference control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( op->o_pagedresults != SLAP_CONTROL_NONE ) { + rs->sr_text = "returnContinuationReference control specified with pagedResults control"; + return LDAP_PROTOCOL_ERROR; + } + + if ( !BER_BVISEMPTY( &ctrl->ldctl_value ) ) { + rs->sr_text = "returnContinuationReference control: value must be NULL"; + return LDAP_PROTOCOL_ERROR; + } + + op->o_returnContRef = ctrl->ldctl_iscritical ? SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL; + + return LDAP_SUCCESS; +} + +static int +ldap_exop_chained_request( + Operation *op, + SlapReply *rs ) +{ + Statslog( LDAP_DEBUG_STATS, "%s CHAINED REQUEST\n", + op->o_log_prefix, 0, 0, 0, 0 ); + + rs->sr_err = backend_check_restrictions( op, rs, + (struct berval *)&slap_EXOP_CHAINEDREQUEST ); + if ( rs->sr_err != LDAP_SUCCESS ) { + return rs->sr_err; + } + + /* by now, just reject requests */ + rs->sr_text = "under development"; + return LDAP_UNWILLING_TO_PERFORM; +} + + +static slap_overinst distproc; + +int +distproc_initialize( void ) +{ + int rc; + + /* Make sure we don't exceed the bits reserved for userland */ + config_check_userland( DP_LAST ); + + rc = load_extop( (struct berval *)&slap_EXOP_CHAINEDREQUEST, + SLAP_EXOP_HIDE, ldap_exop_chained_request ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "slapd-distproc: " + "unable to register chainedRequest exop: %d.\n", + rc, 0, 0 ); + return rc; + } + +#ifdef LDAP_DEVEL + rc = supported_feature_load( &slap_FEATURE_CANCHAINOPS ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "slapd-distproc: " + "unable to register canChainOperations supported feature: %d.\n", + rc, 0, 0 ); + return rc; + } +#endif + + rc = register_supported_control( LDAP_CONTROL_X_RETURNCONTREF, + SLAP_CTRL_GLOBAL|SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE, NULL, + ldap_distproc_parse_returnContRef_ctrl, &sc_returnContRef ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "slapd-distproc: " + "unable to register returnContinuationReference control: %d.\n", + rc, 0, 0 ); + return rc; + } + + distproc.on_bi.bi_type = "distproc"; + distproc.on_bi.bi_db_init = ldap_distproc_db_init; + distproc.on_bi.bi_db_config = ldap_distproc_db_config; + distproc.on_bi.bi_db_open = ldap_distproc_db_open; + distproc.on_bi.bi_db_close = ldap_distproc_db_close; + distproc.on_bi.bi_db_destroy = ldap_distproc_db_destroy; + + /* ... otherwise the underlying backend's function would be called, + * likely passing an invalid entry; on the contrary, the requested + * operational attributes should have been returned while chasing + * the referrals. This all in all is a bit messy, because part + * of the operational attributes are generated by the backend; + * part by the frontend; back-ldap should receive all the available + * ones from the remote server, but then, on its own, it strips those + * it assumes will be (re)generated by the frontend (e.g. + * subschemaSubentry, entryDN, ...) */ + distproc.on_bi.bi_operational = ldap_distproc_operational; + + distproc.on_bi.bi_connection_destroy = ldap_distproc_connection_destroy; + + distproc.on_response = ldap_distproc_response; + + distproc.on_bi.bi_cf_ocs = distproc_ocs; + + rc = config_register_schema( distproc_cfg, distproc_ocs ); + if ( rc ) { + return rc; + } + + return overlay_register( &distproc ); +} + +#endif /* SLAP_DISTPROC */ diff --git a/servers/slapd/back-ldap/extended.c b/servers/slapd/back-ldap/extended.c new file mode 100644 index 0000000..57f3c6f --- /dev/null +++ b/servers/slapd/back-ldap/extended.c @@ -0,0 +1,410 @@ +/* extended.c - ldap backend extended routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-ldap.h" +#include "lber_pvt.h" + +typedef int (ldap_back_exop_f)( Operation *op, SlapReply *rs, ldapconn_t **lc ); + +static ldap_back_exop_f ldap_back_exop_passwd; +static ldap_back_exop_f ldap_back_exop_generic; + +static struct exop { + struct berval oid; + ldap_back_exop_f *extended; +} exop_table[] = { + { BER_BVC(LDAP_EXOP_MODIFY_PASSWD), ldap_back_exop_passwd }, + { BER_BVNULL, NULL } +}; + +static int +ldap_back_extended_one( Operation *op, SlapReply *rs, ldap_back_exop_f exop ) +{ + ldapinfo_t *li = (ldapinfo_t *) op->o_bd->be_private; + + ldapconn_t *lc = NULL; + LDAPControl **ctrls = NULL, **oldctrls = NULL; + int rc; + + /* FIXME: this needs to be called here, so it is + * called twice; maybe we could avoid the + * ldap_back_dobind() call inside each extended() + * call ... */ + if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + return -1; + } + + ctrls = oldctrls = op->o_ctrls; + if ( ldap_back_controls_add( op, rs, lc, &ctrls ) ) + { + op->o_ctrls = oldctrls; + send_ldap_extended( op, rs ); + rs->sr_text = NULL; + /* otherwise frontend resends result */ + rc = rs->sr_err = SLAPD_ABANDON; + goto done; + } + + op->o_ctrls = ctrls; + rc = exop( op, rs, &lc ); + + op->o_ctrls = oldctrls; + (void)ldap_back_controls_free( op, rs, &ctrls ); + +done:; + if ( lc != NULL ) { + ldap_back_release_conn( li, lc ); + } + + return rc; +} + +int +ldap_back_extended( + Operation *op, + SlapReply *rs ) +{ + int i; + + RS_ASSERT( !(rs->sr_flags & REP_ENTRY_MASK) ); + rs->sr_flags &= ~REP_ENTRY_MASK; /* paranoia */ + + for ( i = 0; exop_table[i].extended != NULL; i++ ) { + if ( bvmatch( &exop_table[i].oid, &op->oq_extended.rs_reqoid ) ) + { + return ldap_back_extended_one( op, rs, exop_table[i].extended ); + } + } + + /* if we get here, the exop is known; the best that we can do + * is pass it thru as is */ + /* FIXME: maybe a list of OIDs to pass thru would be safer */ + return ldap_back_extended_one( op, rs, ldap_back_exop_generic ); +} + +static int +ldap_back_exop_passwd( + Operation *op, + SlapReply *rs, + ldapconn_t **lcp ) +{ + ldapinfo_t *li = (ldapinfo_t *) op->o_bd->be_private; + + ldapconn_t *lc = *lcp; + req_pwdexop_s *qpw = &op->oq_pwdexop; + LDAPMessage *res; + ber_int_t msgid; + int rc, isproxy, freedn = 0; + int do_retry = 1; + char *text = NULL; + struct berval dn = op->o_req_dn, + ndn = op->o_req_ndn; + + assert( lc != NULL ); + assert( rs->sr_ctrls == NULL ); + + if ( BER_BVISNULL( &ndn ) && op->ore_reqdata != NULL ) { + /* NOTE: most of this code is mutated + * from slap_passwd_parse(); + * But here we only need + * the first berval... */ + + ber_tag_t tag; + ber_len_t len = -1; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + + struct berval tmpid = BER_BVNULL; + + if ( op->ore_reqdata->bv_len == 0 ) { + return LDAP_PROTOCOL_ERROR; + } + + /* ber_init2 uses reqdata directly, doesn't allocate new buffers */ + ber_init2( ber, op->ore_reqdata, 0 ); + + tag = ber_scanf( ber, "{" /*}*/ ); + + if ( tag == LBER_ERROR ) { + return LDAP_PROTOCOL_ERROR; + } + + tag = ber_peek_tag( ber, &len ); + if ( tag == LDAP_TAG_EXOP_MODIFY_PASSWD_ID ) { + tag = ber_get_stringbv( ber, &tmpid, LBER_BV_NOTERM ); + + if ( tag == LBER_ERROR ) { + return LDAP_PROTOCOL_ERROR; + } + } + + if ( !BER_BVISEMPTY( &tmpid ) ) { + char idNull = tmpid.bv_val[tmpid.bv_len]; + tmpid.bv_val[tmpid.bv_len] = '\0'; + rs->sr_err = dnPrettyNormal( NULL, &tmpid, &dn, + &ndn, op->o_tmpmemctx ); + tmpid.bv_val[tmpid.bv_len] = idNull; + if ( rs->sr_err != LDAP_SUCCESS ) { + /* should have been successfully parsed earlier! */ + return rs->sr_err; + } + freedn = 1; + + } else { + dn = op->o_dn; + ndn = op->o_ndn; + } + } + + isproxy = ber_bvcmp( &ndn, &op->o_ndn ); + + Debug( LDAP_DEBUG_ARGS, "==> ldap_back_exop_passwd(\"%s\")%s\n", + dn.bv_val, isproxy ? " (proxy)" : "", 0 ); + +retry: + rc = ldap_passwd( lc->lc_ld, &dn, + qpw->rs_old.bv_val ? &qpw->rs_old : NULL, + qpw->rs_new.bv_val ? &qpw->rs_new : NULL, + op->o_ctrls, NULL, &msgid ); + + if ( rc == LDAP_SUCCESS ) { + /* TODO: set timeout? */ + /* by now, make sure no timeout is used (ITS#6282) */ + struct timeval tv = { -1, 0 }; + if ( ldap_result( lc->lc_ld, msgid, LDAP_MSG_ALL, &tv, &res ) == -1 ) { + ldap_get_option( lc->lc_ld, LDAP_OPT_ERROR_NUMBER, &rc ); + rs->sr_err = rc; + + } else { + /* only touch when activity actually took place... */ + if ( li->li_idle_timeout ) { + lc->lc_time = op->o_time; + } + + /* sigh. parse twice, because parse_passwd + * doesn't give us the err / match / msg info. + */ + rc = ldap_parse_result( lc->lc_ld, res, &rs->sr_err, + (char **)&rs->sr_matched, + &text, + NULL, &rs->sr_ctrls, 0 ); + + if ( rc == LDAP_SUCCESS ) { + if ( rs->sr_err == LDAP_SUCCESS ) { + struct berval newpw; + + /* this never happens because + * the frontend is generating + * the new password, so when + * the passwd exop is proxied, + * it never delegates password + * generation to the remote server + */ + rc = ldap_parse_passwd( lc->lc_ld, res, + &newpw ); + if ( rc == LDAP_SUCCESS && + !BER_BVISNULL( &newpw ) ) + { + rs->sr_type = REP_EXTENDED; + rs->sr_rspdata = slap_passwd_return( &newpw ); + free( newpw.bv_val ); + } + + } else { + rc = rs->sr_err; + } + } + ldap_msgfree( res ); + } + } + + if ( rc != LDAP_SUCCESS ) { + rs->sr_err = slap_map_api2result( rs ); + if ( rs->sr_err == LDAP_UNAVAILABLE && do_retry ) { + do_retry = 0; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + goto retry; + } + } + + if ( LDAP_BACK_QUARANTINE( li ) ) { + ldap_back_quarantine( op, rs ); + } + + if ( text ) rs->sr_text = text; + send_ldap_extended( op, rs ); + /* otherwise frontend resends result */ + rc = rs->sr_err = SLAPD_ABANDON; + + } else if ( LDAP_BACK_QUARANTINE( li ) ) { + ldap_back_quarantine( op, rs ); + } + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_EXTENDED ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + + if ( freedn ) { + op->o_tmpfree( dn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( ndn.bv_val, op->o_tmpmemctx ); + } + + /* these have to be freed anyway... */ + if ( rs->sr_matched ) { + free( (char *)rs->sr_matched ); + rs->sr_matched = NULL; + } + + if ( rs->sr_ctrls ) { + ldap_controls_free( rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + + if ( text ) { + free( text ); + rs->sr_text = NULL; + } + + /* in case, cleanup handler */ + if ( lc == NULL ) { + *lcp = NULL; + } + + return rc; +} + +static int +ldap_back_exop_generic( + Operation *op, + SlapReply *rs, + ldapconn_t **lcp ) +{ + ldapinfo_t *li = (ldapinfo_t *) op->o_bd->be_private; + + ldapconn_t *lc = *lcp; + LDAPMessage *res; + ber_int_t msgid; + int rc; + int do_retry = 1; + char *text = NULL; + + Debug( LDAP_DEBUG_ARGS, "==> ldap_back_exop_generic(%s, \"%s\")\n", + op->ore_reqoid.bv_val, op->o_req_dn.bv_val, 0 ); + assert( lc != NULL ); + assert( rs->sr_ctrls == NULL ); + +retry: + rc = ldap_extended_operation( lc->lc_ld, + op->ore_reqoid.bv_val, op->ore_reqdata, + op->o_ctrls, NULL, &msgid ); + + if ( rc == LDAP_SUCCESS ) { + /* TODO: set timeout? */ + /* by now, make sure no timeout is used (ITS#6282) */ + struct timeval tv = { -1, 0 }; + if ( ldap_result( lc->lc_ld, msgid, LDAP_MSG_ALL, &tv, &res ) == -1 ) { + ldap_get_option( lc->lc_ld, LDAP_OPT_ERROR_NUMBER, &rc ); + rs->sr_err = rc; + + } else { + /* only touch when activity actually took place... */ + if ( li->li_idle_timeout ) { + lc->lc_time = op->o_time; + } + + /* sigh. parse twice, because parse_passwd + * doesn't give us the err / match / msg info. + */ + rc = ldap_parse_result( lc->lc_ld, res, &rs->sr_err, + (char **)&rs->sr_matched, + &text, + NULL, &rs->sr_ctrls, 0 ); + if ( rc == LDAP_SUCCESS ) { + if ( rs->sr_err == LDAP_SUCCESS ) { + rc = ldap_parse_extended_result( lc->lc_ld, res, + (char **)&rs->sr_rspoid, &rs->sr_rspdata, 0 ); + if ( rc == LDAP_SUCCESS ) { + rs->sr_type = REP_EXTENDED; + } + + } else { + rc = rs->sr_err; + } + } + ldap_msgfree( res ); + } + } + + if ( rc != LDAP_SUCCESS ) { + rs->sr_err = slap_map_api2result( rs ); + if ( rs->sr_err == LDAP_UNAVAILABLE && do_retry ) { + do_retry = 0; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + goto retry; + } + } + + if ( LDAP_BACK_QUARANTINE( li ) ) { + ldap_back_quarantine( op, rs ); + } + + if ( text ) rs->sr_text = text; + send_ldap_extended( op, rs ); + /* otherwise frontend resends result */ + rc = rs->sr_err = SLAPD_ABANDON; + + } else if ( LDAP_BACK_QUARANTINE( li ) ) { + ldap_back_quarantine( op, rs ); + } + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_EXTENDED ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + + /* these have to be freed anyway... */ + if ( rs->sr_matched ) { + free( (char *)rs->sr_matched ); + rs->sr_matched = NULL; + } + + if ( rs->sr_ctrls ) { + ldap_controls_free( rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + + if ( text ) { + free( text ); + rs->sr_text = NULL; + } + + /* in case, cleanup handler */ + if ( lc == NULL ) { + *lcp = NULL; + } + + return rc; +} diff --git a/servers/slapd/back-ldap/init.c b/servers/slapd/back-ldap/init.c new file mode 100644 index 0000000..f592eaa --- /dev/null +++ b/servers/slapd/back-ldap/init.c @@ -0,0 +1,362 @@ +/* init.c - initialize ldap backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "config.h" +#include "back-ldap.h" + +static const ldap_extra_t ldap_extra = { + ldap_back_proxy_authz_ctrl, + ldap_back_controls_free, + slap_idassert_authzfrom_parse, + slap_idassert_passthru_parse_cf, + slap_idassert_parse, + slap_retry_info_destroy, + slap_retry_info_parse, + slap_retry_info_unparse, + ldap_back_connid2str +}; + +int +ldap_back_open( BackendInfo *bi ) +{ + bi->bi_controls = slap_known_controls; + return 0; +} + +int +ldap_back_initialize( BackendInfo *bi ) +{ + int rc; + + bi->bi_flags = +#ifdef LDAP_DYNAMIC_OBJECTS + /* this is set because all the support a proxy has to provide + * is the capability to forward the refresh exop, and to + * pass thru entries that contain the dynamicObject class + * and the entryTtl attribute */ + SLAP_BFLAG_DYNAMIC | +#endif /* LDAP_DYNAMIC_OBJECTS */ + + /* back-ldap recognizes RFC4525 increment; + * let the remote server complain, if needed (ITS#5912) */ + SLAP_BFLAG_INCREMENT; + + bi->bi_open = ldap_back_open; + bi->bi_config = 0; + bi->bi_close = 0; + bi->bi_destroy = 0; + + bi->bi_db_init = ldap_back_db_init; + bi->bi_db_config = config_generic_wrapper; + bi->bi_db_open = ldap_back_db_open; + bi->bi_db_close = ldap_back_db_close; + bi->bi_db_destroy = ldap_back_db_destroy; + + bi->bi_op_bind = ldap_back_bind; + bi->bi_op_unbind = 0; + bi->bi_op_search = ldap_back_search; + bi->bi_op_compare = ldap_back_compare; + bi->bi_op_modify = ldap_back_modify; + bi->bi_op_modrdn = ldap_back_modrdn; + bi->bi_op_add = ldap_back_add; + bi->bi_op_delete = ldap_back_delete; + bi->bi_op_abandon = 0; + + bi->bi_extended = ldap_back_extended; + + bi->bi_chk_referrals = 0; + bi->bi_entry_get_rw = ldap_back_entry_get; + + bi->bi_connection_init = 0; + bi->bi_connection_destroy = ldap_back_conn_destroy; + + bi->bi_extra = (void *)&ldap_extra; + + rc = ldap_back_init_cf( bi ); + if ( rc ) { + return rc; + } + + rc = chain_initialize(); + if ( rc ) { + return rc; + } + + rc = pbind_initialize(); + if ( rc ) { + return rc; + } + +#ifdef SLAP_DISTPROC + rc = distproc_initialize(); + if ( rc ) { + return rc; + } +#endif + return rc; +} + +int +ldap_back_db_init( Backend *be, ConfigReply *cr ) +{ + ldapinfo_t *li; + int rc; + unsigned i; + + li = (ldapinfo_t *)ch_calloc( 1, sizeof( ldapinfo_t ) ); + if ( li == NULL ) { + return -1; + } + + li->li_rebind_f = ldap_back_default_rebind; + li->li_urllist_f = ldap_back_default_urllist; + li->li_urllist_p = li; + ldap_pvt_thread_mutex_init( &li->li_uri_mutex ); + + BER_BVZERO( &li->li_acl_authcID ); + BER_BVZERO( &li->li_acl_authcDN ); + BER_BVZERO( &li->li_acl_passwd ); + + li->li_acl_authmethod = LDAP_AUTH_NONE; + BER_BVZERO( &li->li_acl_sasl_mech ); + li->li_acl.sb_tls = SB_TLS_DEFAULT; + + li->li_idassert_mode = LDAP_BACK_IDASSERT_LEGACY; + + BER_BVZERO( &li->li_idassert_authcID ); + BER_BVZERO( &li->li_idassert_authcDN ); + BER_BVZERO( &li->li_idassert_passwd ); + + BER_BVZERO( &li->li_idassert_authzID ); + + li->li_idassert_authmethod = LDAP_AUTH_NONE; + BER_BVZERO( &li->li_idassert_sasl_mech ); + li->li_idassert_tls = SB_TLS_DEFAULT; + + /* by default, use proxyAuthz control on each operation */ + li->li_idassert_flags = LDAP_BACK_AUTH_PRESCRIPTIVE; + + li->li_idassert_authz = NULL; + + /* initialize flags */ + li->li_flags = LDAP_BACK_F_CHASE_REFERRALS; + + /* initialize version */ + li->li_version = LDAP_VERSION3; + + ldap_pvt_thread_mutex_init( &li->li_conninfo.lai_mutex ); + + for ( i = LDAP_BACK_PCONN_FIRST; i < LDAP_BACK_PCONN_LAST; i++ ) { + li->li_conn_priv[ i ].lic_num = 0; + LDAP_TAILQ_INIT( &li->li_conn_priv[ i ].lic_priv ); + } + li->li_conn_priv_max = LDAP_BACK_CONN_PRIV_DEFAULT; + + ldap_pvt_thread_mutex_init( &li->li_counter_mutex ); + for ( i = 0; i < SLAP_OP_LAST; i++ ) { + ldap_pvt_mp_init( li->li_ops_completed[ i ] ); + } + + be->be_private = li; + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_NOLASTMOD; + + be->be_cf_ocs = be->bd_info->bi_cf_ocs; + + rc = ldap_back_monitor_db_init( be ); + if ( rc != 0 ) { + /* ignore, by now */ + rc = 0; + } + + return rc; +} + +int +ldap_back_db_open( BackendDB *be, ConfigReply *cr ) +{ + ldapinfo_t *li = (ldapinfo_t *)be->be_private; + + slap_bindconf sb = { BER_BVNULL }; + int rc = 0; + + Debug( LDAP_DEBUG_TRACE, + "ldap_back_db_open: URI=%s\n", + li->li_uri != NULL ? li->li_uri : "", 0, 0 ); + + /* by default, use proxyAuthz control on each operation */ + switch ( li->li_idassert_mode ) { + case LDAP_BACK_IDASSERT_LEGACY: + case LDAP_BACK_IDASSERT_SELF: + /* however, since admin connections are pooled and shared, + * only static authzIDs can be native */ + li->li_idassert_flags &= ~LDAP_BACK_AUTH_NATIVE_AUTHZ; + break; + + default: + break; + } + + ber_str2bv( li->li_uri, 0, 0, &sb.sb_uri ); + sb.sb_version = li->li_version; + sb.sb_method = LDAP_AUTH_SIMPLE; + BER_BVSTR( &sb.sb_binddn, "" ); + + if ( LDAP_BACK_T_F_DISCOVER( li ) && !LDAP_BACK_T_F( li ) ) { + rc = slap_discover_feature( &sb, + slap_schema.si_ad_supportedFeatures->ad_cname.bv_val, + LDAP_FEATURE_ABSOLUTE_FILTERS ); + if ( rc == LDAP_COMPARE_TRUE ) { + li->li_flags |= LDAP_BACK_F_T_F; + } + } + + if ( LDAP_BACK_CANCEL_DISCOVER( li ) && !LDAP_BACK_CANCEL( li ) ) { + rc = slap_discover_feature( &sb, + slap_schema.si_ad_supportedExtension->ad_cname.bv_val, + LDAP_EXOP_CANCEL ); + if ( rc == LDAP_COMPARE_TRUE ) { + li->li_flags |= LDAP_BACK_F_CANCEL_EXOP; + } + } + + /* monitor setup */ + rc = ldap_back_monitor_db_open( be ); + if ( rc != 0 ) { + /* ignore by now */ + rc = 0; + } + + li->li_flags |= LDAP_BACK_F_ISOPEN; + + return rc; +} + +void +ldap_back_conn_free( void *v_lc ) +{ + ldapconn_t *lc = v_lc; + + if ( lc->lc_ld != NULL ) { + ldap_unbind_ext( lc->lc_ld, NULL, NULL ); + } + if ( !BER_BVISNULL( &lc->lc_bound_ndn ) ) { + ch_free( lc->lc_bound_ndn.bv_val ); + } + if ( !BER_BVISNULL( &lc->lc_cred ) ) { + memset( lc->lc_cred.bv_val, 0, lc->lc_cred.bv_len ); + ch_free( lc->lc_cred.bv_val ); + } + if ( !BER_BVISNULL( &lc->lc_local_ndn ) ) { + ch_free( lc->lc_local_ndn.bv_val ); + } + lc->lc_q.tqe_prev = NULL; + lc->lc_q.tqe_next = NULL; + ch_free( lc ); +} + +int +ldap_back_db_close( Backend *be, ConfigReply *cr ) +{ + int rc = 0; + + if ( be->be_private ) { + rc = ldap_back_monitor_db_close( be ); + } + + return rc; +} + +int +ldap_back_db_destroy( Backend *be, ConfigReply *cr ) +{ + if ( be->be_private ) { + ldapinfo_t *li = ( ldapinfo_t * )be->be_private; + unsigned i; + + (void)ldap_back_monitor_db_destroy( be ); + + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + + if ( li->li_uri != NULL ) { + ch_free( li->li_uri ); + li->li_uri = NULL; + + assert( li->li_bvuri != NULL ); + ber_bvarray_free( li->li_bvuri ); + li->li_bvuri = NULL; + } + + bindconf_free( &li->li_tls ); + bindconf_free( &li->li_acl ); + bindconf_free( &li->li_idassert.si_bc ); + + if ( li->li_idassert_authz != NULL ) { + ber_bvarray_free( li->li_idassert_authz ); + li->li_idassert_authz = NULL; + } + if ( li->li_conninfo.lai_tree ) { + avl_free( li->li_conninfo.lai_tree, ldap_back_conn_free ); + } + for ( i = LDAP_BACK_PCONN_FIRST; i < LDAP_BACK_PCONN_LAST; i++ ) { + while ( !LDAP_TAILQ_EMPTY( &li->li_conn_priv[ i ].lic_priv ) ) { + ldapconn_t *lc = LDAP_TAILQ_FIRST( &li->li_conn_priv[ i ].lic_priv ); + + LDAP_TAILQ_REMOVE( &li->li_conn_priv[ i ].lic_priv, lc, lc_q ); + ldap_back_conn_free( lc ); + } + } + if ( LDAP_BACK_QUARANTINE( li ) ) { + slap_retry_info_destroy( &li->li_quarantine ); + ldap_pvt_thread_mutex_destroy( &li->li_quarantine_mutex ); + } + + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + ldap_pvt_thread_mutex_destroy( &li->li_conninfo.lai_mutex ); + ldap_pvt_thread_mutex_destroy( &li->li_uri_mutex ); + + for ( i = 0; i < SLAP_OP_LAST; i++ ) { + ldap_pvt_mp_clear( li->li_ops_completed[ i ] ); + } + ldap_pvt_thread_mutex_destroy( &li->li_counter_mutex ); + } + + ch_free( be->be_private ); + + return 0; +} + +#if SLAPD_LDAP == SLAPD_MOD_DYNAMIC + +/* conditionally define the init_module() function */ +SLAP_BACKEND_INIT_MODULE( ldap ) + +#endif /* SLAPD_LDAP == SLAPD_MOD_DYNAMIC */ + diff --git a/servers/slapd/back-ldap/modify.c b/servers/slapd/back-ldap/modify.c new file mode 100644 index 0000000..6cb84d4 --- /dev/null +++ b/servers/slapd/back-ldap/modify.c @@ -0,0 +1,136 @@ +/* modify.c - ldap backend modify function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-ldap.h" + +int +ldap_back_modify( + Operation *op, + SlapReply *rs ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + ldapconn_t *lc = NULL; + LDAPMod **modv = NULL, + *mods = NULL; + Modifications *ml; + int i, j, rc; + ber_int_t msgid; + int isupdate; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + LDAPControl **ctrls = NULL; + + if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + return rs->sr_err; + } + + for ( i = 0, ml = op->orm_modlist; ml; i++, ml = ml->sml_next ) + /* just count mods */ ; + + modv = (LDAPMod **)ch_malloc( ( i + 1 )*sizeof( LDAPMod * ) + + i*sizeof( LDAPMod ) ); + if ( modv == NULL ) { + rc = LDAP_NO_MEMORY; + goto cleanup; + } + mods = (LDAPMod *)&modv[ i + 1 ]; + + isupdate = be_shadow_update( op ); + for ( i = 0, ml = op->orm_modlist; ml; ml = ml->sml_next ) { + if ( !isupdate && !get_relax( op ) && ml->sml_desc->ad_type->sat_no_user_mod ) + { + continue; + } + + modv[ i ] = &mods[ i ]; + mods[ i ].mod_op = ( ml->sml_op | LDAP_MOD_BVALUES ); + mods[ i ].mod_type = ml->sml_desc->ad_cname.bv_val; + + if ( ml->sml_values != NULL ) { + for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ ) + /* just count mods */ ; + mods[ i ].mod_bvalues = + (struct berval **)ch_malloc( ( j + 1 )*sizeof( struct berval * ) ); + for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ ) + { + mods[ i ].mod_bvalues[ j ] = &ml->sml_values[ j ]; + } + mods[ i ].mod_bvalues[ j ] = NULL; + + } else { + mods[ i ].mod_bvalues = NULL; + } + + i++; + } + modv[ i ] = 0; + +retry:; + ctrls = op->o_ctrls; + rc = ldap_back_controls_add( op, rs, lc, &ctrls ); + if ( rc != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = ldap_modify_ext( lc->lc_ld, op->o_req_dn.bv_val, modv, + ctrls, NULL, &msgid ); + rc = ldap_back_op_result( lc, op, rs, msgid, + li->li_timeout[ SLAP_OP_MODIFY ], + ( LDAP_BACK_SENDRESULT | retrying ) ); + if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)ldap_back_controls_free( op, rs, &ctrls ); + goto retry; + } + } + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_MODIFY ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + +cleanup:; + (void)ldap_back_controls_free( op, rs, &ctrls ); + + for ( i = 0; modv[ i ]; i++ ) { + ch_free( modv[ i ]->mod_bvalues ); + } + ch_free( modv ); + + if ( lc != NULL ) { + ldap_back_release_conn( li, lc ); + } + + return rs->sr_err; +} + diff --git a/servers/slapd/back-ldap/modrdn.c b/servers/slapd/back-ldap/modrdn.c new file mode 100644 index 0000000..d52cc9e --- /dev/null +++ b/servers/slapd/back-ldap/modrdn.c @@ -0,0 +1,123 @@ +/* modrdn.c - ldap backend modrdn function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-ldap.h" + +int +ldap_back_modrdn( + Operation *op, + SlapReply *rs ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + ldapconn_t *lc = NULL; + ber_int_t msgid; + LDAPControl **ctrls = NULL; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + int rc = LDAP_SUCCESS; + char *newSup = NULL; + struct berval newrdn = BER_BVNULL; + + if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + return rs->sr_err; + } + + if ( op->orr_newSup ) { + /* needs LDAPv3 */ + switch ( li->li_version ) { + case LDAP_VERSION3: + break; + + case 0: + if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) { + break; + } + /* fall thru */ + + default: + /* op->o_protocol cannot be anything but LDAPv3, + * otherwise wouldn't be here */ + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + send_ldap_result( op, rs ); + goto cleanup; + } + + newSup = op->orr_newSup->bv_val; + } + + /* NOTE: we need to copy the newRDN in case it was formed + * from a DN by simply changing the length (ITS#5397) */ + newrdn = op->orr_newrdn; + if ( newrdn.bv_val[ newrdn.bv_len ] != '\0' ) { + ber_dupbv_x( &newrdn, &op->orr_newrdn, op->o_tmpmemctx ); + } + +retry: + ctrls = op->o_ctrls; + rc = ldap_back_controls_add( op, rs, lc, &ctrls ); + if ( rc != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = ldap_rename( lc->lc_ld, op->o_req_dn.bv_val, + newrdn.bv_val, newSup, + op->orr_deleteoldrdn, ctrls, NULL, &msgid ); + rc = ldap_back_op_result( lc, op, rs, msgid, + li->li_timeout[ SLAP_OP_MODRDN ], + ( LDAP_BACK_SENDRESULT | retrying ) ); + if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)ldap_back_controls_free( op, rs, &ctrls ); + goto retry; + } + } + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_MODRDN ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + +cleanup: + (void)ldap_back_controls_free( op, rs, &ctrls ); + + if ( newrdn.bv_val != op->orr_newrdn.bv_val ) { + op->o_tmpfree( newrdn.bv_val, op->o_tmpmemctx ); + } + + if ( lc != NULL ) { + ldap_back_release_conn( li, lc ); + } + + return rs->sr_err; +} + diff --git a/servers/slapd/back-ldap/monitor.c b/servers/slapd/back-ldap/monitor.c new file mode 100644 index 0000000..aba6c6f --- /dev/null +++ b/servers/slapd/back-ldap/monitor.c @@ -0,0 +1,1072 @@ +/* monitor.c - monitor ldap backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/unistd.h> +#include <ac/stdlib.h> +#include <ac/errno.h> +#include <sys/stat.h> +#include "lutil.h" +#include "back-ldap.h" + +#include "config.h" + +static ObjectClass *oc_olmLDAPDatabase; +static ObjectClass *oc_olmLDAPConnection; + +static ObjectClass *oc_monitorContainer; +static ObjectClass *oc_monitorCounterObject; + +static AttributeDescription *ad_olmDbURIList; +static AttributeDescription *ad_olmDbOperations; +static AttributeDescription *ad_olmDbBoundDN; +static AttributeDescription *ad_olmDbConnFlags; +static AttributeDescription *ad_olmDbConnURI; +static AttributeDescription *ad_olmDbPeerAddress; + +/* + * Stolen from back-monitor/operations.c + * We don't need the normalized rdn's though. + */ +struct ldap_back_monitor_ops_t { + struct berval rdn; +} ldap_back_monitor_op[] = { + { BER_BVC( "cn=Bind" ) }, + { BER_BVC( "cn=Unbind" ) }, + { BER_BVC( "cn=Search" ) }, + { BER_BVC( "cn=Compare" ) }, + { BER_BVC( "cn=Modify" ) }, + { BER_BVC( "cn=Modrdn" ) }, + { BER_BVC( "cn=Add" ) }, + { BER_BVC( "cn=Delete" ) }, + { BER_BVC( "cn=Abandon" ) }, + { BER_BVC( "cn=Extended" ) }, + + { BER_BVNULL } +}; + +/* Corresponds to connection flags in back-ldap.h */ +static struct { + unsigned flag; + struct berval name; +} s_flag[] = { + { LDAP_BACK_FCONN_ISBOUND, BER_BVC( "bound" ) }, + { LDAP_BACK_FCONN_ISANON, BER_BVC( "anonymous" ) }, + { LDAP_BACK_FCONN_ISPRIV, BER_BVC( "privileged" ) }, + { LDAP_BACK_FCONN_ISTLS, BER_BVC( "TLS" ) }, + { LDAP_BACK_FCONN_BINDING, BER_BVC( "binding" ) }, + { LDAP_BACK_FCONN_TAINTED, BER_BVC( "tainted" ) }, + { LDAP_BACK_FCONN_ABANDON, BER_BVC( "abandon" ) }, + { LDAP_BACK_FCONN_ISIDASR, BER_BVC( "idassert" ) }, + { LDAP_BACK_FCONN_CACHED, BER_BVC( "cached" ) }, + + { 0 } +}; + + +/* + * NOTE: there's some confusion in monitor OID arc; + * by now, let's consider: + * + * Subsystems monitor attributes 1.3.6.1.4.1.4203.666.1.55.0 + * Databases monitor attributes 1.3.6.1.4.1.4203.666.1.55.0.1 + * LDAP database monitor attributes 1.3.6.1.4.1.4203.666.1.55.0.1.2 + * + * Subsystems monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0 + * Databases monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0.1 + * LDAP database monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0.1.2 + */ + +static struct { + char *name; + char *oid; +} s_oid[] = { + { "olmLDAPAttributes", "olmDatabaseAttributes:2" }, + { "olmLDAPObjectClasses", "olmDatabaseObjectClasses:2" }, + + { NULL } +}; + +static struct { + char *desc; + AttributeDescription **ad; +} s_at[] = { + { "( olmLDAPAttributes:1 " + "NAME ( 'olmDbURIList' ) " + "DESC 'List of URIs a proxy is serving; can be modified run-time' " + "SUP managedInfo )", + &ad_olmDbURIList }, + { "( olmLDAPAttributes:2 " + "NAME ( 'olmDbOperation' ) " + "DESC 'monitor operations performed' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmDbOperations }, + { "( olmLDAPAttributes:3 " + "NAME ( 'olmDbBoundDN' ) " + "DESC 'monitor connection authorization DN' " + "SUP monitorConnectionAuthzDN " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmDbBoundDN }, + { "( olmLDAPAttributes:4 " + "NAME ( 'olmDbConnFlags' ) " + "DESC 'monitor connection flags' " + "SUP monitoredInfo " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmDbConnFlags }, + { "( olmLDAPAttributes:5 " + "NAME ( 'olmDbConnURI' ) " + "DESC 'monitor connection URI' " + "SUP monitorConnectionPeerAddress " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmDbConnURI }, + { "( olmLDAPAttributes:6 " + "NAME ( 'olmDbConnPeerAddress' ) " + "DESC 'monitor connection peer address' " + "SUP monitorConnectionPeerAddress " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmDbPeerAddress }, + + { NULL } +}; + +static struct { + char *name; + ObjectClass **oc; +} s_moc[] = { + { "monitorContainer", &oc_monitorContainer }, + { "monitorCounterObject", &oc_monitorCounterObject }, + + { NULL } +}; + +static struct { + char *desc; + ObjectClass **oc; +} s_oc[] = { + /* augments an existing object, so it must be AUXILIARY + * FIXME: derive from some ABSTRACT "monitoredEntity"? */ + { "( olmLDAPObjectClasses:1 " + "NAME ( 'olmLDAPDatabase' ) " + "SUP top AUXILIARY " + "MAY ( " + "olmDbURIList " + ") )", + &oc_olmLDAPDatabase }, + { "( olmLDAPObjectClasses:2 " + "NAME ( 'olmLDAPConnection' ) " + "SUP monitorConnection STRUCTURAL " + "MAY ( " + "olmDbBoundDN " + "$ olmDbConnFlags " + "$ olmDbConnURI " + "$ olmDbConnPeerAddress " + ") )", + &oc_olmLDAPConnection }, + + { NULL } +}; + +static int +ldap_back_monitor_update( + Operation *op, + SlapReply *rs, + Entry *e, + void *priv ) +{ + ldapinfo_t *li = (ldapinfo_t *)priv; + + Attribute *a; + + /* update olmDbURIList */ + a = attr_find( e->e_attrs, ad_olmDbURIList ); + if ( a != NULL ) { + struct berval bv; + + assert( a->a_vals != NULL ); + assert( !BER_BVISNULL( &a->a_vals[ 0 ] ) ); + assert( BER_BVISNULL( &a->a_vals[ 1 ] ) ); + + ldap_pvt_thread_mutex_lock( &li->li_uri_mutex ); + if ( li->li_uri ) { + ber_str2bv( li->li_uri, 0, 0, &bv ); + if ( !bvmatch( &a->a_vals[ 0 ], &bv ) ) { + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + } + } + ldap_pvt_thread_mutex_unlock( &li->li_uri_mutex ); + } + + return SLAP_CB_CONTINUE; +} + +static int +ldap_back_monitor_modify( + Operation *op, + SlapReply *rs, + Entry *e, + void *priv ) +{ + ldapinfo_t *li = (ldapinfo_t *) priv; + + Attribute *save_attrs = NULL; + Modifications *ml, + *ml_olmDbURIList = NULL; + struct berval ul = BER_BVNULL; + int got = 0; + + for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) { + if ( ml->sml_desc == ad_olmDbURIList ) { + if ( ml_olmDbURIList != NULL ) { + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + rs->sr_text = "conflicting modifications"; + goto done; + } + + if ( ml->sml_op != LDAP_MOD_REPLACE ) { + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + rs->sr_text = "modification not allowed"; + goto done; + } + + ml_olmDbURIList = ml; + got++; + continue; + } + } + + if ( got == 0 ) { + return SLAP_CB_CONTINUE; + } + + save_attrs = attrs_dup( e->e_attrs ); + + if ( ml_olmDbURIList != NULL ) { + Attribute *a = NULL; + LDAPURLDesc *ludlist = NULL; + int rc; + + ml = ml_olmDbURIList; + assert( ml->sml_nvalues != NULL ); + + if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) { + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + rs->sr_text = "no value provided"; + goto done; + } + + if ( !BER_BVISNULL( &ml->sml_nvalues[ 1 ] ) ) { + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + rs->sr_text = "multiple values provided"; + goto done; + } + + rc = ldap_url_parselist_ext( &ludlist, + ml->sml_nvalues[ 0 ].bv_val, NULL, + LDAP_PVT_URL_PARSE_NOEMPTY_HOST + | LDAP_PVT_URL_PARSE_DEF_PORT ); + if ( rc != LDAP_URL_SUCCESS ) { + rs->sr_err = LDAP_INVALID_SYNTAX; + rs->sr_text = "unable to parse URI list"; + goto done; + } + + ul.bv_val = ldap_url_list2urls( ludlist ); + ldap_free_urllist( ludlist ); + if ( ul.bv_val == NULL ) { + rs->sr_err = LDAP_OTHER; + goto done; + } + ul.bv_len = strlen( ul.bv_val ); + + a = attr_find( e->e_attrs, ad_olmDbURIList ); + if ( a != NULL ) { + if ( a->a_nvals == a->a_vals ) { + a->a_nvals = ch_calloc( sizeof( struct berval ), 2 ); + } + + ber_bvreplace( &a->a_vals[ 0 ], &ul ); + ber_bvreplace( &a->a_nvals[ 0 ], &ul ); + + } else { + attr_merge_normalize_one( e, ad_olmDbURIList, &ul, NULL ); + } + } + + /* apply changes */ + if ( !BER_BVISNULL( &ul ) ) { + ldap_pvt_thread_mutex_lock( &li->li_uri_mutex ); + if ( li->li_uri ) { + ch_free( li->li_uri ); + } + li->li_uri = ul.bv_val; + ldap_pvt_thread_mutex_unlock( &li->li_uri_mutex ); + + BER_BVZERO( &ul ); + } + +done:; + if ( !BER_BVISNULL( &ul ) ) { + ldap_memfree( ul.bv_val ); + } + + if ( rs->sr_err == LDAP_SUCCESS ) { + attrs_free( save_attrs ); + return SLAP_CB_CONTINUE; + } + + attrs_free( e->e_attrs ); + e->e_attrs = save_attrs; + + return rs->sr_err; +} + +static int +ldap_back_monitor_free( + Entry *e, + void **priv ) +{ + ldapinfo_t *li = (ldapinfo_t *)(*priv); + + *priv = NULL; + + if ( !slapd_shutdown ) { + memset( &li->li_monitor_info, 0, sizeof( li->li_monitor_info ) ); + } + + return SLAP_CB_CONTINUE; +} + +static int +ldap_back_monitor_subsystem_destroy( + BackendDB *be, + monitor_subsys_t *ms) +{ + free(ms->mss_dn.bv_val); + BER_BVZERO(&ms->mss_dn); + + free(ms->mss_ndn.bv_val); + BER_BVZERO(&ms->mss_ndn); + + return LDAP_SUCCESS; +} + +/* + * Connection monitoring subsystem: + * Tries to mimick what the cn=connections,cn=monitor subsystem does + * by creating volatile entries for each connection and populating them + * according to the information attached to the connection. + * At this moment the only exposed information is the DN used to bind it. + * Also note that the connection IDs are not and probably never will be + * stable. + */ + +struct ldap_back_monitor_conn_arg { + Operation *op; + monitor_subsys_t *ms; + Entry **ep; +}; + +/* code stolen from daemon.c */ +static int +ldap_back_monitor_conn_peername( + LDAP *ld, + struct berval *bv) +{ + Sockbuf *sockbuf; + ber_socket_t socket; + Sockaddr sa; + socklen_t salen = sizeof(sa); + const char *peeraddr = NULL; + /* we assume INET6_ADDRSTRLEN > INET_ADDRSTRLEN */ + char addr[INET6_ADDRSTRLEN]; +#ifdef LDAP_PF_LOCAL + char peername[MAXPATHLEN + sizeof("PATH=")]; +#elif defined(LDAP_PF_INET6) + char peername[sizeof("IP=[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535")]; +#else /* ! LDAP_PF_LOCAL && ! LDAP_PF_INET6 */ + char peername[sizeof("IP=255.255.255.255:65336")]; +#endif /* LDAP_PF_LOCAL */ + + assert( bv != NULL ); + + ldap_get_option( ld, LDAP_OPT_SOCKBUF, (void **)&sockbuf ); + ber_sockbuf_ctrl( sockbuf, LBER_SB_OPT_GET_FD, &socket ); + getpeername( socket, (struct sockaddr *)&sa, &salen ); + + switch ( sa.sa_addr.sa_family ) { +#ifdef LDAP_PF_LOCAL + case AF_LOCAL: + sprintf( peername, "PATH=%s", sa.sa_un_addr.sun_path ); + break; +#endif /* LDAP_PF_LOCAL */ + +#ifdef LDAP_PF_INET6 + case AF_INET6: + if ( IN6_IS_ADDR_V4MAPPED(&sa.sa_in6_addr.sin6_addr) ) { +#if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP ) + peeraddr = inet_ntop( AF_INET, + ((struct in_addr *)&sa.sa_in6_addr.sin6_addr.s6_addr[12]), + addr, sizeof(addr) ); +#else /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + peeraddr = inet_ntoa( *((struct in_addr *) + &sa.sa_in6_addr.sin6_addr.s6_addr[12]) ); +#endif /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + if ( !peeraddr ) peeraddr = SLAP_STRING_UNKNOWN; + sprintf( peername, "IP=%s:%d", peeraddr, + (unsigned) ntohs( sa.sa_in6_addr.sin6_port ) ); + } else { + peeraddr = inet_ntop( AF_INET6, + &sa.sa_in6_addr.sin6_addr, + addr, sizeof addr ); + if ( !peeraddr ) peeraddr = SLAP_STRING_UNKNOWN; + sprintf( peername, "IP=[%s]:%d", peeraddr, + (unsigned) ntohs( sa.sa_in6_addr.sin6_port ) ); + } + break; +#endif /* LDAP_PF_INET6 */ + + case AF_INET: { +#if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP ) + peeraddr = inet_ntop( AF_INET, &sa.sa_in_addr.sin_addr, + addr, sizeof(addr) ); +#else /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + peeraddr = inet_ntoa( sa.sa_in_addr.sin_addr ); +#endif /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + if ( !peeraddr ) peeraddr = SLAP_STRING_UNKNOWN; + sprintf( peername, "IP=%s:%d", peeraddr, + (unsigned) ntohs( sa.sa_in_addr.sin_port ) ); + } break; + + default: + sprintf( peername, SLAP_STRING_UNKNOWN ); + } + + ber_str2bv( peername, 0, 1, bv ); + return LDAP_SUCCESS; +} + +static int +ldap_back_monitor_conn_entry( + ldapconn_t *lc, + struct ldap_back_monitor_conn_arg *arg ) +{ + Entry *e; + monitor_entry_t *mp; + monitor_extra_t *mbe = arg->op->o_bd->bd_info->bi_extra; + char buf[SLAP_TEXT_BUFLEN]; + char *ptr; + struct berval bv; + int i; + + bv.bv_val = buf; + bv.bv_len = snprintf( bv.bv_val, SLAP_TEXT_BUFLEN, + "cn=Connection %lu", lc->lc_connid ); + + e = mbe->entry_stub( &arg->ms->mss_dn, &arg->ms->mss_ndn, &bv, + oc_monitorContainer, NULL, NULL ); + + attr_merge_normalize_one( e, ad_olmDbBoundDN, &lc->lc_bound_ndn, NULL ); + + for ( i = 0; s_flag[i].flag; i++ ) + { + if ( lc->lc_flags & s_flag[i].flag ) + { + attr_merge_normalize_one( e, ad_olmDbConnFlags, &s_flag[i].name, NULL ); + } + } + + ldap_get_option( lc->lc_ld, LDAP_OPT_URI, &bv.bv_val ); + ptr = strchr( bv.bv_val, ' ' ); + bv.bv_len = ptr ? ptr - bv.bv_val : strlen(bv.bv_val); + attr_merge_normalize_one( e, ad_olmDbConnURI, &bv, NULL ); + ch_free( bv.bv_val ); + + ldap_back_monitor_conn_peername( lc->lc_ld, &bv ); + attr_merge_normalize_one( e, ad_olmDbPeerAddress, &bv, NULL ); + ch_free( bv.bv_val ); + + mp = mbe->entrypriv_create(); + e->e_private = mp; + mp->mp_info = arg->ms; + mp->mp_flags = MONITOR_F_SUB | MONITOR_F_VOLATILE; + + *arg->ep = e; + arg->ep = &mp->mp_next; + + return 0; +} + +static int +ldap_back_monitor_conn_create( + Operation *op, + SlapReply *rs, + struct berval *ndn, + Entry *e_parent, + Entry **ep ) +{ + monitor_entry_t *mp_parent; + monitor_subsys_t *ms; + ldapinfo_t *li; + ldapconn_t *lc; + + struct ldap_back_monitor_conn_arg *arg; + int conn_type; + + assert( e_parent->e_private != NULL ); + + mp_parent = e_parent->e_private; + ms = (monitor_subsys_t *)mp_parent->mp_info; + li = (ldapinfo_t *)ms->mss_private; + + arg = ch_calloc( 1, sizeof(struct ldap_back_monitor_conn_arg) ); + arg->op = op; + arg->ep = ep; + arg->ms = ms; + + for ( conn_type = LDAP_BACK_PCONN_FIRST; + conn_type < LDAP_BACK_PCONN_LAST; + conn_type++ ) + { + LDAP_TAILQ_FOREACH( lc, + &li->li_conn_priv[ conn_type ].lic_priv, + lc_q ) + { + ldap_back_monitor_conn_entry( lc, arg ); + } + } + + avl_apply( li->li_conninfo.lai_tree, (AVL_APPLY)ldap_back_monitor_conn_entry, + arg, -1, AVL_INORDER ); + + ch_free( arg ); + + return 0; +} + +static int +ldap_back_monitor_conn_init( + BackendDB *be, + monitor_subsys_t *ms ) +{ + ldapinfo_t *li = (ldapinfo_t *) ms->mss_private; + monitor_extra_t *mbe; + + Entry *e; + int rc; + + assert( be != NULL ); + mbe = (monitor_extra_t *) be->bd_info->bi_extra; + + ms->mss_dn = ms->mss_ndn = li->li_monitor_info.lmi_ndn; + ms->mss_rdn = li->li_monitor_info.lmi_conn_rdn; + ms->mss_create = ldap_back_monitor_conn_create; + ms->mss_destroy = ldap_back_monitor_subsystem_destroy; + + e = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn, + &ms->mss_rdn, oc_monitorContainer, NULL, NULL ); + if ( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_conn_init: " + "unable to create entry \"%s,%s\"\n", + li->li_monitor_info.lmi_conn_rdn.bv_val, + ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + + ber_dupbv( &ms->mss_dn, &e->e_name ); + ber_dupbv( &ms->mss_ndn, &e->e_nname ); + + rc = mbe->register_entry( e, NULL, ms, MONITOR_F_VOLATILE_CH ); + + /* add labeledURI and special, modifiable URI value */ + if ( rc == LDAP_SUCCESS && li->li_uri != NULL ) { + struct berval bv; + Attribute *a; + LDAPURLDesc *ludlist = NULL; + monitor_callback_t *cb = NULL; + + a = attr_alloc( ad_olmDbURIList ); + + ber_str2bv( li->li_uri, 0, 0, &bv ); + attr_valadd( a, &bv, NULL, 1 ); + attr_normalize( a->a_desc, a->a_vals, &a->a_nvals, NULL ); + + rc = ldap_url_parselist_ext( &ludlist, + li->li_uri, NULL, + LDAP_PVT_URL_PARSE_NOEMPTY_HOST + | LDAP_PVT_URL_PARSE_DEF_PORT ); + if ( rc != LDAP_URL_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_db_open: " + "unable to parse URI list (ignored)\n", + 0, 0, 0 ); + } else { + Attribute *a2 = attr_alloc( slap_schema.si_ad_labeledURI ); + + a->a_next = a2; + + for ( ; ludlist != NULL; ) { + LDAPURLDesc *next = ludlist->lud_next; + + bv.bv_val = ldap_url_desc2str( ludlist ); + assert( bv.bv_val != NULL ); + ldap_free_urldesc( ludlist ); + bv.bv_len = strlen( bv.bv_val ); + attr_valadd( a2, &bv, NULL, 1 ); + ch_free( bv.bv_val ); + + ludlist = next; + } + + attr_normalize( a2->a_desc, a2->a_vals, &a2->a_nvals, NULL ); + } + + cb = ch_calloc( sizeof( monitor_callback_t ), 1 ); + cb->mc_update = ldap_back_monitor_update; + cb->mc_modify = ldap_back_monitor_modify; + cb->mc_free = ldap_back_monitor_free; + cb->mc_private = (void *)li; + + rc = mbe->register_entry_attrs( &ms->mss_ndn, a, cb, NULL, -1, NULL ); + + attr_free( a->a_next ); + attr_free( a ); + + if ( rc != LDAP_SUCCESS ) + { + ch_free( cb ); + } + } + + entry_free( e ); + + return rc; +} + +/* + * Operation monitoring subsystem: + * Looks a lot like the cn=operations,cn=monitor subsystem except that at this + * moment, only completed operations are counted. Each entry has a separate + * callback with all the needed information linked there in the structure + * below so that the callback need not locate it over and over again. + */ + +struct ldap_back_monitor_op_counter { + ldap_pvt_mp_t *data; + ldap_pvt_thread_mutex_t *mutex; +}; + +static void +ldap_back_monitor_ops_dispose( + void **priv) +{ + struct ldap_back_monitor_op_counter *counter = *priv; + + ch_free( counter ); + counter = NULL; +} + +static int +ldap_back_monitor_ops_free( + Entry *e, + void **priv) +{ + ldap_back_monitor_ops_dispose( priv ); + return LDAP_SUCCESS; +} + +static int +ldap_back_monitor_ops_update( + Operation *op, + SlapReply *rs, + Entry *e, + void *priv ) +{ + struct ldap_back_monitor_op_counter *counter = priv; + Attribute *a; + + /*TODO + * what about initiated/completed? + */ + a = attr_find( e->e_attrs, ad_olmDbOperations ); + assert( a != NULL ); + + ldap_pvt_thread_mutex_lock( counter->mutex ); + UI2BV( &a->a_vals[ 0 ], *counter->data ); + ldap_pvt_thread_mutex_unlock( counter->mutex ); + + return SLAP_CB_CONTINUE; +} + +static int +ldap_back_monitor_ops_init( + BackendDB *be, + monitor_subsys_t *ms ) +{ + ldapinfo_t *li = (ldapinfo_t *) ms->mss_private; + + monitor_extra_t *mbe; + Entry *e, *parent; + int rc; + slap_op_t op; + struct berval value = BER_BVC( "0" ); + + assert( be != NULL ); + + mbe = (monitor_extra_t *) be->bd_info->bi_extra; + + ms->mss_dn = ms->mss_ndn = li->li_monitor_info.lmi_ndn; + ms->mss_rdn = li->li_monitor_info.lmi_ops_rdn; + ms->mss_destroy = ldap_back_monitor_subsystem_destroy; + + parent = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn, + &ms->mss_rdn, oc_monitorContainer, NULL, NULL ); + if ( parent == NULL ) { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_ops_init: " + "unable to create entry \"%s,%s\"\n", + li->li_monitor_info.lmi_ops_rdn.bv_val, + ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + + ber_dupbv( &ms->mss_dn, &parent->e_name ); + ber_dupbv( &ms->mss_ndn, &parent->e_nname ); + + rc = mbe->register_entry( parent, NULL, ms, MONITOR_F_PERSISTENT_CH ); + if ( rc != LDAP_SUCCESS ) + { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_ops_init: " + "unable to register entry \"%s\" for monitoring\n", + parent->e_name.bv_val, 0, 0 ); + goto done; + } + + for ( op = 0; op < SLAP_OP_LAST; op++ ) + { + monitor_callback_t *cb; + struct ldap_back_monitor_op_counter *counter; + + e = mbe->entry_stub( &parent->e_name, &parent->e_nname, + &ldap_back_monitor_op[op].rdn, + oc_monitorCounterObject, NULL, NULL ); + if ( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_ops_init: " + "unable to create entry \"%s,%s\"\n", + ldap_back_monitor_op[op].rdn.bv_val, + parent->e_nname.bv_val, 0 ); + return( -1 ); + } + + attr_merge_normalize_one( e, ad_olmDbOperations, &value, NULL ); + + counter = ch_malloc( sizeof( struct ldap_back_monitor_op_counter ) ); + counter->data = &li->li_ops_completed[ op ]; + counter->mutex = &li->li_counter_mutex; + + /* + * We cannot share a single callback between entries. + * + * monitor_cache_destroy() tries to free all callbacks and it's called + * before mss_destroy() so we have no chance of handling it ourselves + */ + cb = ch_calloc( sizeof( monitor_callback_t ), 1 ); + cb->mc_update = ldap_back_monitor_ops_update; + cb->mc_free = ldap_back_monitor_ops_free; + cb->mc_dispose = ldap_back_monitor_ops_dispose; + cb->mc_private = (void *)counter; + + rc = mbe->register_entry( e, cb, ms, 0 ); + + /* TODO: register_entry has stored a duplicate so we might actually reuse it + * instead of recreating it every time... */ + entry_free( e ); + + if ( rc != LDAP_SUCCESS ) + { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_ops_init: " + "unable to register entry \"%s\" for monitoring\n", + e->e_name.bv_val, 0, 0 ); + ch_free( cb ); + break; + } + } + +done: + entry_free( parent ); + + return rc; +} + +/* + * call from within ldap_back_initialize() + */ +static int +ldap_back_monitor_initialize( void ) +{ + int i, code; + ConfigArgs c; + char *argv[ 3 ]; + + static int ldap_back_monitor_initialized = 0; + + /* set to 0 when successfully initialized; otherwise, remember failure */ + static int ldap_back_monitor_initialized_failure = 1; + + /* register schema here; if compiled as dynamic object, + * must be loaded __after__ back_monitor.la */ + + if ( ldap_back_monitor_initialized++ ) { + return ldap_back_monitor_initialized_failure; + } + + if ( backend_info( "monitor" ) == NULL ) { + return -1; + } + + argv[ 0 ] = "back-ldap monitor"; + c.argv = argv; + c.argc = 3; + c.fname = argv[0]; + for ( i = 0; s_oid[ i ].name; i++ ) { + + argv[ 1 ] = s_oid[ i ].name; + argv[ 2 ] = s_oid[ i ].oid; + + if ( parse_oidm( &c, 0, NULL ) != 0 ) { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_initialize: unable to add " + "objectIdentifier \"%s=%s\"\n", + s_oid[ i ].name, s_oid[ i ].oid, 0 ); + return 2; + } + } + + for ( i = 0; s_at[ i ].desc != NULL; i++ ) { + code = register_at( s_at[ i ].desc, s_at[ i ].ad, 1 ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_initialize: register_at failed for attributeType (%s)\n", + s_at[ i ].desc, 0, 0 ); + return 3; + + } else { + (*s_at[ i ].ad)->ad_type->sat_flags |= SLAP_AT_HIDE; + } + } + + for ( i = 0; s_oc[ i ].desc != NULL; i++ ) { + code = register_oc( s_oc[ i ].desc, s_oc[ i ].oc, 1 ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_initialize: register_oc failed for objectClass (%s)\n", + s_oc[ i ].desc, 0, 0 ); + return 4; + + } else { + (*s_oc[ i ].oc)->soc_flags |= SLAP_OC_HIDE; + } + } + + for ( i = 0; s_moc[ i ].name != NULL; i++ ) { + *s_moc[i].oc = oc_find( s_moc[ i ].name ); + if ( ! *s_moc[i].oc ) { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_initialize: failed to find objectClass (%s)\n", + s_moc[ i ].name, 0, 0 ); + return 5; + + } + } + + return ( ldap_back_monitor_initialized_failure = LDAP_SUCCESS ); +} + +/* + * call from within ldap_back_db_init() + */ +int +ldap_back_monitor_db_init( BackendDB *be ) +{ + int rc; + + rc = ldap_back_monitor_initialize(); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + +#if 0 /* uncomment to turn monitoring on by default */ + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_MONITORING; +#endif + + return 0; +} + +/* + * call from within ldap_back_db_open() + */ +int +ldap_back_monitor_db_open( BackendDB *be ) +{ + ldapinfo_t *li = (ldapinfo_t *) be->be_private; + monitor_subsys_t *mss = li->li_monitor_info.lmi_mss; + int rc = 0; + BackendInfo *mi; + monitor_extra_t *mbe; + + if ( !SLAP_DBMONITORING( be ) ) { + return 0; + } + + /* check if monitor is configured and usable */ + mi = backend_info( "monitor" ); + if ( !mi || !mi->bi_extra ) { + SLAP_DBFLAGS( be ) ^= SLAP_DBFLAG_MONITORING; + return 0; + } + mbe = mi->bi_extra; + + /* don't bother if monitor is not configured */ + if ( !mbe->is_configured() ) { + static int warning = 0; + + if ( warning++ == 0 ) { + Debug( LDAP_DEBUG_ANY, "ldap_back_monitor_db_open: " + "monitoring disabled; " + "configure monitor database to enable\n", + 0, 0, 0 ); + } + + return 0; + } + + /* caller (e.g. an overlay based on back-ldap) may want to use + * a different DN and RDNs... */ + if ( BER_BVISNULL( &li->li_monitor_info.lmi_ndn ) ) { + rc = mbe->register_database( be, &li->li_monitor_info.lmi_ndn ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "ldap_back_monitor_db_open: " + "failed to register the databse with back-monitor\n", + 0, 0, 0 ); + } + } + if ( BER_BVISNULL( &li->li_monitor_info.lmi_conn_rdn ) ) { + ber_str2bv( "cn=Connections", 0, 1, + &li->li_monitor_info.lmi_conn_rdn ); + } + if ( BER_BVISNULL( &li->li_monitor_info.lmi_ops_rdn ) ) { + ber_str2bv( "cn=Operations", 0, 1, + &li->li_monitor_info.lmi_ops_rdn ); + } + + /* set up the subsystems used to create the operation and + * volatile connection entries */ + + mss->mss_name = "back-ldap connections"; + mss->mss_flags = MONITOR_F_VOLATILE_CH; + mss->mss_open = ldap_back_monitor_conn_init; + mss->mss_private = li; + + if ( mbe->register_subsys_late( mss ) ) + { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_db_open: " + "failed to register connection subsystem", 0, 0, 0 ); + return -1; + } + + mss++; + + mss->mss_name = "back-ldap operations"; + mss->mss_flags = MONITOR_F_PERSISTENT_CH; + mss->mss_open = ldap_back_monitor_ops_init; + mss->mss_private = li; + + if ( mbe->register_subsys_late( mss ) ) + { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_db_open: " + "failed to register operation subsystem", 0, 0, 0 ); + return -1; + } + + return rc; +} + +/* + * call from within ldap_back_db_close() + */ +int +ldap_back_monitor_db_close( BackendDB *be ) +{ + ldapinfo_t *li = (ldapinfo_t *) be->be_private; + + if ( li && !BER_BVISNULL( &li->li_monitor_info.lmi_ndn ) ) { + BackendInfo *mi; + monitor_extra_t *mbe; + + /* check if monitor is configured and usable */ + mi = backend_info( "monitor" ); + if ( mi && mi->bi_extra ) { + mbe = mi->bi_extra; + + /*TODO + * Unregister all entries our subsystems have created. + * Will only really be necessary when + * SLAPD_CONFIG_DELETE is enabled. + * + * Might need a way to unregister subsystems instead. + */ + } + } + + return 0; +} + +/* + * call from within ldap_back_db_destroy() + */ +int +ldap_back_monitor_db_destroy( BackendDB *be ) +{ + ldapinfo_t *li = (ldapinfo_t *) be->be_private; + + if ( li ) { + memset( &li->li_monitor_info, 0, sizeof( li->li_monitor_info ) ); + } + + return 0; +} + diff --git a/servers/slapd/back-ldap/pbind.c b/servers/slapd/back-ldap/pbind.c new file mode 100644 index 0000000..8fbc024 --- /dev/null +++ b/servers/slapd/back-ldap/pbind.c @@ -0,0 +1,173 @@ +/* pbind.c - passthru Bind overlay */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * Portions Copyright 2003-2010 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "lutil.h" +#include "slap.h" +#include "back-ldap.h" +#include "config.h" + +static BackendInfo *lback; + +static slap_overinst ldappbind; + +static int +ldap_pbind_bind( + Operation *op, + SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + void *private = op->o_bd->be_private; + void *bi = op->o_bd->bd_info; + int rc; + + op->o_bd->bd_info = lback; + op->o_bd->be_private = on->on_bi.bi_private; + rc = lback->bi_op_bind( op, rs ); + op->o_bd->be_private = private; + op->o_bd->bd_info = bi; + + return rc; +} + +static int +ldap_pbind_db_init( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ConfigOCs *be_cf_ocs = be->be_cf_ocs; + void *private = be->be_private; + int rc; + + if ( lback == NULL ) { + lback = backend_info( "ldap" ); + + if ( lback == NULL ) { + return 1; + } + } + + rc = lback->bi_db_init( be, cr ); + on->on_bi.bi_private = be->be_private; + be->be_cf_ocs = be_cf_ocs; + be->be_private = private; + + return rc; +} + +static int +ldap_pbind_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + void *private = be->be_private; + int rc; + int monitoring; + + be->be_private = on->on_bi.bi_private; + monitoring = ( SLAP_DBFLAGS( be ) & SLAP_DBFLAG_MONITORING ); + SLAP_DBFLAGS( be ) &= ~SLAP_DBFLAG_MONITORING; + rc = lback->bi_db_open( be, cr ); + SLAP_DBFLAGS( be ) |= monitoring; + be->be_private = private; + + return rc; +} + +static int +ldap_pbind_db_close( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + void *private = be->be_private; + int rc; + + be->be_private = on->on_bi.bi_private; + rc = lback->bi_db_close( be, cr ); + be->be_private = private; + + return rc; +} + +static int +ldap_pbind_db_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + void *private = be->be_private; + int rc; + + be->be_private = on->on_bi.bi_private; + rc = lback->bi_db_close( be, cr ); + on->on_bi.bi_private = be->be_private; + be->be_private = private; + + return rc; +} + +static int +ldap_pbind_connection_destroy( + BackendDB *be, + Connection *conn +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + void *private = be->be_private; + int rc; + + be->be_private = on->on_bi.bi_private; + rc = lback->bi_connection_destroy( be, conn ); + be->be_private = private; + + return rc; +} + +int +pbind_initialize( void ) +{ + int rc; + + ldappbind.on_bi.bi_type = "pbind"; + ldappbind.on_bi.bi_db_init = ldap_pbind_db_init; + ldappbind.on_bi.bi_db_open = ldap_pbind_db_open; + ldappbind.on_bi.bi_db_close = ldap_pbind_db_close; + ldappbind.on_bi.bi_db_destroy = ldap_pbind_db_destroy; + + ldappbind.on_bi.bi_op_bind = ldap_pbind_bind; + ldappbind.on_bi.bi_connection_destroy = ldap_pbind_connection_destroy; + + rc = ldap_pbind_init_cf( &ldappbind.on_bi ); + if ( rc ) { + return rc; + } + + return overlay_register( &ldappbind ); +} diff --git a/servers/slapd/back-ldap/proto-ldap.h b/servers/slapd/back-ldap/proto-ldap.h new file mode 100644 index 0000000..a0e9f05 --- /dev/null +++ b/servers/slapd/back-ldap/proto-ldap.h @@ -0,0 +1,124 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#ifndef PROTO_LDAP_H +#define PROTO_LDAP_H + +LDAP_BEGIN_DECL + +extern BI_init ldap_back_initialize; +extern BI_open ldap_back_open; + +extern BI_db_init ldap_back_db_init; +extern BI_db_open ldap_back_db_open; +extern BI_db_close ldap_back_db_close; +extern BI_db_destroy ldap_back_db_destroy; + +extern BI_op_bind ldap_back_bind; +extern BI_op_search ldap_back_search; +extern BI_op_compare ldap_back_compare; +extern BI_op_modify ldap_back_modify; +extern BI_op_modrdn ldap_back_modrdn; +extern BI_op_add ldap_back_add; +extern BI_op_delete ldap_back_delete; +extern BI_op_abandon ldap_back_abandon; +extern BI_op_extended ldap_back_extended; + +extern BI_connection_destroy ldap_back_conn_destroy; + +extern BI_entry_get_rw ldap_back_entry_get; + +void ldap_back_release_conn_lock( ldapinfo_t *li, ldapconn_t **lcp, int dolock ); +#define ldap_back_release_conn(li, lc) ldap_back_release_conn_lock((li), &(lc), 1) +int ldap_back_dobind( ldapconn_t **lcp, Operation *op, SlapReply *rs, ldap_back_send_t sendok ); +int ldap_back_retry( ldapconn_t **lcp, Operation *op, SlapReply *rs, ldap_back_send_t sendok ); +int ldap_back_map_result( SlapReply *rs ); +int ldap_back_op_result( ldapconn_t *lc, Operation *op, SlapReply *rs, + ber_int_t msgid, time_t timeout, ldap_back_send_t sendok ); +int ldap_back_cancel( ldapconn_t *lc, Operation *op, SlapReply *rs, ber_int_t msgid, ldap_back_send_t sendok ); + +int ldap_back_init_cf( BackendInfo *bi ); +int ldap_pbind_init_cf( BackendInfo *bi ); + +extern int ldap_back_conndn_cmp( const void *c1, const void *c2); +extern int ldap_back_conn_cmp( const void *c1, const void *c2); +extern int ldap_back_conndn_dup( void *c1, void *c2 ); +extern void ldap_back_conn_free( void *c ); + +extern ldapconn_t * ldap_back_conn_delete( ldapinfo_t *li, ldapconn_t *lc ); + +extern int ldap_back_conn2str( const ldapconn_base_t *lc, char *buf, ber_len_t buflen ); +extern int ldap_back_connid2str( const ldapconn_base_t *lc, char *buf, ber_len_t buflen ); + +extern int +ldap_back_proxy_authz_ctrl( + Operation *op, + SlapReply *rs, + struct berval *bound_ndn, + int version, + slap_idassert_t *si, + LDAPControl *ctrl ); + +extern int +ldap_back_controls_add( + Operation *op, + SlapReply *rs, + ldapconn_t *lc, + LDAPControl ***pctrls ); + +extern int +ldap_back_controls_free( Operation *op, SlapReply *rs, LDAPControl ***pctrls ); + +extern void +ldap_back_quarantine( + Operation *op, + SlapReply *rs ); + +#ifdef LDAP_BACK_PRINT_CONNTREE +extern void +ldap_back_print_conntree( ldapinfo_t *li, char *msg ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + +extern void slap_retry_info_destroy( slap_retry_info_t *ri ); +extern int slap_retry_info_parse( char *in, slap_retry_info_t *ri, + char *buf, ber_len_t buflen ); +extern int slap_retry_info_unparse( slap_retry_info_t *ri, struct berval *bvout ); + +extern int slap_idassert_authzfrom_parse( struct config_args_s *ca, slap_idassert_t *si ); +extern int slap_idassert_passthru_parse_cf( const char *fname, int lineno, const char *arg, slap_idassert_t *si ); +extern int slap_idassert_parse( struct config_args_s *ca, slap_idassert_t *si ); + +extern int chain_initialize( void ); +extern int pbind_initialize( void ); +#ifdef SLAP_DISTPROC +extern int distproc_initialize( void ); +#endif + +extern int ldap_back_monitor_db_init( BackendDB *be ); +extern int ldap_back_monitor_db_open( BackendDB *be ); +extern int ldap_back_monitor_db_close( BackendDB *be ); +extern int ldap_back_monitor_db_destroy( BackendDB *be ); + +extern LDAP_REBIND_PROC ldap_back_default_rebind; +extern LDAP_URLLIST_PROC ldap_back_default_urllist; + +LDAP_END_DECL + +#endif /* PROTO_LDAP_H */ diff --git a/servers/slapd/back-ldap/search.c b/servers/slapd/back-ldap/search.c new file mode 100644 index 0000000..a646d73 --- /dev/null +++ b/servers/slapd/back-ldap/search.c @@ -0,0 +1,1042 @@ +/* search.c - ldap backend search function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "slap.h" +#include "back-ldap.h" +#include "../../../libraries/liblber/lber-int.h" + +#include "lutil.h" + +static int +ldap_build_entry( Operation *op, LDAPMessage *e, Entry *ent, + struct berval *bdn, int remove_unknown_schema ); + + +static ObjectClass * +oc_bvfind_undef_ex( struct berval *ocname, int flag ) +{ + ObjectClass *oc = oc_bvfind( ocname ); + + if ( oc || flag ) { + /* oc defined or remove-unknown-schema flag set */ + return oc; + } + + return oc_bvfind_undef( ocname ); +} + + +/* + * replaces (&) with (objectClass=*) and (|) with (!(objectClass=*)) + * as the best replacement for RFC 4526 absolute true/absolute false + * filters; the only difference (AFAIK) is that they require search + * access to objectClass. + * + * filter->bv_val may be alloc'd on the thread's slab, if equal to + * op->ors_filterstr.bv_val, or realloc'd on the thread's slab otherwise. + */ +static int +ldap_back_munge_filter( + Operation *op, + struct berval *filter ) +{ + char *ptr; + int gotit = 0; + + Debug( LDAP_DEBUG_ARGS, "=> ldap_back_munge_filter \"%s\"\n", + filter->bv_val, 0, 0 ); + + for ( ptr = strchr( filter->bv_val, '(' ); + ptr; + ptr = strchr( ptr, '(' ) ) + { + static struct berval + bv_t = BER_BVC( "(&)" ), + bv_f = BER_BVC( "(|)" ), + bv_T = BER_BVC( "(objectClass=*)" ), + bv_F = BER_BVC( "(!(objectClass=*))" ); + struct berval *oldbv = NULL, + *newbv = NULL, + oldfilter = BER_BVNULL; + + if ( ptr[2] != ')' ) { + ptr++; + continue; + } + + switch ( ptr[1] ) { + case '&': + oldbv = &bv_t; + newbv = &bv_T; + break; + + case '|': + oldbv = &bv_f; + newbv = &bv_F; + break; + + default: + /* should be an error */ + continue; + } + + oldfilter = *filter; + filter->bv_len += newbv->bv_len - oldbv->bv_len; + if ( filter->bv_val == op->ors_filterstr.bv_val ) { + filter->bv_val = op->o_tmpalloc( filter->bv_len + 1, + op->o_tmpmemctx ); + + AC_MEMCPY( filter->bv_val, op->ors_filterstr.bv_val, + ptr - oldfilter.bv_val ); + + } else { + filter->bv_val = op->o_tmprealloc( filter->bv_val, + filter->bv_len + 1, op->o_tmpmemctx ); + } + + ptr = filter->bv_val + ( ptr - oldfilter.bv_val ); + + AC_MEMCPY( &ptr[ newbv->bv_len ], + &ptr[ oldbv->bv_len ], + oldfilter.bv_len - ( ptr - filter->bv_val ) - oldbv->bv_len + 1 ); + AC_MEMCPY( ptr, newbv->bv_val, newbv->bv_len ); + + ptr += newbv->bv_len; + + gotit++; + } + + Debug( LDAP_DEBUG_ARGS, "<= ldap_back_munge_filter \"%s\" (%d)\n", + filter->bv_val, gotit, 0 ); + + return gotit; +} + +int +ldap_back_search( + Operation *op, + SlapReply *rs ) +{ + ldapinfo_t *li = (ldapinfo_t *) op->o_bd->be_private; + + ldapconn_t *lc = NULL; + struct timeval tv; + time_t stoptime = (time_t)(-1); + LDAPMessage *res, + *e; + int rc = 0, + msgid; + struct berval match = BER_BVNULL, + filter = BER_BVNULL; + int i, x; + char **attrs = NULL; + int freetext = 0, filter_undef = 0; + int do_retry = 1, dont_retry = 0; + LDAPControl **ctrls = NULL; + char **references = NULL; + int remove_unknown_schema = + LDAP_BACK_OMIT_UNKNOWN_SCHEMA (li); + + rs_assert_ready( rs ); + rs->sr_flags &= ~REP_ENTRY_MASK; /* paranoia, we can set rs = non-entry */ + + if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + return rs->sr_err; + } + + /* + * FIXME: in case of values return filter, we might want + * to map attrs and maybe rewrite value + */ + + if ( op->ors_tlimit != SLAP_NO_LIMIT ) { + tv.tv_sec = op->ors_tlimit; + tv.tv_usec = 0; + stoptime = op->o_time + op->ors_tlimit; + + } else { + LDAP_BACK_TV_SET( &tv ); + } + + i = 0; + if ( op->ors_attrs ) { + for ( ; !BER_BVISNULL( &op->ors_attrs[i].an_name ); i++ ) + /* just count attrs */ ; + } + + x = 0; + if ( op->o_bd->be_extra_anlist ) { + for ( ; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++ ) + /* just count attrs */ ; + } + + if ( i > 0 || x > 0 ) { + int j = 0; + + attrs = op->o_tmpalloc( ( i + x + 1 )*sizeof( char * ), + op->o_tmpmemctx ); + if ( attrs == NULL ) { + rs->sr_err = LDAP_NO_MEMORY; + rc = -1; + goto finish; + } + + if ( i > 0 ) { + for ( i = 0; !BER_BVISNULL( &op->ors_attrs[i].an_name ); i++, j++ ) { + attrs[ j ] = op->ors_attrs[i].an_name.bv_val; + } + } + + if ( x > 0 ) { + for ( x = 0; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++, j++ ) { + if ( op->o_bd->be_extra_anlist[x].an_desc && + ad_inlist( op->o_bd->be_extra_anlist[x].an_desc, op->ors_attrs ) ) + { + continue; + } + + attrs[ j ] = op->o_bd->be_extra_anlist[x].an_name.bv_val; + } + } + + attrs[ j ] = NULL; + } + + ctrls = op->o_ctrls; + rc = ldap_back_controls_add( op, rs, lc, &ctrls ); + if ( rc != LDAP_SUCCESS ) { + goto finish; + } + + /* deal with <draft-zeilenga-ldap-t-f> filters */ + filter = op->ors_filterstr; +retry: + /* this goes after retry because ldap_back_munge_filter() + * optionally replaces RFC 4526 T-F filters (&) (|) + * if already computed, they will be re-installed + * by filter2bv_undef_x() later */ + if ( !LDAP_BACK_T_F( li ) ) { + ldap_back_munge_filter( op, &filter ); + } + + rs->sr_err = ldap_pvt_search( lc->lc_ld, op->o_req_dn.bv_val, + op->ors_scope, filter.bv_val, + attrs, op->ors_attrsonly, ctrls, NULL, + tv.tv_sec ? &tv : NULL, + op->ors_slimit, op->ors_deref, &msgid ); + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_SEARCH ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + + if ( rs->sr_err != LDAP_SUCCESS ) { + switch ( rs->sr_err ) { + case LDAP_SERVER_DOWN: + if ( do_retry ) { + do_retry = 0; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_DONTSEND ) ) { + goto retry; + } + } + + if ( lc == NULL ) { + /* reset by ldap_back_retry ... */ + rs->sr_err = slap_map_api2result( rs ); + + } else { + rc = ldap_back_op_result( lc, op, rs, msgid, 0, LDAP_BACK_DONTSEND ); + } + + goto finish; + + case LDAP_FILTER_ERROR: + /* first try? */ + if ( !filter_undef && + strstr( filter.bv_val, "(?" ) && + !LDAP_BACK_NOUNDEFFILTER( li ) ) + { + BER_BVZERO( &filter ); + filter2bv_undef_x( op, op->ors_filter, 1, &filter ); + filter_undef = 1; + goto retry; + } + + /* invalid filters return success with no data */ + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + goto finish; + + default: + rs->sr_err = slap_map_api2result( rs ); + rs->sr_text = NULL; + goto finish; + } + } + + /* if needed, initialize timeout */ + if ( li->li_timeout[ SLAP_OP_SEARCH ] ) { + if ( tv.tv_sec == 0 || tv.tv_sec > li->li_timeout[ SLAP_OP_SEARCH ] ) { + tv.tv_sec = li->li_timeout[ SLAP_OP_SEARCH ]; + tv.tv_usec = 0; + } + } + + /* We pull apart the ber result, stuff it into a slapd entry, and + * let send_search_entry stuff it back into ber format. Slow & ugly, + * but this is necessary for version matching, and for ACL processing. + */ + + for ( rc = -2; rc != -1; rc = ldap_result( lc->lc_ld, msgid, LDAP_MSG_ONE, &tv, &res ) ) + { + /* check for abandon */ + if ( op->o_abandon || LDAP_BACK_CONN_ABANDON( lc ) ) { + if ( rc > 0 ) { + ldap_msgfree( res ); + } + (void)ldap_back_cancel( lc, op, rs, msgid, LDAP_BACK_DONTSEND ); + rc = SLAPD_ABANDON; + goto finish; + } + + if ( rc == 0 || rc == -2 ) { + ldap_pvt_thread_yield(); + + /* check timeout */ + if ( li->li_timeout[ SLAP_OP_SEARCH ] ) { + if ( rc == 0 ) { + (void)ldap_back_cancel( lc, op, rs, msgid, LDAP_BACK_DONTSEND ); + rs->sr_text = "Operation timed out"; + rc = rs->sr_err = op->o_protocol >= LDAP_VERSION3 ? + LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER; + goto finish; + } + + } else { + LDAP_BACK_TV_SET( &tv ); + } + + /* check time limit */ + if ( op->ors_tlimit != SLAP_NO_LIMIT + && slap_get_time() > stoptime ) + { + (void)ldap_back_cancel( lc, op, rs, msgid, LDAP_BACK_DONTSEND ); + rc = rs->sr_err = LDAP_TIMELIMIT_EXCEEDED; + goto finish; + } + continue; + + } else { + /* only touch when activity actually took place... */ + if ( li->li_idle_timeout ) { + lc->lc_time = op->o_time; + } + + /* don't retry any more */ + dont_retry = 1; + } + + + if ( rc == LDAP_RES_SEARCH_ENTRY ) { + Entry ent = { 0 }; + struct berval bdn = BER_BVNULL; + + do_retry = 0; + + e = ldap_first_entry( lc->lc_ld, res ); + rc = ldap_build_entry( op, e, &ent, &bdn, + remove_unknown_schema); + if ( rc == LDAP_SUCCESS ) { + ldap_get_entry_controls( lc->lc_ld, res, &rs->sr_ctrls ); + rs->sr_entry = &ent; + rs->sr_attrs = op->ors_attrs; + rs->sr_operational_attrs = NULL; + rs->sr_flags = 0; + rs->sr_err = LDAP_SUCCESS; + rc = rs->sr_err = send_search_entry( op, rs ); + if ( rs->sr_ctrls ) { + ldap_controls_free( rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + rs->sr_entry = NULL; + rs->sr_flags = 0; + if ( !BER_BVISNULL( &ent.e_name ) ) { + assert( ent.e_name.bv_val != bdn.bv_val ); + op->o_tmpfree( ent.e_name.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &ent.e_name ); + } + if ( !BER_BVISNULL( &ent.e_nname ) ) { + op->o_tmpfree( ent.e_nname.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &ent.e_nname ); + } + entry_clean( &ent ); + } + ldap_msgfree( res ); + switch ( rc ) { + case LDAP_SUCCESS: + case LDAP_INSUFFICIENT_ACCESS: + break; + + default: + if ( rc == LDAP_UNAVAILABLE ) { + rc = rs->sr_err = LDAP_OTHER; + } else { + (void)ldap_back_cancel( lc, op, rs, msgid, LDAP_BACK_DONTSEND ); + } + goto finish; + } + + } else if ( rc == LDAP_RES_SEARCH_REFERENCE ) { + if ( LDAP_BACK_NOREFS( li ) ) { + ldap_msgfree( res ); + continue; + } + + do_retry = 0; + rc = ldap_parse_reference( lc->lc_ld, res, + &references, &rs->sr_ctrls, 1 ); + + if ( rc != LDAP_SUCCESS ) { + continue; + } + + /* FIXME: there MUST be at least one */ + if ( references && references[ 0 ] && references[ 0 ][ 0 ] ) { + int cnt; + + for ( cnt = 0; references[ cnt ]; cnt++ ) + /* NO OP */ ; + + /* FIXME: there MUST be at least one */ + rs->sr_ref = op->o_tmpalloc( ( cnt + 1 ) * sizeof( struct berval ), + op->o_tmpmemctx ); + + for ( cnt = 0; references[ cnt ]; cnt++ ) { + ber_str2bv( references[ cnt ], 0, 0, &rs->sr_ref[ cnt ] ); + } + BER_BVZERO( &rs->sr_ref[ cnt ] ); + + /* ignore return value by now */ + RS_ASSERT( !(rs->sr_flags & REP_ENTRY_MASK) ); + rs->sr_entry = NULL; + ( void )send_search_reference( op, rs ); + + } else { + Debug( LDAP_DEBUG_ANY, + "%s ldap_back_search: " + "got SEARCH_REFERENCE " + "with no referrals\n", + op->o_log_prefix, 0, 0 ); + } + + /* cleanup */ + if ( references ) { + ber_memvfree( (void **)references ); + op->o_tmpfree( rs->sr_ref, op->o_tmpmemctx ); + rs->sr_ref = NULL; + references = NULL; + } + + if ( rs->sr_ctrls ) { + ldap_controls_free( rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + + } else if ( rc == LDAP_RES_INTERMEDIATE ) { + /* FIXME: response controls + * are passed without checks */ + rc = ldap_parse_intermediate( lc->lc_ld, + res, + (char **)&rs->sr_rspoid, + &rs->sr_rspdata, + &rs->sr_ctrls, + 0 ); + if ( rc != LDAP_SUCCESS ) { + continue; + } + + slap_send_ldap_intermediate( op, rs ); + + if ( rs->sr_rspoid != NULL ) { + ber_memfree( (char *)rs->sr_rspoid ); + rs->sr_rspoid = NULL; + } + + if ( rs->sr_rspdata != NULL ) { + ber_bvfree( rs->sr_rspdata ); + rs->sr_rspdata = NULL; + } + + if ( rs->sr_ctrls != NULL ) { + ldap_controls_free( rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + + } else { + char *err = NULL; + + rc = ldap_parse_result( lc->lc_ld, res, &rs->sr_err, + &match.bv_val, &err, + &references, &rs->sr_ctrls, 1 ); + if ( rc == LDAP_SUCCESS ) { + if ( err ) { + rs->sr_text = err; + freetext = 1; + } + } else { + rs->sr_err = rc; + } + rs->sr_err = slap_map_api2result( rs ); + + /* RFC 4511: referrals can only appear + * if result code is LDAP_REFERRAL */ + if ( references + && references[ 0 ] + && references[ 0 ][ 0 ] ) + { + if ( rs->sr_err != LDAP_REFERRAL ) { + Debug( LDAP_DEBUG_ANY, + "%s ldap_back_search: " + "got referrals with err=%d\n", + op->o_log_prefix, + rs->sr_err, 0 ); + + } else { + int cnt; + + for ( cnt = 0; references[ cnt ]; cnt++ ) + /* NO OP */ ; + + rs->sr_ref = op->o_tmpalloc( ( cnt + 1 ) * sizeof( struct berval ), + op->o_tmpmemctx ); + + for ( cnt = 0; references[ cnt ]; cnt++ ) { + /* duplicating ...*/ + ber_str2bv( references[ cnt ], 0, 0, &rs->sr_ref[ cnt ] ); + } + BER_BVZERO( &rs->sr_ref[ cnt ] ); + } + + } else if ( rs->sr_err == LDAP_REFERRAL ) { + Debug( LDAP_DEBUG_ANY, + "%s ldap_back_search: " + "got err=%d with null " + "or empty referrals\n", + op->o_log_prefix, + rs->sr_err, 0 ); + + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } + + if ( match.bv_val != NULL ) { + match.bv_len = strlen( match.bv_val ); + } + + rc = 0; + break; + } + + /* if needed, restore timeout */ + if ( li->li_timeout[ SLAP_OP_SEARCH ] ) { + if ( tv.tv_sec == 0 || tv.tv_sec > li->li_timeout[ SLAP_OP_SEARCH ] ) { + tv.tv_sec = li->li_timeout[ SLAP_OP_SEARCH ]; + tv.tv_usec = 0; + } + } + } + + if ( rc == -1 ) { + if ( dont_retry == 0 ) { + if ( do_retry ) { + do_retry = 0; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_DONTSEND ) ) { + goto retry; + } + } + + rs->sr_err = LDAP_SERVER_DOWN; + rs->sr_err = slap_map_api2result( rs ); + goto finish; + + } else if ( LDAP_BACK_ONERR_STOP( li ) ) { + /* if onerr == STOP */ + rs->sr_err = LDAP_SERVER_DOWN; + rs->sr_err = slap_map_api2result( rs ); + goto finish; + } + } + + /* + * Rewrite the matched portion of the search base, if required + */ + if ( !BER_BVISNULL( &match ) && !BER_BVISEMPTY( &match ) ) { + struct berval pmatch; + + if ( dnPretty( NULL, &match, &pmatch, op->o_tmpmemctx ) != LDAP_SUCCESS ) { + pmatch.bv_val = match.bv_val; + match.bv_val = NULL; + } + rs->sr_matched = pmatch.bv_val; + rs->sr_flags |= REP_MATCHED_MUSTBEFREED; + } + +finish:; + if ( !BER_BVISNULL( &match ) ) { + ber_memfree( match.bv_val ); + } + + if ( rs->sr_v2ref ) { + rs->sr_err = LDAP_REFERRAL; + } + + if ( LDAP_BACK_QUARANTINE( li ) ) { + ldap_back_quarantine( op, rs ); + } + + if ( filter.bv_val != op->ors_filterstr.bv_val ) { + op->o_tmpfree( filter.bv_val, op->o_tmpmemctx ); + } + +#if 0 + /* let send_ldap_result play cleanup handlers (ITS#4645) */ + if ( rc != SLAPD_ABANDON ) +#endif + { + send_ldap_result( op, rs ); + } + + (void)ldap_back_controls_free( op, rs, &ctrls ); + + if ( rs->sr_ctrls ) { + ldap_controls_free( rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + + if ( rs->sr_text ) { + if ( freetext ) { + ber_memfree( (char *)rs->sr_text ); + } + rs->sr_text = NULL; + } + + if ( rs->sr_ref ) { + op->o_tmpfree( rs->sr_ref, op->o_tmpmemctx ); + rs->sr_ref = NULL; + } + + if ( references ) { + ber_memvfree( (void **)references ); + } + + if ( attrs ) { + op->o_tmpfree( attrs, op->o_tmpmemctx ); + } + + if ( lc != NULL ) { + ldap_back_release_conn( li, lc ); + } + + if ( rs->sr_err == LDAP_UNAVAILABLE && + /* if we originally bound and wanted rebind-as-user, must drop + * the connection now because we just discarded the credentials. + * ITS#7464, #8142 + */ + LDAP_BACK_SAVECRED( li ) && SLAP_IS_AUTHZ_BACKEND( op ) ) + rs->sr_err = SLAPD_DISCONNECT; + return rs->sr_err; +} + +static int +ldap_build_entry( + Operation *op, + LDAPMessage *e, + Entry *ent, + struct berval *bdn, + int remove_unknown_schema) +{ + struct berval a; + BerElement ber = *ldap_get_message_ber( e ); + Attribute *attr, **attrp; + const char *text; + int last; + char *lastb; + ber_len_t len; + + /* safe assumptions ... */ + assert( ent != NULL ); + BER_BVZERO( &ent->e_bv ); + + if ( ber_scanf( &ber, "{m", bdn ) == LBER_ERROR ) { + return LDAP_DECODING_ERROR; + } + + /* + * Note: this may fail if the target host(s) schema differs + * from the one known to the meta, and a DN with unknown + * attributes is returned. + * + * FIXME: should we log anything, or delegate to dnNormalize? + */ + /* Note: if the distinguished values or the naming attributes + * change, should we massage them as well? + */ + if ( dnPrettyNormal( NULL, bdn, &ent->e_name, &ent->e_nname, + op->o_tmpmemctx ) != LDAP_SUCCESS ) + { + return LDAP_INVALID_DN_SYNTAX; + } + + ent->e_attrs = NULL; + if ( ber_first_element( &ber, &len, &lastb ) != LBER_SEQUENCE ) { + return LDAP_SUCCESS; + } + + attrp = &ent->e_attrs; + while ( ber_next_element( &ber, &len, lastb ) == LBER_SEQUENCE && + ber_scanf( &ber, "{m", &a ) != LBER_ERROR ) { + int i; + slap_syntax_validate_func *validate; + slap_syntax_transform_func *pretty; + + attr = attr_alloc( NULL ); + if ( attr == NULL ) { + return LDAP_OTHER; + } + if ( slap_bv2ad( &a, &attr->a_desc, &text ) + != LDAP_SUCCESS ) + { + if ( slap_bv2undef_ad( &a, &attr->a_desc, &text, + (remove_unknown_schema ? SLAP_AD_NOINSERT : SLAP_AD_PROXIED )) != LDAP_SUCCESS ) + { + Debug( LDAP_DEBUG_ANY, + "%s ldap_build_entry: " + "slap_bv2undef_ad(%s): %s\n", + op->o_log_prefix, a.bv_val, text ); + + ( void )ber_scanf( &ber, "x" /* [W] */ ); + attr_free( attr ); + continue; + } + } + + /* no subschemaSubentry */ + if ( attr->a_desc == slap_schema.si_ad_subschemaSubentry + || attr->a_desc == slap_schema.si_ad_entryDN ) + { + + /* + * We eat target's subschemaSubentry because + * a search for this value is likely not + * to resolve to the appropriate backend; + * later, the local subschemaSubentry is + * added. + * + * We also eat entryDN because the frontend + * will reattach it without checking if already + * present... + */ + ( void )ber_scanf( &ber, "x" /* [W] */ ); + attr_free( attr ); + continue; + } + + if ( ber_scanf( &ber, "[W]", &attr->a_vals ) == LBER_ERROR + || attr->a_vals == NULL ) + { + /* + * Note: attr->a_vals can be null when using + * values result filter + */ + attr->a_vals = (struct berval *)&slap_dummy_bv; + } + + validate = attr->a_desc->ad_type->sat_syntax->ssyn_validate; + pretty = attr->a_desc->ad_type->sat_syntax->ssyn_pretty; + + if ( !validate && !pretty ) { + attr->a_nvals = NULL; + attr_free( attr ); + goto next_attr; + } + + for ( i = 0; !BER_BVISNULL( &attr->a_vals[i] ); i++ ) ; + last = i; + + /* + * check that each value is valid per syntax + * and pretty if appropriate + */ + for ( i = 0; i<last; i++ ) { + struct berval pval; + int rc; + + if ( pretty ) { + rc = ordered_value_pretty( attr->a_desc, + &attr->a_vals[i], &pval, NULL ); + + } else { + rc = ordered_value_validate( attr->a_desc, + &attr->a_vals[i], 0 ); + } + + if ( rc != LDAP_SUCCESS ) { + ObjectClass *oc; + + /* check if, by chance, it's an undefined objectClass */ + if ( attr->a_desc == slap_schema.si_ad_objectClass && + ( oc = oc_bvfind_undef_ex( &attr->a_vals[i], + remove_unknown_schema ) ) != NULL ) + { + ber_dupbv( &pval, &oc->soc_cname ); + rc = LDAP_SUCCESS; + + } else { + ber_memfree( attr->a_vals[i].bv_val ); + if ( --last == i ) { + BER_BVZERO( &attr->a_vals[i] ); + break; + } + attr->a_vals[i] = attr->a_vals[last]; + BER_BVZERO( &attr->a_vals[last] ); + i--; + } + } + + if ( rc == LDAP_SUCCESS && pretty ) { + ber_memfree( attr->a_vals[i].bv_val ); + attr->a_vals[i] = pval; + } + } + attr->a_numvals = last = i; + if ( last == 0 && attr->a_vals != &slap_dummy_bv ) { + attr->a_nvals = NULL; + attr_free( attr ); + goto next_attr; + } + + if ( last && attr->a_desc->ad_type->sat_equality && + attr->a_desc->ad_type->sat_equality->smr_normalize ) + { + attr->a_nvals = ch_malloc( ( last + 1 )*sizeof( struct berval ) ); + for ( i = 0; i < last; i++ ) { + int rc; + + rc = ordered_value_normalize( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + attr->a_desc, + attr->a_desc->ad_type->sat_equality, + &attr->a_vals[i], &attr->a_nvals[i], + NULL ); + + if ( rc != LDAP_SUCCESS ) { + ber_memfree( attr->a_vals[i].bv_val ); + if ( --last == i ) { + BER_BVZERO( &attr->a_vals[i] ); + break; + } + attr->a_vals[i] = attr->a_vals[last]; + BER_BVZERO( &attr->a_vals[last] ); + i--; + } + } + BER_BVZERO( &attr->a_nvals[i] ); + if ( last == 0 ) { + attr_free( attr ); + goto next_attr; + } + + } else { + attr->a_nvals = attr->a_vals; + } + + attr->a_numvals = last; + + /* Handle sorted vals, strip dups but keep the attr */ + if ( attr->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL ) { + while ( attr->a_numvals > 1 ) { + int rc = slap_sort_vals( (Modifications *)attr, &text, &i, op->o_tmpmemctx ); + if ( rc != LDAP_TYPE_OR_VALUE_EXISTS ) + break; + + /* Strip duplicate values */ + if ( attr->a_nvals != attr->a_vals ) + ber_memfree( attr->a_nvals[i].bv_val ); + ber_memfree( attr->a_vals[i].bv_val ); + attr->a_numvals--; + + assert( i >= 0 ); + if ( (unsigned)i < attr->a_numvals ) { + attr->a_vals[i] = attr->a_vals[attr->a_numvals]; + if ( attr->a_nvals != attr->a_vals ) + attr->a_nvals[i] = attr->a_nvals[attr->a_numvals]; + } + BER_BVZERO(&attr->a_vals[attr->a_numvals]); + if ( attr->a_nvals != attr->a_vals ) + BER_BVZERO(&attr->a_nvals[attr->a_numvals]); + } + attr->a_flags |= SLAP_ATTR_SORTED_VALS; + } + + *attrp = attr; + attrp = &attr->a_next; + +next_attr:; + } + + return LDAP_SUCCESS; +} + +/* return 0 IFF we can retrieve the entry with ndn + */ +int +ldap_back_entry_get( + Operation *op, + struct berval *ndn, + ObjectClass *oc, + AttributeDescription *at, + int rw, + Entry **ent ) +{ + ldapinfo_t *li = (ldapinfo_t *) op->o_bd->be_private; + + ldapconn_t *lc = NULL; + int rc; + struct berval bdn; + LDAPMessage *result = NULL, + *e = NULL; + char *attr[3], **attrp = NULL; + char *filter = NULL; + SlapReply rs; + int do_retry = 1; + LDAPControl **ctrls = NULL; + Operation op2 = *op; + + int remove_unknown_schema = + LDAP_BACK_OMIT_UNKNOWN_SCHEMA (li); + *ent = NULL; + + /* Tell getconn this is a privileged op */ + op2.o_do_not_cache = 1; + /* use rootdn to be doubly explicit this is privileged */ + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + /* ldap_back_entry_get() is an entry lookup, so it does not need + * to know what the entry is being looked up for */ + op2.o_tag = LDAP_REQ_SEARCH; + op2.o_ctrls = NULL; + rc = ldap_back_dobind( &lc, &op2, &rs, LDAP_BACK_DONTSEND ); + if ( !rc ) { + return rs.sr_err; + } + + if ( at ) { + attrp = attr; + if ( oc && at != slap_schema.si_ad_objectClass ) { + attr[0] = slap_schema.si_ad_objectClass->ad_cname.bv_val; + attr[1] = at->ad_cname.bv_val; + attr[2] = NULL; + + } else { + attr[0] = at->ad_cname.bv_val; + attr[1] = NULL; + } + } + + if ( oc ) { + char *ptr; + + filter = op->o_tmpalloc( STRLENOF( "(objectClass=" ")" ) + + oc->soc_cname.bv_len + 1, op->o_tmpmemctx ); + ptr = lutil_strcopy( filter, "(objectClass=" ); + ptr = lutil_strcopy( ptr, oc->soc_cname.bv_val ); + *ptr++ = ')'; + *ptr++ = '\0'; + } + +retry: + ctrls = NULL; + rc = ldap_back_controls_add( &op2, &rs, lc, &ctrls ); + if ( rc != LDAP_SUCCESS ) { + goto cleanup; + } + + /* TODO: timeout? */ + rc = ldap_pvt_search_s( lc->lc_ld, ndn->bv_val, LDAP_SCOPE_BASE, filter, + attrp, LDAP_DEREF_NEVER, ctrls, NULL, + NULL, LDAP_NO_LIMIT, 0, &result ); + if ( rc != LDAP_SUCCESS ) { + if ( rc == LDAP_SERVER_DOWN && do_retry ) { + do_retry = 0; + if ( ldap_back_retry( &lc, &op2, &rs, LDAP_BACK_DONTSEND ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)ldap_back_controls_free( &op2, &rs, &ctrls ); + goto retry; + } + } + goto cleanup; + } + + e = ldap_first_entry( lc->lc_ld, result ); + if ( e == NULL ) { + /* the entry exists, but it doesn't match the filter? */ + rc = LDAP_NO_RESULTS_RETURNED; + goto cleanup; + } + + *ent = entry_alloc(); + if ( *ent == NULL ) { + rc = LDAP_NO_MEMORY; + goto cleanup; + } + + rc = ldap_build_entry( op, e, *ent, &bdn, remove_unknown_schema ); + + if ( rc != LDAP_SUCCESS ) { + entry_free( *ent ); + *ent = NULL; + } + +cleanup: + (void)ldap_back_controls_free( &op2, &rs, &ctrls ); + + if ( result ) { + ldap_msgfree( result ); + } + + if ( filter ) { + op->o_tmpfree( filter, op->o_tmpmemctx ); + } + + if ( lc != NULL ) { + ldap_back_release_conn( li, lc ); + } + + return rc; +} diff --git a/servers/slapd/back-ldap/unbind.c b/servers/slapd/back-ldap/unbind.c new file mode 100644 index 0000000..823c9eb --- /dev/null +++ b/servers/slapd/back-ldap/unbind.c @@ -0,0 +1,78 @@ +/* unbind.c - ldap backend unbind function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-ldap.h" + +int +ldap_back_conn_destroy( + Backend *be, + Connection *conn +) +{ + ldapinfo_t *li = (ldapinfo_t *) be->be_private; + ldapconn_t *lc = NULL, lc_curr; + + Debug( LDAP_DEBUG_TRACE, + "=>ldap_back_conn_destroy: fetching conn %ld\n", + conn->c_connid, 0, 0 ); + + lc_curr.lc_conn = conn; + + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, ">>> ldap_back_conn_destroy" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + while ( ( lc = avl_delete( &li->li_conninfo.lai_tree, (caddr_t)&lc_curr, ldap_back_conn_cmp ) ) != NULL ) + { + assert( !LDAP_BACK_PCONN_ISPRIV( lc ) ); + Debug( LDAP_DEBUG_TRACE, + "=>ldap_back_conn_destroy: destroying conn %lu " + "refcnt=%d flags=0x%08x\n", + lc->lc_conn->c_connid, lc->lc_refcnt, lc->lc_lcflags ); + + if ( lc->lc_refcnt > 0 ) { + /* someone else might be accessing the connection; + * mark for deletion */ + LDAP_BACK_CONN_CACHED_CLEAR( lc ); + LDAP_BACK_CONN_TAINTED_SET( lc ); + + } else { + ldap_back_conn_free( lc ); + } + } +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, "<<< ldap_back_conn_destroy" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + + return 0; +} diff --git a/servers/slapd/back-ldif/Makefile.in b/servers/slapd/back-ldif/Makefile.in new file mode 100644 index 0000000..9737ff0 --- /dev/null +++ b/servers/slapd/back-ldif/Makefile.in @@ -0,0 +1,41 @@ +# Makefile.in for back-ldif +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 2005-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +SRCS = ldif.c +OBJS = ldif.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-ldif" +BUILD_MOD = yes + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(yes_DEFS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_R_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBBASE = back_ldif + +XINCPATH = -I.. -I$(srcdir)/.. +XDEFS = $(MODULES_CPPFLAGS) + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ + diff --git a/servers/slapd/back-ldif/ldif.c b/servers/slapd/back-ldif/ldif.c new file mode 100644 index 0000000..addaac5 --- /dev/null +++ b/servers/slapd/back-ldif/ldif.c @@ -0,0 +1,1936 @@ +/* ldif.c - the ldif backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by Eric Stokes for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" +#include <stdio.h> +#include <ac/string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <ac/dirent.h> +#include <fcntl.h> +#include <ac/errno.h> +#include <ac/unistd.h> +#include "slap.h" +#include "lutil.h" +#include "config.h" + +struct ldif_tool { + Entry **entries; /* collected by bi_tool_entry_first() */ + ID elen; /* length of entries[] array */ + ID ecount; /* number of entries */ + ID ecurrent; /* bi_tool_entry_next() position */ +# define ENTRY_BUFF_INCREMENT 500 /* initial entries[] length */ + struct berval *tl_base; + int tl_scope; + Filter *tl_filter; +}; + +/* Per-database data */ +struct ldif_info { + struct berval li_base_path; /* database directory */ + struct ldif_tool li_tool; /* for slap tools */ + /* + * Read-only LDAP requests readlock li_rdwr for filesystem input. + * Update requests first lock li_modop_mutex for filesystem I/O, + * and then writelock li_rdwr as well for filesystem output. + * This allows update requests to do callbacks that acquire + * read locks, e.g. access controls that inspect entries. + * (An alternative would be recursive read/write locks.) + */ + ldap_pvt_thread_mutex_t li_modop_mutex; /* serialize update requests */ + ldap_pvt_thread_rdwr_t li_rdwr; /* no other I/O when writing */ +}; + +static int write_data( int fd, const char *spew, int len, int *save_errno ); + +#ifdef _WIN32 +#define mkdir(a,b) mkdir(a) +#define move_file(from, to) (!MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING)) +#else +#define move_file(from, to) rename(from, to) +#endif +#define move_dir(from, to) rename(from, to) + + +#define LDIF ".ldif" +#define LDIF_FILETYPE_SEP '.' /* LDIF[0] */ + +/* + * Unsafe/translated characters in the filesystem. + * + * LDIF_UNSAFE_CHAR(c) returns true if the character c is not to be used + * in relative filenames, except it should accept '\\', '{' and '}' even + * if unsafe. The value should be a constant expression. + * + * If '\\' is unsafe, #define LDIF_ESCAPE_CHAR as a safe character. + * If '{' and '}' are unsafe, #define IX_FSL/IX_FSR as safe characters. + * (Not digits, '-' or '+'. IX_FSL == IX_FSR is allowed.) + * + * Characters are escaped as LDIF_ESCAPE_CHAR followed by two hex digits, + * except '\\' is replaced with LDIF_ESCAPE_CHAR and {} with IX_FS[LR]. + * Also some LDIF special chars are hex-escaped. + * + * Thus an LDIF filename is a valid normalized RDN (or suffix DN) + * followed by ".ldif", except with '\\' replaced with LDIF_ESCAPE_CHAR. + */ + +#ifndef _WIN32 + +/* + * Unix/MacOSX version. ':' vs '/' can cause confusion on MacOSX so we + * escape both. We escape them on Unix so both OS variants get the same + * filenames. + */ +#define LDIF_ESCAPE_CHAR '\\' +#define LDIF_UNSAFE_CHAR(c) ((c) == '/' || (c) == ':') + +#else /* _WIN32 */ + +/* Windows version - Microsoft's list of unsafe characters, except '\\' */ +#define LDIF_ESCAPE_CHAR '^' /* Not '\\' (unsafe on Windows) */ +#define LDIF_UNSAFE_CHAR(c) \ + ((c) == '/' || (c) == ':' || \ + (c) == '<' || (c) == '>' || (c) == '"' || \ + (c) == '|' || (c) == '?' || (c) == '*') + +#endif /* !_WIN32 */ + +/* + * Left and Right "{num}" prefix to ordered RDNs ("olcDatabase={1}bdb"). + * IX_DN* are for LDAP RDNs, IX_FS* for their .ldif filenames. + */ +#define IX_DNL '{' +#define IX_DNR '}' +#ifndef IX_FSL +#define IX_FSL IX_DNL +#define IX_FSR IX_DNR +#endif + +/* + * Test for unsafe chars, as well as chars handled specially by back-ldif: + * - If the escape char is not '\\', it must itself be escaped. Otherwise + * '\\' and the escape char would map to the same character. + * - Escape the '.' in ".ldif", so the directory for an RDN that actually + * ends with ".ldif" can not conflict with a file of the same name. And + * since some OSes/programs choke on multiple '.'s, escape all of them. + * - If '{' and '}' are translated to some other characters, those + * characters must in turn be escaped when they occur in an RDN. + */ +#ifndef LDIF_NEED_ESCAPE +#define LDIF_NEED_ESCAPE(c) \ + ((LDIF_UNSAFE_CHAR(c)) || \ + LDIF_MAYBE_UNSAFE(c, LDIF_ESCAPE_CHAR) || \ + LDIF_MAYBE_UNSAFE(c, LDIF_FILETYPE_SEP) || \ + LDIF_MAYBE_UNSAFE(c, IX_FSL) || \ + (IX_FSR != IX_FSL && LDIF_MAYBE_UNSAFE(c, IX_FSR))) +#endif +/* + * Helper macro for LDIF_NEED_ESCAPE(): Treat character x as unsafe if + * back-ldif does not already treat is specially. + */ +#define LDIF_MAYBE_UNSAFE(c, x) \ + (!(LDIF_UNSAFE_CHAR(x) || (x) == '\\' || (x) == IX_DNL || (x) == IX_DNR) \ + && (c) == (x)) + +/* Collect other "safe char" tests here, until someone needs a fix. */ +enum { + eq_unsafe = LDIF_UNSAFE_CHAR('='), + safe_filenames = STRLENOF("" LDAP_DIRSEP "") == 1 && !( + LDIF_UNSAFE_CHAR('-') || /* for "{-1}frontend" in bconfig.c */ + LDIF_UNSAFE_CHAR(LDIF_ESCAPE_CHAR) || + LDIF_UNSAFE_CHAR(IX_FSL) || LDIF_UNSAFE_CHAR(IX_FSR)) +}; +/* Sanity check: Try to force a compilation error if !safe_filenames */ +typedef struct { + int assert_safe_filenames : safe_filenames ? 2 : -2; +} assert_safe_filenames[safe_filenames ? 2 : -2]; + + +static ConfigTable ldifcfg[] = { + { "directory", "dir", 2, 2, 0, ARG_BERVAL|ARG_OFFSET, + (void *)offsetof(struct ldif_info, li_base_path), + "( OLcfgDbAt:0.1 NAME 'olcDbDirectory' " + "DESC 'Directory for database content' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED, + NULL, NULL, NULL, NULL } +}; + +static ConfigOCs ldifocs[] = { + { "( OLcfgDbOc:2.1 " + "NAME 'olcLdifConfig' " + "DESC 'LDIF backend configuration' " + "SUP olcDatabaseConfig " + "MUST ( olcDbDirectory ) )", Cft_Database, ldifcfg }, + { NULL, 0, NULL } +}; + + +/* + * Handle file/directory names. + */ + +/* Set *res = LDIF filename path for the normalized DN */ +static int +ndn2path( Operation *op, struct berval *dn, struct berval *res, int empty_ok ) +{ + BackendDB *be = op->o_bd; + struct ldif_info *li = (struct ldif_info *) be->be_private; + struct berval *suffixdn = &be->be_nsuffix[0]; + const char *start, *end, *next, *p; + char ch, *ptr; + ber_len_t len; + static const char hex[] = "0123456789ABCDEF"; + + assert( dn != NULL ); + assert( !BER_BVISNULL( dn ) ); + assert( suffixdn != NULL ); + assert( !BER_BVISNULL( suffixdn ) ); + assert( dnIsSuffix( dn, suffixdn ) ); + + if ( dn->bv_len == 0 && !empty_ok ) { + return LDAP_UNWILLING_TO_PERFORM; + } + + start = dn->bv_val; + end = start + dn->bv_len; + + /* Room for dir, dirsep, dn, LDIF, "\hexpair"-escaping of unsafe chars */ + len = li->li_base_path.bv_len + dn->bv_len + (1 + STRLENOF( LDIF )); + for ( p = start; p < end; ) { + ch = *p++; + if ( LDIF_NEED_ESCAPE( ch ) ) + len += 2; + } + res->bv_val = ch_malloc( len + 1 ); + + ptr = lutil_strcopy( res->bv_val, li->li_base_path.bv_val ); + for ( next = end - suffixdn->bv_len; end > start; end = next ) { + /* Set p = start of DN component, next = &',' or start of DN */ + while ( (p = next) > start ) { + --next; + if ( DN_SEPARATOR( *next ) ) + break; + } + /* Append <dirsep> <p..end-1: RDN or database-suffix> */ + for ( *ptr++ = LDAP_DIRSEP[0]; p < end; *ptr++ = ch ) { + ch = *p++; + if ( LDIF_ESCAPE_CHAR != '\\' && ch == '\\' ) { + ch = LDIF_ESCAPE_CHAR; + } else if ( IX_FSL != IX_DNL && ch == IX_DNL ) { + ch = IX_FSL; + } else if ( IX_FSR != IX_DNR && ch == IX_DNR ) { + ch = IX_FSR; + } else if ( LDIF_NEED_ESCAPE( ch ) ) { + *ptr++ = LDIF_ESCAPE_CHAR; + *ptr++ = hex[(ch & 0xFFU) >> 4]; + ch = hex[ch & 0x0FU]; + } + } + } + ptr = lutil_strcopy( ptr, LDIF ); + res->bv_len = ptr - res->bv_val; + + assert( res->bv_len <= len ); + + return LDAP_SUCCESS; +} + +/* + * *dest = dupbv(<dir + LDAP_DIRSEP>), plus room for <more>-sized filename. + * Return pointer past the dirname. + */ +static char * +fullpath_alloc( struct berval *dest, const struct berval *dir, ber_len_t more ) +{ + char *s = SLAP_MALLOC( dir->bv_len + more + 2 ); + + dest->bv_val = s; + if ( s == NULL ) { + dest->bv_len = 0; + Debug( LDAP_DEBUG_ANY, "back-ldif: out of memory\n", 0, 0, 0 ); + } else { + s = lutil_strcopy( dest->bv_val, dir->bv_val ); + *s++ = LDAP_DIRSEP[0]; + *s = '\0'; + dest->bv_len = s - dest->bv_val; + } + return s; +} + +/* + * Append filename to fullpath_alloc() dirname or replace previous filename. + * dir_end = fullpath_alloc() return value. + */ +#define FILL_PATH(fpath, dir_end, filename) \ + ((fpath)->bv_len = lutil_strcopy(dir_end, filename) - (fpath)->bv_val) + + +/* .ldif entry filename length <-> subtree dirname length. */ +#define ldif2dir_len(bv) ((bv).bv_len -= STRLENOF(LDIF)) +#define dir2ldif_len(bv) ((bv).bv_len += STRLENOF(LDIF)) +/* .ldif entry filename <-> subtree dirname, both with dirname length. */ +#define ldif2dir_name(bv) ((bv).bv_val[(bv).bv_len] = '\0') +#define dir2ldif_name(bv) ((bv).bv_val[(bv).bv_len] = LDIF_FILETYPE_SEP) + +/* Get the parent directory path, plus the LDIF suffix overwritten by a \0. */ +static int +get_parent_path( struct berval *dnpath, struct berval *res ) +{ + ber_len_t i = dnpath->bv_len; + + while ( i > 0 && dnpath->bv_val[ --i ] != LDAP_DIRSEP[0] ) ; + if ( res == NULL ) { + res = dnpath; + } else { + res->bv_val = SLAP_MALLOC( i + 1 + STRLENOF(LDIF) ); + if ( res->bv_val == NULL ) + return LDAP_OTHER; + AC_MEMCPY( res->bv_val, dnpath->bv_val, i ); + } + res->bv_len = i; + strcpy( res->bv_val + i, LDIF ); + res->bv_val[i] = '\0'; + return LDAP_SUCCESS; +} + +/* Make temporary filename pattern for mkstemp() based on dnpath. */ +static char * +ldif_tempname( const struct berval *dnpath ) +{ + static const char suffix[] = ".XXXXXX"; + ber_len_t len = dnpath->bv_len - STRLENOF( LDIF ); + char *name = SLAP_MALLOC( len + sizeof( suffix ) ); + + if ( name != NULL ) { + AC_MEMCPY( name, dnpath->bv_val, len ); + strcpy( name + len, suffix ); + } + return name; +} + +/* CRC-32 table for the polynomial: + * x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. + * + * As used by zlib + */ + +static const ber_uint_t crctab[256] = { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, + 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, + 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, + 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, + 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, + 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, + 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, + 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, + 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, + 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, + 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, + 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, + 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, + 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, + 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, + 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, + 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, + 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, + 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, + 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, + 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, + 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, + 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, + 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, + 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, + 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, + 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, + 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, + 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, + 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, + 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, + 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, + 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, + 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, + 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, + 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, + 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, + 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, + 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, + 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, + 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, + 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, + 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, + 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, + 0x2d02ef8dL +}; + +#define CRC1 crc = crctab[(crc ^ *buf++) & 0xff] ^ (crc >> 8) +#define CRC8 CRC1; CRC1; CRC1; CRC1; CRC1; CRC1; CRC1; CRC1 +unsigned int +crc32(const void *vbuf, int len) +{ + const unsigned char *buf = vbuf; + ber_uint_t crc = 0xffffffff; + + while (len > 7) { + CRC8; + len -= 8; + } + while (len) { + CRC1; + len--; + } + + return crc ^ 0xffffffff; +} + +/* + * Read a file, or stat() it if datap == NULL. Allocate and fill *datap. + * Return LDAP_SUCCESS, LDAP_NO_SUCH_OBJECT (no such file), or another error. + */ +static int +ldif_read_file( const char *path, char **datap ) +{ + int rc = LDAP_SUCCESS, fd, len; + int res = -1; /* 0:success, <0:error, >0:file too big/growing. */ + struct stat st; + char *data = NULL, *ptr = NULL; + const char *msg; + + if ( datap == NULL ) { + res = stat( path, &st ); + goto done; + } + fd = open( path, O_RDONLY ); + if ( fd >= 0 ) { + if ( fstat( fd, &st ) == 0 ) { + if ( st.st_size > INT_MAX - 2 ) { + res = 1; + } else { + len = st.st_size + 1; /* +1 detects file size > st.st_size */ + *datap = data = ptr = SLAP_MALLOC( len + 1 ); + if ( ptr != NULL ) { + while ( len && (res = read( fd, ptr, len )) ) { + if ( res > 0 ) { + len -= res; + ptr += res; + } else if ( errno != EINTR ) { + break; + } + } + *ptr = '\0'; + } + } + } + if ( close( fd ) < 0 ) + res = -1; + } + + done: + if ( res == 0 ) { +#ifdef LDAP_DEBUG + msg = "entry file exists"; + if ( datap ) { + msg = "read entry file"; + len = ptr - data; + ptr = strstr( data, "\n# CRC32" ); + if (!ptr) { + msg = "read entry file without checksum"; + } else { + unsigned int crc1 = 0, crc2 = 1; + if ( sscanf( ptr + 9, "%08x", &crc1) == 1) { + ptr = strchr(ptr+1, '\n'); + if ( ptr ) { + ptr++; + len -= (ptr - data); + crc2 = crc32( ptr, len ); + } + } + if ( crc1 != crc2 ) { + Debug( LDAP_DEBUG_ANY, "ldif_read_file: checksum error on \"%s\"\n", + path, 0, 0 ); + return rc; + } + } + } + Debug( LDAP_DEBUG_TRACE, "ldif_read_file: %s: \"%s\"\n", msg, path, 0 ); +#endif /* LDAP_DEBUG */ + } else { + if ( res < 0 && errno == ENOENT ) { + Debug( LDAP_DEBUG_TRACE, "ldif_read_file: " + "no entry file \"%s\"\n", path, 0, 0 ); + rc = LDAP_NO_SUCH_OBJECT; + } else { + msg = res < 0 ? STRERROR( errno ) : "bad stat() size"; + Debug( LDAP_DEBUG_ANY, "ldif_read_file: %s for \"%s\"\n", + msg, path, 0 ); + rc = LDAP_OTHER; + } + if ( data != NULL ) + SLAP_FREE( data ); + } + return rc; +} + +/* + * return nonnegative for success or -1 for error + * do not return numbers less than -1 + */ +static int +spew_file( int fd, const char *spew, int len, int *save_errno ) +{ + int writeres; +#define HEADER "# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.\n" + char header[sizeof(HEADER "# CRC32 12345678\n")]; + + sprintf(header, HEADER "# CRC32 %08x\n", crc32(spew, len)); + writeres = write_data(fd, header, sizeof(header)-1, save_errno); + return writeres < 0 ? writeres : write_data(fd, spew, len, save_errno); +} + +static int +write_data( int fd, const char *spew, int len, int *save_errno ) +{ + int writeres = 0; + while(len > 0) { + writeres = write(fd, spew, len); + if(writeres == -1) { + *save_errno = errno; + if (*save_errno != EINTR) + break; + } + else { + spew += writeres; + len -= writeres; + } + } + return writeres; +} + +/* Write an entry LDIF file. Create parentdir first if non-NULL. */ +static int +ldif_write_entry( + Operation *op, + Entry *e, + const struct berval *path, + const char *parentdir, + const char **text ) +{ + int rc = LDAP_OTHER, res, save_errno = 0; + int fd, entry_length; + char *entry_as_string, *tmpfname; + + if ( op->o_abandon ) + return SLAPD_ABANDON; + + if ( parentdir != NULL && mkdir( parentdir, 0750 ) < 0 ) { + save_errno = errno; + Debug( LDAP_DEBUG_ANY, "ldif_write_entry: %s \"%s\": %s\n", + "cannot create parent directory", + parentdir, STRERROR( save_errno ) ); + *text = "internal error (cannot create parent directory)"; + return rc; + } + + tmpfname = ldif_tempname( path ); + fd = tmpfname == NULL ? -1 : mkstemp( tmpfname ); + if ( fd < 0 ) { + save_errno = errno; + Debug( LDAP_DEBUG_ANY, "ldif_write_entry: %s for \"%s\": %s\n", + "cannot create file", e->e_dn, STRERROR( save_errno ) ); + *text = "internal error (cannot create file)"; + + } else { + ber_len_t dn_len = e->e_name.bv_len; + struct berval rdn; + + /* Only save the RDN onto disk */ + dnRdn( &e->e_name, &rdn ); + if ( rdn.bv_len != dn_len ) { + e->e_name.bv_val[rdn.bv_len] = '\0'; + e->e_name.bv_len = rdn.bv_len; + } + + res = -2; + ldap_pvt_thread_mutex_lock( &entry2str_mutex ); + entry_as_string = entry2str( e, &entry_length ); + if ( entry_as_string != NULL ) + res = spew_file( fd, entry_as_string, entry_length, &save_errno ); + ldap_pvt_thread_mutex_unlock( &entry2str_mutex ); + + /* Restore full DN */ + if ( rdn.bv_len != dn_len ) { + e->e_name.bv_val[rdn.bv_len] = ','; + e->e_name.bv_len = dn_len; + } + + if ( close( fd ) < 0 && res >= 0 ) { + res = -1; + save_errno = errno; + } + + if ( res >= 0 ) { + if ( move_file( tmpfname, path->bv_val ) == 0 ) { + Debug( LDAP_DEBUG_TRACE, "ldif_write_entry: " + "wrote entry \"%s\"\n", e->e_name.bv_val, 0, 0 ); + rc = LDAP_SUCCESS; + } else { + save_errno = errno; + Debug( LDAP_DEBUG_ANY, "ldif_write_entry: " + "could not put entry file for \"%s\" in place: %s\n", + e->e_name.bv_val, STRERROR( save_errno ), 0 ); + *text = "internal error (could not put entry file in place)"; + } + } else if ( res == -1 ) { + Debug( LDAP_DEBUG_ANY, "ldif_write_entry: %s \"%s\": %s\n", + "write error to", tmpfname, STRERROR( save_errno ) ); + *text = "internal error (write error to entry file)"; + } + + if ( rc != LDAP_SUCCESS ) { + unlink( tmpfname ); + } + } + + if ( tmpfname ) + SLAP_FREE( tmpfname ); + return rc; +} + +/* + * Read the entry at path, or if entryp==NULL just see if it exists. + * pdn and pndn are the parent's DN and normalized DN, or both NULL. + * Return an LDAP result code. + */ +static int +ldif_read_entry( + Operation *op, + const char *path, + struct berval *pdn, + struct berval *pndn, + Entry **entryp, + const char **text ) +{ + int rc; + Entry *entry; + char *entry_as_string; + struct berval rdn; + + /* TODO: Does slapd prevent Abandon of Bind as per rfc4511? + * If so we need not check for LDAP_REQ_BIND here. + */ + if ( op->o_abandon && op->o_tag != LDAP_REQ_BIND ) + return SLAPD_ABANDON; + + rc = ldif_read_file( path, entryp ? &entry_as_string : NULL ); + + switch ( rc ) { + case LDAP_SUCCESS: + if ( entryp == NULL ) + break; + *entryp = entry = str2entry( entry_as_string ); + SLAP_FREE( entry_as_string ); + if ( entry == NULL ) { + rc = LDAP_OTHER; + if ( text != NULL ) + *text = "internal error (cannot parse some entry file)"; + break; + } + if ( pdn == NULL || BER_BVISEMPTY( pdn ) ) + break; + /* Append parent DN to DN from LDIF file */ + rdn = entry->e_name; + build_new_dn( &entry->e_name, pdn, &rdn, NULL ); + SLAP_FREE( rdn.bv_val ); + rdn = entry->e_nname; + build_new_dn( &entry->e_nname, pndn, &rdn, NULL ); + SLAP_FREE( rdn.bv_val ); + break; + + case LDAP_OTHER: + if ( text != NULL ) + *text = entryp + ? "internal error (cannot read some entry file)" + : "internal error (cannot stat some entry file)"; + break; + } + + return rc; +} + +/* + * Read the operation's entry, or if entryp==NULL just see if it exists. + * Return an LDAP result code. May set *text to a message on failure. + * If pathp is non-NULL, set it to the entry filename on success. + */ +static int +get_entry( + Operation *op, + Entry **entryp, + struct berval *pathp, + const char **text ) +{ + int rc; + struct berval path, pdn, pndn; + + dnParent( &op->o_req_dn, &pdn ); + dnParent( &op->o_req_ndn, &pndn ); + rc = ndn2path( op, &op->o_req_ndn, &path, 0 ); + if ( rc != LDAP_SUCCESS ) { + goto done; + } + + rc = ldif_read_entry( op, path.bv_val, &pdn, &pndn, entryp, text ); + + if ( rc == LDAP_SUCCESS && pathp != NULL ) { + *pathp = path; + } else { + SLAP_FREE( path.bv_val ); + } + done: + return rc; +} + + +/* + * RDN-named directory entry, with special handling of "attr={num}val" RDNs. + * For sorting, filename "attr=val.ldif" is truncated to "attr="val\0ldif", + * and filename "attr={num}val.ldif" to "attr={\0um}val.ldif". + * Does not sort escaped chars correctly, would need to un-escape them. + */ +typedef struct bvlist { + struct bvlist *next; + char *trunc; /* filename was truncated here */ + int inum; /* num from "attr={num}" in filename, or INT_MIN */ + char savech; /* original char at *trunc */ + /* BVL_NAME(&bvlist) is the filename, allocated after the struct: */ +# define BVL_NAME(bvl) ((char *) ((bvl) + 1)) +# define BVL_SIZE(namelen) (sizeof(bvlist) + (namelen) + 1) +} bvlist; + +static int +ldif_send_entry( Operation *op, SlapReply *rs, Entry *e, int scope ) +{ + int rc = LDAP_SUCCESS; + + if ( scope == LDAP_SCOPE_BASE || scope == LDAP_SCOPE_SUBTREE ) { + if ( rs == NULL ) { + /* Save the entry for tool mode */ + struct ldif_tool *tl = + &((struct ldif_info *) op->o_bd->be_private)->li_tool; + + if ( tl->ecount >= tl->elen ) { + /* Allocate/grow entries */ + ID elen = tl->elen ? tl->elen * 2 : ENTRY_BUFF_INCREMENT; + Entry **entries = (Entry **) SLAP_REALLOC( tl->entries, + sizeof(Entry *) * elen ); + if ( entries == NULL ) { + Debug( LDAP_DEBUG_ANY, + "ldif_send_entry: out of memory\n", 0, 0, 0 ); + rc = LDAP_OTHER; + goto done; + } + tl->elen = elen; + tl->entries = entries; + } + tl->entries[tl->ecount++] = e; + return rc; + } + + else if ( !get_manageDSAit( op ) && is_entry_referral( e ) ) { + /* Send a continuation reference. + * (ldif_back_referrals() handles baseobject referrals.) + * Don't check the filter since it's only a candidate. + */ + BerVarray refs = get_entry_referrals( op, e ); + rs->sr_ref = referral_rewrite( refs, &e->e_name, NULL, scope ); + rs->sr_entry = e; + rc = send_search_reference( op, rs ); + ber_bvarray_free( rs->sr_ref ); + ber_bvarray_free( refs ); + rs->sr_ref = NULL; + rs->sr_entry = NULL; + } + + else if ( test_filter( op, e, op->ors_filter ) == LDAP_COMPARE_TRUE ) { + rs->sr_entry = e; + rs->sr_attrs = op->ors_attrs; + /* Could set REP_ENTRY_MUSTBEFREED too for efficiency, + * but refraining lets us test unFREEable MODIFIABLE + * entries. Like entries built on the stack. + */ + rs->sr_flags = REP_ENTRY_MODIFIABLE; + rc = send_search_entry( op, rs ); + rs->sr_entry = NULL; + rs->sr_attrs = NULL; + } + } + + done: + entry_free( e ); + return rc; +} + +/* Read LDIF directory <path> into <listp>. Set *fname_maxlenp. */ +static int +ldif_readdir( + Operation *op, + SlapReply *rs, + const struct berval *path, + bvlist **listp, + ber_len_t *fname_maxlenp ) +{ + int rc = LDAP_SUCCESS; + DIR *dir_of_path; + + *listp = NULL; + *fname_maxlenp = 0; + + dir_of_path = opendir( path->bv_val ); + if ( dir_of_path == NULL ) { + int save_errno = errno; + struct ldif_info *li = (struct ldif_info *) op->o_bd->be_private; + int is_rootDSE = (path->bv_len == li->li_base_path.bv_len); + + /* Absent directory is OK (leaf entry), except the database dir */ + if ( is_rootDSE || save_errno != ENOENT ) { + Debug( LDAP_DEBUG_ANY, + "=> ldif_search_entry: failed to opendir \"%s\": %s\n", + path->bv_val, STRERROR( save_errno ), 0 ); + rc = LDAP_OTHER; + if ( rs != NULL ) + rs->sr_text = + save_errno != ENOENT ? "internal error (bad directory)" + : "internal error (database directory does not exist)"; + } + + } else { + bvlist *ptr; + struct dirent *dir; + int save_errno = 0; + + while ( (dir = readdir( dir_of_path )) != NULL ) { + size_t fname_len; + bvlist *bvl, **prev; + char *trunc, *idxp, *endp, *endp2; + + fname_len = strlen( dir->d_name ); + if ( fname_len < STRLENOF( "x=" LDIF )) /* min filename size */ + continue; + if ( strcmp( dir->d_name + fname_len - STRLENOF(LDIF), LDIF )) + continue; + + if ( *fname_maxlenp < fname_len ) + *fname_maxlenp = fname_len; + + bvl = SLAP_MALLOC( BVL_SIZE( fname_len ) ); + if ( bvl == NULL ) { + rc = LDAP_OTHER; + save_errno = errno; + break; + } + strcpy( BVL_NAME( bvl ), dir->d_name ); + + /* Make it sortable by ("attr=val" or <preceding {num}, num>) */ + trunc = BVL_NAME( bvl ) + fname_len - STRLENOF( LDIF ); + if ( (idxp = strchr( BVL_NAME( bvl ) + 2, IX_FSL )) != NULL && + (endp = strchr( ++idxp, IX_FSR )) != NULL && endp > idxp && + (eq_unsafe || idxp[-2] == '=' || endp + 1 == trunc) ) + { + /* attr={n}val or bconfig.c's "pseudo-indexed" attr=val{n} */ + bvl->inum = strtol( idxp, &endp2, 10 ); + if ( endp2 == endp ) { + trunc = idxp; + goto truncate; + } + } + bvl->inum = INT_MIN; + truncate: + bvl->trunc = trunc; + bvl->savech = *trunc; + *trunc = '\0'; + + /* Insertion sort */ + for ( prev = listp; (ptr = *prev) != NULL; prev = &ptr->next ) { + int cmp = strcmp( BVL_NAME( bvl ), BVL_NAME( ptr )); + if ( cmp < 0 || (cmp == 0 && bvl->inum < ptr->inum) ) + break; + } + *prev = bvl; + bvl->next = ptr; + } + + if ( closedir( dir_of_path ) < 0 ) { + save_errno = errno; + rc = LDAP_OTHER; + if ( rs != NULL ) + rs->sr_text = "internal error (bad directory)"; + } + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "ldif_search_entry: %s \"%s\": %s\n", + "error reading directory", path->bv_val, + STRERROR( save_errno ) ); + } + } + + return rc; +} + +/* + * Send an entry, recursively search its children, and free or save it. + * Return an LDAP result code. Parameters: + * op, rs operation and reply. rs == NULL for slap tools. + * e entry to search, or NULL for rootDSE. + * scope scope for the part of the search from this entry. + * path LDIF filename -- bv_len and non-directory part are overwritten. + */ +static int +ldif_search_entry( + Operation *op, + SlapReply *rs, + Entry *e, + int scope, + struct berval *path ) +{ + int rc = LDAP_SUCCESS; + struct berval dn = BER_BVC( "" ), ndn = BER_BVC( "" ); + + if ( scope != LDAP_SCOPE_BASE && e != NULL ) { + /* Copy DN/NDN since we send the entry with REP_ENTRY_MODIFIABLE, + * which bconfig.c seems to need. (TODO: see config_rename_one.) + */ + if ( ber_dupbv( &dn, &e->e_name ) == NULL || + ber_dupbv( &ndn, &e->e_nname ) == NULL ) + { + Debug( LDAP_DEBUG_ANY, + "ldif_search_entry: out of memory\n", 0, 0, 0 ); + rc = LDAP_OTHER; + goto done; + } + } + + /* Send the entry if appropriate, and free or save it */ + if ( e != NULL ) + rc = ldif_send_entry( op, rs, e, scope ); + + /* Search the children */ + if ( scope != LDAP_SCOPE_BASE && rc == LDAP_SUCCESS ) { + bvlist *list, *ptr; + struct berval fpath; /* becomes child pathname */ + char *dir_end; /* will point past dirname in fpath */ + + ldif2dir_len( *path ); + ldif2dir_name( *path ); + rc = ldif_readdir( op, rs, path, &list, &fpath.bv_len ); + + if ( list != NULL ) { + const char **text = rs == NULL ? NULL : &rs->sr_text; + + if ( scope == LDAP_SCOPE_ONELEVEL ) + scope = LDAP_SCOPE_BASE; + else if ( scope == LDAP_SCOPE_SUBORDINATE ) + scope = LDAP_SCOPE_SUBTREE; + + /* Allocate fpath and fill in directory part */ + dir_end = fullpath_alloc( &fpath, path, fpath.bv_len ); + if ( dir_end == NULL ) + rc = LDAP_OTHER; + + do { + ptr = list; + + if ( rc == LDAP_SUCCESS ) { + *ptr->trunc = ptr->savech; + FILL_PATH( &fpath, dir_end, BVL_NAME( ptr )); + + rc = ldif_read_entry( op, fpath.bv_val, &dn, &ndn, + &e, text ); + switch ( rc ) { + case LDAP_SUCCESS: + rc = ldif_search_entry( op, rs, e, scope, &fpath ); + break; + case LDAP_NO_SUCH_OBJECT: + /* Only the search baseDN may produce noSuchObject. */ + rc = LDAP_OTHER; + if ( rs != NULL ) + rs->sr_text = "internal error " + "(did someone just remove an entry file?)"; + Debug( LDAP_DEBUG_ANY, "ldif_search_entry: " + "file listed in parent directory does not exist: " + "\"%s\"\n", fpath.bv_val, 0, 0 ); + break; + } + } + + list = ptr->next; + SLAP_FREE( ptr ); + } while ( list != NULL ); + + if ( !BER_BVISNULL( &fpath ) ) + SLAP_FREE( fpath.bv_val ); + } + } + + done: + if ( !BER_BVISEMPTY( &dn ) ) + ber_memfree( dn.bv_val ); + if ( !BER_BVISEMPTY( &ndn ) ) + ber_memfree( ndn.bv_val ); + return rc; +} + +static int +search_tree( Operation *op, SlapReply *rs ) +{ + int rc = LDAP_SUCCESS; + Entry *e = NULL; + struct berval path; + struct berval pdn, pndn; + + (void) ndn2path( op, &op->o_req_ndn, &path, 1 ); + if ( !BER_BVISEMPTY( &op->o_req_ndn ) ) { + /* Read baseObject */ + dnParent( &op->o_req_dn, &pdn ); + dnParent( &op->o_req_ndn, &pndn ); + rc = ldif_read_entry( op, path.bv_val, &pdn, &pndn, &e, + rs == NULL ? NULL : &rs->sr_text ); + } + if ( rc == LDAP_SUCCESS ) + rc = ldif_search_entry( op, rs, e, op->ors_scope, &path ); + + ch_free( path.bv_val ); + return rc; +} + + +/* + * Prepare to create or rename an entry: + * Check that the entry does not already exist. + * Check that the parent entry exists and can have subordinates, + * unless need_dir is NULL or adding the suffix entry. + * + * Return an LDAP result code. May set *text to a message on failure. + * If success, set *dnpath to LDIF entry path and *need_dir to + * (directory must be created ? dirname : NULL). + */ +static int +ldif_prepare_create( + Operation *op, + Entry *e, + struct berval *dnpath, + char **need_dir, + const char **text ) +{ + struct ldif_info *li = (struct ldif_info *) op->o_bd->be_private; + struct berval *ndn = &e->e_nname; + struct berval ppath = BER_BVNULL; + struct stat st; + Entry *parent = NULL; + int rc; + + if ( op->o_abandon ) + return SLAPD_ABANDON; + + rc = ndn2path( op, ndn, dnpath, 0 ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( stat( dnpath->bv_val, &st ) == 0 ) { /* entry .ldif file */ + rc = LDAP_ALREADY_EXISTS; + + } else if ( errno != ENOENT ) { + Debug( LDAP_DEBUG_ANY, + "ldif_prepare_create: cannot stat \"%s\": %s\n", + dnpath->bv_val, STRERROR( errno ), 0 ); + rc = LDAP_OTHER; + *text = "internal error (cannot check entry file)"; + + } else if ( need_dir != NULL ) { + *need_dir = NULL; + rc = get_parent_path( dnpath, &ppath ); + /* If parent dir exists, so does parent .ldif: + * The directory gets created after and removed before the .ldif. + * Except with the database directory, which has no matching entry. + */ + if ( rc == LDAP_SUCCESS && stat( ppath.bv_val, &st ) < 0 ) { + rc = errno == ENOENT && ppath.bv_len > li->li_base_path.bv_len + ? LDAP_NO_SUCH_OBJECT : LDAP_OTHER; + } + switch ( rc ) { + case LDAP_NO_SUCH_OBJECT: + /* No parent dir, check parent .ldif */ + dir2ldif_name( ppath ); + rc = ldif_read_entry( op, ppath.bv_val, NULL, NULL, + (op->o_tag != LDAP_REQ_ADD || get_manageDSAit( op ) + ? &parent : NULL), + text ); + switch ( rc ) { + case LDAP_SUCCESS: + /* Check that parent is not a referral, unless + * ldif_back_referrals() already checked. + */ + if ( parent != NULL ) { + int is_ref = is_entry_referral( parent ); + entry_free( parent ); + if ( is_ref ) { + rc = LDAP_AFFECTS_MULTIPLE_DSAS; + *text = op->o_tag == LDAP_REQ_MODDN + ? "newSuperior is a referral object" + : "parent is a referral object"; + break; + } + } + /* Must create parent directory. */ + ldif2dir_name( ppath ); + *need_dir = ppath.bv_val; + break; + case LDAP_NO_SUCH_OBJECT: + *text = op->o_tag == LDAP_REQ_MODDN + ? "newSuperior object does not exist" + : "parent does not exist"; + break; + } + break; + case LDAP_OTHER: + Debug( LDAP_DEBUG_ANY, + "ldif_prepare_create: cannot stat \"%s\" parent dir: %s\n", + ndn->bv_val, STRERROR( errno ), 0 ); + *text = "internal error (cannot stat parent dir)"; + break; + } + if ( *need_dir == NULL && ppath.bv_val != NULL ) + SLAP_FREE( ppath.bv_val ); + } + + if ( rc != LDAP_SUCCESS ) { + SLAP_FREE( dnpath->bv_val ); + BER_BVZERO( dnpath ); + } + return rc; +} + +static int +apply_modify_to_entry( + Entry *entry, + Modifications *modlist, + Operation *op, + SlapReply *rs, + char *textbuf ) +{ + int rc = modlist ? LDAP_UNWILLING_TO_PERFORM : LDAP_SUCCESS; + int is_oc = 0; + Modification *mods; + + if (!acl_check_modlist(op, entry, modlist)) { + return LDAP_INSUFFICIENT_ACCESS; + } + + for (; modlist != NULL; modlist = modlist->sml_next) { + mods = &modlist->sml_mod; + + if ( mods->sm_desc == slap_schema.si_ad_objectClass ) { + is_oc = 1; + } + switch (mods->sm_op) { + case LDAP_MOD_ADD: + rc = modify_add_values(entry, mods, + get_permissiveModify(op), + &rs->sr_text, textbuf, + SLAP_TEXT_BUFLEN ); + break; + + case LDAP_MOD_DELETE: + rc = modify_delete_values(entry, mods, + get_permissiveModify(op), + &rs->sr_text, textbuf, + SLAP_TEXT_BUFLEN ); + break; + + case LDAP_MOD_REPLACE: + rc = modify_replace_values(entry, mods, + get_permissiveModify(op), + &rs->sr_text, textbuf, + SLAP_TEXT_BUFLEN ); + break; + + case LDAP_MOD_INCREMENT: + rc = modify_increment_values( entry, + mods, get_permissiveModify(op), + &rs->sr_text, textbuf, + SLAP_TEXT_BUFLEN ); + break; + + case SLAP_MOD_SOFTADD: + mods->sm_op = LDAP_MOD_ADD; + rc = modify_add_values(entry, mods, + get_permissiveModify(op), + &rs->sr_text, textbuf, + SLAP_TEXT_BUFLEN ); + mods->sm_op = SLAP_MOD_SOFTADD; + if (rc == LDAP_TYPE_OR_VALUE_EXISTS) { + rc = LDAP_SUCCESS; + } + break; + + case SLAP_MOD_SOFTDEL: + mods->sm_op = LDAP_MOD_DELETE; + rc = modify_delete_values(entry, mods, + get_permissiveModify(op), + &rs->sr_text, textbuf, + SLAP_TEXT_BUFLEN ); + mods->sm_op = SLAP_MOD_SOFTDEL; + if (rc == LDAP_NO_SUCH_ATTRIBUTE) { + rc = LDAP_SUCCESS; + } + break; + + case SLAP_MOD_ADD_IF_NOT_PRESENT: + if ( attr_find( entry->e_attrs, mods->sm_desc ) ) { + rc = LDAP_SUCCESS; + break; + } + mods->sm_op = LDAP_MOD_ADD; + rc = modify_add_values(entry, mods, + get_permissiveModify(op), + &rs->sr_text, textbuf, + SLAP_TEXT_BUFLEN ); + mods->sm_op = SLAP_MOD_ADD_IF_NOT_PRESENT; + break; + } + if(rc != LDAP_SUCCESS) break; + } + + if ( rc == LDAP_SUCCESS ) { + rs->sr_text = NULL; /* Needed at least with SLAP_MOD_SOFTADD */ + if ( is_oc ) { + entry->e_ocflags = 0; + } + /* check that the entry still obeys the schema */ + rc = entry_schema_check( op, entry, NULL, 0, 0, NULL, + &rs->sr_text, textbuf, SLAP_TEXT_BUFLEN ); + } + + return rc; +} + + +static int +ldif_back_referrals( Operation *op, SlapReply *rs ) +{ + struct ldif_info *li = (struct ldif_info *) op->o_bd->be_private; + struct berval path, dn = op->o_req_dn, ndn = op->o_req_ndn; + ber_len_t min_dnlen; + Entry *entry = NULL, **entryp; + BerVarray ref; + int rc; + + min_dnlen = op->o_bd->be_nsuffix[0].bv_len; + if ( min_dnlen == 0 ) { + /* Catch root DSE (empty DN), it is not a referral */ + min_dnlen = 1; + } + if ( ndn2path( op, &ndn, &path, 0 ) != LDAP_SUCCESS ) { + return LDAP_SUCCESS; /* Root DSE again */ + } + + entryp = get_manageDSAit( op ) ? NULL : &entry; + ldap_pvt_thread_rdwr_rlock( &li->li_rdwr ); + + for (;;) { + dnParent( &dn, &dn ); + dnParent( &ndn, &ndn ); + rc = ldif_read_entry( op, path.bv_val, &dn, &ndn, + entryp, &rs->sr_text ); + if ( rc != LDAP_NO_SUCH_OBJECT ) + break; + + rc = LDAP_SUCCESS; + if ( ndn.bv_len < min_dnlen ) + break; + (void) get_parent_path( &path, NULL ); + dir2ldif_name( path ); + entryp = &entry; + } + + ldap_pvt_thread_rdwr_runlock( &li->li_rdwr ); + SLAP_FREE( path.bv_val ); + + if ( entry != NULL ) { + if ( is_entry_referral( entry ) ) { + Debug( LDAP_DEBUG_TRACE, + "ldif_back_referrals: tag=%lu target=\"%s\" matched=\"%s\"\n", + (unsigned long) op->o_tag, op->o_req_dn.bv_val, entry->e_dn ); + + ref = get_entry_referrals( op, entry ); + rs->sr_ref = referral_rewrite( ref, &entry->e_name, &op->o_req_dn, + op->o_tag == LDAP_REQ_SEARCH ? + op->ors_scope : LDAP_SCOPE_DEFAULT ); + ber_bvarray_free( ref ); + + if ( rs->sr_ref != NULL ) { + /* send referral */ + rc = rs->sr_err = LDAP_REFERRAL; + rs->sr_matched = entry->e_dn; + send_ldap_result( op, rs ); + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } else { + rc = LDAP_OTHER; + rs->sr_text = "bad referral object"; + } + rs->sr_matched = NULL; + } + + entry_free( entry ); + } + + return rc; +} + + +/* LDAP operations */ + +static int +ldif_back_bind( Operation *op, SlapReply *rs ) +{ + struct ldif_info *li; + Attribute *a; + AttributeDescription *password = slap_schema.si_ad_userPassword; + int return_val; + Entry *entry = NULL; + + switch ( be_rootdn_bind( op, rs ) ) { + case SLAP_CB_CONTINUE: + break; + + default: + /* in case of success, front end will send result; + * otherwise, be_rootdn_bind() did */ + return rs->sr_err; + } + + li = (struct ldif_info *) op->o_bd->be_private; + ldap_pvt_thread_rdwr_rlock(&li->li_rdwr); + return_val = get_entry(op, &entry, NULL, NULL); + + /* no object is found for them */ + if(return_val != LDAP_SUCCESS) { + rs->sr_err = return_val = LDAP_INVALID_CREDENTIALS; + goto return_result; + } + + /* they don't have userpassword */ + if((a = attr_find(entry->e_attrs, password)) == NULL) { + rs->sr_err = LDAP_INAPPROPRIATE_AUTH; + return_val = 1; + goto return_result; + } + + /* authentication actually failed */ + if(slap_passwd_check(op, entry, a, &op->oq_bind.rb_cred, + &rs->sr_text) != 0) { + rs->sr_err = LDAP_INVALID_CREDENTIALS; + return_val = 1; + goto return_result; + } + + /* let the front-end send success */ + return_val = LDAP_SUCCESS; + + return_result: + ldap_pvt_thread_rdwr_runlock(&li->li_rdwr); + if(return_val != LDAP_SUCCESS) + send_ldap_result( op, rs ); + if(entry != NULL) + entry_free(entry); + return return_val; +} + +static int +ldif_back_search( Operation *op, SlapReply *rs ) +{ + struct ldif_info *li = (struct ldif_info *) op->o_bd->be_private; + + ldap_pvt_thread_rdwr_rlock(&li->li_rdwr); + rs->sr_err = search_tree( op, rs ); + ldap_pvt_thread_rdwr_runlock(&li->li_rdwr); + send_ldap_result(op, rs); + + return rs->sr_err; +} + +static int +ldif_back_add( Operation *op, SlapReply *rs ) +{ + struct ldif_info *li = (struct ldif_info *) op->o_bd->be_private; + Entry * e = op->ora_e; + struct berval path; + char *parentdir; + char textbuf[SLAP_TEXT_BUFLEN]; + int rc; + + Debug( LDAP_DEBUG_TRACE, "ldif_back_add: \"%s\"\n", e->e_dn, 0, 0 ); + + rc = entry_schema_check( op, e, NULL, 0, 1, NULL, + &rs->sr_text, textbuf, sizeof( textbuf ) ); + if ( rc != LDAP_SUCCESS ) + goto send_res; + + rc = slap_add_opattrs( op, &rs->sr_text, textbuf, sizeof( textbuf ), 1 ); + if ( rc != LDAP_SUCCESS ) + goto send_res; + + ldap_pvt_thread_mutex_lock( &li->li_modop_mutex ); + + rc = ldif_prepare_create( op, e, &path, &parentdir, &rs->sr_text ); + if ( rc == LDAP_SUCCESS ) { + ldap_pvt_thread_rdwr_wlock( &li->li_rdwr ); + rc = ldif_write_entry( op, e, &path, parentdir, &rs->sr_text ); + ldap_pvt_thread_rdwr_wunlock( &li->li_rdwr ); + + SLAP_FREE( path.bv_val ); + if ( parentdir != NULL ) + SLAP_FREE( parentdir ); + } + + ldap_pvt_thread_mutex_unlock( &li->li_modop_mutex ); + + send_res: + rs->sr_err = rc; + Debug( LDAP_DEBUG_TRACE, "ldif_back_add: err: %d text: %s\n", + rc, rs->sr_text ? rs->sr_text : "", 0 ); + send_ldap_result( op, rs ); + slap_graduate_commit_csn( op ); + rs->sr_text = NULL; /* remove possible pointer to textbuf */ + return rs->sr_err; +} + +static int +ldif_back_modify( Operation *op, SlapReply *rs ) +{ + struct ldif_info *li = (struct ldif_info *) op->o_bd->be_private; + Modifications * modlst = op->orm_modlist; + struct berval path; + Entry *entry; + char textbuf[SLAP_TEXT_BUFLEN]; + int rc; + + slap_mods_opattrs( op, &op->orm_modlist, 1 ); + + ldap_pvt_thread_mutex_lock( &li->li_modop_mutex ); + + rc = get_entry( op, &entry, &path, &rs->sr_text ); + if ( rc == LDAP_SUCCESS ) { + rc = apply_modify_to_entry( entry, modlst, op, rs, textbuf ); + if ( rc == LDAP_SUCCESS ) { + ldap_pvt_thread_rdwr_wlock( &li->li_rdwr ); + rc = ldif_write_entry( op, entry, &path, NULL, &rs->sr_text ); + ldap_pvt_thread_rdwr_wunlock( &li->li_rdwr ); + } + + entry_free( entry ); + SLAP_FREE( path.bv_val ); + } + + ldap_pvt_thread_mutex_unlock( &li->li_modop_mutex ); + + rs->sr_err = rc; + send_ldap_result( op, rs ); + slap_graduate_commit_csn( op ); + rs->sr_text = NULL; /* remove possible pointer to textbuf */ + return rs->sr_err; +} + +static int +ldif_back_delete( Operation *op, SlapReply *rs ) +{ + struct ldif_info *li = (struct ldif_info *) op->o_bd->be_private; + struct berval path; + int rc = LDAP_SUCCESS; + + if ( BER_BVISEMPTY( &op->o_csn )) { + struct berval csn; + char csnbuf[LDAP_PVT_CSNSTR_BUFSIZE]; + + csn.bv_val = csnbuf; + csn.bv_len = sizeof( csnbuf ); + slap_get_csn( op, &csn, 1 ); + } + + ldap_pvt_thread_mutex_lock( &li->li_modop_mutex ); + ldap_pvt_thread_rdwr_wlock( &li->li_rdwr ); + if ( op->o_abandon ) { + rc = SLAPD_ABANDON; + goto done; + } + + rc = ndn2path( op, &op->o_req_ndn, &path, 0 ); + if ( rc != LDAP_SUCCESS ) { + goto done; + } + + ldif2dir_len( path ); + ldif2dir_name( path ); + if ( rmdir( path.bv_val ) < 0 ) { + switch ( errno ) { + case ENOTEMPTY: + rc = LDAP_NOT_ALLOWED_ON_NONLEAF; + break; + case ENOENT: + /* is leaf, go on */ + break; + default: + rc = LDAP_OTHER; + rs->sr_text = "internal error (cannot delete subtree directory)"; + break; + } + } + + if ( rc == LDAP_SUCCESS ) { + dir2ldif_name( path ); + if ( unlink( path.bv_val ) < 0 ) { + rc = LDAP_NO_SUCH_OBJECT; + if ( errno != ENOENT ) { + rc = LDAP_OTHER; + rs->sr_text = "internal error (cannot delete entry file)"; + } + } + } + + if ( rc == LDAP_OTHER ) { + Debug( LDAP_DEBUG_ANY, "ldif_back_delete: %s \"%s\": %s\n", + "cannot delete", path.bv_val, STRERROR( errno ) ); + } + + SLAP_FREE( path.bv_val ); + done: + ldap_pvt_thread_rdwr_wunlock( &li->li_rdwr ); + ldap_pvt_thread_mutex_unlock( &li->li_modop_mutex ); + rs->sr_err = rc; + send_ldap_result( op, rs ); + slap_graduate_commit_csn( op ); + return rs->sr_err; +} + + +static int +ldif_move_entry( + Operation *op, + Entry *entry, + int same_ndn, + struct berval *oldpath, + const char **text ) +{ + struct ldif_info *li = (struct ldif_info *) op->o_bd->be_private; + struct berval newpath; + char *parentdir = NULL, *trash; + int rc, rename_res; + + if ( same_ndn ) { + rc = LDAP_SUCCESS; + newpath = *oldpath; + } else { + rc = ldif_prepare_create( op, entry, &newpath, + op->orr_newSup ? &parentdir : NULL, text ); + } + + if ( rc == LDAP_SUCCESS ) { + ldap_pvt_thread_rdwr_wlock( &li->li_rdwr ); + + rc = ldif_write_entry( op, entry, &newpath, parentdir, text ); + if ( rc == LDAP_SUCCESS && !same_ndn ) { + trash = oldpath->bv_val; /* will be .ldif file to delete */ + ldif2dir_len( newpath ); + ldif2dir_len( *oldpath ); + /* Move subdir before deleting old entry, + * so .ldif always exists if subdir does. + */ + ldif2dir_name( newpath ); + ldif2dir_name( *oldpath ); + rename_res = move_dir( oldpath->bv_val, newpath.bv_val ); + if ( rename_res != 0 && errno != ENOENT ) { + rc = LDAP_OTHER; + *text = "internal error (cannot move this subtree)"; + trash = newpath.bv_val; + } + + /* Delete old entry, or if error undo change */ + for (;;) { + dir2ldif_name( newpath ); + dir2ldif_name( *oldpath ); + if ( unlink( trash ) == 0 ) + break; + if ( rc == LDAP_SUCCESS ) { + /* Prepare to undo change and return failure */ + rc = LDAP_OTHER; + *text = "internal error (cannot move this entry)"; + trash = newpath.bv_val; + if ( rename_res != 0 ) + continue; + /* First move subdirectory back */ + ldif2dir_name( newpath ); + ldif2dir_name( *oldpath ); + if ( move_dir( newpath.bv_val, oldpath->bv_val ) == 0 ) + continue; + } + *text = "added new but couldn't delete old entry!"; + break; + } + + if ( rc != LDAP_SUCCESS ) { + char s[128]; + snprintf( s, sizeof s, "%s (%s)", *text, STRERROR( errno )); + Debug( LDAP_DEBUG_ANY, + "ldif_move_entry: %s: \"%s\" -> \"%s\"\n", + s, op->o_req_dn.bv_val, entry->e_dn ); + } + } + + ldap_pvt_thread_rdwr_wunlock( &li->li_rdwr ); + if ( !same_ndn ) + SLAP_FREE( newpath.bv_val ); + if ( parentdir != NULL ) + SLAP_FREE( parentdir ); + } + + return rc; +} + +static int +ldif_back_modrdn( Operation *op, SlapReply *rs ) +{ + struct ldif_info *li = (struct ldif_info *) op->o_bd->be_private; + struct berval new_dn = BER_BVNULL, new_ndn = BER_BVNULL; + struct berval p_dn, old_path; + Entry *entry; + char textbuf[SLAP_TEXT_BUFLEN]; + int rc, same_ndn; + + slap_mods_opattrs( op, &op->orr_modlist, 1 ); + + ldap_pvt_thread_mutex_lock( &li->li_modop_mutex ); + + rc = get_entry( op, &entry, &old_path, &rs->sr_text ); + if ( rc == LDAP_SUCCESS ) { + /* build new dn, and new ndn for the entry */ + if ( op->oq_modrdn.rs_newSup != NULL ) { + p_dn = *op->oq_modrdn.rs_newSup; + } else { + dnParent( &entry->e_name, &p_dn ); + } + build_new_dn( &new_dn, &p_dn, &op->oq_modrdn.rs_newrdn, NULL ); + dnNormalize( 0, NULL, NULL, &new_dn, &new_ndn, NULL ); + same_ndn = !ber_bvcmp( &entry->e_nname, &new_ndn ); + ber_memfree_x( entry->e_name.bv_val, NULL ); + ber_memfree_x( entry->e_nname.bv_val, NULL ); + entry->e_name = new_dn; + entry->e_nname = new_ndn; + + /* perform the modifications */ + rc = apply_modify_to_entry( entry, op->orr_modlist, op, rs, textbuf ); + if ( rc == LDAP_SUCCESS ) + rc = ldif_move_entry( op, entry, same_ndn, &old_path, + &rs->sr_text ); + + entry_free( entry ); + SLAP_FREE( old_path.bv_val ); + } + + ldap_pvt_thread_mutex_unlock( &li->li_modop_mutex ); + rs->sr_err = rc; + send_ldap_result( op, rs ); + slap_graduate_commit_csn( op ); + rs->sr_text = NULL; /* remove possible pointer to textbuf */ + return rs->sr_err; +} + + +/* Return LDAP_SUCCESS IFF we retrieve the specified entry. */ +static int +ldif_back_entry_get( + Operation *op, + struct berval *ndn, + ObjectClass *oc, + AttributeDescription *at, + int rw, + Entry **e ) +{ + struct ldif_info *li = (struct ldif_info *) op->o_bd->be_private; + struct berval op_dn = op->o_req_dn, op_ndn = op->o_req_ndn; + int rc; + + assert( ndn != NULL ); + assert( !BER_BVISNULL( ndn ) ); + + ldap_pvt_thread_rdwr_rlock( &li->li_rdwr ); + op->o_req_dn = *ndn; + op->o_req_ndn = *ndn; + rc = get_entry( op, e, NULL, NULL ); + op->o_req_dn = op_dn; + op->o_req_ndn = op_ndn; + ldap_pvt_thread_rdwr_runlock( &li->li_rdwr ); + + if ( rc == LDAP_SUCCESS && oc && !is_entry_objectclass_or_sub( *e, oc ) ) { + rc = LDAP_NO_SUCH_ATTRIBUTE; + entry_free( *e ); + *e = NULL; + } + + return rc; +} + + +/* Slap tools */ + +static int +ldif_tool_entry_open( BackendDB *be, int mode ) +{ + struct ldif_tool *tl = &((struct ldif_info *) be->be_private)->li_tool; + + tl->ecurrent = 0; + return 0; +} + +static int +ldif_tool_entry_close( BackendDB *be ) +{ + struct ldif_tool *tl = &((struct ldif_info *) be->be_private)->li_tool; + Entry **entries = tl->entries; + ID i; + + for ( i = tl->ecount; i--; ) + if ( entries[i] ) + entry_free( entries[i] ); + SLAP_FREE( entries ); + tl->entries = NULL; + tl->ecount = tl->elen = 0; + return 0; +} + +static ID +ldif_tool_entry_next( BackendDB *be ) +{ + struct ldif_tool *tl = &((struct ldif_info *) be->be_private)->li_tool; + + do { + Entry *e = tl->entries[ tl->ecurrent ]; + + if ( tl->ecurrent >= tl->ecount ) { + return NOID; + } + + ++tl->ecurrent; + + if ( tl->tl_base && !dnIsSuffixScope( &e->e_nname, tl->tl_base, tl->tl_scope ) ) { + continue; + } + + if ( tl->tl_filter && test_filter( NULL, e, tl->tl_filter ) != LDAP_COMPARE_TRUE ) { + continue; + } + + break; + } while ( 1 ); + + return tl->ecurrent; +} + +static ID +ldif_tool_entry_first_x( BackendDB *be, struct berval *base, int scope, Filter *f ) +{ + struct ldif_tool *tl = &((struct ldif_info *) be->be_private)->li_tool; + + tl->tl_base = base; + tl->tl_scope = scope; + tl->tl_filter = f; + + if ( tl->entries == NULL ) { + Operation op = {0}; + + op.o_bd = be; + op.o_req_dn = *be->be_suffix; + op.o_req_ndn = *be->be_nsuffix; + op.ors_scope = LDAP_SCOPE_SUBTREE; + if ( search_tree( &op, NULL ) != LDAP_SUCCESS ) { + tl->ecurrent = tl->ecount; /* fail ldif_tool_entry_next() */ + return NOID; /* fail ldif_tool_entry_get() */ + } + } + return ldif_tool_entry_next( be ); +} + +static Entry * +ldif_tool_entry_get( BackendDB *be, ID id ) +{ + struct ldif_tool *tl = &((struct ldif_info *) be->be_private)->li_tool; + Entry *e = NULL; + + --id; + if ( id < tl->ecount ) { + e = tl->entries[id]; + tl->entries[id] = NULL; + } + return e; +} + +static ID +ldif_tool_entry_put( BackendDB *be, Entry *e, struct berval *text ) +{ + int rc; + const char *errmsg = NULL; + struct berval path; + char *parentdir; + Operation op = {0}; + + op.o_bd = be; + rc = ldif_prepare_create( &op, e, &path, &parentdir, &errmsg ); + if ( rc == LDAP_SUCCESS ) { + rc = ldif_write_entry( &op, e, &path, parentdir, &errmsg ); + + SLAP_FREE( path.bv_val ); + if ( parentdir != NULL ) + SLAP_FREE( parentdir ); + if ( rc == LDAP_SUCCESS ) + return 1; + } + + if ( errmsg == NULL && rc != LDAP_OTHER ) + errmsg = ldap_err2string( rc ); + if ( errmsg != NULL ) + snprintf( text->bv_val, text->bv_len, "%s", errmsg ); + return NOID; +} + + +/* Setup */ + +static int +ldif_back_db_init( BackendDB *be, ConfigReply *cr ) +{ + struct ldif_info *li; + + li = ch_calloc( 1, sizeof(struct ldif_info) ); + be->be_private = li; + be->be_cf_ocs = ldifocs; + ldap_pvt_thread_mutex_init( &li->li_modop_mutex ); + ldap_pvt_thread_rdwr_init( &li->li_rdwr ); + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_ONE_SUFFIX; + return 0; +} + +static int +ldif_back_db_destroy( Backend *be, ConfigReply *cr ) +{ + struct ldif_info *li = be->be_private; + + ch_free( li->li_base_path.bv_val ); + ldap_pvt_thread_rdwr_destroy( &li->li_rdwr ); + ldap_pvt_thread_mutex_destroy( &li->li_modop_mutex ); + free( be->be_private ); + return 0; +} + +static int +ldif_back_db_open( Backend *be, ConfigReply *cr ) +{ + struct ldif_info *li = (struct ldif_info *) be->be_private; + if( BER_BVISEMPTY(&li->li_base_path)) {/* missing base path */ + Debug( LDAP_DEBUG_ANY, "missing base path for back-ldif\n", 0, 0, 0); + return 1; + } + return 0; +} + +int +ldif_back_initialize( BackendInfo *bi ) +{ + static char *controls[] = { + LDAP_CONTROL_MANAGEDSAIT, + NULL + }; + int rc; + + bi->bi_flags |= + SLAP_BFLAG_INCREMENT | + SLAP_BFLAG_REFERRALS; + + bi->bi_controls = controls; + + bi->bi_open = 0; + bi->bi_close = 0; + bi->bi_config = 0; + bi->bi_destroy = 0; + + bi->bi_db_init = ldif_back_db_init; + bi->bi_db_config = config_generic_wrapper; + bi->bi_db_open = ldif_back_db_open; + bi->bi_db_close = 0; + bi->bi_db_destroy = ldif_back_db_destroy; + + bi->bi_op_bind = ldif_back_bind; + bi->bi_op_unbind = 0; + bi->bi_op_search = ldif_back_search; + bi->bi_op_compare = 0; + bi->bi_op_modify = ldif_back_modify; + bi->bi_op_modrdn = ldif_back_modrdn; + bi->bi_op_add = ldif_back_add; + bi->bi_op_delete = ldif_back_delete; + bi->bi_op_abandon = 0; + + bi->bi_extended = 0; + + bi->bi_chk_referrals = ldif_back_referrals; + + bi->bi_connection_init = 0; + bi->bi_connection_destroy = 0; + + bi->bi_entry_get_rw = ldif_back_entry_get; + +#if 0 /* NOTE: uncomment to completely disable access control */ + bi->bi_access_allowed = slap_access_always_allowed; +#endif + + bi->bi_tool_entry_open = ldif_tool_entry_open; + bi->bi_tool_entry_close = ldif_tool_entry_close; + bi->bi_tool_entry_first = backend_tool_entry_first; + bi->bi_tool_entry_first_x = ldif_tool_entry_first_x; + bi->bi_tool_entry_next = ldif_tool_entry_next; + bi->bi_tool_entry_get = ldif_tool_entry_get; + bi->bi_tool_entry_put = ldif_tool_entry_put; + bi->bi_tool_entry_reindex = 0; + bi->bi_tool_sync = 0; + + bi->bi_tool_dn2id_get = 0; + bi->bi_tool_entry_modify = 0; + + bi->bi_cf_ocs = ldifocs; + + rc = config_register_schema( ldifcfg, ldifocs ); + if ( rc ) return rc; + return 0; +} diff --git a/servers/slapd/back-mdb/Makefile.in b/servers/slapd/back-mdb/Makefile.in new file mode 100644 index 0000000..48303eb --- /dev/null +++ b/servers/slapd/back-mdb/Makefile.in @@ -0,0 +1,62 @@ +# Makefile.in for back-mdb +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 2011-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +SRCS = init.c tools.c config.c \ + add.c bind.c compare.c delete.c modify.c modrdn.c search.c \ + extended.c operational.c \ + attr.c index.c key.c filterindex.c \ + dn2entry.c dn2id.c id2entry.c idl.c \ + nextid.c monitor.c + +OBJS = init.lo tools.lo config.lo \ + add.lo bind.lo compare.lo delete.lo modify.lo modrdn.lo search.lo \ + extended.lo operational.lo \ + attr.lo index.lo key.lo filterindex.lo \ + dn2entry.lo dn2id.lo id2entry.lo idl.lo \ + nextid.lo monitor.lo mdb.lo midl.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries +MDB_SUBDIR = $(srcdir)/$(LDAP_LIBDIR)/liblmdb + +BUILD_OPT = "--enable-mdb" +BUILD_MOD = @BUILD_MDB@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_MDB@_DEFS) +MOD_LIBS = $(MDB_LIBS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_R_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBBASE = back_mdb + +XINCPATH = -I.. -I$(srcdir)/.. -I$(MDB_SUBDIR) +XDEFS = $(MODULES_CPPFLAGS) + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ + +mdb.lo: $(MDB_SUBDIR)/mdb.c + $(LTCOMPILE_MOD) $(MDB_SUBDIR)/mdb.c + +midl.lo: $(MDB_SUBDIR)/midl.c + $(LTCOMPILE_MOD) $(MDB_SUBDIR)/midl.c + +veryclean-local-lib: FORCE + $(RM) $(XXHEADERS) $(XXSRCS) .links diff --git a/servers/slapd/back-mdb/add.c b/servers/slapd/back-mdb/add.c new file mode 100644 index 0000000..78232c3 --- /dev/null +++ b/servers/slapd/back-mdb/add.c @@ -0,0 +1,465 @@ +/* add.c - ldap mdb back-end add routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" + +int +mdb_add(Operation *op, SlapReply *rs ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + struct berval pdn; + Entry *p = NULL, *oe = op->ora_e; + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof textbuf; + AttributeDescription *children = slap_schema.si_ad_children; + AttributeDescription *entry = slap_schema.si_ad_entry; + MDB_txn *txn = NULL; + MDB_cursor *mc = NULL; + MDB_cursor *mcd; + ID eid, pid = 0; + mdb_op_info opinfo = {{{ 0 }}}, *moi = &opinfo; + int subentry; + int numads = mdb->mi_numads; + + int success; + + LDAPControl **postread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + +#ifdef LDAP_X_TXN + int settle = 0; +#endif + + Debug(LDAP_DEBUG_ARGS, "==> " LDAP_XSTRING(mdb_add) ": %s\n", + op->ora_e->e_name.bv_val, 0, 0); + +#ifdef LDAP_X_TXN + if( op->o_txnSpec ) { + /* acquire connection lock */ + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + if( op->o_conn->c_txn == CONN_TXN_INACTIVE ) { + rs->sr_text = "invalid transaction identifier"; + rs->sr_err = LDAP_X_TXN_ID_INVALID; + goto txnReturn; + } else if( op->o_conn->c_txn == CONN_TXN_SETTLE ) { + settle=1; + goto txnReturn; + } + + if( op->o_conn->c_txn_backend == NULL ) { + op->o_conn->c_txn_backend = op->o_bd; + + } else if( op->o_conn->c_txn_backend != op->o_bd ) { + rs->sr_text = "transaction cannot span multiple database contexts"; + rs->sr_err = LDAP_AFFECTS_MULTIPLE_DSAS; + goto txnReturn; + } + + /* insert operation into transaction */ + + rs->sr_text = "transaction specified"; + rs->sr_err = LDAP_X_TXN_SPECIFY_OKAY; + +txnReturn: + /* release connection lock */ + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + + if( !settle ) { + send_ldap_result( op, rs ); + return rs->sr_err; + } + } +#endif + + ctrls[num_ctrls] = 0; + + /* check entry's schema */ + rs->sr_err = entry_schema_check( op, op->ora_e, NULL, + get_relax(op), 1, NULL, &rs->sr_text, textbuf, textlen ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": entry failed schema check: " + "%s (%d)\n", rs->sr_text, rs->sr_err, 0 ); + goto return_results; + } + + /* begin transaction */ + rs->sr_err = mdb_opinfo_get( op, mdb, 0, &moi ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": txn_begin failed: %s (%d)\n", + mdb_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + txn = moi->moi_txn; + + /* add opattrs to shadow as well, only missing attrs will actually + * be added; helps compatibility with older OL versions */ + rs->sr_err = slap_add_opattrs( op, &rs->sr_text, textbuf, textlen, 1 ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": entry failed op attrs add: " + "%s (%d)\n", rs->sr_text, rs->sr_err, 0 ); + goto return_results; + } + + if ( get_assert( op ) && + ( test_filter( op, op->ora_e, get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto return_results; + } + + subentry = is_entry_subentry( op->ora_e ); + + /* + * Get the parent dn and see if the corresponding entry exists. + */ + if ( be_issuffix( op->o_bd, &op->ora_e->e_nname ) ) { + pdn = slap_empty_bv; + } else { + dnParent( &op->ora_e->e_nname, &pdn ); + } + + rs->sr_err = mdb_cursor_open( txn, mdb->mi_dn2id, &mcd ); + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": mdb_cursor_open failed (%d)\n", + rs->sr_err, 0, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + /* get entry or parent */ + rs->sr_err = mdb_dn2entry( op, txn, mcd, &op->ora_e->e_nname, &p, NULL, 1 ); + switch( rs->sr_err ) { + case 0: + rs->sr_err = LDAP_ALREADY_EXISTS; + mdb_entry_return( op, p ); + p = NULL; + goto return_results; + case MDB_NOTFOUND: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + if ( !p ) + p = (Entry *)&slap_entry_root; + + if ( !bvmatch( &pdn, &p->e_nname ) ) { + rs->sr_matched = ber_strdup_x( p->e_name.bv_val, + op->o_tmpmemctx ); + if ( p != (Entry *)&slap_entry_root && is_entry_referral( p )) { + BerVarray ref = get_entry_referrals( op, p ); + rs->sr_ref = referral_rewrite( ref, &p->e_name, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + ber_bvarray_free( ref ); + } else { + rs->sr_ref = NULL; + } + if ( p != (Entry *)&slap_entry_root ) + mdb_entry_return( op, p ); + p = NULL; + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": parent " + "does not exist\n", 0, 0, 0 ); + + rs->sr_err = LDAP_REFERRAL; + rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED; + goto return_results; + } + + rs->sr_err = access_allowed( op, p, + children, NULL, ACL_WADD, NULL ); + + if ( ! rs->sr_err ) { + if ( p != (Entry *)&slap_entry_root ) + mdb_entry_return( op, p ); + p = NULL; + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": no write access to parent\n", + 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to parent"; + goto return_results;; + } + + if ( p != (Entry *)&slap_entry_root ) { + if ( is_entry_subentry( p ) ) { + mdb_entry_return( op, p ); + p = NULL; + /* parent is a subentry, don't allow add */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": parent is subentry\n", + 0, 0, 0 ); + rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; + rs->sr_text = "parent is a subentry"; + goto return_results;; + } + + if ( is_entry_alias( p ) ) { + mdb_entry_return( op, p ); + p = NULL; + /* parent is an alias, don't allow add */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": parent is alias\n", + 0, 0, 0 ); + rs->sr_err = LDAP_ALIAS_PROBLEM; + rs->sr_text = "parent is an alias"; + goto return_results;; + } + + if ( is_entry_referral( p ) ) { + BerVarray ref = get_entry_referrals( op, p ); + /* parent is a referral, don't allow add */ + rs->sr_matched = ber_strdup_x( p->e_name.bv_val, + op->o_tmpmemctx ); + rs->sr_ref = referral_rewrite( ref, &p->e_name, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + ber_bvarray_free( ref ); + mdb_entry_return( op, p ); + p = NULL; + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": parent is referral\n", + 0, 0, 0 ); + + rs->sr_err = LDAP_REFERRAL; + rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED; + goto return_results; + } + + } + + if ( subentry ) { + /* FIXME: */ + /* parent must be an administrative point of the required kind */ + } + + /* free parent */ + if ( p != (Entry *)&slap_entry_root ) { + pid = p->e_id; + if ( p->e_nname.bv_len ) { + struct berval ppdn; + + /* ITS#5326: use parent's DN if differs from provided one */ + dnParent( &op->ora_e->e_name, &ppdn ); + if ( !dn_match( &p->e_name, &ppdn ) ) { + struct berval rdn; + struct berval newdn; + + dnRdn( &op->ora_e->e_name, &rdn ); + + build_new_dn( &newdn, &p->e_name, &rdn, NULL ); + if ( op->ora_e->e_name.bv_val != op->o_req_dn.bv_val ) + ber_memfree( op->ora_e->e_name.bv_val ); + op->ora_e->e_name = newdn; + + /* FIXME: should check whether + * dnNormalize(newdn) == e->e_nname ... */ + } + } + + mdb_entry_return( op, p ); + } + p = NULL; + + rs->sr_err = access_allowed( op, op->ora_e, + entry, NULL, ACL_WADD, NULL ); + + if ( ! rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": no write access to entry\n", + 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to entry"; + goto return_results;; + } + + /* + * Check ACL for attribute write access + */ + if (!acl_check_modlist(op, oe, op->ora_modlist)) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": no write access to attribute\n", + 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to attribute"; + goto return_results;; + } + + rs->sr_err = mdb_cursor_open( txn, mdb->mi_id2entry, &mc ); + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": mdb_cursor_open failed (%d)\n", + rs->sr_err, 0, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + rs->sr_err = mdb_next_id( op->o_bd, mc, &eid ); + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": next_id failed (%d)\n", + rs->sr_err, 0, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + op->ora_e->e_id = eid; + + /* dn2id index */ + rs->sr_err = mdb_dn2id_add( op, mcd, mcd, pid, 1, 1, op->ora_e ); + mdb_cursor_close( mcd ); + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": dn2id_add failed: %s (%d)\n", + mdb_strerror(rs->sr_err), rs->sr_err, 0 ); + + switch( rs->sr_err ) { + case MDB_KEYEXIST: + rs->sr_err = LDAP_ALREADY_EXISTS; + break; + default: + rs->sr_err = LDAP_OTHER; + } + goto return_results; + } + + /* attribute indexes */ + rs->sr_err = mdb_index_entry_add( op, txn, op->ora_e ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": index_entry_add failed\n", + 0, 0, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "index generation failed"; + goto return_results; + } + + /* id2entry index */ + rs->sr_err = mdb_id2entry_add( op, txn, mc, op->ora_e ); + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": id2entry_add failed\n", + 0, 0, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "entry store failed"; + goto return_results; + } + + /* post-read */ + if( op->o_postread ) { + if( postread_ctrl == NULL ) { + postread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if ( slap_read_controls( op, rs, op->ora_e, + &slap_post_read_bv, postread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_add) ": post-read " + "failed!\n", 0, 0, 0 ); + if ( op->o_postread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + if ( moi == &opinfo ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next ); + opinfo.moi_oe.oe_key = NULL; + if ( op->o_noop ) { + mdb->mi_numads = numads; + mdb_txn_abort( txn ); + rs->sr_err = LDAP_X_NO_OPERATION; + txn = NULL; + goto return_results; + } + + rs->sr_err = mdb_txn_commit( txn ); + txn = NULL; + if ( rs->sr_err != 0 ) { + mdb->mi_numads = numads; + rs->sr_text = "txn_commit failed"; + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_add) ": %s : %s (%d)\n", + rs->sr_text, mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + goto return_results; + } + } + + Debug(LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": added%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + op->ora_e->e_id, op->ora_e->e_dn ); + + rs->sr_text = NULL; + if( num_ctrls ) rs->sr_ctrls = ctrls; + +return_results: + success = rs->sr_err; + send_ldap_result( op, rs ); + + if( moi == &opinfo ) { + if( txn != NULL ) { + mdb->mi_numads = numads; + mdb_txn_abort( txn ); + } + if ( opinfo.moi_oe.oe_key ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next ); + } + } else { + moi->moi_ref--; + } + + if( success == LDAP_SUCCESS ) { +#if 0 + if ( mdb->bi_txn_cp_kbyte ) { + TXN_CHECKPOINT( mdb->bi_dbenv, + mdb->bi_txn_cp_kbyte, mdb->bi_txn_cp_min, 0 ); + } +#endif + } + + slap_graduate_commit_csn( op ); + + if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) { + slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *postread_ctrl, op->o_tmpmemctx ); + } + return rs->sr_err; +} diff --git a/servers/slapd/back-mdb/attr.c b/servers/slapd/back-mdb/attr.c new file mode 100644 index 0000000..721623d --- /dev/null +++ b/servers/slapd/back-mdb/attr.c @@ -0,0 +1,641 @@ +/* attr.c - backend routines for dealing with attributes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-mdb.h" +#include "config.h" +#include "lutil.h" + +/* Find the ad, return -1 if not found, + * set point for insertion if ins is non-NULL + */ +int +mdb_attr_slot( struct mdb_info *mdb, AttributeDescription *ad, int *ins ) +{ + unsigned base = 0, cursor = 0; + unsigned n = mdb->mi_nattrs; + int val = 0; + + while ( 0 < n ) { + unsigned pivot = n >> 1; + cursor = base + pivot; + + val = SLAP_PTRCMP( ad, mdb->mi_attrs[cursor]->ai_desc ); + if ( val < 0 ) { + n = pivot; + } else if ( val > 0 ) { + base = cursor + 1; + n -= pivot + 1; + } else { + return cursor; + } + } + if ( ins ) { + if ( val > 0 ) + ++cursor; + *ins = cursor; + } + return -1; +} + +static int +ainfo_insert( struct mdb_info *mdb, AttrInfo *a ) +{ + int x; + int i = mdb_attr_slot( mdb, a->ai_desc, &x ); + + /* Is it a dup? */ + if ( i >= 0 ) + return -1; + + mdb->mi_attrs = ch_realloc( mdb->mi_attrs, ( mdb->mi_nattrs+1 ) * + sizeof( AttrInfo * )); + if ( x < mdb->mi_nattrs ) + AC_MEMCPY( &mdb->mi_attrs[x+1], &mdb->mi_attrs[x], + ( mdb->mi_nattrs - x ) * sizeof( AttrInfo *)); + mdb->mi_attrs[x] = a; + mdb->mi_nattrs++; + return 0; +} + +AttrInfo * +mdb_attr_mask( + struct mdb_info *mdb, + AttributeDescription *desc ) +{ + int i = mdb_attr_slot( mdb, desc, NULL ); + return i < 0 ? NULL : mdb->mi_attrs[i]; +} + +/* Open all un-opened index DB handles */ +int +mdb_attr_dbs_open( + BackendDB *be, MDB_txn *tx0, ConfigReply *cr ) +{ + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + MDB_txn *txn; + MDB_dbi *dbis = NULL; + int i, flags; + int rc; + + txn = tx0; + if ( txn == NULL ) { + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, 0, &txn ); + if ( rc ) { + snprintf( cr->msg, sizeof(cr->msg), "database \"%s\": " + "txn_begin failed: %s (%d).", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_attr_dbs) ": %s\n", + cr->msg, 0, 0 ); + return rc; + } + dbis = ch_calloc( 1, mdb->mi_nattrs * sizeof(MDB_dbi) ); + } else { + rc = 0; + } + + flags = MDB_DUPSORT|MDB_DUPFIXED|MDB_INTEGERDUP; + if ( !(slapMode & SLAP_TOOL_READONLY) ) + flags |= MDB_CREATE; + + for ( i=0; i<mdb->mi_nattrs; i++ ) { + if ( mdb->mi_attrs[i]->ai_dbi ) /* already open */ + continue; + rc = mdb_dbi_open( txn, mdb->mi_attrs[i]->ai_desc->ad_type->sat_cname.bv_val, + flags, &mdb->mi_attrs[i]->ai_dbi ); + if ( rc ) { + snprintf( cr->msg, sizeof(cr->msg), "database \"%s\": " + "mdb_dbi_open(%s) failed: %s (%d).", + be->be_suffix[0].bv_val, + mdb->mi_attrs[i]->ai_desc->ad_type->sat_cname.bv_val, + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_attr_dbs) ": %s\n", + cr->msg, 0, 0 ); + break; + } + /* Remember newly opened DBI handles */ + if ( dbis ) + dbis[i] = mdb->mi_attrs[i]->ai_dbi; + } + + /* Only commit if this is our txn */ + if ( tx0 == NULL ) { + if ( !rc ) { + rc = mdb_txn_commit( txn ); + if ( rc ) { + snprintf( cr->msg, sizeof(cr->msg), "database \"%s\": " + "txn_commit failed: %s (%d).", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_attr_dbs) ": %s\n", + cr->msg, 0, 0 ); + } + } else { + mdb_txn_abort( txn ); + } + /* Something failed, forget anything we just opened */ + if ( rc ) { + for ( i=0; i<mdb->mi_nattrs; i++ ) { + if ( dbis[i] ) { + mdb->mi_attrs[i]->ai_dbi = 0; + mdb->mi_attrs[i]->ai_indexmask |= MDB_INDEX_DELETING; + } + } + mdb_attr_flush( mdb ); + } + ch_free( dbis ); + } + + return rc; +} + +void +mdb_attr_dbs_close( + struct mdb_info *mdb +) +{ + int i; + for ( i=0; i<mdb->mi_nattrs; i++ ) + if ( mdb->mi_attrs[i]->ai_dbi ) { + mdb_dbi_close( mdb->mi_dbenv, mdb->mi_attrs[i]->ai_dbi ); + mdb->mi_attrs[i]->ai_dbi = 0; + } +} + +int +mdb_attr_index_config( + struct mdb_info *mdb, + const char *fname, + int lineno, + int argc, + char **argv, + struct config_reply_s *c_reply) +{ + int rc = 0; + int i; + slap_mask_t mask; + char **attrs; + char **indexes = NULL; + + attrs = ldap_str2charray( argv[0], "," ); + + if( attrs == NULL ) { + fprintf( stderr, "%s: line %d: " + "no attributes specified: %s\n", + fname, lineno, argv[0] ); + return LDAP_PARAM_ERROR; + } + + if ( argc > 1 ) { + indexes = ldap_str2charray( argv[1], "," ); + + if( indexes == NULL ) { + fprintf( stderr, "%s: line %d: " + "no indexes specified: %s\n", + fname, lineno, argv[1] ); + rc = LDAP_PARAM_ERROR; + goto done; + } + } + + if( indexes == NULL ) { + mask = mdb->mi_defaultmask; + + } else { + mask = 0; + + for ( i = 0; indexes[i] != NULL; i++ ) { + slap_mask_t index; + rc = slap_str2index( indexes[i], &index ); + + if( rc != LDAP_SUCCESS ) { + if ( c_reply ) + { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "index type \"%s\" undefined", indexes[i] ); + + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + rc = LDAP_PARAM_ERROR; + goto done; + } + + mask |= index; + } + } + + if( !mask ) { + if ( c_reply ) + { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "no indexes selected" ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + rc = LDAP_PARAM_ERROR; + goto done; + } + + for ( i = 0; attrs[i] != NULL; i++ ) { + AttrInfo *a; + AttributeDescription *ad; + const char *text; +#ifdef LDAP_COMP_MATCH + ComponentReference* cr = NULL; + AttrInfo *a_cr = NULL; +#endif + + if( strcasecmp( attrs[i], "default" ) == 0 ) { + mdb->mi_defaultmask |= mask; + continue; + } + +#ifdef LDAP_COMP_MATCH + if ( is_component_reference( attrs[i] ) ) { + rc = extract_component_reference( attrs[i], &cr ); + if ( rc != LDAP_SUCCESS ) { + if ( c_reply ) + { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "index component reference\"%s\" undefined", + attrs[i] ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + goto done; + } + cr->cr_indexmask = mask; + /* + * After extracting a component reference + * only the name of a attribute will be remaining + */ + } else { + cr = NULL; + } +#endif + ad = NULL; + rc = slap_str2ad( attrs[i], &ad, &text ); + + if( rc != LDAP_SUCCESS ) { + if ( c_reply ) + { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "index attribute \"%s\" undefined", + attrs[i] ); + + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + goto done; + } + + if( ad == slap_schema.si_ad_entryDN || slap_ad_is_binary( ad ) ) { + if (c_reply) { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "index of attribute \"%s\" disallowed", attrs[i] ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + rc = LDAP_UNWILLING_TO_PERFORM; + goto done; + } + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_APPROX ) && !( + ad->ad_type->sat_approx + && ad->ad_type->sat_approx->smr_indexer + && ad->ad_type->sat_approx->smr_filter ) ) + { + if (c_reply) { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "approx index of attribute \"%s\" disallowed", attrs[i] ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + rc = LDAP_INAPPROPRIATE_MATCHING; + goto done; + } + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_EQUALITY ) && !( + ad->ad_type->sat_equality + && ad->ad_type->sat_equality->smr_indexer + && ad->ad_type->sat_equality->smr_filter ) ) + { + if (c_reply) { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "equality index of attribute \"%s\" disallowed", attrs[i] ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + rc = LDAP_INAPPROPRIATE_MATCHING; + goto done; + } + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_SUBSTR ) && !( + ad->ad_type->sat_substr + && ad->ad_type->sat_substr->smr_indexer + && ad->ad_type->sat_substr->smr_filter ) ) + { + if (c_reply) { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "substr index of attribute \"%s\" disallowed", attrs[i] ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + rc = LDAP_INAPPROPRIATE_MATCHING; + goto done; + } + + Debug( LDAP_DEBUG_CONFIG, "index %s 0x%04lx\n", + ad->ad_cname.bv_val, mask, 0 ); + + a = (AttrInfo *) ch_malloc( sizeof(AttrInfo) ); + +#ifdef LDAP_COMP_MATCH + a->ai_cr = NULL; +#endif + a->ai_cursor = NULL; + a->ai_flist = NULL; + a->ai_clist = NULL; + a->ai_root = NULL; + a->ai_desc = ad; + a->ai_dbi = 0; + + if ( mdb->mi_flags & MDB_IS_OPEN ) { + a->ai_indexmask = 0; + a->ai_newmask = mask; + } else { + a->ai_indexmask = mask; + a->ai_newmask = 0; + } + +#ifdef LDAP_COMP_MATCH + if ( cr ) { + a_cr = mdb_attr_mask( mdb, ad ); + if ( a_cr ) { + /* + * AttrInfo is already in AVL + * just add the extracted component reference + * in the AttrInfo + */ + rc = insert_component_reference( cr, &a_cr->ai_cr ); + if ( rc != LDAP_SUCCESS) { + fprintf( stderr, " error during inserting component reference in %s ", attrs[i]); + rc = LDAP_PARAM_ERROR; + goto done; + } + continue; + } else { + rc = insert_component_reference( cr, &a->ai_cr ); + if ( rc != LDAP_SUCCESS) { + fprintf( stderr, " error during inserting component reference in %s ", attrs[i]); + rc = LDAP_PARAM_ERROR; + goto done; + } + } + } +#endif + rc = ainfo_insert( mdb, a ); + if( rc ) { + if ( mdb->mi_flags & MDB_IS_OPEN ) { + AttrInfo *b = mdb_attr_mask( mdb, ad ); + /* If there is already an index defined for this attribute + * it must be replaced. Otherwise we end up with multiple + * olcIndex values for the same attribute */ + if ( b->ai_indexmask & MDB_INDEX_DELETING ) { + /* If we were editing this attr, reset it */ + b->ai_indexmask &= ~MDB_INDEX_DELETING; + /* If this is leftover from a previous add, commit it */ + if ( b->ai_newmask ) + b->ai_indexmask = b->ai_newmask; + b->ai_newmask = a->ai_newmask; + ch_free( a ); + rc = 0; + continue; + } + } + if (c_reply) { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "duplicate index definition for attr \"%s\"", + attrs[i] ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + + rc = LDAP_PARAM_ERROR; + goto done; + } + } + +done: + ldap_charray_free( attrs ); + if ( indexes != NULL ) ldap_charray_free( indexes ); + + return rc; +} + +static int +mdb_attr_index_unparser( void *v1, void *v2 ) +{ + AttrInfo *ai = v1; + BerVarray *bva = v2; + struct berval bv; + char *ptr; + + slap_index2bvlen( ai->ai_indexmask, &bv ); + if ( bv.bv_len ) { + bv.bv_len += ai->ai_desc->ad_cname.bv_len + 1; + ptr = ch_malloc( bv.bv_len+1 ); + bv.bv_val = lutil_strcopy( ptr, ai->ai_desc->ad_cname.bv_val ); + *bv.bv_val++ = ' '; + slap_index2bv( ai->ai_indexmask, &bv ); + bv.bv_val = ptr; + ber_bvarray_add( bva, &bv ); + } + return 0; +} + +static AttributeDescription addef = { NULL, NULL, BER_BVC("default") }; +static AttrInfo aidef = { &addef }; + +void +mdb_attr_index_unparse( struct mdb_info *mdb, BerVarray *bva ) +{ + int i; + + if ( mdb->mi_defaultmask ) { + aidef.ai_indexmask = mdb->mi_defaultmask; + mdb_attr_index_unparser( &aidef, bva ); + } + for ( i=0; i<mdb->mi_nattrs; i++ ) + mdb_attr_index_unparser( mdb->mi_attrs[i], bva ); +} + +void +mdb_attr_info_free( AttrInfo *ai ) +{ +#ifdef LDAP_COMP_MATCH + free( ai->ai_cr ); +#endif + free( ai ); +} + +void +mdb_attr_index_destroy( struct mdb_info *mdb ) +{ + int i; + + for ( i=0; i<mdb->mi_nattrs; i++ ) + mdb_attr_info_free( mdb->mi_attrs[i] ); + + free( mdb->mi_attrs ); +} + +void mdb_attr_index_free( struct mdb_info *mdb, AttributeDescription *ad ) +{ + int i; + + i = mdb_attr_slot( mdb, ad, NULL ); + if ( i >= 0 ) { + mdb_attr_info_free( mdb->mi_attrs[i] ); + mdb->mi_nattrs--; + for (; i<mdb->mi_nattrs; i++) + mdb->mi_attrs[i] = mdb->mi_attrs[i+1]; + } +} + +void mdb_attr_flush( struct mdb_info *mdb ) +{ + int i; + + for ( i=0; i<mdb->mi_nattrs; i++ ) { + if ( mdb->mi_attrs[i]->ai_indexmask & MDB_INDEX_DELETING ) { + int j; + mdb_attr_info_free( mdb->mi_attrs[i] ); + mdb->mi_nattrs--; + for (j=i; j<mdb->mi_nattrs; j++) + mdb->mi_attrs[j] = mdb->mi_attrs[j+1]; + i--; + } + } +} + +int mdb_ad_read( struct mdb_info *mdb, MDB_txn *txn ) +{ + int i, rc; + MDB_cursor *mc; + MDB_val key, data; + struct berval bdata; + const char *text; + AttributeDescription *ad; + + rc = mdb_cursor_open( txn, mdb->mi_ad2id, &mc ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "mdb_ad_read: cursor_open failed %s(%d)\n", + mdb_strerror(rc), rc, 0); + return rc; + } + + /* our array is 1-based, an index of 0 means no data */ + i = mdb->mi_numads+1; + key.mv_size = sizeof(int); + key.mv_data = &i; + + rc = mdb_cursor_get( mc, &key, &data, MDB_SET ); + + while ( rc == MDB_SUCCESS ) { + bdata.bv_len = data.mv_size; + bdata.bv_val = data.mv_data; + ad = NULL; + rc = slap_bv2ad( &bdata, &ad, &text ); + if ( rc ) { + rc = slap_bv2undef_ad( &bdata, &mdb->mi_ads[i], &text, 0 ); + } else { + if ( ad->ad_index >= MDB_MAXADS ) { + Debug( LDAP_DEBUG_ANY, + "mdb_adb_read: too many AttributeDescriptions in use\n", + 0, 0, 0 ); + return LDAP_OTHER; + } + mdb->mi_adxs[ad->ad_index] = i; + mdb->mi_ads[i] = ad; + } + i++; + rc = mdb_cursor_get( mc, &key, &data, MDB_NEXT ); + } + mdb->mi_numads = i-1; + +done: + if ( rc == MDB_NOTFOUND ) + rc = 0; + + mdb_cursor_close( mc ); + + return rc; +} + +int mdb_ad_get( struct mdb_info *mdb, MDB_txn *txn, AttributeDescription *ad ) +{ + int i, rc; + MDB_val key, val; + + rc = mdb_ad_read( mdb, txn ); + if (rc) + return rc; + + if ( mdb->mi_adxs[ad->ad_index] ) + return 0; + + i = mdb->mi_numads+1; + key.mv_size = sizeof(int); + key.mv_data = &i; + val.mv_size = ad->ad_cname.bv_len; + val.mv_data = ad->ad_cname.bv_val; + + rc = mdb_put( txn, mdb->mi_ad2id, &key, &val, 0 ); + if ( rc == MDB_SUCCESS ) { + mdb->mi_adxs[ad->ad_index] = i; + mdb->mi_ads[i] = ad; + mdb->mi_numads = i; + } else { + Debug( LDAP_DEBUG_ANY, + "mdb_ad_get: mdb_put failed %s(%d)\n", + mdb_strerror(rc), rc, 0); + } + + return rc; +} + +void mdb_ad_unwind( struct mdb_info *mdb, int prev_ads ) +{ + int i; + + for (i=mdb->mi_numads; i>prev_ads; i--) { + mdb->mi_adxs[mdb->mi_ads[i]->ad_index] = 0; + mdb->mi_ads[i] = NULL; + } + mdb->mi_numads = i; +} diff --git a/servers/slapd/back-mdb/back-mdb.h b/servers/slapd/back-mdb/back-mdb.h new file mode 100644 index 0000000..8891028 --- /dev/null +++ b/servers/slapd/back-mdb/back-mdb.h @@ -0,0 +1,205 @@ +/* back-mdb.h - mdb back-end header file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#ifndef _BACK_MDB_H_ +#define _BACK_MDB_H_ + +#include <portable.h> +#include "slap.h" +#include "lmdb.h" + +LDAP_BEGIN_DECL + +#undef MDB_TOOL_IDL_CACHING /* currently broken */ + +#define DN_BASE_PREFIX SLAP_INDEX_EQUALITY_PREFIX +#define DN_ONE_PREFIX '%' +#define DN_SUBTREE_PREFIX '@' + +#define MDB_AD2ID 0 +#define MDB_DN2ID 1 +#define MDB_ID2ENTRY 2 +#define MDB_NDB 3 + +/* The default search IDL stack cache depth */ +#define DEFAULT_SEARCH_STACK_DEPTH 16 + +/* The minimum we can function with */ +#define MINIMUM_SEARCH_STACK_DEPTH 8 + +#define MDB_INDICES 128 + +#define MDB_MAXADS 65536 + +/* Default to 10MB max */ +#define DEFAULT_MAPSIZE (10*1048576) + +/* Most users will never see this */ +#define DEFAULT_RTXN_SIZE 10000 + +#ifdef LDAP_DEVEL +#define MDB_MONITOR_IDX +#endif /* LDAP_DEVEL */ + +typedef struct mdb_monitor_t { + void *mdm_cb; + struct berval mdm_ndn; +} mdb_monitor_t; + +/* From ldap_rq.h */ +struct re_s; + +struct mdb_info { + MDB_env *mi_dbenv; + + /* DB_ENV parameters */ + char *mi_dbenv_home; + unsigned mi_dbenv_flags; + int mi_dbenv_mode; + + size_t mi_mapsize; + ID mi_nextid; + + slap_mask_t mi_defaultmask; + int mi_nattrs; + struct mdb_attrinfo **mi_attrs; + void *mi_search_stack; + int mi_search_stack_depth; + int mi_readers; + + unsigned mi_rtxn_size; + int mi_txn_cp; + unsigned mi_txn_cp_min; + unsigned mi_txn_cp_kbyte; + struct re_s *mi_txn_cp_task; + struct re_s *mi_index_task; + + mdb_monitor_t mi_monitor; + +#ifdef MDB_MONITOR_IDX + ldap_pvt_thread_mutex_t mi_idx_mutex; + Avlnode *mi_idx; +#endif /* MDB_MONITOR_IDX */ + + int mi_flags; +#define MDB_IS_OPEN 0x01 +#define MDB_OPEN_INDEX 0x02 +#define MDB_DEL_INDEX 0x08 +#define MDB_RE_OPEN 0x10 +#define MDB_NEED_UPGRADE 0x20 + + int mi_numads; + + MDB_dbi mi_dbis[MDB_NDB]; + AttributeDescription *mi_ads[MDB_MAXADS]; + int mi_adxs[MDB_MAXADS]; +}; + +#define mi_id2entry mi_dbis[MDB_ID2ENTRY] +#define mi_dn2id mi_dbis[MDB_DN2ID] +#define mi_ad2id mi_dbis[MDB_AD2ID] + +typedef struct mdb_op_info { + OpExtra moi_oe; + MDB_txn* moi_txn; + int moi_ref; + char moi_flag; +} mdb_op_info; +#define MOI_READER 0x01 +#define MOI_FREEIT 0x02 + +/* Copy an ID "src" to pointer "dst" in big-endian byte order */ +#define MDB_ID2DISK( src, dst ) \ + do { int i0; ID tmp; unsigned char *_p; \ + tmp = (src); _p = (unsigned char *)(dst); \ + for ( i0=sizeof(ID)-1; i0>=0; i0-- ) { \ + _p[i0] = tmp & 0xff; tmp >>= 8; \ + } \ + } while(0) + +/* Copy a pointer "src" to a pointer "dst" from big-endian to native order */ +#define MDB_DISK2ID( src, dst ) \ + do { unsigned i0; ID tmp = 0; unsigned char *_p; \ + _p = (unsigned char *)(src); \ + for ( i0=0; i0<sizeof(ID); i0++ ) { \ + tmp <<= 8; tmp |= *_p++; \ + } *(dst) = tmp; \ + } while (0) + +LDAP_END_DECL + +/* for the cache of attribute information (which are indexed, etc.) */ +typedef struct mdb_attrinfo { + AttributeDescription *ai_desc; /* attribute description cn;lang-en */ + slap_mask_t ai_indexmask; /* how the attr is indexed */ + slap_mask_t ai_newmask; /* new settings to replace old mask */ +#ifdef LDAP_COMP_MATCH + ComponentReference* ai_cr; /*component indexing*/ +#endif + Avlnode *ai_root; /* for tools */ + void *ai_flist; /* for tools */ + void *ai_clist; /* for tools */ + MDB_cursor *ai_cursor; /* for tools */ + int ai_idx; /* position in AI array */ + MDB_dbi ai_dbi; +} AttrInfo; + +/* These flags must not clash with SLAP_INDEX flags or ops in slap.h! */ +#define MDB_INDEX_DELETING 0x8000U /* index is being modified */ +#define MDB_INDEX_UPDATE_OP 0x03 /* performing an index update */ + +/* For slapindex to record which attrs in an entry belong to which + * index database + */ +typedef struct AttrList { + struct AttrList *next; + Attribute *attr; +} AttrList; + +#ifndef CACHELINE +#define CACHELINE 64 +#endif + +#if defined(__i386) || defined(__x86_64) +#define MISALIGNED_OK 1 +#else +#define ALIGNER (sizeof(size_t)-1) +#endif + +typedef struct IndexRbody { + AttrInfo *ai; + AttrList *attrs; + void *tptr; + int i; +} IndexRbody; + +typedef struct IndexRec { + union { + IndexRbody irb; +#define ir_ai iru.irb.ai +#define ir_attrs iru.irb.attrs +#define ir_tptr iru.irb.tptr +#define ir_i iru.irb.i + /* cache line alignment */ + char pad[(sizeof(IndexRbody)+CACHELINE-1) & (!CACHELINE-1)]; + } iru; +} IndexRec; + +#define MAXRDNS SLAP_LDAPDN_MAXLEN/4 + +#include "proto-mdb.h" + +#endif /* _BACK_MDB_H_ */ diff --git a/servers/slapd/back-mdb/bind.c b/servers/slapd/back-mdb/bind.c new file mode 100644 index 0000000..283cd5f --- /dev/null +++ b/servers/slapd/back-mdb/bind.c @@ -0,0 +1,158 @@ +/* bind.c - mdb backend bind routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/unistd.h> + +#include "back-mdb.h" + +int +mdb_bind( Operation *op, SlapReply *rs ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + Entry *e; + Attribute *a; + + AttributeDescription *password = slap_schema.si_ad_userPassword; + + MDB_txn *rtxn; + mdb_op_info opinfo = {{{0}}}, *moi = &opinfo; + + Debug( LDAP_DEBUG_ARGS, + "==> " LDAP_XSTRING(mdb_bind) ": dn: %s\n", + op->o_req_dn.bv_val, 0, 0); + + /* allow noauth binds */ + switch ( be_rootdn_bind( op, NULL ) ) { + case LDAP_SUCCESS: + /* frontend will send result */ + return rs->sr_err = LDAP_SUCCESS; + + default: + /* give the database a chance */ + /* NOTE: this behavior departs from that of other backends, + * since the others, in case of password checking failure + * do not give the database a chance. If an entry with + * rootdn's name does not exist in the database the result + * will be the same. See ITS#4962 for discussion. */ + break; + } + + rs->sr_err = mdb_opinfo_get(op, mdb, 1, &moi); + switch(rs->sr_err) { + case 0: + break; + default: + rs->sr_text = "internal error"; + send_ldap_result( op, rs ); + return rs->sr_err; + } + + rtxn = moi->moi_txn; + + /* get entry with reader lock */ + rs->sr_err = mdb_dn2entry( op, rtxn, NULL, &op->o_req_ndn, &e, NULL, 0 ); + + switch(rs->sr_err) { + case MDB_NOTFOUND: + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + case 0: + break; + case LDAP_BUSY: + rs->sr_text = "ldap_server_busy"; + goto done; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto done; + } + + ber_dupbv( &op->oq_bind.rb_edn, &e->e_name ); + + /* check for deleted */ + if ( is_entry_subentry( e ) ) { + /* entry is an subentry, don't allow bind */ + Debug( LDAP_DEBUG_TRACE, "entry is subentry\n", 0, + 0, 0 ); + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + if ( is_entry_alias( e ) ) { + /* entry is an alias, don't allow bind */ + Debug( LDAP_DEBUG_TRACE, "entry is alias\n", 0, 0, 0 ); + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + if ( is_entry_referral( e ) ) { + Debug( LDAP_DEBUG_TRACE, "entry is referral\n", 0, + 0, 0 ); + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + switch ( op->oq_bind.rb_method ) { + case LDAP_AUTH_SIMPLE: + a = attr_find( e->e_attrs, password ); + if ( a == NULL ) { + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + if ( slap_passwd_check( op, e, a, &op->oq_bind.rb_cred, + &rs->sr_text ) != 0 ) + { + /* failure; stop front end from sending result */ + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + rs->sr_err = 0; + break; + + default: + assert( 0 ); /* should not be reachable */ + rs->sr_err = LDAP_STRONG_AUTH_NOT_SUPPORTED; + rs->sr_text = "authentication method not supported"; + } + +done: + if ( moi == &opinfo ) { + mdb_txn_reset( moi->moi_txn ); + LDAP_SLIST_REMOVE( &op->o_extra, &moi->moi_oe, OpExtra, oe_next ); + } else { + moi->moi_ref--; + } + /* free entry and reader lock */ + if( e != NULL ) { + mdb_entry_return( op, e ); + } + + if ( rs->sr_err ) { + send_ldap_result( op, rs ); + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + } + /* front end will send result on success (rs->sr_err==0) */ + return rs->sr_err; +} diff --git a/servers/slapd/back-mdb/compare.c b/servers/slapd/back-mdb/compare.c new file mode 100644 index 0000000..9da3772 --- /dev/null +++ b/servers/slapd/back-mdb/compare.c @@ -0,0 +1,142 @@ +/* compare.c - mdb backend compare routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" + +int +mdb_compare( Operation *op, SlapReply *rs ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + Entry *e = NULL; + int manageDSAit = get_manageDSAit( op ); + + MDB_txn *rtxn; + mdb_op_info opinfo = {{{0}}}, *moi = &opinfo; + + rs->sr_err = mdb_opinfo_get(op, mdb, 1, &moi); + switch(rs->sr_err) { + case 0: + break; + default: + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return rs->sr_err; + } + + rtxn = moi->moi_txn; + + /* get entry */ + rs->sr_err = mdb_dn2entry( op, rtxn, NULL, &op->o_req_ndn, &e, NULL, 1 ); + switch( rs->sr_err ) { + case MDB_NOTFOUND: + case 0: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + if ( rs->sr_err == MDB_NOTFOUND ) { + if ( e != NULL ) { + /* return referral only if "disclose" is granted on the object */ + if ( ! access_allowed( op, e, slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + + } else { + rs->sr_matched = ch_strdup( e->e_dn ); + if ( is_entry_referral( e )) { + BerVarray ref = get_entry_referrals( op, e ); + rs->sr_ref = referral_rewrite( ref, &e->e_name, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + ber_bvarray_free( ref ); + } else { + rs->sr_ref = NULL; + } + rs->sr_err = LDAP_REFERRAL; + } + mdb_entry_return( op, e ); + e = NULL; + + } else { + rs->sr_ref = referral_rewrite( default_referral, + NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + rs->sr_err = rs->sr_ref ? LDAP_REFERRAL : LDAP_NO_SUCH_OBJECT; + } + + rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED; + send_ldap_result( op, rs ); + goto done; + } + + if (!manageDSAit && is_entry_referral( e ) ) { + /* return referral only if "disclose" is granted on the object */ + if ( !access_allowed( op, e, slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } else { + /* entry is a referral, don't allow compare */ + rs->sr_ref = get_entry_referrals( op, e ); + rs->sr_err = LDAP_REFERRAL; + rs->sr_matched = e->e_name.bv_val; + } + + Debug( LDAP_DEBUG_TRACE, "entry is referral\n", 0, 0, 0 ); + + send_ldap_result( op, rs ); + + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + rs->sr_matched = NULL; + goto done; + } + + rs->sr_err = slap_compare_entry( op, e, op->orc_ava ); + +return_results: + send_ldap_result( op, rs ); + + switch ( rs->sr_err ) { + case LDAP_COMPARE_FALSE: + case LDAP_COMPARE_TRUE: + rs->sr_err = LDAP_SUCCESS; + break; + } + +done: + if ( moi == &opinfo ) { + mdb_txn_reset( moi->moi_txn ); + LDAP_SLIST_REMOVE( &op->o_extra, &moi->moi_oe, OpExtra, oe_next ); + } else { + moi->moi_ref--; + } + /* free entry */ + if ( e != NULL ) { + mdb_entry_return( op, e ); + } + + return rs->sr_err; +} diff --git a/servers/slapd/back-mdb/config.c b/servers/slapd/back-mdb/config.c new file mode 100644 index 0000000..d318e74 --- /dev/null +++ b/servers/slapd/back-mdb/config.c @@ -0,0 +1,695 @@ +/* config.c - mdb backend configuration file routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/errno.h> + +#include "back-mdb.h" + +#include "config.h" + +#include "lutil.h" +#include "ldap_rq.h" + +static ConfigDriver mdb_cf_gen; + +enum { + MDB_CHKPT = 1, + MDB_DIRECTORY, + MDB_DBNOSYNC, + MDB_ENVFLAGS, + MDB_INDEX, + MDB_MAXREADERS, + MDB_MAXSIZE, + MDB_MODE, + MDB_SSTACK +}; + +static ConfigTable mdbcfg[] = { + { "directory", "dir", 2, 2, 0, ARG_STRING|ARG_MAGIC|MDB_DIRECTORY, + mdb_cf_gen, "( OLcfgDbAt:0.1 NAME 'olcDbDirectory' " + "DESC 'Directory for database content' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "checkpoint", "kbyte> <min", 3, 3, 0, ARG_MAGIC|MDB_CHKPT, + mdb_cf_gen, "( OLcfgDbAt:1.2 NAME 'olcDbCheckpoint' " + "DESC 'Database checkpoint interval in kbytes and minutes' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )",NULL, NULL }, + { "dbnosync", NULL, 1, 2, 0, ARG_ON_OFF|ARG_MAGIC|MDB_DBNOSYNC, + mdb_cf_gen, "( OLcfgDbAt:1.4 NAME 'olcDbNoSync' " + "DESC 'Disable synchronous database writes' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "envflags", "flags", 2, 0, 0, ARG_MAGIC|MDB_ENVFLAGS, + mdb_cf_gen, "( OLcfgDbAt:12.3 NAME 'olcDbEnvFlags' " + "DESC 'Database environment flags' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "index", "attr> <[pres,eq,approx,sub]", 2, 3, 0, ARG_MAGIC|MDB_INDEX, + mdb_cf_gen, "( OLcfgDbAt:0.2 NAME 'olcDbIndex' " + "DESC 'Attribute index parameters' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "maxreaders", "num", 2, 2, 0, ARG_UINT|ARG_MAGIC|MDB_MAXREADERS, + mdb_cf_gen, "( OLcfgDbAt:12.1 NAME 'olcDbMaxReaders' " + "DESC 'Maximum number of threads that may access the DB concurrently' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "maxsize", "size", 2, 2, 0, ARG_ULONG|ARG_MAGIC|MDB_MAXSIZE, + mdb_cf_gen, "( OLcfgDbAt:12.2 NAME 'olcDbMaxSize' " + "DESC 'Maximum size of DB in bytes' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "mode", "mode", 2, 2, 0, ARG_MAGIC|MDB_MODE, + mdb_cf_gen, "( OLcfgDbAt:0.3 NAME 'olcDbMode' " + "DESC 'Unix permissions of database files' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "rtxnsize", "entries", 2, 2, 0, ARG_UINT|ARG_OFFSET, + (void *)offsetof(struct mdb_info, mi_rtxn_size), + "( OLcfgDbAt:12.5 NAME 'olcDbRtxnSize' " + "DESC 'Number of entries to process in one read transaction' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "searchstack", "depth", 2, 2, 0, ARG_INT|ARG_MAGIC|MDB_SSTACK, + mdb_cf_gen, "( OLcfgDbAt:1.9 NAME 'olcDbSearchStack' " + "DESC 'Depth of search stack in IDLs' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED, + NULL, NULL, NULL, NULL } +}; + +static ConfigOCs mdbocs[] = { + { + "( OLcfgDbOc:12.1 " + "NAME 'olcMdbConfig' " + "DESC 'MDB backend configuration' " + "SUP olcDatabaseConfig " + "MUST olcDbDirectory " + "MAY ( olcDbCheckpoint $ olcDbEnvFlags $ " + "olcDbNoSync $ olcDbIndex $ olcDbMaxReaders $ olcDbMaxSize $ " + "olcDbMode $ olcDbSearchStack $ olcDbRtxnSize ) )", + Cft_Database, mdbcfg }, + { NULL, 0, NULL } +}; + +static slap_verbmasks mdb_envflags[] = { + { BER_BVC("nosync"), MDB_NOSYNC }, + { BER_BVC("nometasync"), MDB_NOMETASYNC }, + { BER_BVC("writemap"), MDB_WRITEMAP }, + { BER_BVC("mapasync"), MDB_MAPASYNC }, + { BER_BVC("nordahead"), MDB_NORDAHEAD }, + { BER_BVNULL, 0 } +}; + +/* perform periodic syncs */ +static void * +mdb_checkpoint( void *ctx, void *arg ) +{ + struct re_s *rtask = arg; + struct mdb_info *mdb = rtask->arg; + + mdb_env_sync( mdb->mi_dbenv, 1 ); + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + ldap_pvt_runqueue_stoptask( &slapd_rq, rtask ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + return NULL; +} + +/* reindex entries on the fly */ +static void * +mdb_online_index( void *ctx, void *arg ) +{ + struct re_s *rtask = arg; + BackendDB *be = rtask->arg; + struct mdb_info *mdb = be->be_private; + + Connection conn = {0}; + OperationBuffer opbuf; + Operation *op; + + MDB_cursor *curs; + MDB_val key, data; + MDB_txn *txn; + ID id; + Entry *e; + int rc, getnext = 1; + int i; + + connection_fake_init( &conn, &opbuf, ctx ); + op = &opbuf.ob_op; + + op->o_bd = be; + + id = 1; + key.mv_size = sizeof(ID); + + while ( 1 ) { + if ( slapd_shutdown ) + break; + + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, 0, &txn ); + if ( rc ) + break; + rc = mdb_cursor_open( txn, mdb->mi_id2entry, &curs ); + if ( rc ) { + mdb_txn_abort( txn ); + break; + } + if ( getnext ) { + getnext = 0; + key.mv_data = &id; + rc = mdb_cursor_get( curs, &key, &data, MDB_SET_RANGE ); + if ( rc ) { + mdb_txn_abort( txn ); + if ( rc == MDB_NOTFOUND ) + rc = 0; + break; + } + memcpy( &id, key.mv_data, sizeof( id )); + } + + rc = mdb_id2entry( op, curs, id, &e ); + mdb_cursor_close( curs ); + if ( rc ) { + mdb_txn_abort( txn ); + if ( rc == MDB_NOTFOUND ) { + id++; + getnext = 1; + continue; + } + break; + } + rc = mdb_index_entry( op, txn, MDB_INDEX_UPDATE_OP, e ); + mdb_entry_return( op, e ); + if ( rc == 0 ) { + rc = mdb_txn_commit( txn ); + txn = NULL; + } else { + mdb_txn_abort( txn ); + txn = NULL; + } + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_online_index) ": database %s: " + "txn_commit failed: %s (%d)\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + break; + } + id++; + getnext = 1; + } + + for ( i = 0; i < mdb->mi_nattrs; i++ ) { + if ( mdb->mi_attrs[ i ]->ai_indexmask & MDB_INDEX_DELETING + || mdb->mi_attrs[ i ]->ai_newmask == 0 ) + { + continue; + } + mdb->mi_attrs[ i ]->ai_indexmask = mdb->mi_attrs[ i ]->ai_newmask; + mdb->mi_attrs[ i ]->ai_newmask = 0; + } + + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + ldap_pvt_runqueue_stoptask( &slapd_rq, rtask ); + mdb->mi_index_task = NULL; + ldap_pvt_runqueue_remove( &slapd_rq, rtask ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + return NULL; +} + +/* Cleanup loose ends after Modify completes */ +static int +mdb_cf_cleanup( ConfigArgs *c ) +{ + struct mdb_info *mdb = c->be->be_private; + int rc = 0; + + if ( mdb->mi_flags & MDB_DEL_INDEX ) { + mdb_attr_flush( mdb ); + mdb->mi_flags ^= MDB_DEL_INDEX; + } + + if ( mdb->mi_flags & MDB_RE_OPEN ) { + mdb->mi_flags ^= MDB_RE_OPEN; + rc = c->be->bd_info->bi_db_close( c->be, &c->reply ); + if ( rc == 0 ) + rc = c->be->bd_info->bi_db_open( c->be, &c->reply ); + /* If this fails, we need to restart */ + if ( rc ) { + slapd_shutdown = 2; + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "failed to reopen database, rc=%d", rc ); + Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(mdb_cf_cleanup) + ": %s\n", c->cr_msg, 0, 0 ); + rc = LDAP_OTHER; + } + } + + if ( mdb->mi_flags & MDB_OPEN_INDEX ) { + mdb->mi_flags ^= MDB_OPEN_INDEX; + rc = mdb_attr_dbs_open( c->be, NULL, &c->reply ); + if ( rc ) + rc = LDAP_OTHER; + } + return rc; +} + +static int +mdb_cf_gen( ConfigArgs *c ) +{ + struct mdb_info *mdb = c->be->be_private; + int rc; + + if ( c->op == SLAP_CONFIG_EMIT ) { + rc = 0; + switch( c->type ) { + case MDB_MODE: { + char buf[64]; + struct berval bv; + bv.bv_len = snprintf( buf, sizeof(buf), "0%o", mdb->mi_dbenv_mode ); + if ( bv.bv_len > 0 && bv.bv_len < sizeof(buf) ) { + bv.bv_val = buf; + value_add_one( &c->rvalue_vals, &bv ); + } else { + rc = 1; + } + } break; + + case MDB_CHKPT: + if ( mdb->mi_txn_cp ) { + char buf[64]; + struct berval bv; + bv.bv_len = snprintf( buf, sizeof(buf), "%ld %ld", + (long) mdb->mi_txn_cp_kbyte, (long) mdb->mi_txn_cp_min ); + if ( bv.bv_len > 0 && bv.bv_len < sizeof(buf) ) { + bv.bv_val = buf; + value_add_one( &c->rvalue_vals, &bv ); + } else { + rc = 1; + } + } else { + rc = 1; + } + break; + + case MDB_DIRECTORY: + if ( mdb->mi_dbenv_home ) { + c->value_string = ch_strdup( mdb->mi_dbenv_home ); + } else { + rc = 1; + } + break; + + case MDB_DBNOSYNC: + if ( mdb->mi_dbenv_flags & MDB_NOSYNC ) + c->value_int = 1; + break; + + case MDB_ENVFLAGS: + if ( mdb->mi_dbenv_flags ) { + mask_to_verbs( mdb_envflags, mdb->mi_dbenv_flags, &c->rvalue_vals ); + } + if ( !c->rvalue_vals ) rc = 1; + break; + + case MDB_INDEX: + mdb_attr_index_unparse( mdb, &c->rvalue_vals ); + if ( !c->rvalue_vals ) rc = 1; + break; + + case MDB_SSTACK: + c->value_int = mdb->mi_search_stack_depth; + break; + + case MDB_MAXREADERS: + c->value_int = mdb->mi_readers; + break; + + case MDB_MAXSIZE: + c->value_ulong = mdb->mi_mapsize; + break; + } + return rc; + } else if ( c->op == LDAP_MOD_DELETE ) { + rc = 0; + switch( c->type ) { + case MDB_MODE: +#if 0 + /* FIXME: does it make any sense to change the mode, + * if we don't exec a chmod()? */ + mdb->bi_dbenv_mode = SLAPD_DEFAULT_DB_MODE; + break; +#endif + + /* single-valued no-ops */ + case MDB_SSTACK: + case MDB_MAXREADERS: + case MDB_MAXSIZE: + break; + + case MDB_CHKPT: + if ( mdb->mi_txn_cp_task ) { + struct re_s *re = mdb->mi_txn_cp_task; + mdb->mi_txn_cp_task = NULL; + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re ) ) + ldap_pvt_runqueue_stoptask( &slapd_rq, re ); + ldap_pvt_runqueue_remove( &slapd_rq, re ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + mdb->mi_txn_cp = 0; + break; + case MDB_DIRECTORY: + mdb->mi_flags |= MDB_RE_OPEN; + ch_free( mdb->mi_dbenv_home ); + mdb->mi_dbenv_home = NULL; + c->cleanup = mdb_cf_cleanup; + ldap_pvt_thread_pool_purgekey( mdb->mi_dbenv ); + break; + case MDB_DBNOSYNC: + mdb_env_set_flags( mdb->mi_dbenv, MDB_NOSYNC, 0 ); + mdb->mi_dbenv_flags &= ~MDB_NOSYNC; + break; + + case MDB_ENVFLAGS: + if ( c->valx == -1 ) { + int i; + for ( i=0; mdb_envflags[i].mask; i++) { + if ( mdb->mi_dbenv_flags & mdb_envflags[i].mask ) { + /* not all flags are runtime resettable */ + rc = mdb_env_set_flags( mdb->mi_dbenv, mdb_envflags[i].mask, 0 ); + if ( rc ) { + mdb->mi_flags |= MDB_RE_OPEN; + c->cleanup = mdb_cf_cleanup; + rc = 0; + } + mdb->mi_dbenv_flags ^= mdb_envflags[i].mask; + } + } + } else { + int i = verb_to_mask( c->line, mdb_envflags ); + if ( mdb_envflags[i].mask & mdb->mi_dbenv_flags ) { + rc = mdb_env_set_flags( mdb->mi_dbenv, mdb_envflags[i].mask, 0 ); + if ( rc ) { + mdb->mi_flags |= MDB_RE_OPEN; + c->cleanup = mdb_cf_cleanup; + rc = 0; + } + mdb->mi_dbenv_flags ^= mdb_envflags[i].mask; + } else { + /* unknown keyword */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: unknown keyword \"%s\"", + c->argv[0], c->argv[i] ); + Debug( LDAP_DEBUG_CONFIG, "%s %s\n", c->log, c->cr_msg, 0 ); + rc = 1; + } + } + break; + + case MDB_INDEX: + if ( c->valx == -1 ) { + int i; + + /* delete all */ + for ( i = 0; i < mdb->mi_nattrs; i++ ) { + mdb->mi_attrs[i]->ai_indexmask |= MDB_INDEX_DELETING; + } + mdb->mi_defaultmask = 0; + mdb->mi_flags |= MDB_DEL_INDEX; + c->cleanup = mdb_cf_cleanup; + + } else { + struct berval bv, def = BER_BVC("default"); + char *ptr; + + for (ptr = c->line; !isspace( (unsigned char) *ptr ); ptr++); + + bv.bv_val = c->line; + bv.bv_len = ptr - bv.bv_val; + if ( bvmatch( &bv, &def )) { + mdb->mi_defaultmask = 0; + + } else { + int i; + char **attrs; + char sep; + + sep = bv.bv_val[ bv.bv_len ]; + bv.bv_val[ bv.bv_len ] = '\0'; + attrs = ldap_str2charray( bv.bv_val, "," ); + + for ( i = 0; attrs[ i ]; i++ ) { + AttributeDescription *ad = NULL; + const char *text; + AttrInfo *ai; + + slap_str2ad( attrs[ i ], &ad, &text ); + /* if we got here... */ + assert( ad != NULL ); + + ai = mdb_attr_mask( mdb, ad ); + /* if we got here... */ + assert( ai != NULL ); + + ai->ai_indexmask |= MDB_INDEX_DELETING; + mdb->mi_flags |= MDB_DEL_INDEX; + c->cleanup = mdb_cf_cleanup; + } + + bv.bv_val[ bv.bv_len ] = sep; + ldap_charray_free( attrs ); + } + } + break; + } + return rc; + } + + switch( c->type ) { + case MDB_MODE: + if ( ASCII_DIGIT( c->argv[1][0] ) ) { + long mode; + char *next; + errno = 0; + mode = strtol( c->argv[1], &next, 0 ); + if ( errno != 0 || next == c->argv[1] || next[0] != '\0' ) { + fprintf( stderr, "%s: " + "unable to parse mode=\"%s\".\n", + c->log, c->argv[1] ); + return 1; + } + mdb->mi_dbenv_mode = mode; + + } else { + char *m = c->argv[1]; + int who, what, mode = 0; + + if ( strlen( m ) != STRLENOF("-rwxrwxrwx") ) { + return 1; + } + + if ( m[0] != '-' ) { + return 1; + } + + m++; + for ( who = 0; who < 3; who++ ) { + for ( what = 0; what < 3; what++, m++ ) { + if ( m[0] == '-' ) { + continue; + } else if ( m[0] != "rwx"[what] ) { + return 1; + } + mode += ((1 << (2 - what)) << 3*(2 - who)); + } + } + mdb->mi_dbenv_mode = mode; + } + break; + case MDB_CHKPT: { + unsigned cp_kbyte, cp_min; + if ( lutil_atoux( &cp_kbyte, c->argv[1], 0 ) != 0 ) { + fprintf( stderr, "%s: " + "invalid kbyte \"%s\" in \"checkpoint\".\n", + c->log, c->argv[1] ); + return 1; + } + if ( lutil_atoux( &cp_min, c->argv[2], 0 ) != 0 ) { + fprintf( stderr, "%s: " + "invalid minutes \"%s\" in \"checkpoint\".\n", + c->log, c->argv[2] ); + return 1; + } + mdb->mi_txn_cp = 1; + mdb->mi_txn_cp_kbyte = cp_kbyte; + mdb->mi_txn_cp_min = cp_min; + /* If we're in server mode and time-based checkpointing is enabled, + * submit a task to perform periodic checkpoints. + */ + if ((slapMode & SLAP_SERVER_MODE) && mdb->mi_txn_cp_min ) { + struct re_s *re = mdb->mi_txn_cp_task; + if ( re ) { + re->interval.tv_sec = mdb->mi_txn_cp_min * 60; + } else { + if ( c->be->be_suffix == NULL || BER_BVISNULL( &c->be->be_suffix[0] ) ) { + fprintf( stderr, "%s: " + "\"checkpoint\" must occur after \"suffix\".\n", + c->log ); + return 1; + } + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + mdb->mi_txn_cp_task = ldap_pvt_runqueue_insert( &slapd_rq, + mdb->mi_txn_cp_min * 60, mdb_checkpoint, mdb, + LDAP_XSTRING(mdb_checkpoint), c->be->be_suffix[0].bv_val ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + } + } break; + + case MDB_DIRECTORY: { + FILE *f; + char *ptr, *testpath; + int len; + + len = strlen( c->value_string ); + testpath = ch_malloc( len + STRLENOF(LDAP_DIRSEP) + STRLENOF("DUMMY") + 1 ); + ptr = lutil_strcopy( testpath, c->value_string ); + *ptr++ = LDAP_DIRSEP[0]; + strcpy( ptr, "DUMMY" ); + f = fopen( testpath, "w" ); + if ( f ) { + fclose( f ); + unlink( testpath ); + } + ch_free( testpath ); + if ( !f ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: invalid path: %s", + c->log, strerror( errno )); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + return -1; + } + + if ( mdb->mi_dbenv_home ) + ch_free( mdb->mi_dbenv_home ); + mdb->mi_dbenv_home = c->value_string; + + } + break; + + case MDB_DBNOSYNC: + if ( c->value_int ) + mdb->mi_dbenv_flags |= MDB_NOSYNC; + else + mdb->mi_dbenv_flags &= ~MDB_NOSYNC; + if ( mdb->mi_flags & MDB_IS_OPEN ) { + mdb_env_set_flags( mdb->mi_dbenv, MDB_NOSYNC, + c->value_int ); + } + break; + + case MDB_ENVFLAGS: { + int i, j; + for ( i=1; i<c->argc; i++ ) { + j = verb_to_mask( c->argv[i], mdb_envflags ); + if ( mdb_envflags[j].mask ) { + if ( mdb->mi_flags & MDB_IS_OPEN ) + rc = mdb_env_set_flags( mdb->mi_dbenv, mdb_envflags[j].mask, 1 ); + else + rc = 0; + if ( rc ) { + mdb->mi_flags |= MDB_RE_OPEN; + c->cleanup = mdb_cf_cleanup; + rc = 0; + } + mdb->mi_dbenv_flags |= mdb_envflags[j].mask; + } else { + /* unknown keyword */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: unknown keyword \"%s\"", + c->argv[0], c->argv[i] ); + Debug( LDAP_DEBUG_ANY, "%s %s\n", c->log, c->cr_msg, 0 ); + return 1; + } + } + } + break; + + case MDB_INDEX: + rc = mdb_attr_index_config( mdb, c->fname, c->lineno, + c->argc - 1, &c->argv[1], &c->reply); + + if( rc != LDAP_SUCCESS ) return 1; + if ( mdb->mi_flags & MDB_IS_OPEN ) { + mdb->mi_flags |= MDB_OPEN_INDEX; + c->cleanup = mdb_cf_cleanup; + if ( !mdb->mi_index_task ) { + /* Start the task as soon as we finish here. Set a long + * interval (10 hours) so that it only gets scheduled once. + */ + if ( c->be->be_suffix == NULL || BER_BVISNULL( &c->be->be_suffix[0] ) ) { + fprintf( stderr, "%s: " + "\"index\" must occur after \"suffix\".\n", + c->log ); + return 1; + } + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + mdb->mi_index_task = ldap_pvt_runqueue_insert( &slapd_rq, 36000, + mdb_online_index, c->be, + LDAP_XSTRING(mdb_online_index), c->be->be_suffix[0].bv_val ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + } + break; + + case MDB_SSTACK: + if ( c->value_int < MINIMUM_SEARCH_STACK_DEPTH ) { + fprintf( stderr, + "%s: depth %d too small, using %d\n", + c->log, c->value_int, MINIMUM_SEARCH_STACK_DEPTH ); + c->value_int = MINIMUM_SEARCH_STACK_DEPTH; + } + mdb->mi_search_stack_depth = c->value_int; + break; + + case MDB_MAXREADERS: + mdb->mi_readers = c->value_int; + if ( mdb->mi_flags & MDB_IS_OPEN ) { + mdb->mi_flags |= MDB_RE_OPEN; + c->cleanup = mdb_cf_cleanup; + } + break; + + case MDB_MAXSIZE: + mdb->mi_mapsize = c->value_ulong; + if ( mdb->mi_flags & MDB_IS_OPEN ) { + mdb->mi_flags |= MDB_RE_OPEN; + c->cleanup = mdb_cf_cleanup; + } + break; + + } + return 0; +} + +int mdb_back_init_cf( BackendInfo *bi ) +{ + int rc; + bi->bi_cf_ocs = mdbocs; + + rc = config_register_schema( mdbcfg, mdbocs ); + if ( rc ) return rc; + return 0; +} diff --git a/servers/slapd/back-mdb/delete.c b/servers/slapd/back-mdb/delete.c new file mode 100644 index 0000000..5d3a574 --- /dev/null +++ b/servers/slapd/back-mdb/delete.c @@ -0,0 +1,479 @@ +/* delete.c - mdb backend delete routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "lutil.h" +#include "back-mdb.h" + +int +mdb_delete( Operation *op, SlapReply *rs ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + struct berval pdn = {0, NULL}; + Entry *e = NULL; + Entry *p = NULL; + int manageDSAit = get_manageDSAit( op ); + AttributeDescription *children = slap_schema.si_ad_children; + AttributeDescription *entry = slap_schema.si_ad_entry; + MDB_txn *txn = NULL; + MDB_cursor *mc; + mdb_op_info opinfo = {{{ 0 }}}, *moi = &opinfo; + + LDAPControl **preread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + + int parent_is_glue = 0; + int parent_is_leaf = 0; + +#ifdef LDAP_X_TXN + int settle = 0; +#endif + + Debug( LDAP_DEBUG_ARGS, "==> " LDAP_XSTRING(mdb_delete) ": %s\n", + op->o_req_dn.bv_val, 0, 0 ); + +#ifdef LDAP_X_TXN + if( op->o_txnSpec ) { + /* acquire connection lock */ + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + if( op->o_conn->c_txn == CONN_TXN_INACTIVE ) { + rs->sr_text = "invalid transaction identifier"; + rs->sr_err = LDAP_X_TXN_ID_INVALID; + goto txnReturn; + } else if( op->o_conn->c_txn == CONN_TXN_SETTLE ) { + settle=1; + goto txnReturn; + } + + if( op->o_conn->c_txn_backend == NULL ) { + op->o_conn->c_txn_backend = op->o_bd; + + } else if( op->o_conn->c_txn_backend != op->o_bd ) { + rs->sr_text = "transaction cannot span multiple database contexts"; + rs->sr_err = LDAP_AFFECTS_MULTIPLE_DSAS; + goto txnReturn; + } + + /* insert operation into transaction */ + + rs->sr_text = "transaction specified"; + rs->sr_err = LDAP_X_TXN_SPECIFY_OKAY; + +txnReturn: + /* release connection lock */ + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + + if( !settle ) { + send_ldap_result( op, rs ); + return rs->sr_err; + } + } +#endif + + ctrls[num_ctrls] = 0; + + /* begin transaction */ + rs->sr_err = mdb_opinfo_get( op, mdb, 0, &moi ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_delete) ": txn_begin failed: " + "%s (%d)\n", mdb_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + txn = moi->moi_txn; + + /* allocate CSN */ + if ( BER_BVISNULL( &op->o_csn ) ) { + struct berval csn; + char csnbuf[LDAP_PVT_CSNSTR_BUFSIZE]; + + csn.bv_val = csnbuf; + csn.bv_len = sizeof(csnbuf); + slap_get_csn( op, &csn, 1 ); + } + + if ( !be_issuffix( op->o_bd, &op->o_req_ndn ) ) { + dnParent( &op->o_req_ndn, &pdn ); + } + + rs->sr_err = mdb_cursor_open( txn, mdb->mi_dn2id, &mc ); + if ( rs->sr_err ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + /* get parent */ + rs->sr_err = mdb_dn2entry( op, txn, mc, &pdn, &p, NULL, 1 ); + switch( rs->sr_err ) { + case 0: + case MDB_NOTFOUND: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + if ( rs->sr_err == MDB_NOTFOUND ) { + Debug( LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(mdb_delete) ": no such object %s\n", + op->o_req_dn.bv_val, 0, 0); + + if ( p && !BER_BVISEMPTY( &p->e_name )) { + rs->sr_matched = ch_strdup( p->e_name.bv_val ); + if ( is_entry_referral( p )) { + BerVarray ref = get_entry_referrals( op, p ); + rs->sr_ref = referral_rewrite( ref, &p->e_name, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + ber_bvarray_free( ref ); + } else { + rs->sr_ref = NULL; + } + } else { + rs->sr_ref = referral_rewrite( default_referral, NULL, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + } + if ( p ) { + mdb_entry_return( op, p ); + p = NULL; + } + + rs->sr_err = LDAP_REFERRAL; + rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED; + goto return_results; + } + + /* get entry */ + rs->sr_err = mdb_dn2entry( op, txn, mc, &op->o_req_ndn, &e, NULL, 0 ); + switch( rs->sr_err ) { + case MDB_NOTFOUND: + e = p; + p = NULL; + case 0: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + /* FIXME : dn2entry() should return non-glue entry */ + if ( rs->sr_err == MDB_NOTFOUND || ( !manageDSAit && is_entry_glue( e ))) { + Debug( LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(mdb_delete) ": no such object %s\n", + op->o_req_dn.bv_val, 0, 0); + + rs->sr_matched = ch_strdup( e->e_dn ); + if ( is_entry_referral( e )) { + BerVarray ref = get_entry_referrals( op, e ); + rs->sr_ref = referral_rewrite( ref, &e->e_name, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + ber_bvarray_free( ref ); + } else { + rs->sr_ref = NULL; + } + mdb_entry_return( op, e ); + e = NULL; + + rs->sr_err = LDAP_REFERRAL; + rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED; + goto return_results; + } + + if ( pdn.bv_len != 0 ) { + /* check parent for "children" acl */ + rs->sr_err = access_allowed( op, p, + children, NULL, ACL_WDEL, NULL ); + + if ( !rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_delete) ": no write " + "access to parent\n", 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to parent"; + goto return_results; + } + + } else { + /* no parent, must be root to delete */ + if( ! be_isroot( op ) ) { + if ( be_issuffix( op->o_bd, (struct berval *)&slap_empty_bv ) + || be_shadow_update( op ) ) { + p = (Entry *)&slap_entry_root; + + /* check parent for "children" acl */ + rs->sr_err = access_allowed( op, p, + children, NULL, ACL_WDEL, NULL ); + + p = NULL; + + if ( !rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_delete) + ": no access to parent\n", + 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to parent"; + goto return_results; + } + + } else { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_delete) + ": no parent and not root\n", 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto return_results; + } + } + } + + if ( get_assert( op ) && + ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto return_results; + } + + rs->sr_err = access_allowed( op, e, + entry, NULL, ACL_WDEL, NULL ); + + if ( !rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_delete) ": no write access " + "to entry\n", 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to entry"; + goto return_results; + } + + if ( !manageDSAit && is_entry_referral( e ) ) { + /* entry is a referral, don't allow delete */ + rs->sr_ref = get_entry_referrals( op, e ); + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_delete) ": entry is referral\n", + 0, 0, 0 ); + + rs->sr_err = LDAP_REFERRAL; + rs->sr_matched = ch_strdup( e->e_name.bv_val ); + rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED; + goto return_results; + } + + /* pre-read */ + if( op->o_preread ) { + if( preread_ctrl == NULL ) { + preread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if( slap_read_controls( op, rs, e, + &slap_pre_read_bv, preread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_delete) ": pre-read " + "failed!\n", 0, 0, 0 ); + if ( op->o_preread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + rs->sr_text = NULL; + + /* Can't do it if we have kids */ + rs->sr_err = mdb_dn2id_children( op, txn, e ); + if( rs->sr_err != MDB_NOTFOUND ) { + switch( rs->sr_err ) { + case 0: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(mdb_delete) + ": non-leaf %s\n", + op->o_req_dn.bv_val, 0, 0); + rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF; + rs->sr_text = "subordinate objects must be deleted first"; + break; + default: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(mdb_delete) + ": has_children failed: %s (%d)\n", + mdb_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + } + goto return_results; + } + + /* delete from dn2id */ + rs->sr_err = mdb_dn2id_delete( op, mc, e->e_id, 1 ); + mdb_cursor_close( mc ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_delete) ": dn2id failed: " + "%s (%d)\n", mdb_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_text = "DN index delete failed"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + + /* delete indices for old attributes */ + rs->sr_err = mdb_index_entry_del( op, txn, e ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_delete) ": index failed: " + "%s (%d)\n", mdb_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_text = "entry index delete failed"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + + /* fixup delete CSN */ + if ( !SLAP_SHADOW( op->o_bd )) { + struct berval vals[2]; + + assert( !BER_BVISNULL( &op->o_csn ) ); + vals[0] = op->o_csn; + BER_BVZERO( &vals[1] ); + rs->sr_err = mdb_index_values( op, txn, slap_schema.si_ad_entryCSN, + vals, 0, SLAP_INDEX_ADD_OP ); + if ( rs->sr_err != LDAP_SUCCESS ) { + rs->sr_text = "entryCSN index update failed"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + } + + /* delete from id2entry */ + rs->sr_err = mdb_id2entry_delete( op->o_bd, txn, e ); + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_delete) ": id2entry failed: " + "%s (%d)\n", mdb_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_text = "entry delete failed"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + + if ( pdn.bv_len != 0 ) { + parent_is_glue = is_entry_glue(p); + rs->sr_err = mdb_dn2id_children( op, txn, p ); + if ( rs->sr_err != MDB_NOTFOUND ) { + switch( rs->sr_err ) { + case 0: + break; + default: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(mdb_delete) + ": has_children failed: %s (%d)\n", + mdb_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + parent_is_leaf = 1; + } + mdb_entry_return( op, p ); + p = NULL; + } + + if( moi == &opinfo ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next ); + opinfo.moi_oe.oe_key = NULL; + if( op->o_noop ) { + mdb_txn_abort( txn ); + rs->sr_err = LDAP_X_NO_OPERATION; + txn = NULL; + goto return_results; + } else { + rs->sr_err = mdb_txn_commit( txn ); + } + txn = NULL; + } + + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_delete) ": txn_%s failed: %s (%d)\n", + op->o_noop ? "abort (no-op)" : "commit", + mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "commit failed"; + + goto return_results; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_delete) ": deleted%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + e->e_id, op->o_req_dn.bv_val ); + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + if( num_ctrls ) rs->sr_ctrls = ctrls; + +return_results: + if ( rs->sr_err == LDAP_SUCCESS && parent_is_glue && parent_is_leaf ) { + op->o_delete_glue_parent = 1; + } + + if ( p != NULL ) { + mdb_entry_return( op, p ); + } + + /* free entry */ + if( e != NULL ) { + mdb_entry_return( op, e ); + } + + if( moi == &opinfo ) { + if( txn != NULL ) { + mdb_txn_abort( txn ); + } + if ( opinfo.moi_oe.oe_key ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next ); + } + } else { + moi->moi_ref--; + } + + send_ldap_result( op, rs ); + slap_graduate_commit_csn( op ); + + if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) { + slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *preread_ctrl, op->o_tmpmemctx ); + } + +#if 0 + if( rs->sr_err == LDAP_SUCCESS && mdb->bi_txn_cp_kbyte ) { + TXN_CHECKPOINT( mdb->bi_dbenv, + mdb->bi_txn_cp_kbyte, mdb->bi_txn_cp_min, 0 ); + } +#endif + return rs->sr_err; +} diff --git a/servers/slapd/back-mdb/dn2entry.c b/servers/slapd/back-mdb/dn2entry.c new file mode 100644 index 0000000..e2377f5 --- /dev/null +++ b/servers/slapd/back-mdb/dn2entry.c @@ -0,0 +1,79 @@ +/* dn2entry.c - routines to deal with the dn2id / id2entry glue */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" + +/* + * dn2entry - look up dn in the cache/indexes and return the corresponding + * entry. If the requested DN is not found and matched is TRUE, return info + * for the closest ancestor of the DN. Otherwise e is NULL. + */ + +int +mdb_dn2entry( + Operation *op, + MDB_txn *tid, + MDB_cursor *m2, + struct berval *dn, + Entry **e, + ID *nsubs, + int matched ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + int rc, rc2; + ID id = NOID; + struct berval mbv, nmbv; + MDB_cursor *mc; + + Debug(LDAP_DEBUG_TRACE, "mdb_dn2entry(\"%s\")\n", + dn->bv_val ? dn->bv_val : "", 0, 0 ); + + *e = NULL; + + rc = mdb_dn2id( op, tid, m2, dn, &id, nsubs, &mbv, &nmbv ); + if ( rc ) { + if ( matched ) { + rc2 = mdb_cursor_open( tid, mdb->mi_id2entry, &mc ); + if ( rc2 == MDB_SUCCESS ) { + rc2 = mdb_id2entry( op, mc, id, e ); + mdb_cursor_close( mc ); + } + } + + } else { + rc = mdb_cursor_open( tid, mdb->mi_id2entry, &mc ); + if ( rc == MDB_SUCCESS ) { + rc = mdb_id2entry( op, mc, id, e ); + mdb_cursor_close(mc); + } + } + if ( *e ) { + (*e)->e_name = mbv; + if ( rc == MDB_SUCCESS ) + ber_dupbv_x( &(*e)->e_nname, dn, op->o_tmpmemctx ); + else + ber_dupbv_x( &(*e)->e_nname, &nmbv, op->o_tmpmemctx ); + } else { + op->o_tmpfree( mbv.bv_val, op->o_tmpmemctx ); + } + + return rc; +} diff --git a/servers/slapd/back-mdb/dn2id.c b/servers/slapd/back-mdb/dn2id.c new file mode 100644 index 0000000..25e9fe8 --- /dev/null +++ b/servers/slapd/back-mdb/dn2id.c @@ -0,0 +1,981 @@ +/* dn2id.c - routines to deal with the dn2id index */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" +#include "idl.h" +#include "lutil.h" + +/* Management routines for a hierarchically structured database. + * + * Instead of a ldbm-style dn2id database, we use a hierarchical one. Each + * entry in this database is a struct diskNode, keyed by entryID and with + * the data containing the RDN and entryID of the node's children. We use + * a B-Tree with sorted duplicates to store all the children of a node under + * the same key. Also, the first item under the key contains the entry's own + * rdn and the ID of the node's parent, to allow bottom-up tree traversal as + * well as top-down. To keep this info first in the list, the high bit of all + * subsequent nrdnlen's is always set. This means we can only accomodate + * RDNs up to length 32767, but that's fine since full DNs are already + * restricted to 8192. + * + * Also each child node contains a count of the number of entries in + * its subtree, appended after its entryID. + * + * The diskNode is a variable length structure. This definition is not + * directly usable for in-memory manipulation. + */ +typedef struct diskNode { + unsigned char nrdnlen[2]; + char nrdn[1]; + char rdn[1]; /* variable placement */ + unsigned char entryID[sizeof(ID)]; /* variable placement */ + /* unsigned char nsubs[sizeof(ID)]; in child nodes only */ +} diskNode; + +/* Sort function for the sorted duplicate data items of a dn2id key. + * Sorts based on normalized RDN, in length order. + */ +int +mdb_dup_compare( + const MDB_val *usrkey, + const MDB_val *curkey +) +{ + diskNode *un, *cn; + int rc, nrlen; + + un = (diskNode *)usrkey->mv_data; + cn = (diskNode *)curkey->mv_data; + + /* data is not aligned, cannot compare directly */ + rc = un->nrdnlen[0] - cn->nrdnlen[0]; + if ( rc ) return rc; + rc = un->nrdnlen[1] - cn->nrdnlen[1]; + if ( rc ) return rc; + + nrlen = ((un->nrdnlen[0] & 0x7f) << 8) | un->nrdnlen[1]; + return strncmp( un->nrdn, cn->nrdn, nrlen ); +} + +/* We add two elements to the DN2ID database - a data item under the parent's + * entryID containing the child's RDN and entryID, and an item under the + * child's entryID containing the parent's entryID. + */ +int +mdb_dn2id_add( + Operation *op, + MDB_cursor *mcp, + MDB_cursor *mcd, + ID pid, + ID nsubs, + int upsub, + Entry *e ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_val key, data; + ID nid; + int rc, rlen, nrlen; + diskNode *d; + char *ptr; + + Debug( LDAP_DEBUG_TRACE, "=> mdb_dn2id_add 0x%lx: \"%s\"\n", + e->e_id, e->e_ndn ? e->e_ndn : "", 0 ); + + nrlen = dn_rdnlen( op->o_bd, &e->e_nname ); + if (nrlen) { + rlen = dn_rdnlen( op->o_bd, &e->e_name ); + } else { + nrlen = e->e_nname.bv_len; + rlen = e->e_name.bv_len; + } + + d = op->o_tmpalloc(sizeof(diskNode) + rlen + nrlen + sizeof(ID), op->o_tmpmemctx); + d->nrdnlen[1] = nrlen & 0xff; + d->nrdnlen[0] = (nrlen >> 8) | 0x80; + ptr = lutil_strncopy( d->nrdn, e->e_nname.bv_val, nrlen ); + *ptr++ = '\0'; + ptr = lutil_strncopy( ptr, e->e_name.bv_val, rlen ); + *ptr++ = '\0'; + memcpy( ptr, &e->e_id, sizeof( ID )); + ptr += sizeof( ID ); + memcpy( ptr, &nsubs, sizeof( ID )); + + key.mv_size = sizeof(ID); + key.mv_data = &nid; + + nid = pid; + + /* Need to make dummy root node once. Subsequent attempts + * will fail harmlessly. + */ + if ( pid == 0 ) { + diskNode dummy = {{0, 0}, "", "", ""}; + data.mv_data = &dummy; + data.mv_size = sizeof(diskNode); + + mdb_cursor_put( mcp, &key, &data, MDB_NODUPDATA ); + } + + data.mv_data = d; + data.mv_size = sizeof(diskNode) + rlen + nrlen + sizeof( ID ); + + /* Add our child node under parent's key */ + rc = mdb_cursor_put( mcp, &key, &data, MDB_NODUPDATA ); + + /* Add our own node */ + if (rc == 0) { + int flag = MDB_NODUPDATA; + nid = e->e_id; + /* drop subtree count */ + data.mv_size -= sizeof( ID ); + ptr -= sizeof( ID ); + memcpy( ptr, &pid, sizeof( ID )); + d->nrdnlen[0] ^= 0x80; + + if ((slapMode & SLAP_TOOL_MODE) || (e->e_id == mdb->mi_nextid)) + flag |= MDB_APPEND; + rc = mdb_cursor_put( mcd, &key, &data, flag ); + } + op->o_tmpfree( d, op->o_tmpmemctx ); + + /* Add our subtree count to all superiors */ + if ( rc == 0 && upsub && pid ) { + ID subs; + nid = pid; + do { + /* Get parent's RDN */ + rc = mdb_cursor_get( mcp, &key, &data, MDB_SET ); + if ( !rc ) { + char *p2; + ptr = (char *)data.mv_data + data.mv_size - sizeof( ID ); + memcpy( &nid, ptr, sizeof( ID )); + /* Get parent's node under grandparent */ + d = data.mv_data; + rlen = ( d->nrdnlen[0] << 8 ) | d->nrdnlen[1]; + p2 = op->o_tmpalloc( rlen + 2, op->o_tmpmemctx ); + memcpy( p2, data.mv_data, rlen+2 ); + *p2 ^= 0x80; + data.mv_data = p2; + rc = mdb_cursor_get( mcp, &key, &data, MDB_GET_BOTH ); + op->o_tmpfree( p2, op->o_tmpmemctx ); + if ( !rc ) { + /* Get parent's subtree count */ + ptr = (char *)data.mv_data + data.mv_size - sizeof( ID ); + memcpy( &subs, ptr, sizeof( ID )); + subs += nsubs; + p2 = op->o_tmpalloc( data.mv_size, op->o_tmpmemctx ); + memcpy( p2, data.mv_data, data.mv_size - sizeof( ID )); + memcpy( p2+data.mv_size - sizeof( ID ), &subs, sizeof( ID )); + data.mv_data = p2; + rc = mdb_cursor_put( mcp, &key, &data, MDB_CURRENT ); + op->o_tmpfree( p2, op->o_tmpmemctx ); + } + } + if ( rc ) + break; + } while ( nid ); + } + + Debug( LDAP_DEBUG_TRACE, "<= mdb_dn2id_add 0x%lx: %d\n", e->e_id, rc, 0 ); + + return rc; +} + +/* mc must have been set by mdb_dn2id */ +int +mdb_dn2id_delete( + Operation *op, + MDB_cursor *mc, + ID id, + ID nsubs ) +{ + ID nid; + char *ptr; + int rc; + + Debug( LDAP_DEBUG_TRACE, "=> mdb_dn2id_delete 0x%lx\n", + id, 0, 0 ); + + /* Delete our ID from the parent's list */ + rc = mdb_cursor_del( mc, 0 ); + + /* Delete our ID from the tree. With sorted duplicates, this + * will leave any child nodes still hanging around. This is OK + * for modrdn, which will add our info back in later. + */ + if ( rc == 0 ) { + MDB_val key, data; + if ( nsubs ) { + mdb_cursor_get( mc, &key, NULL, MDB_GET_CURRENT ); + memcpy( &nid, key.mv_data, sizeof( ID )); + } + key.mv_size = sizeof(ID); + key.mv_data = &id; + rc = mdb_cursor_get( mc, &key, &data, MDB_SET ); + if ( rc == 0 ) + rc = mdb_cursor_del( mc, 0 ); + } + + /* Delete our subtree count from all superiors */ + if ( rc == 0 && nsubs && nid ) { + MDB_val key, data; + ID subs; + key.mv_data = &nid; + key.mv_size = sizeof( ID ); + do { + rc = mdb_cursor_get( mc, &key, &data, MDB_SET ); + if ( !rc ) { + char *p2; + diskNode *d; + int rlen; + ptr = (char *)data.mv_data + data.mv_size - sizeof( ID ); + memcpy( &nid, ptr, sizeof( ID )); + /* Get parent's node under grandparent */ + d = data.mv_data; + rlen = ( d->nrdnlen[0] << 8 ) | d->nrdnlen[1]; + p2 = op->o_tmpalloc( rlen + 2, op->o_tmpmemctx ); + memcpy( p2, data.mv_data, rlen+2 ); + *p2 ^= 0x80; + data.mv_data = p2; + rc = mdb_cursor_get( mc, &key, &data, MDB_GET_BOTH ); + op->o_tmpfree( p2, op->o_tmpmemctx ); + if ( !rc ) { + /* Get parent's subtree count */ + ptr = (char *)data.mv_data + data.mv_size - sizeof( ID ); + memcpy( &subs, ptr, sizeof( ID )); + subs -= nsubs; + p2 = op->o_tmpalloc( data.mv_size, op->o_tmpmemctx ); + memcpy( p2, data.mv_data, data.mv_size - sizeof( ID )); + memcpy( p2+data.mv_size - sizeof( ID ), &subs, sizeof( ID )); + data.mv_data = p2; + rc = mdb_cursor_put( mc, &key, &data, MDB_CURRENT ); + op->o_tmpfree( p2, op->o_tmpmemctx ); + } + + } + if ( rc ) + break; + } while ( nid ); + } + + Debug( LDAP_DEBUG_TRACE, "<= mdb_dn2id_delete 0x%lx: %d\n", id, rc, 0 ); + return rc; +} + +/* return last found ID in *id if no match + * If mc is provided, it will be left pointing to the RDN's + * record under the parent's ID. If nsubs is provided, return + * the number of entries in this entry's subtree. + */ +int +mdb_dn2id( + Operation *op, + MDB_txn *txn, + MDB_cursor *mc, + struct berval *in, + ID *id, + ID *nsubs, + struct berval *matched, + struct berval *nmatched ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_cursor *cursor; + MDB_dbi dbi = mdb->mi_dn2id; + MDB_val key, data; + int rc = 0, nrlen; + diskNode *d; + char *ptr; + char dn[SLAP_LDAPDN_MAXLEN]; + ID pid, nid; + struct berval tmp; + + Debug( LDAP_DEBUG_TRACE, "=> mdb_dn2id(\"%s\")\n", in->bv_val ? in->bv_val : "", 0, 0 ); + + if ( matched ) { + matched->bv_val = dn + sizeof(dn) - 1; + matched->bv_len = 0; + *matched->bv_val-- = '\0'; + } + if ( nmatched ) { + nmatched->bv_len = 0; + nmatched->bv_val = 0; + } + + if ( !in->bv_len ) { + *id = 0; + nid = 0; + goto done; + } + + tmp = *in; + + if ( op->o_bd->be_nsuffix[0].bv_len ) { + nrlen = tmp.bv_len - op->o_bd->be_nsuffix[0].bv_len; + tmp.bv_val += nrlen; + tmp.bv_len = op->o_bd->be_nsuffix[0].bv_len; + } else { + for ( ptr = tmp.bv_val + tmp.bv_len - 1; ptr >= tmp.bv_val; ptr-- ) + if (DN_SEPARATOR(*ptr)) + break; + ptr++; + tmp.bv_len -= ptr - tmp.bv_val; + tmp.bv_val = ptr; + } + nid = 0; + key.mv_size = sizeof(ID); + + if ( mc ) { + cursor = mc; + } else { + rc = mdb_cursor_open( txn, dbi, &cursor ); + if ( rc ) goto done; + } + + for (;;) { + key.mv_data = &pid; + pid = nid; + + data.mv_size = sizeof(diskNode) + tmp.bv_len; + d = op->o_tmpalloc( data.mv_size, op->o_tmpmemctx ); + d->nrdnlen[1] = tmp.bv_len & 0xff; + d->nrdnlen[0] = (tmp.bv_len >> 8) | 0x80; + ptr = lutil_strncopy( d->nrdn, tmp.bv_val, tmp.bv_len ); + *ptr = '\0'; + data.mv_data = d; + rc = mdb_cursor_get( cursor, &key, &data, MDB_GET_BOTH ); + op->o_tmpfree( d, op->o_tmpmemctx ); + if ( rc ) + break; + ptr = (char *) data.mv_data + data.mv_size - 2*sizeof(ID); + memcpy( &nid, ptr, sizeof(ID)); + + /* grab the non-normalized RDN */ + if ( matched ) { + int rlen; + d = data.mv_data; + rlen = data.mv_size - sizeof(diskNode) - tmp.bv_len - sizeof(ID); + matched->bv_len += rlen; + matched->bv_val -= rlen + 1; + ptr = lutil_strcopy( matched->bv_val, d->rdn + tmp.bv_len ); + if ( pid ) { + *ptr = ','; + matched->bv_len++; + } + } + if ( nmatched ) { + nmatched->bv_val = tmp.bv_val; + } + + if ( tmp.bv_val > in->bv_val ) { + for (ptr = tmp.bv_val - 2; ptr > in->bv_val && + !DN_SEPARATOR(*ptr); ptr--) /* empty */; + if ( ptr >= in->bv_val ) { + if (DN_SEPARATOR(*ptr)) ptr++; + tmp.bv_len = tmp.bv_val - ptr - 1; + tmp.bv_val = ptr; + } + } else { + break; + } + } + *id = nid; + /* return subtree count if requested */ + if ( !rc && nsubs ) { + ptr = (char *)data.mv_data + data.mv_size - sizeof(ID); + memcpy( nsubs, ptr, sizeof( ID )); + } + if ( !mc ) + mdb_cursor_close( cursor ); +done: + if ( matched ) { + if ( matched->bv_len ) { + ptr = op->o_tmpalloc( matched->bv_len+1, op->o_tmpmemctx ); + strcpy( ptr, matched->bv_val ); + matched->bv_val = ptr; + } else { + if ( BER_BVISEMPTY( &op->o_bd->be_nsuffix[0] ) && !nid ) { + ber_dupbv( matched, (struct berval *)&slap_empty_bv ); + } else { + matched->bv_val = NULL; + } + } + } + if ( nmatched ) { + if ( nmatched->bv_val ) { + nmatched->bv_len = in->bv_len - (nmatched->bv_val - in->bv_val); + } else { + *nmatched = slap_empty_bv; + } + } + + if( rc != 0 ) { + Debug( LDAP_DEBUG_TRACE, "<= mdb_dn2id: get failed: %s (%d)\n", + mdb_strerror( rc ), rc, 0 ); + } else { + Debug( LDAP_DEBUG_TRACE, "<= mdb_dn2id: got id=0x%lx\n", + nid, 0, 0 ); + } + + return rc; +} + +/* return IDs from root to parent of DN */ +int +mdb_dn2sups( + Operation *op, + MDB_txn *txn, + struct berval *in, + ID *ids ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_cursor *cursor; + MDB_dbi dbi = mdb->mi_dn2id; + MDB_val key, data; + int rc = 0, nrlen; + diskNode *d; + char *ptr; + ID pid, nid; + struct berval tmp; + + Debug( LDAP_DEBUG_TRACE, "=> mdb_dn2sups(\"%s\")\n", in->bv_val, 0, 0 ); + + if ( !in->bv_len ) { + goto done; + } + + tmp = *in; + + nrlen = tmp.bv_len - op->o_bd->be_nsuffix[0].bv_len; + tmp.bv_val += nrlen; + tmp.bv_len = op->o_bd->be_nsuffix[0].bv_len; + nid = 0; + key.mv_size = sizeof(ID); + + rc = mdb_cursor_open( txn, dbi, &cursor ); + if ( rc ) goto done; + + for (;;) { + key.mv_data = &pid; + pid = nid; + + data.mv_size = sizeof(diskNode) + tmp.bv_len; + d = op->o_tmpalloc( data.mv_size, op->o_tmpmemctx ); + d->nrdnlen[1] = tmp.bv_len & 0xff; + d->nrdnlen[0] = (tmp.bv_len >> 8) | 0x80; + ptr = lutil_strncopy( d->nrdn, tmp.bv_val, tmp.bv_len ); + *ptr = '\0'; + data.mv_data = d; + rc = mdb_cursor_get( cursor, &key, &data, MDB_GET_BOTH ); + op->o_tmpfree( d, op->o_tmpmemctx ); + if ( rc ) + break; + ptr = (char *) data.mv_data + data.mv_size - 2*sizeof(ID); + memcpy( &nid, ptr, sizeof(ID)); + + if ( pid ) + mdb_idl_insert( ids, pid ); + + if ( tmp.bv_val > in->bv_val ) { + for (ptr = tmp.bv_val - 2; ptr > in->bv_val && + !DN_SEPARATOR(*ptr); ptr--) /* empty */; + if ( ptr >= in->bv_val ) { + if (DN_SEPARATOR(*ptr)) ptr++; + tmp.bv_len = tmp.bv_val - ptr - 1; + tmp.bv_val = ptr; + } + } else { + break; + } + } + mdb_cursor_close( cursor ); +done: + if( rc != 0 ) { + Debug( LDAP_DEBUG_TRACE, "<= mdb_dn2sups: get failed: %s (%d)\n", + mdb_strerror( rc ), rc, 0 ); + } + + return rc; +} + +int +mdb_dn2id_children( + Operation *op, + MDB_txn *txn, + Entry *e ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_dbi dbi = mdb->mi_dn2id; + MDB_val key, data; + MDB_cursor *cursor; + int rc; + ID id; + + key.mv_size = sizeof(ID); + key.mv_data = &id; + id = e->e_id; + + rc = mdb_cursor_open( txn, dbi, &cursor ); + if ( rc ) return rc; + + rc = mdb_cursor_get( cursor, &key, &data, MDB_SET ); + if ( rc == 0 ) { + size_t dkids; + rc = mdb_cursor_count( cursor, &dkids ); + if ( rc == 0 ) { + if ( dkids < 2 ) rc = MDB_NOTFOUND; + } + } + mdb_cursor_close( cursor ); + return rc; +} + +int +mdb_id2name( + Operation *op, + MDB_txn *txn, + MDB_cursor **cursp, + ID id, + struct berval *name, + struct berval *nname ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_dbi dbi = mdb->mi_dn2id; + MDB_val key, data; + MDB_cursor *cursor; + int rc, len, nlen; + char dn[SLAP_LDAPDN_MAXLEN], ndn[SLAP_LDAPDN_MAXLEN], *ptr; + char *dptr, *nptr; + diskNode *d; + + key.mv_size = sizeof(ID); + + if ( !*cursp ) { + rc = mdb_cursor_open( txn, dbi, cursp ); + if ( rc ) return rc; + } + cursor = *cursp; + + len = 0; + nlen = 0; + dptr = dn; + nptr = ndn; + while (id) { + unsigned int nrlen, rlen; + key.mv_data = &id; + data.mv_size = 0; + data.mv_data = ""; + rc = mdb_cursor_get( cursor, &key, &data, MDB_SET ); + if ( rc ) break; + ptr = data.mv_data; + ptr += data.mv_size - sizeof(ID); + memcpy( &id, ptr, sizeof(ID) ); + d = data.mv_data; + nrlen = (d->nrdnlen[0] << 8) | d->nrdnlen[1]; + rlen = data.mv_size - sizeof(diskNode) - nrlen; + assert( nrlen < 1024 && rlen < 1024 ); /* FIXME: Sanity check */ + if (nptr > ndn) { + *nptr++ = ','; + *dptr++ = ','; + } + /* copy name and trailing NUL */ + memcpy( nptr, d->nrdn, nrlen+1 ); + memcpy( dptr, d->nrdn+nrlen+1, rlen+1 ); + nptr += nrlen; + dptr += rlen; + } + if ( rc == 0 ) { + name->bv_len = dptr - dn; + nname->bv_len = nptr - ndn; + name->bv_val = op->o_tmpalloc( name->bv_len + 1, op->o_tmpmemctx ); + nname->bv_val = op->o_tmpalloc( nname->bv_len + 1, op->o_tmpmemctx ); + memcpy( name->bv_val, dn, name->bv_len ); + name->bv_val[name->bv_len] = '\0'; + memcpy( nname->bv_val, ndn, nname->bv_len ); + nname->bv_val[nname->bv_len] = '\0'; + } + return rc; +} + +/* Find each id in ids that is a child of base and move it to res. + */ +int +mdb_idscope( + Operation *op, + MDB_txn *txn, + ID base, + ID *ids, + ID *res ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_dbi dbi = mdb->mi_dn2id; + MDB_val key, data; + MDB_cursor *cursor; + ID ida, id, cid = 0, ci0 = 0, idc = 0; + char *ptr; + int rc, copy; + + key.mv_size = sizeof(ID); + + MDB_IDL_ZERO( res ); + + rc = mdb_cursor_open( txn, dbi, &cursor ); + if ( rc ) return rc; + + /* first see if base has any children at all */ + key.mv_data = &base; + rc = mdb_cursor_get( cursor, &key, &data, MDB_SET ); + if ( rc ) { + goto leave; + } + { + size_t dkids; + rc = mdb_cursor_count( cursor, &dkids ); + if ( rc == 0 ) { + if ( dkids < 2 ) { + goto leave; + } + } + } + + ida = mdb_idl_first( ids, &cid ); + + /* Don't bother moving out of ids if it's a range */ + if (!MDB_IDL_IS_RANGE(ids)) { + idc = ids[0]; + ci0 = cid; + } + + while (ida != NOID) { + copy = 1; + id = ida; + while (id) { + key.mv_data = &id; + rc = mdb_cursor_get( cursor, &key, &data, MDB_SET ); + if ( rc ) { + /* not found, drop this from ids */ + copy = 0; + break; + } + ptr = data.mv_data; + ptr += data.mv_size - sizeof(ID); + memcpy( &id, ptr, sizeof(ID) ); + if ( id == base ) { + if ( res[0] >= MDB_IDL_DB_SIZE-1 ) { + /* too many aliases in scope. Fallback to range */ + MDB_IDL_RANGE( res, MDB_IDL_FIRST( ids ), MDB_IDL_LAST( ids )); + goto leave; + } + res[0]++; + res[res[0]] = ida; + copy = 0; + break; + } + if ( op->ors_scope == LDAP_SCOPE_ONELEVEL ) + break; + } + if (idc) { + if (copy) { + if (ci0 != cid) + ids[ci0] = ids[cid]; + ci0++; + } else + idc--; + } + ida = mdb_idl_next( ids, &cid ); + } + if (!MDB_IDL_IS_RANGE( ids )) + ids[0] = idc; + +leave: + mdb_cursor_close( cursor ); + return rc; +} + +/* See if base is a child of any of the scopes + */ +int +mdb_idscopes( + Operation *op, + IdScopes *isc ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_dbi dbi = mdb->mi_dn2id; + MDB_val key, data; + ID id, prev; + ID2 id2; + char *ptr; + int rc = 0; + unsigned int x; + unsigned int nrlen, rlen; + diskNode *d; + + key.mv_size = sizeof(ID); + + if ( !isc->mc ) { + rc = mdb_cursor_open( isc->mt, dbi, &isc->mc ); + if ( rc ) return rc; + } + + id = isc->id; + + /* Catch entries from deref'd aliases */ + x = mdb_id2l_search( isc->scopes, id ); + if ( x <= isc->scopes[0].mid && isc->scopes[x].mid == id ) { + isc->nscope = x; + return MDB_SUCCESS; + } + + isc->sctmp[0].mid = 0; + while (id) { + if ( !rc ) { + key.mv_data = &id; + rc = mdb_cursor_get( isc->mc, &key, &data, MDB_SET ); + if ( rc ) + return rc; + + /* save RDN info */ + } + d = data.mv_data; + nrlen = (d->nrdnlen[0] << 8) | d->nrdnlen[1]; + rlen = data.mv_size - sizeof(diskNode) - nrlen; + isc->nrdns[isc->numrdns].bv_len = nrlen; + isc->nrdns[isc->numrdns].bv_val = d->nrdn; + isc->rdns[isc->numrdns].bv_len = rlen; + isc->rdns[isc->numrdns].bv_val = d->nrdn+nrlen+1; + isc->numrdns++; + + if (!rc && id != isc->id) { + /* remember our chain of parents */ + id2.mid = id; + id2.mval = data; + mdb_id2l_insert( isc->sctmp, &id2 ); + } + ptr = data.mv_data; + ptr += data.mv_size - sizeof(ID); + prev = id; + memcpy( &id, ptr, sizeof(ID) ); + /* If we didn't advance, some parent is missing */ + if ( id == prev ) + return MDB_NOTFOUND; + + x = mdb_id2l_search( isc->scopes, id ); + if ( x <= isc->scopes[0].mid && isc->scopes[x].mid == id ) { + if ( !isc->scopes[x].mval.mv_data ) { + /* This node is in scope, add parent chain to scope */ + int i; + for ( i = 1; i <= isc->sctmp[0].mid; i++ ) { + rc = mdb_id2l_insert( isc->scopes, &isc->sctmp[i] ); + if ( rc ) + break; + } + /* check id again since inserts may have changed its position */ + if ( isc->scopes[x].mid != id ) + x = mdb_id2l_search( isc->scopes, id ); + isc->nscope = x; + return MDB_SUCCESS; + } + data = isc->scopes[x].mval; + rc = 1; + } + if ( op->ors_scope == LDAP_SCOPE_ONELEVEL ) + break; + } + return MDB_SUCCESS; +} + +/* See if ID is a child of any of the scopes, + * return MDB_KEYEXIST if so. + */ +int +mdb_idscopechk( + Operation *op, + IdScopes *isc ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_val key, data; + ID id, prev; + char *ptr; + int rc = 0; + unsigned int x; + + key.mv_size = sizeof(ID); + + if ( !isc->mc ) { + rc = mdb_cursor_open( isc->mt, mdb->mi_dn2id, &isc->mc ); + if ( rc ) return rc; + } + + id = isc->id; + + while (id) { + if ( !rc ) { + key.mv_data = &id; + rc = mdb_cursor_get( isc->mc, &key, &data, MDB_SET ); + if ( rc ) + return rc; + } + + ptr = data.mv_data; + ptr += data.mv_size - sizeof(ID); + prev = id; + memcpy( &id, ptr, sizeof(ID) ); + /* If we didn't advance, some parent is missing */ + if ( id == prev ) + return MDB_NOTFOUND; + + x = mdb_id2l_search( isc->scopes, id ); + if ( x <= isc->scopes[0].mid && isc->scopes[x].mid == id ) + return MDB_KEYEXIST; + } + return MDB_SUCCESS; +} + +int +mdb_dn2id_walk( + Operation *op, + IdScopes *isc +) +{ + MDB_val key, data; + diskNode *d; + char *ptr; + int rc, n; + ID nsubs; + + if ( !isc->numrdns ) { + key.mv_data = &isc->id; + key.mv_size = sizeof(ID); + rc = mdb_cursor_get( isc->mc, &key, &data, MDB_SET ); + isc->scopes[0].mid = isc->id; + isc->numrdns++; + isc->nscope = 0; + /* skip base if not a subtree walk */ + if ( isc->oscope == LDAP_SCOPE_SUBTREE || + isc->oscope == LDAP_SCOPE_BASE ) + return rc; + } + if ( isc->oscope == LDAP_SCOPE_BASE ) + return MDB_NOTFOUND; + + for (;;) { + /* Get next sibling */ + rc = mdb_cursor_get( isc->mc, &key, &data, MDB_NEXT_DUP ); + if ( !rc ) { + ptr = (char *)data.mv_data + data.mv_size - 2*sizeof(ID); + d = data.mv_data; + memcpy( &isc->id, ptr, sizeof(ID)); + + /* If we're pushing down, see if there's any children to find */ + if ( isc->nscope ) { + ptr += sizeof(ID); + memcpy( &nsubs, ptr, sizeof(ID)); + /* No children, go to next sibling */ + if ( nsubs < 2 ) + continue; + } + n = isc->numrdns; + isc->scopes[n].mid = isc->id; + n--; + isc->nrdns[n].bv_len = ((d->nrdnlen[0] & 0x7f) << 8) | d->nrdnlen[1]; + isc->nrdns[n].bv_val = d->nrdn; + isc->rdns[n].bv_val = d->nrdn+isc->nrdns[n].bv_len+1; + isc->rdns[n].bv_len = data.mv_size - sizeof(diskNode) - isc->nrdns[n].bv_len - sizeof(ID); + /* return this ID to caller */ + if ( !isc->nscope ) + break; + + /* push down to child */ + key.mv_data = &isc->id; + mdb_cursor_get( isc->mc, &key, &data, MDB_SET ); + isc->nscope = 0; + isc->numrdns++; + continue; + + } else if ( rc == MDB_NOTFOUND ) { + if ( !isc->nscope && isc->oscope != LDAP_SCOPE_ONELEVEL ) { + /* reset to first dup */ + mdb_cursor_get( isc->mc, &key, NULL, MDB_GET_CURRENT ); + mdb_cursor_get( isc->mc, &key, &data, MDB_SET ); + isc->nscope = 1; + continue; + } else { + isc->numrdns--; + /* stack is empty? */ + if ( !isc->numrdns ) + break; + /* pop up to prev node */ + n = isc->numrdns - 1; + key.mv_data = &isc->scopes[n].mid; + key.mv_size = sizeof(ID); + data.mv_data = isc->nrdns[n].bv_val - 2; + data.mv_size = 1; /* just needs to be non-zero, mdb_dup_compare doesn't care */ + mdb_cursor_get( isc->mc, &key, &data, MDB_GET_BOTH ); + continue; + } + } else { + break; + } + } + return rc; +} + +/* restore the nrdn/rdn pointers after a txn reset */ +void mdb_dn2id_wrestore ( + Operation *op, + IdScopes *isc +) +{ + MDB_val key, data; + diskNode *d; + int rc, n, nrlen; + char *ptr; + + /* We only need to restore up to the n-1th element, + * the nth element will be replaced anyway + */ + key.mv_size = sizeof(ID); + for ( n=0; n<isc->numrdns-1; n++ ) { + key.mv_data = &isc->scopes[n+1].mid; + rc = mdb_cursor_get( isc->mc, &key, &data, MDB_SET ); + if ( rc ) + continue; + /* we can't use this data directly since its nrlen + * is missing the high bit setting, so copy it and + * set it properly. we just copy enough to satisfy + * mdb_dup_compare. + */ + d = data.mv_data; + nrlen = ((d->nrdnlen[0] & 0x7f) << 8) | d->nrdnlen[1]; + ptr = op->o_tmpalloc( nrlen+2, op->o_tmpmemctx ); + memcpy( ptr, data.mv_data, nrlen+2 ); + key.mv_data = &isc->scopes[n].mid; + data.mv_data = ptr; + data.mv_size = 1; + *ptr |= 0x80; + mdb_cursor_get( isc->mc, &key, &data, MDB_GET_BOTH ); + op->o_tmpfree( ptr, op->o_tmpmemctx ); + + /* now we're back to where we wanted to be */ + d = data.mv_data; + isc->nrdns[n].bv_val = d->nrdn; + isc->rdns[n].bv_val = d->nrdn+isc->nrdns[n].bv_len+1; + } +} diff --git a/servers/slapd/back-mdb/extended.c b/servers/slapd/back-mdb/extended.c new file mode 100644 index 0000000..82074a7 --- /dev/null +++ b/servers/slapd/back-mdb/extended.c @@ -0,0 +1,54 @@ +/* extended.c - mdb backend extended routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" +#include "lber_pvt.h" + +static struct exop { + struct berval *oid; + BI_op_extended *extended; +} exop_table[] = { + { NULL, NULL } +}; + +int +mdb_extended( Operation *op, SlapReply *rs ) +/* struct berval *reqoid, + struct berval *reqdata, + char **rspoid, + struct berval **rspdata, + LDAPControl *** rspctrls, + const char** text, + BerVarray *refs +) */ +{ + int i; + + for( i=0; exop_table[i].extended != NULL; i++ ) { + if( ber_bvcmp( exop_table[i].oid, &op->oq_extended.rs_reqoid ) == 0 ) { + return (exop_table[i].extended)( op, rs ); + } + } + + rs->sr_text = "not supported within naming context"; + return rs->sr_err = LDAP_UNWILLING_TO_PERFORM; +} + diff --git a/servers/slapd/back-mdb/filterindex.c b/servers/slapd/back-mdb/filterindex.c new file mode 100644 index 0000000..6983f16 --- /dev/null +++ b/servers/slapd/back-mdb/filterindex.c @@ -0,0 +1,1173 @@ +/* filterindex.c - generate the list of candidate entries from a filter */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" +#include "idl.h" +#ifdef LDAP_COMP_MATCH +#include <component.h> +#endif + +static int presence_candidates( + Operation *op, + MDB_txn *rtxn, + AttributeDescription *desc, + ID *ids ); + +static int equality_candidates( + Operation *op, + MDB_txn *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp ); +static int inequality_candidates( + Operation *op, + MDB_txn *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp, + int gtorlt ); +static int approx_candidates( + Operation *op, + MDB_txn *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp ); +static int substring_candidates( + Operation *op, + MDB_txn *rtxn, + SubstringsAssertion *sub, + ID *ids, + ID *tmp ); + +static int list_candidates( + Operation *op, + MDB_txn *rtxn, + Filter *flist, + int ftype, + ID *ids, + ID *tmp, + ID *stack ); + +static int +ext_candidates( + Operation *op, + MDB_txn *rtxn, + MatchingRuleAssertion *mra, + ID *ids, + ID *tmp, + ID *stack); + +#ifdef LDAP_COMP_MATCH +static int +comp_candidates ( + Operation *op, + MDB_txn *rtxn, + MatchingRuleAssertion *mra, + ComponentFilter *f, + ID *ids, + ID *tmp, + ID *stack); + +static int +ava_comp_candidates ( + Operation *op, + MDB_txn *rtxn, + AttributeAssertion *ava, + AttributeAliasing *aa, + ID *ids, + ID *tmp, + ID *stack); +#endif + +int +mdb_filter_candidates( + Operation *op, + MDB_txn *rtxn, + Filter *f, + ID *ids, + ID *tmp, + ID *stack ) +{ + int rc = 0; +#ifdef LDAP_COMP_MATCH + AttributeAliasing *aa; +#endif + Debug( LDAP_DEBUG_FILTER, "=> mdb_filter_candidates\n", 0, 0, 0 ); + + if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) { + MDB_IDL_ZERO( ids ); + goto out; + } + + switch ( f->f_choice ) { + case SLAPD_FILTER_COMPUTED: + switch( f->f_result ) { + case SLAPD_COMPARE_UNDEFINED: + /* This technically is not the same as FALSE, but it + * certainly will produce no matches. + */ + /* FALL THRU */ + case LDAP_COMPARE_FALSE: + MDB_IDL_ZERO( ids ); + break; + case LDAP_COMPARE_TRUE: + MDB_IDL_ALL( ids ); + break; + case LDAP_SUCCESS: + /* this is a pre-computed scope, leave it alone */ + break; + } + break; + case LDAP_FILTER_PRESENT: + Debug( LDAP_DEBUG_FILTER, "\tPRESENT\n", 0, 0, 0 ); + rc = presence_candidates( op, rtxn, f->f_desc, ids ); + break; + + case LDAP_FILTER_EQUALITY: + Debug( LDAP_DEBUG_FILTER, "\tEQUALITY\n", 0, 0, 0 ); +#ifdef LDAP_COMP_MATCH + if ( is_aliased_attribute && ( aa = is_aliased_attribute ( f->f_ava->aa_desc ) ) ) { + rc = ava_comp_candidates ( op, rtxn, f->f_ava, aa, ids, tmp, stack ); + } + else +#endif + { + rc = equality_candidates( op, rtxn, f->f_ava, ids, tmp ); + } + break; + + case LDAP_FILTER_APPROX: + Debug( LDAP_DEBUG_FILTER, "\tAPPROX\n", 0, 0, 0 ); + rc = approx_candidates( op, rtxn, f->f_ava, ids, tmp ); + break; + + case LDAP_FILTER_SUBSTRINGS: + Debug( LDAP_DEBUG_FILTER, "\tSUBSTRINGS\n", 0, 0, 0 ); + rc = substring_candidates( op, rtxn, f->f_sub, ids, tmp ); + break; + + case LDAP_FILTER_GE: + /* if no GE index, use pres */ + Debug( LDAP_DEBUG_FILTER, "\tGE\n", 0, 0, 0 ); + if( f->f_ava->aa_desc->ad_type->sat_ordering && + ( f->f_ava->aa_desc->ad_type->sat_ordering->smr_usage & SLAP_MR_ORDERED_INDEX ) ) + rc = inequality_candidates( op, rtxn, f->f_ava, ids, tmp, LDAP_FILTER_GE ); + else + rc = presence_candidates( op, rtxn, f->f_ava->aa_desc, ids ); + break; + + case LDAP_FILTER_LE: + /* if no LE index, use pres */ + Debug( LDAP_DEBUG_FILTER, "\tLE\n", 0, 0, 0 ); + if( f->f_ava->aa_desc->ad_type->sat_ordering && + ( f->f_ava->aa_desc->ad_type->sat_ordering->smr_usage & SLAP_MR_ORDERED_INDEX ) ) + rc = inequality_candidates( op, rtxn, f->f_ava, ids, tmp, LDAP_FILTER_LE ); + else + rc = presence_candidates( op, rtxn, f->f_ava->aa_desc, ids ); + break; + + case LDAP_FILTER_NOT: + /* no indexing to support NOT filters */ + Debug( LDAP_DEBUG_FILTER, "\tNOT\n", 0, 0, 0 ); + MDB_IDL_ALL( ids ); + break; + + case LDAP_FILTER_AND: + Debug( LDAP_DEBUG_FILTER, "\tAND\n", 0, 0, 0 ); + rc = list_candidates( op, rtxn, + f->f_and, LDAP_FILTER_AND, ids, tmp, stack ); + break; + + case LDAP_FILTER_OR: + Debug( LDAP_DEBUG_FILTER, "\tOR\n", 0, 0, 0 ); + rc = list_candidates( op, rtxn, + f->f_or, LDAP_FILTER_OR, ids, tmp, stack ); + break; + case LDAP_FILTER_EXT: + Debug( LDAP_DEBUG_FILTER, "\tEXT\n", 0, 0, 0 ); + rc = ext_candidates( op, rtxn, f->f_mra, ids, tmp, stack ); + break; + default: + Debug( LDAP_DEBUG_FILTER, "\tUNKNOWN %lu\n", + (unsigned long) f->f_choice, 0, 0 ); + /* Must not return NULL, otherwise extended filters break */ + MDB_IDL_ALL( ids ); + } + if ( ids[2] == NOID && MDB_IDL_IS_RANGE( ids )) { + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + ID last; + + if ( mdb->mi_nextid ) { + last = mdb->mi_nextid; + } else { + MDB_cursor *mc; + MDB_val key; + + last = 0; + rc = mdb_cursor_open( rtxn, mdb->mi_id2entry, &mc ); + if ( !rc ) { + rc = mdb_cursor_get( mc, &key, NULL, MDB_LAST ); + if ( !rc ) + memcpy( &last, key.mv_data, sizeof( last )); + mdb_cursor_close( mc ); + } + } + if ( last ) { + ids[2] = last; + } else { + MDB_IDL_ZERO( ids ); + } + } + +out: + Debug( LDAP_DEBUG_FILTER, + "<= mdb_filter_candidates: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) MDB_IDL_FIRST( ids ), + (long) MDB_IDL_LAST( ids ) ); + + return rc; +} + +#ifdef LDAP_COMP_MATCH +static int +comp_list_candidates( + Operation *op, + MDB_txn *rtxn, + MatchingRuleAssertion* mra, + ComponentFilter *flist, + int ftype, + ID *ids, + ID *tmp, + ID *save ) +{ + int rc = 0; + ComponentFilter *f; + + Debug( LDAP_DEBUG_FILTER, "=> comp_list_candidates 0x%x\n", ftype, 0, 0 ); + for ( f = flist; f != NULL; f = f->cf_next ) { + /* ignore precomputed scopes */ + if ( f->cf_choice == SLAPD_FILTER_COMPUTED && + f->cf_result == LDAP_SUCCESS ) { + continue; + } + MDB_IDL_ZERO( save ); + rc = comp_candidates( op, rtxn, mra, f, save, tmp, save+MDB_IDL_UM_SIZE ); + + if ( rc != 0 ) { + if ( ftype == LDAP_COMP_FILTER_AND ) { + rc = 0; + continue; + } + break; + } + + if ( ftype == LDAP_COMP_FILTER_AND ) { + if ( f == flist ) { + MDB_IDL_CPY( ids, save ); + } else { + mdb_idl_intersection( ids, save ); + } + if( MDB_IDL_IS_ZERO( ids ) ) + break; + } else { + if ( f == flist ) { + MDB_IDL_CPY( ids, save ); + } else { + mdb_idl_union( ids, save ); + } + } + } + + if( rc == LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_FILTER, + "<= comp_list_candidates: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) MDB_IDL_FIRST(ids), + (long) MDB_IDL_LAST(ids) ); + + } else { + Debug( LDAP_DEBUG_FILTER, + "<= comp_list_candidates: undefined rc=%d\n", + rc, 0, 0 ); + } + + return rc; +} + +static int +comp_equality_candidates ( + Operation *op, + MDB_txn *rtxn, + MatchingRuleAssertion *mra, + ComponentAssertion *ca, + ID *ids, + ID *tmp, + ID *stack) +{ + MDB_dbi dbi; + int i; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + struct berval *keys = NULL; + MatchingRule *mr = mra->ma_rule; + Syntax *sat_syntax; + ComponentReference* cr_list, *cr; + AttrInfo *ai; + + MDB_IDL_ALL( ids ); + + if ( !ca->ca_comp_ref ) + return 0; + + ai = mdb_attr_mask( op->o_bd->be_private, mra->ma_desc ); + if( ai ) { + cr_list = ai->ai_cr; + } + else { + return 0; + } + /* find a component reference to be indexed */ + sat_syntax = ca->ca_ma_rule->smr_syntax; + for ( cr = cr_list ; cr ; cr = cr->cr_next ) { + if ( cr->cr_string.bv_len == ca->ca_comp_ref->cr_string.bv_len && + strncmp( cr->cr_string.bv_val, ca->ca_comp_ref->cr_string.bv_val,cr->cr_string.bv_len ) == 0 ) + break; + } + + if ( !cr ) + return 0; + + rc = mdb_index_param( op->o_bd, mra->ma_desc, LDAP_FILTER_EQUALITY, + &dbi, &mask, &prefix ); + + if( rc != LDAP_SUCCESS ) { + return 0; + } + + if( !mr ) { + return 0; + } + + if( !mr->smr_filter ) { + return 0; + } + + rc = (ca->ca_ma_rule->smr_filter)( + LDAP_FILTER_EQUALITY, + cr->cr_indexmask, + sat_syntax, + ca->ca_ma_rule, + &prefix, + &ca->ca_ma_value, + &keys, op->o_tmpmemctx ); + + if( rc != LDAP_SUCCESS ) { + return 0; + } + + if( keys == NULL ) { + return 0; + } + for ( i= 0; keys[i].bv_val != NULL; i++ ) { + rc = mdb_key_read( op->o_bd, rtxn, dbi, &keys[i], tmp, NULL, 0 ); + + if( rc == MDB_NOTFOUND ) { + MDB_IDL_ZERO( ids ); + rc = 0; + break; + } else if( rc != LDAP_SUCCESS ) { + break; + } + + if( MDB_IDL_IS_ZERO( tmp ) ) { + MDB_IDL_ZERO( ids ); + break; + } + + if ( i == 0 ) { + MDB_IDL_CPY( ids, tmp ); + } else { + mdb_idl_intersection( ids, tmp ); + } + + if( MDB_IDL_IS_ZERO( ids ) ) + break; + } + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, + "<= comp_equality_candidates: id=%ld, first=%ld, last=%ld\n", + (long) ids[0], + (long) MDB_IDL_FIRST(ids), + (long) MDB_IDL_LAST(ids) ); + return( rc ); +} + +static int +ava_comp_candidates ( + Operation *op, + MDB_txn *rtxn, + AttributeAssertion *ava, + AttributeAliasing *aa, + ID *ids, + ID *tmp, + ID *stack ) +{ + MatchingRuleAssertion mra; + + mra.ma_rule = ava->aa_desc->ad_type->sat_equality; + if ( !mra.ma_rule ) { + MDB_IDL_ALL( ids ); + return 0; + } + mra.ma_desc = aa->aa_aliased_ad; + mra.ma_rule = ava->aa_desc->ad_type->sat_equality; + + return comp_candidates ( op, rtxn, &mra, ava->aa_cf, ids, tmp, stack ); +} + +static int +comp_candidates ( + Operation *op, + MDB_txn *rtxn, + MatchingRuleAssertion *mra, + ComponentFilter *f, + ID *ids, + ID *tmp, + ID *stack) +{ + int rc; + + if ( !f ) return LDAP_PROTOCOL_ERROR; + + Debug( LDAP_DEBUG_FILTER, "comp_candidates\n", 0, 0, 0 ); + switch ( f->cf_choice ) { + case SLAPD_FILTER_COMPUTED: + rc = f->cf_result; + break; + case LDAP_COMP_FILTER_AND: + rc = comp_list_candidates( op, rtxn, mra, f->cf_and, LDAP_COMP_FILTER_AND, ids, tmp, stack ); + break; + case LDAP_COMP_FILTER_OR: + rc = comp_list_candidates( op, rtxn, mra, f->cf_or, LDAP_COMP_FILTER_OR, ids, tmp, stack ); + break; + case LDAP_COMP_FILTER_NOT: + /* No component indexing supported for NOT filter */ + Debug( LDAP_DEBUG_FILTER, "\tComponent NOT\n", 0, 0, 0 ); + MDB_IDL_ALL( ids ); + rc = LDAP_PROTOCOL_ERROR; + break; + case LDAP_COMP_FILTER_ITEM: + rc = comp_equality_candidates( op, rtxn, mra, f->cf_ca, ids, tmp, stack ); + break; + default: + MDB_IDL_ALL( ids ); + rc = LDAP_PROTOCOL_ERROR; + } + + return( rc ); +} +#endif + +static int +ext_candidates( + Operation *op, + MDB_txn *rtxn, + MatchingRuleAssertion *mra, + ID *ids, + ID *tmp, + ID *stack) +{ +#ifdef LDAP_COMP_MATCH + /* + * Currently Only Component Indexing for componentFilterMatch is supported + * Indexing for an extensible filter is not supported yet + */ + if ( mra->ma_cf ) { + return comp_candidates ( op, rtxn, mra, mra->ma_cf, ids, tmp, stack); + } +#endif + if ( mra->ma_desc == slap_schema.si_ad_entryDN ) { + int rc; + ID id; + + MDB_IDL_ZERO( ids ); + if ( mra->ma_rule == slap_schema.si_mr_distinguishedNameMatch ) { +base: + rc = mdb_dn2id( op, rtxn, NULL, &mra->ma_value, &id, NULL, NULL, NULL ); + if ( rc == MDB_SUCCESS ) { + mdb_idl_insert( ids, id ); + } + return 0; + } else if ( mra->ma_rule && mra->ma_rule->smr_match == + dnRelativeMatch && dnIsSuffix( &mra->ma_value, + op->o_bd->be_nsuffix )) { + int scope; + if ( mra->ma_rule == slap_schema.si_mr_dnSuperiorMatch ) { + mdb_dn2sups( op, rtxn, &mra->ma_value, ids ); + return 0; + } + if ( mra->ma_rule == slap_schema.si_mr_dnSubtreeMatch ) + scope = LDAP_SCOPE_SUBTREE; + else if ( mra->ma_rule == slap_schema.si_mr_dnOneLevelMatch ) + scope = LDAP_SCOPE_ONELEVEL; + else if ( mra->ma_rule == slap_schema.si_mr_dnSubordinateMatch ) + scope = LDAP_SCOPE_SUBORDINATE; + else + goto base; /* scope = LDAP_SCOPE_BASE; */ +#if 0 + if ( scope > LDAP_SCOPE_BASE ) { + ei = NULL; + rc = mdb_cache_find_ndn( op, rtxn, &mra->ma_value, &ei ); + if ( ei ) + mdb_cache_entryinfo_unlock( ei ); + if ( rc == LDAP_SUCCESS ) { + int sc = op->ors_scope; + op->ors_scope = scope; + rc = mdb_dn2idl( op, rtxn, &mra->ma_value, ei, ids, + stack ); + op->ors_scope = sc; + } + return 0; + } +#endif + } + } + + MDB_IDL_ALL( ids ); + return 0; +} + +static int +list_candidates( + Operation *op, + MDB_txn *rtxn, + Filter *flist, + int ftype, + ID *ids, + ID *tmp, + ID *save ) +{ + int rc = 0; + Filter *f; + + Debug( LDAP_DEBUG_FILTER, "=> mdb_list_candidates 0x%x\n", ftype, 0, 0 ); + for ( f = flist; f != NULL; f = f->f_next ) { + /* ignore precomputed scopes */ + if ( f->f_choice == SLAPD_FILTER_COMPUTED && + f->f_result == LDAP_SUCCESS ) { + continue; + } + MDB_IDL_ZERO( save ); + rc = mdb_filter_candidates( op, rtxn, f, save, tmp, + save+MDB_IDL_UM_SIZE ); + + if ( rc != 0 ) { + if ( ftype == LDAP_FILTER_AND ) { + rc = 0; + continue; + } + break; + } + + + if ( ftype == LDAP_FILTER_AND ) { + if ( f == flist ) { + MDB_IDL_CPY( ids, save ); + } else { + mdb_idl_intersection( ids, save ); + } + if( MDB_IDL_IS_ZERO( ids ) ) + break; + } else { + if ( f == flist ) { + MDB_IDL_CPY( ids, save ); + } else { + mdb_idl_union( ids, save ); + } + } + } + + if( rc == LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_FILTER, + "<= mdb_list_candidates: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) MDB_IDL_FIRST(ids), + (long) MDB_IDL_LAST(ids) ); + + } else { + Debug( LDAP_DEBUG_FILTER, + "<= mdb_list_candidates: undefined rc=%d\n", + rc, 0, 0 ); + } + + return rc; +} + +static int +presence_candidates( + Operation *op, + MDB_txn *rtxn, + AttributeDescription *desc, + ID *ids ) +{ + MDB_dbi dbi; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + + Debug( LDAP_DEBUG_TRACE, "=> mdb_presence_candidates (%s)\n", + desc->ad_cname.bv_val, 0, 0 ); + + MDB_IDL_ALL( ids ); + + if( desc == slap_schema.si_ad_objectClass ) { + return 0; + } + + rc = mdb_index_param( op->o_bd, desc, LDAP_FILTER_PRESENT, + &dbi, &mask, &prefix ); + + if( rc == LDAP_INAPPROPRIATE_MATCHING ) { + /* not indexed */ + Debug( LDAP_DEBUG_TRACE, + "<= mdb_presence_candidates: (%s) not indexed\n", + desc->ad_cname.bv_val, 0, 0 ); + return 0; + } + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_presence_candidates: (%s) index_param " + "returned=%d\n", + desc->ad_cname.bv_val, rc, 0 ); + return 0; + } + + if( prefix.bv_val == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_presence_candidates: (%s) no prefix\n", + desc->ad_cname.bv_val, 0, 0 ); + return -1; + } + + rc = mdb_key_read( op->o_bd, rtxn, dbi, &prefix, ids, NULL, 0 ); + + if( rc == MDB_NOTFOUND ) { + MDB_IDL_ZERO( ids ); + rc = 0; + } else if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_presense_candidates: (%s) " + "key read failed (%d)\n", + desc->ad_cname.bv_val, rc, 0 ); + goto done; + } + + Debug(LDAP_DEBUG_TRACE, + "<= mdb_presence_candidates: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) MDB_IDL_FIRST(ids), + (long) MDB_IDL_LAST(ids) ); + +done: + return rc; +} + +static int +equality_candidates( + Operation *op, + MDB_txn *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp ) +{ + MDB_dbi dbi; + int i; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + struct berval *keys = NULL; + MatchingRule *mr; + + Debug( LDAP_DEBUG_TRACE, "=> mdb_equality_candidates (%s)\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + + if ( ava->aa_desc == slap_schema.si_ad_entryDN ) { + ID id; + rc = mdb_dn2id( op, rtxn, NULL, &ava->aa_value, &id, NULL, NULL, NULL ); + if ( rc == LDAP_SUCCESS ) { + /* exactly one ID can match */ + ids[0] = 1; + ids[1] = id; + } + if ( rc == MDB_NOTFOUND ) { + MDB_IDL_ZERO( ids ); + rc = 0; + } + return rc; + } + + MDB_IDL_ALL( ids ); + + rc = mdb_index_param( op->o_bd, ava->aa_desc, LDAP_FILTER_EQUALITY, + &dbi, &mask, &prefix ); + + if ( rc == LDAP_INAPPROPRIATE_MATCHING ) { + Debug( LDAP_DEBUG_ANY, + "<= mdb_equality_candidates: (%s) not indexed\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + return 0; + } + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "<= mdb_equality_candidates: (%s) " + "index_param failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc, 0 ); + return 0; + } + + mr = ava->aa_desc->ad_type->sat_equality; + if( !mr ) { + return 0; + } + + if( !mr->smr_filter ) { + return 0; + } + + rc = (mr->smr_filter)( + LDAP_FILTER_EQUALITY, + mask, + ava->aa_desc->ad_type->sat_syntax, + mr, + &prefix, + &ava->aa_value, + &keys, op->o_tmpmemctx ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_equality_candidates: (%s, %s) " + "MR filter failed (%d)\n", + prefix.bv_val, ava->aa_desc->ad_cname.bv_val, rc ); + return 0; + } + + if( keys == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_equality_candidates: (%s) no keys\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + return 0; + } + + for ( i= 0; keys[i].bv_val != NULL; i++ ) { + rc = mdb_key_read( op->o_bd, rtxn, dbi, &keys[i], tmp, NULL, 0 ); + + if( rc == MDB_NOTFOUND ) { + MDB_IDL_ZERO( ids ); + rc = 0; + break; + } else if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_equality_candidates: (%s) " + "key read failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc, 0 ); + break; + } + + if( MDB_IDL_IS_ZERO( tmp ) ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_equality_candidates: (%s) NULL\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + MDB_IDL_ZERO( ids ); + break; + } + + if ( i == 0 ) { + MDB_IDL_CPY( ids, tmp ); + } else { + mdb_idl_intersection( ids, tmp ); + } + + if( MDB_IDL_IS_ZERO( ids ) ) + break; + } + + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, + "<= mdb_equality_candidates: id=%ld, first=%ld, last=%ld\n", + (long) ids[0], + (long) MDB_IDL_FIRST(ids), + (long) MDB_IDL_LAST(ids) ); + return( rc ); +} + + +static int +approx_candidates( + Operation *op, + MDB_txn *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp ) +{ + MDB_dbi dbi; + int i; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + struct berval *keys = NULL; + MatchingRule *mr; + + Debug( LDAP_DEBUG_TRACE, "=> mdb_approx_candidates (%s)\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + + MDB_IDL_ALL( ids ); + + rc = mdb_index_param( op->o_bd, ava->aa_desc, LDAP_FILTER_APPROX, + &dbi, &mask, &prefix ); + + if ( rc == LDAP_INAPPROPRIATE_MATCHING ) { + Debug( LDAP_DEBUG_ANY, + "<= mdb_approx_candidates: (%s) not indexed\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + return 0; + } + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "<= mdb_approx_candidates: (%s) " + "index_param failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc, 0 ); + return 0; + } + + mr = ava->aa_desc->ad_type->sat_approx; + if( !mr ) { + /* no approx matching rule, try equality matching rule */ + mr = ava->aa_desc->ad_type->sat_equality; + } + + if( !mr ) { + return 0; + } + + if( !mr->smr_filter ) { + return 0; + } + + rc = (mr->smr_filter)( + LDAP_FILTER_APPROX, + mask, + ava->aa_desc->ad_type->sat_syntax, + mr, + &prefix, + &ava->aa_value, + &keys, op->o_tmpmemctx ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_approx_candidates: (%s, %s) " + "MR filter failed (%d)\n", + prefix.bv_val, ava->aa_desc->ad_cname.bv_val, rc ); + return 0; + } + + if( keys == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_approx_candidates: (%s) no keys (%s)\n", + prefix.bv_val, ava->aa_desc->ad_cname.bv_val, 0 ); + return 0; + } + + for ( i= 0; keys[i].bv_val != NULL; i++ ) { + rc = mdb_key_read( op->o_bd, rtxn, dbi, &keys[i], tmp, NULL, 0 ); + + if( rc == MDB_NOTFOUND ) { + MDB_IDL_ZERO( ids ); + rc = 0; + break; + } else if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_approx_candidates: (%s) " + "key read failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc, 0 ); + break; + } + + if( MDB_IDL_IS_ZERO( tmp ) ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_approx_candidates: (%s) NULL\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + MDB_IDL_ZERO( ids ); + break; + } + + if ( i == 0 ) { + MDB_IDL_CPY( ids, tmp ); + } else { + mdb_idl_intersection( ids, tmp ); + } + + if( MDB_IDL_IS_ZERO( ids ) ) + break; + } + + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, "<= mdb_approx_candidates %ld, first=%ld, last=%ld\n", + (long) ids[0], + (long) MDB_IDL_FIRST(ids), + (long) MDB_IDL_LAST(ids) ); + return( rc ); +} + +static int +substring_candidates( + Operation *op, + MDB_txn *rtxn, + SubstringsAssertion *sub, + ID *ids, + ID *tmp ) +{ + MDB_dbi dbi; + int i; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + struct berval *keys = NULL; + MatchingRule *mr; + + Debug( LDAP_DEBUG_TRACE, "=> mdb_substring_candidates (%s)\n", + sub->sa_desc->ad_cname.bv_val, 0, 0 ); + + MDB_IDL_ALL( ids ); + + rc = mdb_index_param( op->o_bd, sub->sa_desc, LDAP_FILTER_SUBSTRINGS, + &dbi, &mask, &prefix ); + + if ( rc == LDAP_INAPPROPRIATE_MATCHING ) { + Debug( LDAP_DEBUG_ANY, + "<= mdb_substring_candidates: (%s) not indexed\n", + sub->sa_desc->ad_cname.bv_val, 0, 0 ); + return 0; + } + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "<= mdb_substring_candidates: (%s) " + "index_param failed (%d)\n", + sub->sa_desc->ad_cname.bv_val, rc, 0 ); + return 0; + } + + mr = sub->sa_desc->ad_type->sat_substr; + + if( !mr ) { + return 0; + } + + if( !mr->smr_filter ) { + return 0; + } + + rc = (mr->smr_filter)( + LDAP_FILTER_SUBSTRINGS, + mask, + sub->sa_desc->ad_type->sat_syntax, + mr, + &prefix, + sub, + &keys, op->o_tmpmemctx ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_substring_candidates: (%s) " + "MR filter failed (%d)\n", + sub->sa_desc->ad_cname.bv_val, rc, 0 ); + return 0; + } + + if( keys == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_substring_candidates: (0x%04lx) no keys (%s)\n", + mask, sub->sa_desc->ad_cname.bv_val, 0 ); + return 0; + } + + for ( i= 0; keys[i].bv_val != NULL; i++ ) { + rc = mdb_key_read( op->o_bd, rtxn, dbi, &keys[i], tmp, NULL, 0 ); + + if( rc == MDB_NOTFOUND ) { + MDB_IDL_ZERO( ids ); + rc = 0; + break; + } else if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_substring_candidates: (%s) " + "key read failed (%d)\n", + sub->sa_desc->ad_cname.bv_val, rc, 0 ); + break; + } + + if( MDB_IDL_IS_ZERO( tmp ) ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_substring_candidates: (%s) NULL\n", + sub->sa_desc->ad_cname.bv_val, 0, 0 ); + MDB_IDL_ZERO( ids ); + break; + } + + if ( i == 0 ) { + MDB_IDL_CPY( ids, tmp ); + } else { + mdb_idl_intersection( ids, tmp ); + } + + if( MDB_IDL_IS_ZERO( ids ) ) + break; + } + + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, "<= mdb_substring_candidates: %ld, first=%ld, last=%ld\n", + (long) ids[0], + (long) MDB_IDL_FIRST(ids), + (long) MDB_IDL_LAST(ids) ); + return( rc ); +} + +static int +inequality_candidates( + Operation *op, + MDB_txn *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp, + int gtorlt ) +{ + MDB_dbi dbi; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + struct berval *keys = NULL; + MatchingRule *mr; + MDB_cursor *cursor = NULL; + + Debug( LDAP_DEBUG_TRACE, "=> mdb_inequality_candidates (%s)\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + + MDB_IDL_ALL( ids ); + + rc = mdb_index_param( op->o_bd, ava->aa_desc, LDAP_FILTER_EQUALITY, + &dbi, &mask, &prefix ); + + if ( rc == LDAP_INAPPROPRIATE_MATCHING ) { + Debug( LDAP_DEBUG_ANY, + "<= mdb_inequality_candidates: (%s) not indexed\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + return 0; + } + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "<= mdb_inequality_candidates: (%s) " + "index_param failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc, 0 ); + return 0; + } + + mr = ava->aa_desc->ad_type->sat_equality; + if( !mr ) { + return 0; + } + + if( !mr->smr_filter ) { + return 0; + } + + rc = (mr->smr_filter)( + LDAP_FILTER_EQUALITY, + mask, + ava->aa_desc->ad_type->sat_syntax, + mr, + &prefix, + &ava->aa_value, + &keys, op->o_tmpmemctx ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_inequality_candidates: (%s, %s) " + "MR filter failed (%d)\n", + prefix.bv_val, ava->aa_desc->ad_cname.bv_val, rc ); + return 0; + } + + if( keys == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_inequality_candidates: (%s) no keys\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + return 0; + } + + MDB_IDL_ZERO( ids ); + while(1) { + rc = mdb_key_read( op->o_bd, rtxn, dbi, &keys[0], tmp, &cursor, gtorlt ); + + if( rc == MDB_NOTFOUND ) { + rc = 0; + break; + } else if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_inequality_candidates: (%s) " + "key read failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc, 0 ); + break; + } + + if( MDB_IDL_IS_ZERO( tmp ) ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_inequality_candidates: (%s) NULL\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + break; + } + + mdb_idl_union( ids, tmp ); + + if( op->ors_limit && op->ors_limit->lms_s_unchecked != -1 && + MDB_IDL_N( ids ) >= (unsigned) op->ors_limit->lms_s_unchecked ) { + mdb_cursor_close( cursor ); + break; + } + } + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, + "<= mdb_inequality_candidates: id=%ld, first=%ld, last=%ld\n", + (long) ids[0], + (long) MDB_IDL_FIRST(ids), + (long) MDB_IDL_LAST(ids) ); + return( rc ); +} diff --git a/servers/slapd/back-mdb/id2entry.c b/servers/slapd/back-mdb/id2entry.c new file mode 100644 index 0000000..25d3492 --- /dev/null +++ b/servers/slapd/back-mdb/id2entry.c @@ -0,0 +1,767 @@ +/* id2entry.c - routines to deal with the id2entry database */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/errno.h> + +#include "back-mdb.h" + +typedef struct Ecount { + ber_len_t len; + int nattrs; + int nvals; + int offset; +} Ecount; + +static int mdb_entry_partsize(struct mdb_info *mdb, MDB_txn *txn, Entry *e, + Ecount *eh); +static int mdb_entry_encode(Operation *op, Entry *e, MDB_val *data, + Ecount *ec); +static Entry *mdb_entry_alloc( Operation *op, int nattrs, int nvals ); + +#define ADD_FLAGS (MDB_NOOVERWRITE|MDB_APPEND) + +static int mdb_id2entry_put( + Operation *op, + MDB_txn *txn, + MDB_cursor *mc, + Entry *e, + int flag ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + Ecount ec; + MDB_val key, data; + int rc, prev_ads = mdb->mi_numads; + + /* We only store rdns, and they go in the dn2id database. */ + + key.mv_data = &e->e_id; + key.mv_size = sizeof(ID); + + rc = mdb_entry_partsize( mdb, txn, e, &ec ); + if (rc) { + rc = LDAP_OTHER; + goto fail; + } + + flag |= MDB_RESERVE; + + if (e->e_id < mdb->mi_nextid) + flag &= ~MDB_APPEND; + +again: + data.mv_size = ec.len; + if ( mc ) + rc = mdb_cursor_put( mc, &key, &data, flag ); + else + rc = mdb_put( txn, mdb->mi_id2entry, &key, &data, flag ); + if (rc == MDB_SUCCESS) { + rc = mdb_entry_encode( op, e, &data, &ec ); + if( rc != LDAP_SUCCESS ) + goto fail; + } + if (rc) { + /* Was there a hole from slapadd? */ + if ( (flag & MDB_NOOVERWRITE) && data.mv_size == 0 ) { + flag &= ~ADD_FLAGS; + goto again; + } + Debug( LDAP_DEBUG_ANY, + "mdb_id2entry_put: mdb_put failed: %s(%d) \"%s\"\n", + mdb_strerror(rc), rc, + e->e_nname.bv_val ); + if ( rc != MDB_KEYEXIST ) + rc = LDAP_OTHER; + } +fail: + if (rc) { + mdb_ad_unwind( mdb, prev_ads ); + } + return rc; +} + +/* + * This routine adds (or updates) an entry on disk. + * The cache should be already be updated. + */ + + +int mdb_id2entry_add( + Operation *op, + MDB_txn *txn, + MDB_cursor *mc, + Entry *e ) +{ + return mdb_id2entry_put(op, txn, mc, e, ADD_FLAGS); +} + +int mdb_id2entry_update( + Operation *op, + MDB_txn *txn, + MDB_cursor *mc, + Entry *e ) +{ + return mdb_id2entry_put(op, txn, mc, e, 0); +} + +int mdb_id2edata( + Operation *op, + MDB_cursor *mc, + ID id, + MDB_val *data ) +{ + MDB_val key; + int rc; + + key.mv_data = &id; + key.mv_size = sizeof(ID); + + /* fetch it */ + rc = mdb_cursor_get( mc, &key, data, MDB_SET ); + /* stubs from missing parents - DB is actually invalid */ + if ( rc == MDB_SUCCESS && !data->mv_size ) + rc = MDB_NOTFOUND; + return rc; +} + +int mdb_id2entry( + Operation *op, + MDB_cursor *mc, + ID id, + Entry **e ) +{ + MDB_val key, data; + int rc = 0; + + *e = NULL; + + key.mv_data = &id; + key.mv_size = sizeof(ID); + + /* fetch it */ + rc = mdb_cursor_get( mc, &key, &data, MDB_SET ); + if ( rc == MDB_NOTFOUND ) { + /* Looking for root entry on an empty-dn suffix? */ + if ( !id && BER_BVISEMPTY( &op->o_bd->be_nsuffix[0] )) { + struct berval gluebv = BER_BVC("glue"); + Entry *r = mdb_entry_alloc(op, 2, 4); + Attribute *a = r->e_attrs; + struct berval *bptr; + + r->e_id = 0; + r->e_ocflags = SLAP_OC_GLUE|SLAP_OC__END; + bptr = a->a_vals; + a->a_flags = SLAP_ATTR_DONT_FREE_DATA | SLAP_ATTR_DONT_FREE_VALS; + a->a_desc = slap_schema.si_ad_objectClass; + a->a_nvals = a->a_vals; + a->a_numvals = 1; + *bptr++ = gluebv; + BER_BVZERO(bptr); + bptr++; + a->a_next = a+1; + a = a->a_next; + a->a_flags = SLAP_ATTR_DONT_FREE_DATA | SLAP_ATTR_DONT_FREE_VALS; + a->a_desc = slap_schema.si_ad_structuralObjectClass; + a->a_vals = bptr; + a->a_nvals = a->a_vals; + a->a_numvals = 1; + *bptr++ = gluebv; + BER_BVZERO(bptr); + a->a_next = NULL; + *e = r; + return MDB_SUCCESS; + } + } + /* stubs from missing parents - DB is actually invalid */ + if ( rc == MDB_SUCCESS && !data.mv_size ) + rc = MDB_NOTFOUND; + if ( rc ) return rc; + + rc = mdb_entry_decode( op, mdb_cursor_txn( mc ), &data, e ); + if ( rc ) return rc; + + (*e)->e_id = id; + (*e)->e_name.bv_val = NULL; + (*e)->e_nname.bv_val = NULL; + + return rc; +} + +int mdb_id2entry_delete( + BackendDB *be, + MDB_txn *tid, + Entry *e ) +{ + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + MDB_dbi dbi = mdb->mi_id2entry; + MDB_val key; + int rc; + + key.mv_data = &e->e_id; + key.mv_size = sizeof(ID); + + /* delete from database */ + rc = mdb_del( tid, dbi, &key, NULL ); + + return rc; +} + +static Entry * mdb_entry_alloc( + Operation *op, + int nattrs, + int nvals ) +{ + Entry *e = op->o_tmpalloc( sizeof(Entry) + + nattrs * sizeof(Attribute) + + nvals * sizeof(struct berval), op->o_tmpmemctx ); + BER_BVZERO(&e->e_bv); + e->e_private = e; + if (nattrs) { + e->e_attrs = (Attribute *)(e+1); + e->e_attrs->a_vals = (struct berval *)(e->e_attrs+nattrs); + } else { + e->e_attrs = NULL; + } + + return e; +} + +int mdb_entry_return( + Operation *op, + Entry *e +) +{ + if ( !e ) + return 0; + if ( e->e_private ) { + if ( op->o_hdr ) { + op->o_tmpfree( e->e_nname.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( e->e_name.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( e, op->o_tmpmemctx ); + } else { + ch_free( e->e_nname.bv_val ); + ch_free( e->e_name.bv_val ); + ch_free( e ); + } + } else { + entry_free( e ); + } + return 0; +} + +int mdb_entry_release( + Operation *op, + Entry *e, + int rw ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + struct mdb_op_info *moi = NULL; + + /* slapMode : SLAP_SERVER_MODE, SLAP_TOOL_MODE, + SLAP_TRUNCATE_MODE, SLAP_UNDEFINED_MODE */ + + int release = 1; + if ( slapMode & SLAP_SERVER_MODE ) { + OpExtra *oex; + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + release = 0; + if ( oex->oe_key == mdb ) { + mdb_entry_return( op, e ); + moi = (mdb_op_info *)oex; + /* If it was setup by entry_get we should probably free it */ + if ( moi->moi_flag & MOI_FREEIT ) { + moi->moi_ref--; + if ( moi->moi_ref < 1 ) { + mdb_txn_reset( moi->moi_txn ); + moi->moi_ref = 0; + LDAP_SLIST_REMOVE( &op->o_extra, &moi->moi_oe, OpExtra, oe_next ); + op->o_tmpfree( moi, op->o_tmpmemctx ); + } + } + break; + } + } + } + + if (release) + mdb_entry_return( op, e ); + + return 0; +} + +/* return LDAP_SUCCESS IFF we can retrieve the specified entry. + */ +int mdb_entry_get( + Operation *op, + struct berval *ndn, + ObjectClass *oc, + AttributeDescription *at, + int rw, + Entry **ent ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + struct mdb_op_info *moi = NULL; + MDB_txn *txn = NULL; + Entry *e = NULL; + int rc; + const char *at_name = at ? at->ad_cname.bv_val : "(null)"; + + Debug( LDAP_DEBUG_ARGS, + "=> mdb_entry_get: ndn: \"%s\"\n", ndn->bv_val, 0, 0 ); + Debug( LDAP_DEBUG_ARGS, + "=> mdb_entry_get: oc: \"%s\", at: \"%s\"\n", + oc ? oc->soc_cname.bv_val : "(null)", at_name, 0); + + rc = mdb_opinfo_get( op, mdb, rw == 0, &moi ); + if ( rc ) + return LDAP_OTHER; + txn = moi->moi_txn; + + /* can we find entry */ + rc = mdb_dn2entry( op, txn, NULL, ndn, &e, NULL, 0 ); + switch( rc ) { + case MDB_NOTFOUND: + case 0: + break; + default: + return (rc != LDAP_BUSY) ? LDAP_OTHER : LDAP_BUSY; + } + if (e == NULL) { + Debug( LDAP_DEBUG_ACL, + "=> mdb_entry_get: cannot find entry: \"%s\"\n", + ndn->bv_val, 0, 0 ); + rc = LDAP_NO_SUCH_OBJECT; + goto return_results; + } + + Debug( LDAP_DEBUG_ACL, + "=> mdb_entry_get: found entry: \"%s\"\n", + ndn->bv_val, 0, 0 ); + + if ( oc && !is_entry_objectclass( e, oc, 0 )) { + Debug( LDAP_DEBUG_ACL, + "<= mdb_entry_get: failed to find objectClass %s\n", + oc->soc_cname.bv_val, 0, 0 ); + rc = LDAP_NO_SUCH_ATTRIBUTE; + goto return_results; + } + + /* NOTE: attr_find() or attrs_find()? */ + if ( at && attr_find( e->e_attrs, at ) == NULL ) { + Debug( LDAP_DEBUG_ACL, + "<= mdb_entry_get: failed to find attribute %s\n", + at->ad_cname.bv_val, 0, 0 ); + rc = LDAP_NO_SUCH_ATTRIBUTE; + goto return_results; + } + +return_results: + if( rc != LDAP_SUCCESS ) { + /* free entry */ + mdb_entry_release( op, e, rw ); + } else { + *ent = e; + } + + Debug( LDAP_DEBUG_TRACE, + "mdb_entry_get: rc=%d\n", + rc, 0, 0 ); + return(rc); +} + +static void +mdb_reader_free( void *key, void *data ) +{ + MDB_txn *txn = data; + + if ( txn ) mdb_txn_abort( txn ); +} + +/* free up any keys used by the main thread */ +void +mdb_reader_flush( MDB_env *env ) +{ + void *data; + void *ctx = ldap_pvt_thread_pool_context(); + + if ( !ldap_pvt_thread_pool_getkey( ctx, env, &data, NULL ) ) { + ldap_pvt_thread_pool_setkey( ctx, env, NULL, 0, NULL, NULL ); + mdb_reader_free( env, data ); + } +} + +extern MDB_txn *mdb_tool_txn; + +int +mdb_opinfo_get( Operation *op, struct mdb_info *mdb, int rdonly, mdb_op_info **moip ) +{ + int rc, renew = 0; + void *data; + void *ctx; + mdb_op_info *moi = NULL; + OpExtra *oex; + + assert( op != NULL ); + + if ( !mdb || !moip ) return -1; + + /* If no op was provided, try to find the ctx anyway... */ + if ( op ) { + ctx = op->o_threadctx; + } else { + ctx = ldap_pvt_thread_pool_context(); + } + + if ( op ) { + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == mdb ) break; + } + moi = (mdb_op_info *)oex; + } + + if ( !moi ) { + moi = *moip; + + if ( !moi ) { + if ( op ) { + moi = op->o_tmpalloc(sizeof(struct mdb_op_info),op->o_tmpmemctx); + } else { + moi = ch_malloc(sizeof(mdb_op_info)); + } + moi->moi_flag = MOI_FREEIT; + *moip = moi; + } + LDAP_SLIST_INSERT_HEAD( &op->o_extra, &moi->moi_oe, oe_next ); + moi->moi_oe.oe_key = mdb; + moi->moi_ref = 0; + moi->moi_txn = NULL; + } + + if ( !rdonly ) { + /* This op started as a reader, but now wants to write. */ + if ( moi->moi_flag & MOI_READER ) { + moi = *moip; + LDAP_SLIST_INSERT_HEAD( &op->o_extra, &moi->moi_oe, oe_next ); + } else { + /* This op is continuing an existing write txn */ + *moip = moi; + } + moi->moi_ref++; + if ( !moi->moi_txn ) { + if (( slapMode & SLAP_TOOL_MODE ) && mdb_tool_txn ) { + moi->moi_txn = mdb_tool_txn; + } else { + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, 0, &moi->moi_txn ); + if (rc) { + Debug( LDAP_DEBUG_ANY, "mdb_opinfo_get: err %s(%d)\n", + mdb_strerror(rc), rc, 0 ); + } + return rc; + } + } + return 0; + } + + /* OK, this is a reader */ + if ( !moi->moi_txn ) { + if (( slapMode & SLAP_TOOL_MODE ) && mdb_tool_txn ) { + moi->moi_txn = mdb_tool_txn; + goto ok; + } + if ( !ctx ) { + /* Shouldn't happen unless we're single-threaded */ + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, MDB_RDONLY, &moi->moi_txn ); + if (rc) { + Debug( LDAP_DEBUG_ANY, "mdb_opinfo_get: err %s(%d)\n", + mdb_strerror(rc), rc, 0 ); + } + return rc; + } + if ( ldap_pvt_thread_pool_getkey( ctx, mdb->mi_dbenv, &data, NULL ) ) { + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, MDB_RDONLY, &moi->moi_txn ); + if (rc) { + Debug( LDAP_DEBUG_ANY, "mdb_opinfo_get: err %s(%d)\n", + mdb_strerror(rc), rc, 0 ); + return rc; + } + data = moi->moi_txn; + if ( ( rc = ldap_pvt_thread_pool_setkey( ctx, mdb->mi_dbenv, + data, mdb_reader_free, NULL, NULL ) ) ) { + mdb_txn_abort( moi->moi_txn ); + moi->moi_txn = NULL; + Debug( LDAP_DEBUG_ANY, "mdb_opinfo_get: thread_pool_setkey failed err (%d)\n", + rc, 0, 0 ); + return rc; + } + } else { + moi->moi_txn = data; + renew = 1; + } + moi->moi_flag |= MOI_READER; + } +ok: + if ( moi->moi_ref < 1 ) { + moi->moi_ref = 0; + } + if ( renew ) { + rc = mdb_txn_renew( moi->moi_txn ); + assert(!rc); + } + moi->moi_ref++; + if ( *moip != moi ) + *moip = moi; + + return 0; +} + +/* Count up the sizes of the components of an entry */ +static int mdb_entry_partsize(struct mdb_info *mdb, MDB_txn *txn, Entry *e, + Ecount *eh) +{ + ber_len_t len; + int i, nat = 0, nval = 0, nnval = 0; + Attribute *a; + + len = 4*sizeof(int); /* nattrs, nvals, ocflags, offset */ + for (a=e->e_attrs; a; a=a->a_next) { + /* For AttributeDesc, we only store the attr index */ + nat++; + if (a->a_desc->ad_index >= MDB_MAXADS) { + Debug( LDAP_DEBUG_ANY, "mdb_entry_partsize: too many AttributeDescriptions used\n", + 0, 0, 0 ); + return LDAP_OTHER; + } + if (!mdb->mi_adxs[a->a_desc->ad_index]) { + int rc = mdb_ad_get(mdb, txn, a->a_desc); + if (rc) + return rc; + } + len += 2*sizeof(int); /* AD index, numvals */ + nval += a->a_numvals + 1; /* empty berval at end */ + for (i=0; i<a->a_numvals; i++) { + len += a->a_vals[i].bv_len + 1 + sizeof(int); /* len */ + } + if (a->a_nvals != a->a_vals) { + nval += a->a_numvals + 1; + nnval++; + for (i=0; i<a->a_numvals; i++) { + len += a->a_nvals[i].bv_len + 1 + sizeof(int);; + } + } + } + /* padding */ + len = (len + sizeof(ID)-1) & ~(sizeof(ID)-1); + eh->len = len; + eh->nattrs = nat; + eh->nvals = nval; + eh->offset = nat + nval - nnval; + return 0; +} + +#define HIGH_BIT (1U<<(sizeof(unsigned int)*CHAR_BIT-1)) + +/* Flatten an Entry into a buffer. The buffer starts with the count of the + * number of attributes in the entry, the total number of values in the + * entry, and the e_ocflags. It then contains a list of integers for each + * attribute. For each attribute the first integer gives the index of the + * matching AttributeDescription, followed by the number of values in the + * attribute. If the high bit of the attr index is set, the attribute's + * values are already sorted. + * If the high bit of numvals is set, the attribute also has normalized + * values present. (Note - a_numvals is an unsigned int, so this means + * it's possible to receive an attribute that we can't encode due to size + * overflow. In practice, this should not be an issue.) Then the length + * of each value is listed. If there are normalized values, their lengths + * come next. This continues for each attribute. After all of the lengths + * for the last attribute, the actual values are copied, with a NUL + * terminator after each value. The buffer is padded to the sizeof(ID). + * The entire buffer size is precomputed so that a single malloc can be + * performed. + */ +static int mdb_entry_encode(Operation *op, Entry *e, MDB_val *data, Ecount *eh) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + ber_len_t i; + Attribute *a; + unsigned char *ptr; + unsigned int *lp, l; + + Debug( LDAP_DEBUG_TRACE, "=> mdb_entry_encode(0x%08lx): %s\n", + (long) e->e_id, e->e_dn, 0 ); + + /* make sure e->e_ocflags is set */ + if (is_entry_referral(e)) + ; /* empty */ + + lp = (unsigned int *)data->mv_data; + *lp++ = eh->nattrs; + *lp++ = eh->nvals; + *lp++ = (unsigned int)e->e_ocflags; + *lp++ = eh->offset; + ptr = (unsigned char *)(lp + eh->offset); + + for (a=e->e_attrs; a; a=a->a_next) { + if (!a->a_desc->ad_index) + return LDAP_UNDEFINED_TYPE; + l = mdb->mi_adxs[a->a_desc->ad_index]; + if (a->a_flags & SLAP_ATTR_SORTED_VALS) + l |= HIGH_BIT; + *lp++ = l; + l = a->a_numvals; + if (a->a_nvals != a->a_vals) + l |= HIGH_BIT; + *lp++ = l; + if (a->a_vals) { + for (i=0; a->a_vals[i].bv_val; i++); + assert( i == a->a_numvals ); + for (i=0; i<a->a_numvals; i++) { + *lp++ = a->a_vals[i].bv_len; + memcpy(ptr, a->a_vals[i].bv_val, + a->a_vals[i].bv_len); + ptr += a->a_vals[i].bv_len; + *ptr++ = '\0'; + } + if (a->a_nvals != a->a_vals) { + for (i=0; i<a->a_numvals; i++) { + *lp++ = a->a_nvals[i].bv_len; + memcpy(ptr, a->a_nvals[i].bv_val, + a->a_nvals[i].bv_len); + ptr += a->a_nvals[i].bv_len; + *ptr++ = '\0'; + } + } + } + } + + Debug( LDAP_DEBUG_TRACE, "<= mdb_entry_encode(0x%08lx): %s\n", + (long) e->e_id, e->e_dn, 0 ); + + return 0; +} + +/* Retrieve an Entry that was stored using entry_encode above. + * + * Note: everything is stored in a single contiguous block, so + * you can not free individual attributes or names from this + * structure. Attempting to do so will likely corrupt memory. + */ + +int mdb_entry_decode(Operation *op, MDB_txn *txn, MDB_val *data, Entry **e) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + int i, j, nattrs, nvals; + int rc; + Attribute *a; + Entry *x; + const char *text; + unsigned int *lp = (unsigned int *)data->mv_data; + unsigned char *ptr; + BerVarray bptr; + + Debug( LDAP_DEBUG_TRACE, + "=> mdb_entry_decode:\n", + 0, 0, 0 ); + + nattrs = *lp++; + nvals = *lp++; + x = mdb_entry_alloc(op, nattrs, nvals); + x->e_ocflags = *lp++; + if (!nvals) { + goto done; + } + a = x->e_attrs; + bptr = a->a_vals; + i = *lp++; + ptr = (unsigned char *)(lp + i); + + for (;nattrs>0; nattrs--) { + int have_nval = 0; + a->a_flags = SLAP_ATTR_DONT_FREE_DATA | SLAP_ATTR_DONT_FREE_VALS; + i = *lp++; + if (i & HIGH_BIT) { + i ^= HIGH_BIT; + a->a_flags |= SLAP_ATTR_SORTED_VALS; + } + if (i > mdb->mi_numads) { + rc = mdb_ad_read(mdb, txn); + if (rc) + return rc; + if (i > mdb->mi_numads) { + Debug( LDAP_DEBUG_ANY, + "mdb_entry_decode: attribute index %d not recognized\n", + i, 0, 0 ); + return LDAP_OTHER; + } + } + a->a_desc = mdb->mi_ads[i]; + a->a_numvals = *lp++; + if (a->a_numvals & HIGH_BIT) { + a->a_numvals ^= HIGH_BIT; + have_nval = 1; + } + a->a_vals = bptr; + for (i=0; i<a->a_numvals; i++) { + bptr->bv_len = *lp++;; + bptr->bv_val = (char *)ptr; + ptr += bptr->bv_len+1; + bptr++; + } + bptr->bv_val = NULL; + bptr->bv_len = 0; + bptr++; + + if (have_nval) { + a->a_nvals = bptr; + for (i=0; i<a->a_numvals; i++) { + bptr->bv_len = *lp++; + bptr->bv_val = (char *)ptr; + ptr += bptr->bv_len+1; + bptr++; + } + bptr->bv_val = NULL; + bptr->bv_len = 0; + bptr++; + } else { + a->a_nvals = a->a_vals; + } + /* FIXME: This is redundant once a sorted entry is saved into the DB */ + if (( a->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL ) + && !(a->a_flags & SLAP_ATTR_SORTED_VALS)) { + rc = slap_sort_vals( (Modifications *)a, &text, &j, NULL ); + if ( rc == LDAP_SUCCESS ) { + a->a_flags |= SLAP_ATTR_SORTED_VALS; + } else if ( rc == LDAP_TYPE_OR_VALUE_EXISTS ) { + /* should never happen */ + Debug( LDAP_DEBUG_ANY, + "mdb_entry_decode: attributeType %s value #%d provided more than once\n", + a->a_desc->ad_cname.bv_val, j, 0 ); + return rc; + } + } + a->a_next = a+1; + a = a->a_next; + } + a[-1].a_next = NULL; +done: + + Debug(LDAP_DEBUG_TRACE, "<= mdb_entry_decode\n", + 0, 0, 0 ); + *e = x; + return 0; +} diff --git a/servers/slapd/back-mdb/idl.c b/servers/slapd/back-mdb/idl.c new file mode 100644 index 0000000..3b4a194 --- /dev/null +++ b/servers/slapd/back-mdb/idl.c @@ -0,0 +1,1276 @@ +/* idl.c - ldap id list handling routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" +#include "idl.h" + +#define IDL_MAX(x,y) ( (x) > (y) ? (x) : (y) ) +#define IDL_MIN(x,y) ( (x) < (y) ? (x) : (y) ) +#define IDL_CMP(x,y) ( (x) < (y) ? -1 : (x) > (y) ) + +#if IDL_DEBUG > 0 +static void idl_check( ID *ids ) +{ + if( MDB_IDL_IS_RANGE( ids ) ) { + assert( MDB_IDL_RANGE_FIRST(ids) <= MDB_IDL_RANGE_LAST(ids) ); + } else { + ID i; + for( i=1; i < ids[0]; i++ ) { + assert( ids[i+1] > ids[i] ); + } + } +} + +#if IDL_DEBUG > 1 +static void idl_dump( ID *ids ) +{ + if( MDB_IDL_IS_RANGE( ids ) ) { + Debug( LDAP_DEBUG_ANY, + "IDL: range ( %ld - %ld )\n", + (long) MDB_IDL_RANGE_FIRST( ids ), + (long) MDB_IDL_RANGE_LAST( ids ) ); + + } else { + ID i; + Debug( LDAP_DEBUG_ANY, "IDL: size %ld", (long) ids[0], 0, 0 ); + + for( i=1; i<=ids[0]; i++ ) { + if( i % 16 == 1 ) { + Debug( LDAP_DEBUG_ANY, "\n", 0, 0, 0 ); + } + Debug( LDAP_DEBUG_ANY, " %02lx", (long) ids[i], 0, 0 ); + } + + Debug( LDAP_DEBUG_ANY, "\n", 0, 0, 0 ); + } + + idl_check( ids ); +} +#endif /* IDL_DEBUG > 1 */ +#endif /* IDL_DEBUG > 0 */ + +unsigned mdb_idl_search( ID *ids, ID id ) +{ +#define IDL_BINARY_SEARCH 1 +#ifdef IDL_BINARY_SEARCH + /* + * binary search of id in ids + * if found, returns position of id + * if not found, returns first postion greater than id + */ + unsigned base = 0; + unsigned cursor = 1; + int val = 0; + unsigned n = ids[0]; + +#if IDL_DEBUG > 0 + idl_check( ids ); +#endif + + while( 0 < n ) { + unsigned pivot = n >> 1; + cursor = base + pivot + 1; + val = IDL_CMP( id, ids[cursor] ); + + if( val < 0 ) { + n = pivot; + + } else if ( val > 0 ) { + base = cursor; + n -= pivot + 1; + + } else { + return cursor; + } + } + + if( val > 0 ) { + ++cursor; + } + return cursor; + +#else + /* (reverse) linear search */ + int i; + +#if IDL_DEBUG > 0 + idl_check( ids ); +#endif + + for( i=ids[0]; i; i-- ) { + if( id > ids[i] ) { + break; + } + } + + return i+1; +#endif +} + +int mdb_idl_insert( ID *ids, ID id ) +{ + unsigned x; + +#if IDL_DEBUG > 1 + Debug( LDAP_DEBUG_ANY, "insert: %04lx at %d\n", (long) id, x, 0 ); + idl_dump( ids ); +#elif IDL_DEBUG > 0 + idl_check( ids ); +#endif + + if (MDB_IDL_IS_RANGE( ids )) { + /* if already in range, treat as a dup */ + if (id >= MDB_IDL_RANGE_FIRST(ids) && id <= MDB_IDL_RANGE_LAST(ids)) + return -1; + if (id < MDB_IDL_RANGE_FIRST(ids)) + ids[1] = id; + else if (id > MDB_IDL_RANGE_LAST(ids)) + ids[2] = id; + return 0; + } + + x = mdb_idl_search( ids, id ); + assert( x > 0 ); + + if( x < 1 ) { + /* internal error */ + return -2; + } + + if ( x <= ids[0] && ids[x] == id ) { + /* duplicate */ + return -1; + } + + if ( ++ids[0] >= MDB_IDL_DB_MAX ) { + if( id < ids[1] ) { + ids[1] = id; + ids[2] = ids[ids[0]-1]; + } else if ( ids[ids[0]-1] < id ) { + ids[2] = id; + } else { + ids[2] = ids[ids[0]-1]; + } + ids[0] = NOID; + + } else { + /* insert id */ + AC_MEMCPY( &ids[x+1], &ids[x], (ids[0]-x) * sizeof(ID) ); + ids[x] = id; + } + +#if IDL_DEBUG > 1 + idl_dump( ids ); +#elif IDL_DEBUG > 0 + idl_check( ids ); +#endif + + return 0; +} + +static int mdb_idl_delete( ID *ids, ID id ) +{ + unsigned x; + +#if IDL_DEBUG > 1 + Debug( LDAP_DEBUG_ANY, "delete: %04lx at %d\n", (long) id, x, 0 ); + idl_dump( ids ); +#elif IDL_DEBUG > 0 + idl_check( ids ); +#endif + + if (MDB_IDL_IS_RANGE( ids )) { + /* If deleting a range boundary, adjust */ + if ( ids[1] == id ) + ids[1]++; + else if ( ids[2] == id ) + ids[2]--; + /* deleting from inside a range is a no-op */ + + /* If the range has collapsed, re-adjust */ + if ( ids[1] > ids[2] ) + ids[0] = 0; + else if ( ids[1] == ids[2] ) + ids[1] = 1; + return 0; + } + + x = mdb_idl_search( ids, id ); + assert( x > 0 ); + + if( x <= 0 ) { + /* internal error */ + return -2; + } + + if( x > ids[0] || ids[x] != id ) { + /* not found */ + return -1; + + } else if ( --ids[0] == 0 ) { + if( x != 1 ) { + return -3; + } + + } else { + AC_MEMCPY( &ids[x], &ids[x+1], (1+ids[0]-x) * sizeof(ID) ); + } + +#if IDL_DEBUG > 1 + idl_dump( ids ); +#elif IDL_DEBUG > 0 + idl_check( ids ); +#endif + + return 0; +} + +static char * +mdb_show_key( + char *buf, + void *val, + size_t len ) +{ + if ( len == 4 /* LUTIL_HASH_BYTES */ ) { + unsigned char *c = val; + sprintf( buf, "[%02x%02x%02x%02x]", c[0], c[1], c[2], c[3] ); + return buf; + } else { + return val; + } +} + +int +mdb_idl_fetch_key( + BackendDB *be, + MDB_txn *txn, + MDB_dbi dbi, + MDB_val *key, + ID *ids, + MDB_cursor **saved_cursor, + int get_flag ) +{ + MDB_val data, key2, *kptr; + MDB_cursor *cursor; + ID *i; + size_t len; + int rc; + MDB_cursor_op opflag; + + char keybuf[16]; + + Debug( LDAP_DEBUG_ARGS, + "mdb_idl_fetch_key: %s\n", + mdb_show_key( keybuf, key->mv_data, key->mv_size ), 0, 0 ); + + assert( ids != NULL ); + + if ( saved_cursor && *saved_cursor ) { + opflag = MDB_NEXT; + } else if ( get_flag == LDAP_FILTER_GE ) { + opflag = MDB_SET_RANGE; + } else if ( get_flag == LDAP_FILTER_LE ) { + opflag = MDB_FIRST; + } else { + opflag = MDB_SET; + } + + /* If we're not reusing an existing cursor, get a new one */ + if( opflag != MDB_NEXT ) { + rc = mdb_cursor_open( txn, dbi, &cursor ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "=> mdb_idl_fetch_key: " + "cursor failed: %s (%d)\n", mdb_strerror(rc), rc, 0 ); + return rc; + } + } else { + cursor = *saved_cursor; + } + + /* If this is a LE lookup, save original key so we can determine + * when to stop. If this is a GE lookup, save the key since it + * will be overwritten. + */ + if ( get_flag == LDAP_FILTER_LE || get_flag == LDAP_FILTER_GE ) { + key2.mv_data = keybuf; + key2.mv_size = key->mv_size; + AC_MEMCPY( keybuf, key->mv_data, key->mv_size ); + kptr = &key2; + } else { + kptr = key; + } + len = key->mv_size; + rc = mdb_cursor_get( cursor, kptr, &data, opflag ); + + /* skip presence key on range inequality lookups */ + while (rc == 0 && kptr->mv_size != len) { + rc = mdb_cursor_get( cursor, kptr, &data, MDB_NEXT_NODUP ); + } + /* If we're doing a LE compare and the new key is greater than + * our search key, we're done + */ + if (rc == 0 && get_flag == LDAP_FILTER_LE && memcmp( kptr->mv_data, + key->mv_data, key->mv_size ) > 0 ) { + rc = MDB_NOTFOUND; + } + if (rc == 0) { + i = ids+1; + rc = mdb_cursor_get( cursor, key, &data, MDB_GET_MULTIPLE ); + while (rc == 0) { + memcpy( i, data.mv_data, data.mv_size ); + i += data.mv_size / sizeof(ID); + rc = mdb_cursor_get( cursor, key, &data, MDB_NEXT_MULTIPLE ); + } + if ( rc == MDB_NOTFOUND ) rc = 0; + ids[0] = i - &ids[1]; + /* On disk, a range is denoted by 0 in the first element */ + if (ids[1] == 0) { + if (ids[0] != MDB_IDL_RANGE_SIZE) { + Debug( LDAP_DEBUG_ANY, "=> mdb_idl_fetch_key: " + "range size mismatch: expected %d, got %ld\n", + MDB_IDL_RANGE_SIZE, ids[0], 0 ); + mdb_cursor_close( cursor ); + return -1; + } + MDB_IDL_RANGE( ids, ids[2], ids[3] ); + } + data.mv_size = MDB_IDL_SIZEOF(ids); + } + + if ( saved_cursor && rc == 0 ) { + if ( !*saved_cursor ) + *saved_cursor = cursor; + } + else + mdb_cursor_close( cursor ); + + if( rc == MDB_NOTFOUND ) { + return rc; + + } else if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "=> mdb_idl_fetch_key: " + "get failed: %s (%d)\n", + mdb_strerror(rc), rc, 0 ); + return rc; + + } else if ( data.mv_size == 0 || data.mv_size % sizeof( ID ) ) { + /* size not multiple of ID size */ + Debug( LDAP_DEBUG_ANY, "=> mdb_idl_fetch_key: " + "odd size: expected %ld multiple, got %ld\n", + (long) sizeof( ID ), (long) data.mv_size, 0 ); + return -1; + + } else if ( data.mv_size != MDB_IDL_SIZEOF(ids) ) { + /* size mismatch */ + Debug( LDAP_DEBUG_ANY, "=> mdb_idl_fetch_key: " + "get size mismatch: expected %ld, got %ld\n", + (long) ((1 + ids[0]) * sizeof( ID )), (long) data.mv_size, 0 ); + return -1; + } + + return rc; +} + +int +mdb_idl_insert_keys( + BackendDB *be, + MDB_cursor *cursor, + struct berval *keys, + ID id ) +{ + struct mdb_info *mdb = be->be_private; + MDB_val key, data; + ID lo, hi, *i; + char *err; + int rc = 0, k; + unsigned int flag = MDB_NODUPDATA; +#ifndef MISALIGNED_OK + int kbuf[2]; +#endif + + { + char buf[16]; + Debug( LDAP_DEBUG_ARGS, + "mdb_idl_insert_keys: %lx %s\n", + (long) id, mdb_show_key( buf, keys->bv_val, keys->bv_len ), 0 ); + } + + assert( id != NOID ); + +#ifndef MISALIGNED_OK + if (keys[0].bv_len & ALIGNER) + kbuf[1] = 0; +#endif + for ( k=0; keys[k].bv_val; k++ ) { + /* Fetch the first data item for this key, to see if it + * exists and if it's a range. + */ +#ifndef MISALIGNED_OK + if (keys[k].bv_len & ALIGNER) { + key.mv_size = sizeof(kbuf); + key.mv_data = kbuf; + memcpy(key.mv_data, keys[k].bv_val, keys[k].bv_len); + } else +#endif + { + key.mv_size = keys[k].bv_len; + key.mv_data = keys[k].bv_val; + } + rc = mdb_cursor_get( cursor, &key, &data, MDB_SET ); + err = "c_get"; + if ( rc == 0 ) { + i = data.mv_data; + memcpy(&lo, data.mv_data, sizeof(ID)); + if ( lo != 0 ) { + /* not a range, count the number of items */ + size_t count; + rc = mdb_cursor_count( cursor, &count ); + if ( rc != 0 ) { + err = "c_count"; + goto fail; + } + if ( count >= MDB_IDL_DB_MAX ) { + /* No room, convert to a range */ + lo = *i; + rc = mdb_cursor_get( cursor, &key, &data, MDB_LAST_DUP ); + if ( rc != 0 && rc != MDB_NOTFOUND ) { + err = "c_get last_dup"; + goto fail; + } + i = data.mv_data; + hi = *i; + /* Update hi/lo if needed */ + if ( id < lo ) { + lo = id; + } else if ( id > hi ) { + hi = id; + } + /* delete the old key */ + rc = mdb_cursor_del( cursor, MDB_NODUPDATA ); + if ( rc != 0 ) { + err = "c_del dups"; + goto fail; + } + /* Store the range */ + data.mv_size = sizeof(ID); + data.mv_data = &id; + id = 0; + rc = mdb_cursor_put( cursor, &key, &data, 0 ); + if ( rc != 0 ) { + err = "c_put range"; + goto fail; + } + id = lo; + rc = mdb_cursor_put( cursor, &key, &data, 0 ); + if ( rc != 0 ) { + err = "c_put lo"; + goto fail; + } + id = hi; + rc = mdb_cursor_put( cursor, &key, &data, 0 ); + if ( rc != 0 ) { + err = "c_put hi"; + goto fail; + } + } else { + /* There's room, just store it */ + if (id == mdb->mi_nextid) + flag |= MDB_APPENDDUP; + goto put1; + } + } else { + /* It's a range, see if we need to rewrite + * the boundaries + */ + lo = i[1]; + hi = i[2]; + if ( id < lo || id > hi ) { + /* position on lo */ + rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT_DUP ); + if ( rc != 0 ) { + err = "c_get lo"; + goto fail; + } + if ( id > hi ) { + /* position on hi */ + rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT_DUP ); + if ( rc != 0 ) { + err = "c_get hi"; + goto fail; + } + } + data.mv_size = sizeof(ID); + data.mv_data = &id; + /* Replace the current lo/hi */ + rc = mdb_cursor_put( cursor, &key, &data, MDB_CURRENT ); + if ( rc != 0 ) { + err = "c_put lo/hi"; + goto fail; + } + } + } + } else if ( rc == MDB_NOTFOUND ) { + flag &= ~MDB_APPENDDUP; +put1: data.mv_data = &id; + data.mv_size = sizeof(ID); + rc = mdb_cursor_put( cursor, &key, &data, flag ); + /* Don't worry if it's already there */ + if ( rc == MDB_KEYEXIST ) + rc = 0; + if ( rc ) { + err = "c_put id"; + goto fail; + } + } else { + /* initial c_get failed, nothing was done */ +fail: + Debug( LDAP_DEBUG_ANY, "=> mdb_idl_insert_keys: " + "%s failed: %s (%d)\n", err, mdb_strerror(rc), rc ); + break; + } + } + return rc; +} + +int +mdb_idl_delete_keys( + BackendDB *be, + MDB_cursor *cursor, + struct berval *keys, + ID id ) +{ + int rc = 0, k; + MDB_val key, data; + ID lo, hi, tmp, *i; + char *err; +#ifndef MISALIGNED_OK + int kbuf[2]; +#endif + + { + char buf[16]; + Debug( LDAP_DEBUG_ARGS, + "mdb_idl_delete_keys: %lx %s\n", + (long) id, mdb_show_key( buf, keys->bv_val, keys->bv_len ), 0 ); + } + assert( id != NOID ); + +#ifndef MISALIGNED_OK + if (keys[0].bv_len & ALIGNER) + kbuf[1] = 0; +#endif + for ( k=0; keys[k].bv_val; k++) { + /* Fetch the first data item for this key, to see if it + * exists and if it's a range. + */ +#ifndef MISALIGNED_OK + if (keys[k].bv_len & ALIGNER) { + key.mv_size = sizeof(kbuf); + key.mv_data = kbuf; + memcpy(key.mv_data, keys[k].bv_val, keys[k].bv_len); + } else +#endif + { + key.mv_size = keys[k].bv_len; + key.mv_data = keys[k].bv_val; + } + rc = mdb_cursor_get( cursor, &key, &data, MDB_SET ); + err = "c_get"; + if ( rc == 0 ) { + memcpy( &tmp, data.mv_data, sizeof(ID) ); + i = data.mv_data; + if ( tmp != 0 ) { + /* Not a range, just delete it */ + data.mv_data = &id; + rc = mdb_cursor_get( cursor, &key, &data, MDB_GET_BOTH ); + if ( rc != 0 ) { + err = "c_get id"; + goto fail; + } + rc = mdb_cursor_del( cursor, 0 ); + if ( rc != 0 ) { + err = "c_del id"; + goto fail; + } + } else { + /* It's a range, see if we need to rewrite + * the boundaries + */ + lo = i[1]; + hi = i[2]; + if ( id == lo || id == hi ) { + ID lo2 = lo, hi2 = hi; + if ( id == lo ) { + lo2++; + } else if ( id == hi ) { + hi2--; + } + if ( lo2 >= hi2 ) { + /* The range has collapsed... */ + /* delete the range marker */ + rc = mdb_cursor_del( cursor, 0 ); + if ( rc != 0 ) { + err = "c_del dup1"; + goto fail; + } + /* skip past deleted marker */ + rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT_DUP ); + if ( rc != 0 ) { + err = "c_get dup1"; + goto fail; + } + /* delete the requested id */ + if ( id == hi ) { + /* skip lo */ + rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT_DUP ); + if ( rc != 0 ) { + err = "c_get dup2"; + goto fail; + } + } + rc = mdb_cursor_del( cursor, 0 ); + if ( rc != 0 ) { + err = "c_del dup2"; + goto fail; + } + } else { + /* position on lo */ + rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT_DUP ); + if ( id == lo ) + data.mv_data = &lo2; + else { + /* position on hi */ + rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT_DUP ); + data.mv_data = &hi2; + } + /* Replace the current lo/hi */ + data.mv_size = sizeof(ID); + rc = mdb_cursor_put( cursor, &key, &data, MDB_CURRENT ); + if ( rc != 0 ) { + err = "c_put lo/hi"; + goto fail; + } + } + } + } + } else { + /* initial c_get failed, nothing was done */ +fail: + if ( rc == MDB_NOTFOUND ) + rc = 0; + if ( rc ) { + Debug( LDAP_DEBUG_ANY, "=> mdb_idl_delete_key: " + "%s failed: %s (%d)\n", err, mdb_strerror(rc), rc ); + break; + } + } + } + return rc; +} + + +/* + * idl_intersection - return a = a intersection b + */ +int +mdb_idl_intersection( + ID *a, + ID *b ) +{ + ID ida, idb; + ID idmax, idmin; + ID cursora = 0, cursorb = 0, cursorc; + int swap = 0; + + if ( MDB_IDL_IS_ZERO( a ) || MDB_IDL_IS_ZERO( b ) ) { + a[0] = 0; + return 0; + } + + idmin = IDL_MAX( MDB_IDL_FIRST(a), MDB_IDL_FIRST(b) ); + idmax = IDL_MIN( MDB_IDL_LAST(a), MDB_IDL_LAST(b) ); + if ( idmin > idmax ) { + a[0] = 0; + return 0; + } else if ( idmin == idmax ) { + a[0] = 1; + a[1] = idmin; + return 0; + } + + if ( MDB_IDL_IS_RANGE( a ) ) { + if ( MDB_IDL_IS_RANGE(b) ) { + /* If both are ranges, just shrink the boundaries */ + a[1] = idmin; + a[2] = idmax; + return 0; + } else { + /* Else swap so that b is the range, a is a list */ + ID *tmp = a; + a = b; + b = tmp; + swap = 1; + } + } + + /* If a range completely covers the list, the result is + * just the list. + */ + if ( MDB_IDL_IS_RANGE( b ) + && MDB_IDL_RANGE_FIRST( b ) <= MDB_IDL_FIRST( a ) + && MDB_IDL_RANGE_LAST( b ) >= MDB_IDL_LLAST( a ) ) { + goto done; + } + + /* Fine, do the intersection one element at a time. + * First advance to idmin in both IDLs. + */ + cursora = cursorb = idmin; + ida = mdb_idl_first( a, &cursora ); + idb = mdb_idl_first( b, &cursorb ); + cursorc = 0; + + while( ida <= idmax || idb <= idmax ) { + if( ida == idb ) { + a[++cursorc] = ida; + ida = mdb_idl_next( a, &cursora ); + idb = mdb_idl_next( b, &cursorb ); + } else if ( ida < idb ) { + ida = mdb_idl_next( a, &cursora ); + } else { + idb = mdb_idl_next( b, &cursorb ); + } + } + a[0] = cursorc; +done: + if (swap) + MDB_IDL_CPY( b, a ); + + return 0; +} + + +/* + * idl_union - return a = a union b + */ +int +mdb_idl_union( + ID *a, + ID *b ) +{ + ID ida, idb; + ID cursora = 0, cursorb = 0, cursorc; + + if ( MDB_IDL_IS_ZERO( b ) ) { + return 0; + } + + if ( MDB_IDL_IS_ZERO( a ) ) { + MDB_IDL_CPY( a, b ); + return 0; + } + + if ( MDB_IDL_IS_RANGE( a ) || MDB_IDL_IS_RANGE(b) ) { +over: ida = IDL_MIN( MDB_IDL_FIRST(a), MDB_IDL_FIRST(b) ); + idb = IDL_MAX( MDB_IDL_LAST(a), MDB_IDL_LAST(b) ); + a[0] = NOID; + a[1] = ida; + a[2] = idb; + return 0; + } + + ida = mdb_idl_first( a, &cursora ); + idb = mdb_idl_first( b, &cursorb ); + + cursorc = b[0]; + + /* The distinct elements of a are cat'd to b */ + while( ida != NOID || idb != NOID ) { + if ( ida < idb ) { + if( ++cursorc > MDB_IDL_UM_MAX ) { + goto over; + } + b[cursorc] = ida; + ida = mdb_idl_next( a, &cursora ); + + } else { + if ( ida == idb ) + ida = mdb_idl_next( a, &cursora ); + idb = mdb_idl_next( b, &cursorb ); + } + } + + /* b is copied back to a in sorted order */ + a[0] = cursorc; + cursora = 1; + cursorb = 1; + cursorc = b[0]+1; + while (cursorb <= b[0] || cursorc <= a[0]) { + if (cursorc > a[0]) + idb = NOID; + else + idb = b[cursorc]; + if (cursorb <= b[0] && b[cursorb] < idb) + a[cursora++] = b[cursorb++]; + else { + a[cursora++] = idb; + cursorc++; + } + } + + return 0; +} + + +#if 0 +/* + * mdb_idl_notin - return a intersection ~b (or a minus b) + */ +int +mdb_idl_notin( + ID *a, + ID *b, + ID *ids ) +{ + ID ida, idb; + ID cursora = 0, cursorb = 0; + + if( MDB_IDL_IS_ZERO( a ) || + MDB_IDL_IS_ZERO( b ) || + MDB_IDL_IS_RANGE( b ) ) + { + MDB_IDL_CPY( ids, a ); + return 0; + } + + if( MDB_IDL_IS_RANGE( a ) ) { + MDB_IDL_CPY( ids, a ); + return 0; + } + + ida = mdb_idl_first( a, &cursora ), + idb = mdb_idl_first( b, &cursorb ); + + ids[0] = 0; + + while( ida != NOID ) { + if ( idb == NOID ) { + /* we could shortcut this */ + ids[++ids[0]] = ida; + ida = mdb_idl_next( a, &cursora ); + + } else if ( ida < idb ) { + ids[++ids[0]] = ida; + ida = mdb_idl_next( a, &cursora ); + + } else if ( ida > idb ) { + idb = mdb_idl_next( b, &cursorb ); + + } else { + ida = mdb_idl_next( a, &cursora ); + idb = mdb_idl_next( b, &cursorb ); + } + } + + return 0; +} +#endif + +ID mdb_idl_first( ID *ids, ID *cursor ) +{ + ID pos; + + if ( ids[0] == 0 ) { + *cursor = NOID; + return NOID; + } + + if ( MDB_IDL_IS_RANGE( ids ) ) { + if( *cursor < ids[1] ) { + *cursor = ids[1]; + } + return *cursor; + } + + if ( *cursor == 0 ) + pos = 1; + else + pos = mdb_idl_search( ids, *cursor ); + + if( pos > ids[0] ) { + return NOID; + } + + *cursor = pos; + return ids[pos]; +} + +ID mdb_idl_next( ID *ids, ID *cursor ) +{ + if ( MDB_IDL_IS_RANGE( ids ) ) { + if( ids[2] < ++(*cursor) ) { + return NOID; + } + return *cursor; + } + + if ( ++(*cursor) <= ids[0] ) { + return ids[*cursor]; + } + + return NOID; +} + +/* Add one ID to an unsorted list. We ensure that the first element is the + * minimum and the last element is the maximum, for fast range compaction. + * this means IDLs up to length 3 are always sorted... + */ +int mdb_idl_append_one( ID *ids, ID id ) +{ + if (MDB_IDL_IS_RANGE( ids )) { + /* if already in range, treat as a dup */ + if (id >= MDB_IDL_RANGE_FIRST(ids) && id <= MDB_IDL_RANGE_LAST(ids)) + return -1; + if (id < MDB_IDL_RANGE_FIRST(ids)) + ids[1] = id; + else if (id > MDB_IDL_RANGE_LAST(ids)) + ids[2] = id; + return 0; + } + if ( ids[0] ) { + ID tmp; + + if (id < ids[1]) { + tmp = ids[1]; + ids[1] = id; + id = tmp; + } + if ( ids[0] > 1 && id < ids[ids[0]] ) { + tmp = ids[ids[0]]; + ids[ids[0]] = id; + id = tmp; + } + } + ids[0]++; + if ( ids[0] >= MDB_IDL_UM_MAX ) { + ids[0] = NOID; + ids[2] = id; + } else { + ids[ids[0]] = id; + } + return 0; +} + +/* Append sorted list b to sorted list a. The result is unsorted but + * a[1] is the min of the result and a[a[0]] is the max. + */ +int mdb_idl_append( ID *a, ID *b ) +{ + ID ida, idb, tmp, swap = 0; + + if ( MDB_IDL_IS_ZERO( b ) ) { + return 0; + } + + if ( MDB_IDL_IS_ZERO( a ) ) { + MDB_IDL_CPY( a, b ); + return 0; + } + + ida = MDB_IDL_LAST( a ); + idb = MDB_IDL_LAST( b ); + if ( MDB_IDL_IS_RANGE( a ) || MDB_IDL_IS_RANGE(b) || + a[0] + b[0] >= MDB_IDL_UM_MAX ) { + a[2] = IDL_MAX( ida, idb ); + a[1] = IDL_MIN( a[1], b[1] ); + a[0] = NOID; + return 0; + } + + if ( b[0] > 1 && ida > idb ) { + swap = idb; + a[a[0]] = idb; + b[b[0]] = ida; + } + + if ( b[1] < a[1] ) { + tmp = a[1]; + a[1] = b[1]; + } else { + tmp = b[1]; + } + a[0]++; + a[a[0]] = tmp; + + if ( b[0] > 1 ) { + int i = b[0] - 1; + AC_MEMCPY(a+a[0]+1, b+2, i * sizeof(ID)); + a[0] += i; + } + if ( swap ) { + b[b[0]] = swap; + } + return 0; +} + +#if 1 + +/* Quicksort + Insertion sort for small arrays */ + +#define SMALL 8 +#define SWAP(a,b) itmp=(a);(a)=(b);(b)=itmp + +void +mdb_idl_sort( ID *ids, ID *tmp ) +{ + int *istack = (int *)tmp; /* Private stack, not used by caller */ + int i,j,k,l,ir,jstack; + ID a, itmp; + + if ( MDB_IDL_IS_RANGE( ids )) + return; + + ir = ids[0]; + l = 1; + jstack = 0; + for(;;) { + if (ir - l < SMALL) { /* Insertion sort */ + for (j=l+1;j<=ir;j++) { + a = ids[j]; + for (i=j-1;i>=1;i--) { + if (ids[i] <= a) break; + ids[i+1] = ids[i]; + } + ids[i+1] = a; + } + if (jstack == 0) break; + ir = istack[jstack--]; + l = istack[jstack--]; + } else { + k = (l + ir) >> 1; /* Choose median of left, center, right */ + SWAP(ids[k], ids[l+1]); + if (ids[l] > ids[ir]) { + SWAP(ids[l], ids[ir]); + } + if (ids[l+1] > ids[ir]) { + SWAP(ids[l+1], ids[ir]); + } + if (ids[l] > ids[l+1]) { + SWAP(ids[l], ids[l+1]); + } + i = l+1; + j = ir; + a = ids[l+1]; + for(;;) { + do i++; while(ids[i] < a); + do j--; while(ids[j] > a); + if (j < i) break; + SWAP(ids[i],ids[j]); + } + ids[l+1] = ids[j]; + ids[j] = a; + jstack += 2; + if (ir-i+1 >= j-l) { + istack[jstack] = ir; + istack[jstack-1] = i; + ir = j-1; + } else { + istack[jstack] = j-1; + istack[jstack-1] = l; + l = i; + } + } + } +} + +#else + +/* 8 bit Radix sort + insertion sort + * + * based on code from http://www.cubic.org/docs/radix.htm + * with improvements by ebackes@symas.com and hyc@symas.com + * + * This code is O(n) but has a relatively high constant factor. For lists + * up to ~50 Quicksort is slightly faster; up to ~100 they are even. + * Much faster than quicksort for lists longer than ~100. Insertion + * sort is actually superior for lists <50. + */ + +#define BUCKETS (1<<8) +#define SMALL 50 + +void +mdb_idl_sort( ID *ids, ID *tmp ) +{ + int count, soft_limit, phase = 0, size = ids[0]; + ID *idls[2]; + unsigned char *maxv = (unsigned char *)&ids[size]; + + if ( MDB_IDL_IS_RANGE( ids )) + return; + + /* Use insertion sort for small lists */ + if ( size <= SMALL ) { + int i,j; + ID a; + + for (j=1;j<=size;j++) { + a = ids[j]; + for (i=j-1;i>=1;i--) { + if (ids[i] <= a) break; + ids[i+1] = ids[i]; + } + ids[i+1] = a; + } + return; + } + + tmp[0] = size; + idls[0] = ids; + idls[1] = tmp; + +#if BYTE_ORDER == BIG_ENDIAN + for (soft_limit = 0; !maxv[soft_limit]; soft_limit++); +#else + for (soft_limit = sizeof(ID)-1; !maxv[soft_limit]; soft_limit--); +#endif + + for ( +#if BYTE_ORDER == BIG_ENDIAN + count = sizeof(ID)-1; count >= soft_limit; --count +#else + count = 0; count <= soft_limit; ++count +#endif + ) { + unsigned int num[BUCKETS], * np, n, sum; + int i; + ID *sp, *source, *dest; + unsigned char *bp, *source_start; + + source = idls[phase]+1; + dest = idls[phase^1]+1; + source_start = ((unsigned char *) source) + count; + + np = num; + for ( i = BUCKETS; i > 0; --i ) *np++ = 0; + + /* count occurences of every byte value */ + bp = source_start; + for ( i = size; i > 0; --i, bp += sizeof(ID) ) + num[*bp]++; + + /* transform count into index by summing elements and storing + * into same array + */ + sum = 0; + np = num; + for ( i = BUCKETS; i > 0; --i ) { + n = *np; + *np++ = sum; + sum += n; + } + + /* fill dest with the right values in the right place */ + bp = source_start; + sp = source; + for ( i = size; i > 0; --i, bp += sizeof(ID) ) { + np = num + *bp; + dest[*np] = *sp++; + ++(*np); + } + phase ^= 1; + } + + /* copy back from temp if needed */ + if ( phase ) { + ids++; tmp++; + for ( count = 0; count < size; ++count ) + *ids++ = *tmp++; + } +} +#endif /* Quick vs Radix */ + +unsigned mdb_id2l_search( ID2L ids, ID id ) +{ + /* + * binary search of id in ids + * if found, returns position of id + * if not found, returns first position greater than id + */ + unsigned base = 0; + unsigned cursor = 1; + int val = 0; + unsigned n = ids[0].mid; + + while( 0 < n ) { + unsigned pivot = n >> 1; + cursor = base + pivot + 1; + val = IDL_CMP( id, ids[cursor].mid ); + + if( val < 0 ) { + n = pivot; + + } else if ( val > 0 ) { + base = cursor; + n -= pivot + 1; + + } else { + return cursor; + } + } + + if( val > 0 ) { + ++cursor; + } + return cursor; +} + +int mdb_id2l_insert( ID2L ids, ID2 *id ) +{ + unsigned x, i; + + x = mdb_id2l_search( ids, id->mid ); + assert( x > 0 ); + + if( x < 1 ) { + /* internal error */ + return -2; + } + + if ( x <= ids[0].mid && ids[x].mid == id->mid ) { + /* duplicate */ + return -1; + } + + if ( ids[0].mid >= MDB_IDL_UM_MAX ) { + /* too big */ + return -2; + + } else { + /* insert id */ + ids[0].mid++; + for (i=ids[0].mid; i>x; i--) + ids[i] = ids[i-1]; + ids[x] = *id; + } + + return 0; +} diff --git a/servers/slapd/back-mdb/idl.h b/servers/slapd/back-mdb/idl.h new file mode 100644 index 0000000..1414afe --- /dev/null +++ b/servers/slapd/back-mdb/idl.h @@ -0,0 +1,116 @@ +/* idl.h - ldap mdb back-end ID list header file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#ifndef _MDB_IDL_H_ +#define _MDB_IDL_H_ + +/* IDL sizes - likely should be even bigger + * limiting factors: sizeof(ID), thread stack size + */ +#define MDB_IDL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */ +#define MDB_IDL_DB_SIZE (1<<MDB_IDL_LOGN) +#define MDB_IDL_UM_SIZE (1<<(MDB_IDL_LOGN+1)) +#define MDB_IDL_UM_SIZEOF (MDB_IDL_UM_SIZE * sizeof(ID)) + +#define MDB_IDL_DB_MAX (MDB_IDL_DB_SIZE-1) + +#define MDB_IDL_UM_MAX (MDB_IDL_UM_SIZE-1) + +#define MDB_IDL_IS_RANGE(ids) ((ids)[0] == NOID) +#define MDB_IDL_RANGE_SIZE (3) +#define MDB_IDL_RANGE_SIZEOF (MDB_IDL_RANGE_SIZE * sizeof(ID)) +#define MDB_IDL_SIZEOF(ids) ((MDB_IDL_IS_RANGE(ids) \ + ? MDB_IDL_RANGE_SIZE : ((ids)[0]+1)) * sizeof(ID)) + +#define MDB_IDL_RANGE_FIRST(ids) ((ids)[1]) +#define MDB_IDL_RANGE_LAST(ids) ((ids)[2]) + +#define MDB_IDL_RANGE( ids, f, l ) \ + do { \ + (ids)[0] = NOID; \ + (ids)[1] = (f); \ + (ids)[2] = (l); \ + } while(0) + +#define MDB_IDL_ZERO(ids) \ + do { \ + (ids)[0] = 0; \ + (ids)[1] = 0; \ + (ids)[2] = 0; \ + } while(0) + +#define MDB_IDL_IS_ZERO(ids) ( (ids)[0] == 0 ) +#define MDB_IDL_IS_ALL( range, ids ) ( (ids)[0] == NOID \ + && (ids)[1] <= (range)[1] && (range)[2] <= (ids)[2] ) + +#define MDB_IDL_CPY( dst, src ) (AC_MEMCPY( dst, src, MDB_IDL_SIZEOF( src ) )) + +#define MDB_IDL_ID( mdb, ids, id ) MDB_IDL_RANGE( ids, id, NOID ) +#define MDB_IDL_ALL( ids ) MDB_IDL_RANGE( ids, 1, NOID ) + +#define MDB_IDL_FIRST( ids ) ( (ids)[1] ) +#define MDB_IDL_LLAST( ids ) ( (ids)[(ids)[0]] ) +#define MDB_IDL_LAST( ids ) ( MDB_IDL_IS_RANGE(ids) \ + ? (ids)[2] : (ids)[(ids)[0]] ) + +#define MDB_IDL_N( ids ) ( MDB_IDL_IS_RANGE(ids) \ + ? ((ids)[2]-(ids)[1])+1 : (ids)[0] ) + + /** An ID2 is an ID/value pair. + */ +typedef struct ID2 { + ID mid; /**< The ID */ + MDB_val mval; /**< The value */ +} ID2; + + /** An ID2L is an ID2 List, a sorted array of ID2s. + * The first element's \b mid member is a count of how many actual + * elements are in the array. The \b mptr member of the first element is unused. + * The array is sorted in ascending order by \b mid. + */ +typedef ID2 *ID2L; + +typedef struct IdScopes { + MDB_txn *mt; + MDB_cursor *mc; + ID id; + ID2L scopes; + ID2L sctmp; + int numrdns; + int nscope; + int oscope; + struct berval rdns[MAXRDNS]; + struct berval nrdns[MAXRDNS]; +} IdScopes; + +LDAP_BEGIN_DECL + /** Search for an ID in an ID2L. + * @param[in] ids The ID2L to search. + * @param[in] id The ID to search for. + * @return The index of the first ID2 whose \b mid member is greater than or equal to \b id. + */ +unsigned mdb_id2l_search( ID2L ids, ID id ); + + + /** Insert an ID2 into a ID2L. + * @param[in,out] ids The ID2L to insert into. + * @param[in] id The ID2 to insert. + * @return 0 on success, -1 if the ID was already present in the MIDL2. + */ +int mdb_id2l_insert( ID2L ids, ID2 *id ); +LDAP_END_DECL + +#endif diff --git a/servers/slapd/back-mdb/index.c b/servers/slapd/back-mdb/index.c new file mode 100644 index 0000000..2d699bd --- /dev/null +++ b/servers/slapd/back-mdb/index.c @@ -0,0 +1,575 @@ +/* index.c - routines for dealing with attribute indexes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-mdb.h" +#include "lutil_hash.h" + +static char presence_keyval[] = {0,0,0,0,0}; +static struct berval presence_key[2] = {BER_BVC(presence_keyval), BER_BVNULL}; + +AttrInfo *mdb_index_mask( + Backend *be, + AttributeDescription *desc, + struct berval *atname ) +{ + AttributeType *at; + AttrInfo *ai = mdb_attr_mask( be->be_private, desc ); + + if( ai ) { + *atname = desc->ad_cname; + return ai; + } + + /* If there is a tagging option, did we ever index the base + * type? If so, check for mask, otherwise it's not there. + */ + if( slap_ad_is_tagged( desc ) && desc != desc->ad_type->sat_ad ) { + /* has tagging option */ + ai = mdb_attr_mask( be->be_private, desc->ad_type->sat_ad ); + + if ( ai && !( ai->ai_indexmask & SLAP_INDEX_NOTAGS ) ) { + *atname = desc->ad_type->sat_cname; + return ai; + } + } + + /* see if supertype defined mask for its subtypes */ + for( at = desc->ad_type; at != NULL ; at = at->sat_sup ) { + /* If no AD, we've never indexed this type */ + if ( !at->sat_ad ) continue; + + ai = mdb_attr_mask( be->be_private, at->sat_ad ); + + if ( ai && !( ai->ai_indexmask & SLAP_INDEX_NOSUBTYPES ) ) { + *atname = at->sat_cname; + return ai; + } + } + + return 0; +} + +/* This function is only called when evaluating search filters. + */ +int mdb_index_param( + Backend *be, + AttributeDescription *desc, + int ftype, + MDB_dbi *dbip, + slap_mask_t *maskp, + struct berval *prefixp ) +{ + AttrInfo *ai; + slap_mask_t mask, type = 0; + + ai = mdb_index_mask( be, desc, prefixp ); + + if ( !ai ) { +#ifdef MDB_MONITOR_IDX + switch ( ftype ) { + case LDAP_FILTER_PRESENT: + type = SLAP_INDEX_PRESENT; + break; + case LDAP_FILTER_APPROX: + type = SLAP_INDEX_APPROX; + break; + case LDAP_FILTER_EQUALITY: + type = SLAP_INDEX_EQUALITY; + break; + case LDAP_FILTER_SUBSTRINGS: + type = SLAP_INDEX_SUBSTR; + break; + default: + return LDAP_INAPPROPRIATE_MATCHING; + } + mdb_monitor_idx_add( be->be_private, desc, type ); +#endif /* MDB_MONITOR_IDX */ + + return LDAP_INAPPROPRIATE_MATCHING; + } + mask = ai->ai_indexmask; + + switch( ftype ) { + case LDAP_FILTER_PRESENT: + type = SLAP_INDEX_PRESENT; + if( IS_SLAP_INDEX( mask, SLAP_INDEX_PRESENT ) ) { + *prefixp = presence_key[0]; + goto done; + } + break; + + case LDAP_FILTER_APPROX: + type = SLAP_INDEX_APPROX; + if ( desc->ad_type->sat_approx ) { + if( IS_SLAP_INDEX( mask, SLAP_INDEX_APPROX ) ) { + goto done; + } + break; + } + + /* Use EQUALITY rule and index for approximate match */ + /* fall thru */ + + case LDAP_FILTER_EQUALITY: + type = SLAP_INDEX_EQUALITY; + if( IS_SLAP_INDEX( mask, SLAP_INDEX_EQUALITY ) ) { + goto done; + } + break; + + case LDAP_FILTER_SUBSTRINGS: + type = SLAP_INDEX_SUBSTR; + if( IS_SLAP_INDEX( mask, SLAP_INDEX_SUBSTR ) ) { + goto done; + } + break; + + default: + return LDAP_OTHER; + } + +#ifdef MDB_MONITOR_IDX + mdb_monitor_idx_add( be->be_private, desc, type ); +#endif /* MDB_MONITOR_IDX */ + + return LDAP_INAPPROPRIATE_MATCHING; + +done: + *dbip = ai->ai_dbi; + *maskp = mask; + return LDAP_SUCCESS; +} + +static int indexer( + Operation *op, + MDB_txn *txn, + struct mdb_attrinfo *ai, + AttributeDescription *ad, + struct berval *atname, + BerVarray vals, + ID id, + int opid, + slap_mask_t mask ) +{ + int rc; + struct berval *keys; + MDB_cursor *mc = ai->ai_cursor; + mdb_idl_keyfunc *keyfunc; + char *err; + + assert( mask != 0 ); + + if ( !mc ) { + err = "c_open"; + rc = mdb_cursor_open( txn, ai->ai_dbi, &mc ); + if ( rc ) goto done; + if ( slapMode & SLAP_TOOL_QUICK ) + ai->ai_cursor = mc; + } + + if ( opid == SLAP_INDEX_ADD_OP ) { +#ifdef MDB_TOOL_IDL_CACHING + if (( slapMode & SLAP_TOOL_QUICK ) && slap_tool_thread_max > 2 ) { + keyfunc = mdb_tool_idl_add; + mc = (MDB_cursor *)ai; + } else +#endif + keyfunc = mdb_idl_insert_keys; + } else + keyfunc = mdb_idl_delete_keys; + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_PRESENT ) ) { + rc = keyfunc( op->o_bd, mc, presence_key, id ); + if( rc ) { + err = "presence"; + goto done; + } + } + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_EQUALITY ) ) { + rc = ad->ad_type->sat_equality->smr_indexer( + LDAP_FILTER_EQUALITY, + mask, + ad->ad_type->sat_syntax, + ad->ad_type->sat_equality, + atname, vals, &keys, op->o_tmpmemctx ); + + if( rc == LDAP_SUCCESS && keys != NULL ) { + rc = keyfunc( op->o_bd, mc, keys, id ); + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + if ( rc ) { + err = "equality"; + goto done; + } + } + rc = LDAP_SUCCESS; + } + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_APPROX ) ) { + rc = ad->ad_type->sat_approx->smr_indexer( + LDAP_FILTER_APPROX, + mask, + ad->ad_type->sat_syntax, + ad->ad_type->sat_approx, + atname, vals, &keys, op->o_tmpmemctx ); + + if( rc == LDAP_SUCCESS && keys != NULL ) { + rc = keyfunc( op->o_bd, mc, keys, id ); + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + if ( rc ) { + err = "approx"; + goto done; + } + } + + rc = LDAP_SUCCESS; + } + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_SUBSTR ) ) { + rc = ad->ad_type->sat_substr->smr_indexer( + LDAP_FILTER_SUBSTRINGS, + mask, + ad->ad_type->sat_syntax, + ad->ad_type->sat_substr, + atname, vals, &keys, op->o_tmpmemctx ); + + if( rc == LDAP_SUCCESS && keys != NULL ) { + rc = keyfunc( op->o_bd, mc, keys, id ); + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + if( rc ) { + err = "substr"; + goto done; + } + } + + rc = LDAP_SUCCESS; + } + +done: + if ( !(slapMode & SLAP_TOOL_QUICK)) + mdb_cursor_close( mc ); + switch( rc ) { + /* The callers all know how to deal with these results */ + case 0: + break; + /* Anything else is bad news */ + default: + rc = LDAP_OTHER; + } + return rc; +} + +static int index_at_values( + Operation *op, + MDB_txn *txn, + AttributeDescription *ad, + AttributeType *type, + struct berval *tags, + BerVarray vals, + ID id, + int opid ) +{ + int rc; + slap_mask_t mask = 0; + int ixop = opid; + AttrInfo *ai = NULL; + + if ( opid == MDB_INDEX_UPDATE_OP ) + ixop = SLAP_INDEX_ADD_OP; + + if( type->sat_sup ) { + /* recurse */ + rc = index_at_values( op, txn, NULL, + type->sat_sup, tags, + vals, id, opid ); + + if( rc ) return rc; + } + + /* If this type has no AD, we've never used it before */ + if( type->sat_ad ) { + ai = mdb_attr_mask( op->o_bd->be_private, type->sat_ad ); + if ( ai ) { +#ifdef LDAP_COMP_MATCH + /* component indexing */ + if ( ai->ai_cr ) { + ComponentReference *cr; + for( cr = ai->ai_cr ; cr ; cr = cr->cr_next ) { + rc = indexer( op, txn, ai, cr->cr_ad, &type->sat_cname, + cr->cr_nvals, id, ixop, + cr->cr_indexmask ); + } + } +#endif + ad = type->sat_ad; + /* If we're updating the index, just set the new bits that aren't + * already in the old mask. + */ + if ( opid == MDB_INDEX_UPDATE_OP ) + mask = ai->ai_newmask & ~ai->ai_indexmask; + else + /* For regular updates, if there is a newmask use it. Otherwise + * just use the old mask. + */ + mask = ai->ai_newmask ? ai->ai_newmask : ai->ai_indexmask; + if( mask ) { + rc = indexer( op, txn, ai, ad, &type->sat_cname, + vals, id, ixop, mask ); + + if( rc ) return rc; + } + } + } + + if( tags->bv_len ) { + AttributeDescription *desc; + + desc = ad_find_tags( type, tags ); + if( desc ) { + ai = mdb_attr_mask( op->o_bd->be_private, desc ); + + if( ai ) { + if ( opid == MDB_INDEX_UPDATE_OP ) + mask = ai->ai_newmask & ~ai->ai_indexmask; + else + mask = ai->ai_newmask ? ai->ai_newmask : ai->ai_indexmask; + if ( mask ) { + rc = indexer( op, txn, ai, desc, &desc->ad_cname, + vals, id, ixop, mask ); + + if( rc ) { + return rc; + } + } + } + } + } + + return LDAP_SUCCESS; +} + +int mdb_index_values( + Operation *op, + MDB_txn *txn, + AttributeDescription *desc, + BerVarray vals, + ID id, + int opid ) +{ + int rc; + + /* Never index ID 0 */ + if ( id == 0 ) + return 0; + + rc = index_at_values( op, txn, desc, + desc->ad_type, &desc->ad_tags, + vals, id, opid ); + + return rc; +} + +/* Get the list of which indices apply to this attr */ +int +mdb_index_recset( + struct mdb_info *mdb, + Attribute *a, + AttributeType *type, + struct berval *tags, + IndexRec *ir ) +{ + int rc, slot; + AttrList *al; + + if( type->sat_sup ) { + /* recurse */ + rc = mdb_index_recset( mdb, a, type->sat_sup, tags, ir ); + if( rc ) return rc; + } + /* If this type has no AD, we've never used it before */ + if( type->sat_ad ) { + slot = mdb_attr_slot( mdb, type->sat_ad, NULL ); + if ( slot >= 0 ) { + ir[slot].ir_ai = mdb->mi_attrs[slot]; + al = ch_malloc( sizeof( AttrList )); + al->attr = a; + al->next = ir[slot].ir_attrs; + ir[slot].ir_attrs = al; + } + } + if( tags->bv_len ) { + AttributeDescription *desc; + + desc = ad_find_tags( type, tags ); + if( desc ) { + slot = mdb_attr_slot( mdb, desc, NULL ); + if ( slot >= 0 ) { + ir[slot].ir_ai = mdb->mi_attrs[slot]; + al = ch_malloc( sizeof( AttrList )); + al->attr = a; + al->next = ir[slot].ir_attrs; + ir[slot].ir_attrs = al; + } + } + } + return LDAP_SUCCESS; +} + +/* Apply the indices for the recset */ +int mdb_index_recrun( + Operation *op, + MDB_txn *txn, + struct mdb_info *mdb, + IndexRec *ir0, + ID id, + int base ) +{ + IndexRec *ir; + AttrList *al; + int i, rc = 0; + + /* Never index ID 0 */ + if ( id == 0 ) + return 0; + + for (i=base; i<mdb->mi_nattrs; i+=slap_tool_thread_max-1) { + ir = ir0 + i; + if ( !ir->ir_ai ) continue; + while (( al = ir->ir_attrs )) { + ir->ir_attrs = al->next; + rc = indexer( op, txn, ir->ir_ai, ir->ir_ai->ai_desc, + &ir->ir_ai->ai_desc->ad_type->sat_cname, + al->attr->a_nvals, id, SLAP_INDEX_ADD_OP, + ir->ir_ai->ai_indexmask ); + free( al ); + if ( rc ) break; + } + } + return rc; +} + +int +mdb_index_entry( + Operation *op, + MDB_txn *txn, + int opid, + Entry *e ) +{ + int rc; + Attribute *ap = e->e_attrs; +#if 0 /* ifdef LDAP_COMP_MATCH */ + ComponentReference *cr_list = NULL; + ComponentReference *cr = NULL, *dupped_cr = NULL; + void* decoded_comp; + ComponentSyntaxInfo* csi_attr; + Syntax* syn; + AttributeType* at; + int i, num_attr; + void* mem_op; + struct berval value = {0}; +#endif + + /* Never index ID 0 */ + if ( e->e_id == 0 ) + return 0; + + Debug( LDAP_DEBUG_TRACE, "=> index_entry_%s( %ld, \"%s\" )\n", + opid == SLAP_INDEX_DELETE_OP ? "del" : "add", + (long) e->e_id, e->e_dn ? e->e_dn : "" ); + + /* add each attribute to the indexes */ + for ( ; ap != NULL; ap = ap->a_next ) { +#if 0 /* ifdef LDAP_COMP_MATCH */ + AttrInfo *ai; + /* see if attribute has components to be indexed */ + ai = mdb_attr_mask( op->o_bd->be_private, ap->a_desc->ad_type->sat_ad ); + if ( !ai ) continue; + cr_list = ai->ai_cr; + if ( attr_converter && cr_list ) { + syn = ap->a_desc->ad_type->sat_syntax; + ap->a_comp_data = op->o_tmpalloc( sizeof( ComponentData ), op->o_tmpmemctx ); + /* Memory chunk(nibble) pre-allocation for decoders */ + mem_op = nibble_mem_allocator ( 1024*16, 1024*4 ); + ap->a_comp_data->cd_mem_op = mem_op; + for( cr = cr_list ; cr ; cr = cr->cr_next ) { + /* count how many values in an attribute */ + for( num_attr=0; ap->a_vals[num_attr].bv_val != NULL; num_attr++ ); + num_attr++; + cr->cr_nvals = (BerVarray)op->o_tmpalloc( sizeof( struct berval )*num_attr, op->o_tmpmemctx ); + for( i=0; ap->a_vals[i].bv_val != NULL; i++ ) { + /* decoding attribute value */ + decoded_comp = attr_converter ( ap, syn, &ap->a_vals[i] ); + if ( !decoded_comp ) + return LDAP_DECODING_ERROR; + /* extracting the referenced component */ + dupped_cr = dup_comp_ref( op, cr ); + csi_attr = ((ComponentSyntaxInfo*)decoded_comp)->csi_comp_desc->cd_extract_i( mem_op, dupped_cr, decoded_comp ); + if ( !csi_attr ) + return LDAP_DECODING_ERROR; + cr->cr_asn_type_id = csi_attr->csi_comp_desc->cd_type_id; + cr->cr_ad = (AttributeDescription*)get_component_description ( cr->cr_asn_type_id ); + if ( !cr->cr_ad ) + return LDAP_INVALID_SYNTAX; + at = cr->cr_ad->ad_type; + /* encoding the value of component in GSER */ + rc = component_encoder( mem_op, csi_attr, &value ); + if ( rc != LDAP_SUCCESS ) + return LDAP_ENCODING_ERROR; + /* Normalize the encoded component values */ + if ( at->sat_equality && at->sat_equality->smr_normalize ) { + rc = at->sat_equality->smr_normalize ( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + at->sat_syntax, at->sat_equality, + &value, &cr->cr_nvals[i], op->o_tmpmemctx ); + } else { + cr->cr_nvals[i] = value; + } + } + /* The end of BerVarray */ + cr->cr_nvals[num_attr-1].bv_val = NULL; + cr->cr_nvals[num_attr-1].bv_len = 0; + } + op->o_tmpfree( ap->a_comp_data, op->o_tmpmemctx ); + nibble_mem_free ( mem_op ); + ap->a_comp_data = NULL; + } +#endif + rc = mdb_index_values( op, txn, ap->a_desc, + ap->a_nvals, e->e_id, opid ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= index_entry_%s( %ld, \"%s\" ) failure\n", + opid == SLAP_INDEX_ADD_OP ? "add" : "del", + (long) e->e_id, e->e_dn ); + return rc; + } + } + + Debug( LDAP_DEBUG_TRACE, "<= index_entry_%s( %ld, \"%s\" ) success\n", + opid == SLAP_INDEX_DELETE_OP ? "del" : "add", + (long) e->e_id, e->e_dn ? e->e_dn : "" ); + + return LDAP_SUCCESS; +} diff --git a/servers/slapd/back-mdb/init.c b/servers/slapd/back-mdb/init.c new file mode 100644 index 0000000..44f6f89 --- /dev/null +++ b/servers/slapd/back-mdb/init.c @@ -0,0 +1,497 @@ +/* init.c - initialize mdb backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/unistd.h> +#include <ac/stdlib.h> +#include <ac/errno.h> +#include <sys/stat.h> +#include "back-mdb.h" +#include <lutil.h> +#include <ldap_rq.h> +#include "config.h" + +static const struct berval mdmi_databases[] = { + BER_BVC("ad2i"), + BER_BVC("dn2i"), + BER_BVC("id2e"), + BER_BVNULL +}; + +static int +mdb_id_compare( const MDB_val *a, const MDB_val *b ) +{ + return *(ID *)a->mv_data < *(ID *)b->mv_data ? -1 : *(ID *)a->mv_data > *(ID *)b->mv_data; +} + +static int +mdb_db_init( BackendDB *be, ConfigReply *cr ) +{ + struct mdb_info *mdb; + int rc; + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_db_init) ": Initializing mdb database\n", + 0, 0, 0 ); + + /* allocate backend-database-specific stuff */ + mdb = (struct mdb_info *) ch_calloc( 1, sizeof(struct mdb_info) ); + + /* DBEnv parameters */ + mdb->mi_dbenv_home = ch_strdup( SLAPD_DEFAULT_DB_DIR ); + mdb->mi_dbenv_flags = 0; + mdb->mi_dbenv_mode = SLAPD_DEFAULT_DB_MODE; + + mdb->mi_search_stack_depth = DEFAULT_SEARCH_STACK_DEPTH; + mdb->mi_search_stack = NULL; + + mdb->mi_mapsize = DEFAULT_MAPSIZE; + mdb->mi_rtxn_size = DEFAULT_RTXN_SIZE; + + be->be_private = mdb; + be->be_cf_ocs = be->bd_info->bi_cf_ocs; + +#ifndef MDB_MULTIPLE_SUFFIXES + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_ONE_SUFFIX; +#endif + + rc = mdb_monitor_db_init( be ); + + return rc; +} + +static int +mdb_db_close( BackendDB *be, ConfigReply *cr ); + +static int +mdb_db_open( BackendDB *be, ConfigReply *cr ) +{ + int rc, i; + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + struct stat stat1; + unsigned flags; + char *dbhome; + MDB_txn *txn; + + if ( be->be_suffix == NULL ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": need suffix.\n", + 1, 0, 0 ); + return -1; + } + + Debug( LDAP_DEBUG_ARGS, + LDAP_XSTRING(mdb_db_open) ": \"%s\"\n", + be->be_suffix[0].bv_val, 0, 0 ); + + /* Check existence of dbenv_home. Any error means trouble */ + rc = stat( mdb->mi_dbenv_home, &stat1 ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": database \"%s\": " + "cannot access database directory \"%s\" (%d).\n", + be->be_suffix[0].bv_val, mdb->mi_dbenv_home, errno ); + return -1; + } + + /* mdb is always clean */ + be->be_flags |= SLAP_DBFLAG_CLEAN; + + rc = mdb_env_create( &mdb->mi_dbenv ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": database \"%s\": " + "mdb_env_create failed: %s (%d).\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + goto fail; + } + + if ( mdb->mi_readers ) { + rc = mdb_env_set_maxreaders( mdb->mi_dbenv, mdb->mi_readers ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": database \"%s\": " + "mdb_env_set_maxreaders failed: %s (%d).\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + goto fail; + } + } + + rc = mdb_env_set_mapsize( mdb->mi_dbenv, mdb->mi_mapsize ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": database \"%s\": " + "mdb_env_set_mapsize failed: %s (%d).\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + goto fail; + } + + rc = mdb_env_set_maxdbs( mdb->mi_dbenv, MDB_INDICES ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": database \"%s\": " + "mdb_env_set_maxdbs failed: %s (%d).\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + goto fail; + } + +#ifdef HAVE_EBCDIC + strcpy( path, mdb->mi_dbenv_home ); + __atoe( path ); + dbhome = path; +#else + dbhome = mdb->mi_dbenv_home; +#endif + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_db_open) ": database \"%s\": " + "dbenv_open(%s).\n", + be->be_suffix[0].bv_val, mdb->mi_dbenv_home, 0); + + flags = mdb->mi_dbenv_flags; + + if ( slapMode & SLAP_TOOL_QUICK ) + flags |= MDB_NOSYNC|MDB_WRITEMAP; + + if ( slapMode & SLAP_TOOL_READONLY) + flags |= MDB_RDONLY; + + rc = mdb_env_open( mdb->mi_dbenv, dbhome, + flags, mdb->mi_dbenv_mode ); + + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": database \"%s\" cannot be opened: %s (%d). " + "Restore from backup!\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + goto fail; + } + + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, flags & MDB_RDONLY, &txn ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": database \"%s\" cannot be opened: %s (%d). " + "Restore from backup!\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + goto fail; + } + + /* open (and create) main databases */ + for( i = 0; mdmi_databases[i].bv_val; i++ ) { + flags = MDB_INTEGERKEY; + if( i == MDB_ID2ENTRY ) { + if ( !(slapMode & (SLAP_TOOL_READMAIN|SLAP_TOOL_READONLY) )) + flags |= MDB_CREATE; + } else { + if ( i == MDB_DN2ID ) + flags |= MDB_DUPSORT; + if ( !(slapMode & SLAP_TOOL_READONLY) ) + flags |= MDB_CREATE; + } + + rc = mdb_dbi_open( txn, + mdmi_databases[i].bv_val, + flags, + &mdb->mi_dbis[i] ); + + if ( rc != 0 ) { + snprintf( cr->msg, sizeof(cr->msg), "database \"%s\": " + "mdb_dbi_open(%s/%s) failed: %s (%d).", + be->be_suffix[0].bv_val, + mdb->mi_dbenv_home, mdmi_databases[i].bv_val, + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": %s\n", + cr->msg, 0, 0 ); + goto fail; + } + + if ( i == MDB_ID2ENTRY ) + mdb_set_compare( txn, mdb->mi_dbis[i], mdb_id_compare ); + else if ( i == MDB_DN2ID ) { + MDB_cursor *mc; + MDB_val key, data; + mdb_set_dupsort( txn, mdb->mi_dbis[i], mdb_dup_compare ); + /* check for old dn2id format */ + rc = mdb_cursor_open( txn, mdb->mi_dbis[i], &mc ); + /* first record is always ID 0 */ + rc = mdb_cursor_get( mc, &key, &data, MDB_FIRST ); + if ( rc == 0 ) { + rc = mdb_cursor_get( mc, &key, &data, MDB_NEXT ); + if ( rc == 0 ) { + int len; + unsigned char *ptr; + ptr = data.mv_data; + len = (ptr[0] & 0x7f) << 8 | ptr[1]; + if (data.mv_size < 2*len + 4 + 2*sizeof(ID)) { + snprintf( cr->msg, sizeof(cr->msg), + "database \"%s\": DN index needs upgrade, " + "run \"slapindex entryDN\".", + be->be_suffix[0].bv_val ); + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": %s\n", + cr->msg, 0, 0 ); + if ( !(slapMode & SLAP_TOOL_READMAIN )) + rc = LDAP_OTHER; + mdb->mi_flags |= MDB_NEED_UPGRADE; + } + } + } + mdb_cursor_close( mc ); + if ( rc == LDAP_OTHER ) + goto fail; + } + } + + rc = mdb_ad_read( mdb, txn ); + if ( rc ) { + mdb_txn_abort( txn ); + goto fail; + } + + /* slapcat doesn't need indexes. avoid a failure if + * a configured index wasn't created yet. + */ + if ( !(slapMode & SLAP_TOOL_READONLY) ) { + rc = mdb_attr_dbs_open( be, txn, cr ); + if ( rc ) { + mdb_txn_abort( txn ); + goto fail; + } + } + + rc = mdb_txn_commit(txn); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": database %s: " + "txn_commit failed: %s (%d)\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + goto fail; + } + + /* monitor setup */ + rc = mdb_monitor_db_open( be ); + if ( rc != 0 ) { + goto fail; + } + + mdb->mi_flags |= MDB_IS_OPEN; + + return 0; + +fail: + mdb_db_close( be, NULL ); + return rc; +} + +static int +mdb_db_close( BackendDB *be, ConfigReply *cr ) +{ + int rc; + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + + /* monitor handling */ + (void)mdb_monitor_db_close( be ); + + mdb->mi_flags &= ~MDB_IS_OPEN; + + if( mdb->mi_dbenv ) { + mdb_reader_flush( mdb->mi_dbenv ); + } + + if ( mdb->mi_dbenv ) { + if ( mdb->mi_dbis[0] ) { + int i; + + mdb_attr_dbs_close( mdb ); + for ( i=0; i<MDB_NDB; i++ ) + mdb_dbi_close( mdb->mi_dbenv, mdb->mi_dbis[i] ); + + /* force a sync, but not if we were ReadOnly, + * and not in Quick mode. + */ + if (!(slapMode & (SLAP_TOOL_QUICK|SLAP_TOOL_READONLY))) { + rc = mdb_env_sync( mdb->mi_dbenv, 1 ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "mdb_db_close: database \"%s\": " + "mdb_env_sync failed: %s (%d).\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + } + } + } + + mdb_env_close( mdb->mi_dbenv ); + mdb->mi_dbenv = NULL; + } + + return 0; +} + +static int +mdb_db_destroy( BackendDB *be, ConfigReply *cr ) +{ + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + + /* stop and remove checkpoint task */ + if ( mdb->mi_txn_cp_task ) { + struct re_s *re = mdb->mi_txn_cp_task; + mdb->mi_txn_cp_task = NULL; + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re ) ) + ldap_pvt_runqueue_stoptask( &slapd_rq, re ); + ldap_pvt_runqueue_remove( &slapd_rq, re ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + + /* monitor handling */ + (void)mdb_monitor_db_destroy( be ); + + if( mdb->mi_dbenv_home ) ch_free( mdb->mi_dbenv_home ); + + mdb_attr_index_destroy( mdb ); + + ch_free( mdb ); + be->be_private = NULL; + + return 0; +} + +int +mdb_back_initialize( + BackendInfo *bi ) +{ + int rc; + + static char *controls[] = { + LDAP_CONTROL_ASSERT, + LDAP_CONTROL_MANAGEDSAIT, + LDAP_CONTROL_NOOP, + LDAP_CONTROL_PAGEDRESULTS, + LDAP_CONTROL_PRE_READ, + LDAP_CONTROL_POST_READ, + LDAP_CONTROL_SUBENTRIES, + LDAP_CONTROL_X_PERMISSIVE_MODIFY, +#ifdef LDAP_X_TXN + LDAP_CONTROL_X_TXN_SPEC, +#endif + NULL + }; + + /* initialize the underlying database system */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_back_initialize) ": initialize " + MDB_UCTYPE " backend\n", 0, 0, 0 ); + + bi->bi_flags |= + SLAP_BFLAG_INCREMENT | + SLAP_BFLAG_SUBENTRIES | + SLAP_BFLAG_ALIASES | + SLAP_BFLAG_REFERRALS; + + bi->bi_controls = controls; + + { /* version check */ + int major, minor, patch, ver; + char *version = mdb_version( &major, &minor, &patch ); +#ifdef HAVE_EBCDIC + char v2[1024]; + + /* All our stdio does an ASCII to EBCDIC conversion on + * the output. Strings from the MDB library are already + * in EBCDIC; we have to go back and forth... + */ + strcpy( v2, version ); + __etoa( v2 ); + version = v2; +#endif + ver = (major << 24) | (minor << 16) | patch; + if( ver != MDB_VERSION_FULL ) { + /* fail if a versions don't match */ + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_back_initialize) ": " + "MDB library version mismatch:" + " expected " MDB_VERSION_STRING "," + " got %s\n", version, 0, 0 ); + return -1; + } + + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(mdb_back_initialize) + ": %s\n", version, 0, 0 ); + } + + bi->bi_open = 0; + bi->bi_close = 0; + bi->bi_config = 0; + bi->bi_destroy = 0; + + bi->bi_db_init = mdb_db_init; + bi->bi_db_config = config_generic_wrapper; + bi->bi_db_open = mdb_db_open; + bi->bi_db_close = mdb_db_close; + bi->bi_db_destroy = mdb_db_destroy; + + bi->bi_op_add = mdb_add; + bi->bi_op_bind = mdb_bind; + bi->bi_op_compare = mdb_compare; + bi->bi_op_delete = mdb_delete; + bi->bi_op_modify = mdb_modify; + bi->bi_op_modrdn = mdb_modrdn; + bi->bi_op_search = mdb_search; + + bi->bi_op_unbind = 0; + + bi->bi_extended = mdb_extended; + + bi->bi_chk_referrals = 0; + bi->bi_operational = mdb_operational; + + bi->bi_has_subordinates = mdb_hasSubordinates; + bi->bi_entry_release_rw = mdb_entry_release; + bi->bi_entry_get_rw = mdb_entry_get; + + /* + * hooks for slap tools + */ + bi->bi_tool_entry_open = mdb_tool_entry_open; + bi->bi_tool_entry_close = mdb_tool_entry_close; + bi->bi_tool_entry_first = backend_tool_entry_first; + bi->bi_tool_entry_first_x = mdb_tool_entry_first_x; + bi->bi_tool_entry_next = mdb_tool_entry_next; + bi->bi_tool_entry_get = mdb_tool_entry_get; + bi->bi_tool_entry_put = mdb_tool_entry_put; + bi->bi_tool_entry_reindex = mdb_tool_entry_reindex; + bi->bi_tool_sync = 0; + bi->bi_tool_dn2id_get = mdb_tool_dn2id_get; + bi->bi_tool_entry_modify = mdb_tool_entry_modify; + + bi->bi_connection_init = 0; + bi->bi_connection_destroy = 0; + + rc = mdb_back_init_cf( bi ); + + return rc; +} + +#if (SLAPD_MDB == SLAPD_MOD_DYNAMIC) + +SLAP_BACKEND_INIT_MODULE( mdb ) + +#endif /* SLAPD_MDB == SLAPD_MOD_DYNAMIC */ + diff --git a/servers/slapd/back-mdb/key.c b/servers/slapd/back-mdb/key.c new file mode 100644 index 0000000..1426816 --- /dev/null +++ b/servers/slapd/back-mdb/key.c @@ -0,0 +1,72 @@ +/* index.c - routines for dealing with attribute indexes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-mdb.h" +#include "idl.h" + +/* read a key */ +int +mdb_key_read( + Backend *be, + MDB_txn *txn, + MDB_dbi dbi, + struct berval *k, + ID *ids, + MDB_cursor **saved_cursor, + int get_flag +) +{ + int rc; + MDB_val key; +#ifndef MISALIGNED_OK + int kbuf[2]; +#endif + + Debug( LDAP_DEBUG_TRACE, "=> key_read\n", 0, 0, 0 ); + +#ifndef MISALIGNED_OK + if (k->bv_len & ALIGNER) { + key.mv_size = sizeof(kbuf); + key.mv_data = kbuf; + kbuf[1] = 0; + memcpy(kbuf, k->bv_val, k->bv_len); + } else +#endif + { + key.mv_size = k->bv_len; + key.mv_data = k->bv_val; + } + + rc = mdb_idl_fetch_key( be, txn, dbi, &key, ids, saved_cursor, get_flag ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "<= mdb_index_read: failed (%d)\n", + rc, 0, 0 ); + } else { + Debug( LDAP_DEBUG_TRACE, "<= mdb_index_read %ld candidates\n", + (long) MDB_IDL_N(ids), 0, 0 ); + } + + return rc; +} diff --git a/servers/slapd/back-mdb/modify.c b/servers/slapd/back-mdb/modify.c new file mode 100644 index 0000000..fa377af --- /dev/null +++ b/servers/slapd/back-mdb/modify.c @@ -0,0 +1,748 @@ +/* modify.c - mdb backend modify routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "back-mdb.h" + +static struct berval scbva[] = { + BER_BVC("glue"), + BER_BVNULL +}; + +static void +mdb_modify_idxflags( + Operation *op, + AttributeDescription *desc, + int got_delete, + Attribute *newattrs, + Attribute *oldattrs ) +{ + struct berval ix_at; + AttrInfo *ai; + + /* check if modified attribute was indexed + * but not in case of NOOP... */ + ai = mdb_index_mask( op->o_bd, desc, &ix_at ); + if ( ai ) { + if ( got_delete ) { + Attribute *ap; + struct berval ix2; + + ap = attr_find( oldattrs, desc ); + if ( ap ) ap->a_flags |= SLAP_ATTR_IXDEL; + + /* Find all other attrs that index to same slot */ + for ( ap = newattrs; ap; ap = ap->a_next ) { + ai = mdb_index_mask( op->o_bd, ap->a_desc, &ix2 ); + if ( ai && ix2.bv_val == ix_at.bv_val ) + ap->a_flags |= SLAP_ATTR_IXADD; + } + + } else { + Attribute *ap; + + ap = attr_find( newattrs, desc ); + if ( ap ) ap->a_flags |= SLAP_ATTR_IXADD; + } + } +} + +int mdb_modify_internal( + Operation *op, + MDB_txn *tid, + Modifications *modlist, + Entry *e, + const char **text, + char *textbuf, + size_t textlen ) +{ + int rc, err; + Modification *mod; + Modifications *ml; + Attribute *save_attrs; + Attribute *ap; + int glue_attr_delete = 0; + int got_delete; + + Debug( LDAP_DEBUG_TRACE, "mdb_modify_internal: 0x%08lx: %s\n", + e->e_id, e->e_dn, 0); + + if ( !acl_check_modlist( op, e, modlist )) { + return LDAP_INSUFFICIENT_ACCESS; + } + + /* save_attrs will be disposed of by caller */ + save_attrs = e->e_attrs; + e->e_attrs = attrs_dup( e->e_attrs ); + + for ( ml = modlist; ml != NULL; ml = ml->sml_next ) { + int match; + mod = &ml->sml_mod; + switch( mod->sm_op ) { + case LDAP_MOD_ADD: + case LDAP_MOD_REPLACE: + if ( mod->sm_desc == slap_schema.si_ad_structuralObjectClass ) { + value_match( &match, slap_schema.si_ad_structuralObjectClass, + slap_schema.si_ad_structuralObjectClass-> + ad_type->sat_equality, + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + &mod->sm_values[0], &scbva[0], text ); + if ( !match ) glue_attr_delete = 1; + } + } + if ( glue_attr_delete ) + break; + } + + if ( glue_attr_delete ) { + Attribute **app = &e->e_attrs; + while ( *app != NULL ) { + if ( !is_at_operational( (*app)->a_desc->ad_type )) { + Attribute *save = *app; + *app = (*app)->a_next; + attr_free( save ); + continue; + } + app = &(*app)->a_next; + } + } + + for ( ml = modlist; ml != NULL; ml = ml->sml_next ) { + mod = &ml->sml_mod; + got_delete = 0; + + switch ( mod->sm_op ) { + case LDAP_MOD_ADD: + Debug(LDAP_DEBUG_ARGS, + "mdb_modify_internal: add %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + err = modify_add_values( e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n", + err, *text, 0); + } + break; + + case LDAP_MOD_DELETE: + if ( glue_attr_delete ) { + err = LDAP_SUCCESS; + break; + } + + Debug(LDAP_DEBUG_ARGS, + "mdb_modify_internal: delete %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + err = modify_delete_values( e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n", + err, *text, 0); + } else { + got_delete = 1; + } + break; + + case LDAP_MOD_REPLACE: + Debug(LDAP_DEBUG_ARGS, + "mdb_modify_internal: replace %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + err = modify_replace_values( e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n", + err, *text, 0); + } else { + got_delete = 1; + } + break; + + case LDAP_MOD_INCREMENT: + Debug(LDAP_DEBUG_ARGS, + "mdb_modify_internal: increment %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + err = modify_increment_values( e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, + "mdb_modify_internal: %d %s\n", + err, *text, 0); + } else { + got_delete = 1; + } + break; + + case SLAP_MOD_SOFTADD: + Debug(LDAP_DEBUG_ARGS, + "mdb_modify_internal: softadd %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + /* Avoid problems in index_add_mods() + * We need to add index if necessary. + */ + mod->sm_op = LDAP_MOD_ADD; + + err = modify_add_values( e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + + mod->sm_op = SLAP_MOD_SOFTADD; + + if ( err == LDAP_TYPE_OR_VALUE_EXISTS ) { + err = LDAP_SUCCESS; + } + + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n", + err, *text, 0); + } + break; + + case SLAP_MOD_SOFTDEL: + Debug(LDAP_DEBUG_ARGS, + "mdb_modify_internal: softdel %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + /* Avoid problems in index_delete_mods() + * We need to add index if necessary. + */ + mod->sm_op = LDAP_MOD_DELETE; + + err = modify_delete_values( e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + + mod->sm_op = SLAP_MOD_SOFTDEL; + + if ( err == LDAP_SUCCESS ) { + got_delete = 1; + } else if ( err == LDAP_NO_SUCH_ATTRIBUTE ) { + err = LDAP_SUCCESS; + } + + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n", + err, *text, 0); + } + break; + + case SLAP_MOD_ADD_IF_NOT_PRESENT: + if ( attr_find( e->e_attrs, mod->sm_desc ) != NULL ) { + /* skip */ + err = LDAP_SUCCESS; + break; + } + + Debug(LDAP_DEBUG_ARGS, + "mdb_modify_internal: add_if_not_present %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + /* Avoid problems in index_add_mods() + * We need to add index if necessary. + */ + mod->sm_op = LDAP_MOD_ADD; + + err = modify_add_values( e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + + mod->sm_op = SLAP_MOD_ADD_IF_NOT_PRESENT; + + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n", + err, *text, 0); + } + break; + + default: + Debug(LDAP_DEBUG_ANY, "mdb_modify_internal: invalid op %d\n", + mod->sm_op, 0, 0); + *text = "Invalid modify operation"; + err = LDAP_OTHER; + Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n", + err, *text, 0); + } + + if ( err != LDAP_SUCCESS ) { + attrs_free( e->e_attrs ); + e->e_attrs = save_attrs; + /* unlock entry, delete from cache */ + return err; + } + + /* If objectClass was modified, reset the flags */ + if ( mod->sm_desc == slap_schema.si_ad_objectClass ) { + e->e_ocflags = 0; + } + + if ( glue_attr_delete ) e->e_ocflags = 0; + + + /* check if modified attribute was indexed + * but not in case of NOOP... */ + if ( !op->o_noop ) { + mdb_modify_idxflags( op, mod->sm_desc, got_delete, e->e_attrs, save_attrs ); + } + } + + /* check that the entry still obeys the schema */ + ap = NULL; + rc = entry_schema_check( op, e, save_attrs, get_relax(op), 0, &ap, + text, textbuf, textlen ); + if ( rc != LDAP_SUCCESS || op->o_noop ) { + attrs_free( e->e_attrs ); + /* clear the indexing flags */ + for ( ap = save_attrs; ap != NULL; ap = ap->a_next ) { + ap->a_flags &= ~(SLAP_ATTR_IXADD|SLAP_ATTR_IXDEL); + } + e->e_attrs = save_attrs; + + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "entry failed schema check: %s\n", + *text, 0, 0 ); + } + + /* if NOOP then silently revert to saved attrs */ + return rc; + } + + /* structuralObjectClass modified! */ + if ( ap ) { + assert( ap->a_desc == slap_schema.si_ad_structuralObjectClass ); + if ( !op->o_noop ) { + mdb_modify_idxflags( op, slap_schema.si_ad_structuralObjectClass, + 1, e->e_attrs, save_attrs ); + } + } + + /* update the indices of the modified attributes */ + + /* start with deleting the old index entries */ + for ( ap = save_attrs; ap != NULL; ap = ap->a_next ) { + if ( ap->a_flags & SLAP_ATTR_IXDEL ) { + struct berval *vals; + Attribute *a2; + ap->a_flags &= ~SLAP_ATTR_IXDEL; + a2 = attr_find( e->e_attrs, ap->a_desc ); + if ( a2 ) { + /* need to detect which values were deleted */ + int i, j; + /* let add know there were deletes */ + if ( a2->a_flags & SLAP_ATTR_IXADD ) + a2->a_flags |= SLAP_ATTR_IXDEL; + vals = op->o_tmpalloc( (ap->a_numvals + 1) * + sizeof(struct berval), op->o_tmpmemctx ); + j = 0; + for ( i=0; i < ap->a_numvals; i++ ) { + rc = attr_valfind( a2, SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + &ap->a_nvals[i], NULL, op->o_tmpmemctx ); + /* Save deleted values */ + if ( rc == LDAP_NO_SUCH_ATTRIBUTE ) + vals[j++] = ap->a_nvals[i]; + } + BER_BVZERO(vals+j); + } else { + /* attribute was completely deleted */ + vals = ap->a_nvals; + } + rc = 0; + if ( !BER_BVISNULL( vals )) { + rc = mdb_index_values( op, tid, ap->a_desc, + vals, e->e_id, SLAP_INDEX_DELETE_OP ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "%s: attribute \"%s\" index delete failure\n", + op->o_log_prefix, ap->a_desc->ad_cname.bv_val, 0 ); + attrs_free( e->e_attrs ); + e->e_attrs = save_attrs; + } + } + if ( vals != ap->a_nvals ) + op->o_tmpfree( vals, op->o_tmpmemctx ); + if ( rc ) return rc; + } + } + + /* add the new index entries */ + for ( ap = e->e_attrs; ap != NULL; ap = ap->a_next ) { + if (ap->a_flags & SLAP_ATTR_IXADD) { + ap->a_flags &= ~SLAP_ATTR_IXADD; + if ( ap->a_flags & SLAP_ATTR_IXDEL ) { + /* if any values were deleted, we must readd index + * for all remaining values. + */ + ap->a_flags &= ~SLAP_ATTR_IXDEL; + rc = mdb_index_values( op, tid, ap->a_desc, + ap->a_nvals, + e->e_id, SLAP_INDEX_ADD_OP ); + } else { + int found = 0; + /* if this was only an add, we only need to index + * the added values. + */ + for ( ml = modlist; ml != NULL; ml = ml->sml_next ) { + struct berval *vals; + if ( ml->sml_desc != ap->a_desc || !ml->sml_numvals ) + continue; + found = 1; + switch( ml->sml_op ) { + case LDAP_MOD_ADD: + case LDAP_MOD_REPLACE: + case LDAP_MOD_INCREMENT: + case SLAP_MOD_SOFTADD: + case SLAP_MOD_ADD_IF_NOT_PRESENT: + if ( ml->sml_op == LDAP_MOD_INCREMENT ) + vals = ap->a_nvals; + else if ( ml->sml_nvalues ) + vals = ml->sml_nvalues; + else + vals = ml->sml_values; + rc = mdb_index_values( op, tid, ap->a_desc, + vals, e->e_id, SLAP_INDEX_ADD_OP ); + break; + } + if ( rc ) + break; + } + /* This attr was affected by a modify of a subtype, so + * there was no direct match in the modlist. Just readd + * all of its values. + */ + if ( !found ) { + rc = mdb_index_values( op, tid, ap->a_desc, + ap->a_nvals, + e->e_id, SLAP_INDEX_ADD_OP ); + } + } + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "%s: attribute \"%s\" index add failure\n", + op->o_log_prefix, ap->a_desc->ad_cname.bv_val, 0 ); + attrs_free( e->e_attrs ); + e->e_attrs = save_attrs; + return rc; + } + } + } + + return rc; +} + + +int +mdb_modify( Operation *op, SlapReply *rs ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + Entry *e = NULL; + int manageDSAit = get_manageDSAit( op ); + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof textbuf; + MDB_txn *txn = NULL; + mdb_op_info opinfo = {{{ 0 }}}, *moi = &opinfo; + Entry dummy = {0}; + + LDAPControl **preread_ctrl = NULL; + LDAPControl **postread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + int numads = mdb->mi_numads; + +#ifdef LDAP_X_TXN + int settle = 0; +#endif + + Debug( LDAP_DEBUG_ARGS, LDAP_XSTRING(mdb_modify) ": %s\n", + op->o_req_dn.bv_val, 0, 0 ); + +#ifdef LDAP_X_TXN + if( op->o_txnSpec ) { + /* acquire connection lock */ + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + if( op->o_conn->c_txn == CONN_TXN_INACTIVE ) { + rs->sr_text = "invalid transaction identifier"; + rs->sr_err = LDAP_X_TXN_ID_INVALID; + goto txnReturn; + } else if( op->o_conn->c_txn == CONN_TXN_SETTLE ) { + settle=1; + goto txnReturn; + } + + if( op->o_conn->c_txn_backend == NULL ) { + op->o_conn->c_txn_backend = op->o_bd; + + } else if( op->o_conn->c_txn_backend != op->o_bd ) { + rs->sr_text = "transaction cannot span multiple database contexts"; + rs->sr_err = LDAP_AFFECTS_MULTIPLE_DSAS; + goto txnReturn; + } + + /* insert operation into transaction */ + + rs->sr_text = "transaction specified"; + rs->sr_err = LDAP_X_TXN_SPECIFY_OKAY; + +txnReturn: + /* release connection lock */ + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + + if( !settle ) { + send_ldap_result( op, rs ); + return rs->sr_err; + } + } +#endif + + ctrls[num_ctrls] = NULL; + + /* begin transaction */ + rs->sr_err = mdb_opinfo_get( op, mdb, 0, &moi ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modify) ": txn_begin failed: " + "%s (%d)\n", mdb_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + txn = moi->moi_txn; + + /* Don't touch the opattrs, if this is a contextCSN update + * initiated from updatedn */ + if ( !be_isupdate(op) || !op->orm_modlist || op->orm_modlist->sml_next || + op->orm_modlist->sml_desc != slap_schema.si_ad_contextCSN ) { + + slap_mods_opattrs( op, &op->orm_modlist, 1 ); + } + + /* get entry or ancestor */ + rs->sr_err = mdb_dn2entry( op, txn, NULL, &op->o_req_ndn, &e, NULL, 1 ); + + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modify) ": dn2entry failed (%d)\n", + rs->sr_err, 0, 0 ); + switch( rs->sr_err ) { + case MDB_NOTFOUND: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + } + + /* acquire and lock entry */ + /* FIXME: dn2entry() should return non-glue entry */ + if (( rs->sr_err == MDB_NOTFOUND ) || + ( !manageDSAit && e && is_entry_glue( e ))) + { + if ( e != NULL ) { + rs->sr_matched = ch_strdup( e->e_dn ); + if ( is_entry_referral( e )) { + BerVarray ref = get_entry_referrals( op, e ); + rs->sr_ref = referral_rewrite( ref, &e->e_name, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + ber_bvarray_free( ref ); + } else { + rs->sr_ref = NULL; + } + mdb_entry_return( op, e ); + e = NULL; + + } else { + rs->sr_ref = referral_rewrite( default_referral, NULL, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + } + + rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED; + rs->sr_err = LDAP_REFERRAL; + send_ldap_result( op, rs ); + goto done; + } + + if ( !manageDSAit && is_entry_referral( e ) ) { + /* entry is a referral, don't allow modify */ + rs->sr_ref = get_entry_referrals( op, e ); + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modify) ": entry is referral\n", + 0, 0, 0 ); + + rs->sr_err = LDAP_REFERRAL; + rs->sr_matched = e->e_name.bv_val; + rs->sr_flags = REP_REF_MUSTBEFREED; + send_ldap_result( op, rs ); + rs->sr_matched = NULL; + goto done; + } + + if ( get_assert( op ) && + ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto return_results; + } + + if( op->o_preread ) { + if( preread_ctrl == NULL ) { + preread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if ( slap_read_controls( op, rs, e, + &slap_pre_read_bv, preread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_modify) ": pre-read " + "failed!\n", 0, 0, 0 ); + if ( op->o_preread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + /* Modify the entry */ + dummy = *e; + rs->sr_err = mdb_modify_internal( op, txn, op->orm_modlist, + &dummy, &rs->sr_text, textbuf, textlen ); + + if( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modify) ": modify failed (%d)\n", + rs->sr_err, 0, 0 ); + /* Only free attrs if they were dup'd. */ + if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL; + goto return_results; + } + + /* change the entry itself */ + rs->sr_err = mdb_id2entry_update( op, txn, NULL, &dummy ); + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modify) ": id2entry update failed " "(%d)\n", + rs->sr_err, 0, 0 ); + rs->sr_text = "entry update failed"; + goto return_results; + } + + if( op->o_postread ) { + if( postread_ctrl == NULL ) { + postread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if( slap_read_controls( op, rs, &dummy, + &slap_post_read_bv, postread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_modify) + ": post-read failed!\n", 0, 0, 0 ); + if ( op->o_postread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + /* Only free attrs if they were dup'd. */ + if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL; + if( moi == &opinfo ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next ); + opinfo.moi_oe.oe_key = NULL; + if( op->o_noop ) { + mdb->mi_numads = numads; + mdb_txn_abort( txn ); + rs->sr_err = LDAP_X_NO_OPERATION; + txn = NULL; + goto return_results; + } else { + rs->sr_err = mdb_txn_commit( txn ); + if ( rs->sr_err ) + mdb->mi_numads = numads; + txn = NULL; + } + } + + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_modify) ": txn_%s failed: %s (%d)\n", + op->o_noop ? "abort (no-op)" : "commit", + mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "commit failed"; + + goto return_results; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modify) ": updated%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + dummy.e_id, op->o_req_dn.bv_val ); + + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + if( num_ctrls ) rs->sr_ctrls = ctrls; + +return_results: + if( dummy.e_attrs ) { + attrs_free( dummy.e_attrs ); + } + send_ldap_result( op, rs ); + +#if 0 + if( rs->sr_err == LDAP_SUCCESS && mdb->bi_txn_cp_kbyte ) { + TXN_CHECKPOINT( mdb->bi_dbenv, + mdb->bi_txn_cp_kbyte, mdb->bi_txn_cp_min, 0 ); + } +#endif + +done: + slap_graduate_commit_csn( op ); + + if( moi == &opinfo ) { + if( txn != NULL ) { + mdb->mi_numads = numads; + mdb_txn_abort( txn ); + } + if ( opinfo.moi_oe.oe_key ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next ); + } + } else { + moi->moi_ref--; + } + + if( e != NULL ) { + mdb_entry_return( op, e ); + } + + if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) { + slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *preread_ctrl, op->o_tmpmemctx ); + } + if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) { + slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *postread_ctrl, op->o_tmpmemctx ); + } + + rs->sr_text = NULL; + + return rs->sr_err; +} diff --git a/servers/slapd/back-mdb/modrdn.c b/servers/slapd/back-mdb/modrdn.c new file mode 100644 index 0000000..018d7aa --- /dev/null +++ b/servers/slapd/back-mdb/modrdn.c @@ -0,0 +1,672 @@ +/* modrdn.c - mdb backend modrdn routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" + +int +mdb_modrdn( Operation *op, SlapReply *rs ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + AttributeDescription *children = slap_schema.si_ad_children; + AttributeDescription *entry = slap_schema.si_ad_entry; + struct berval p_dn, p_ndn; + struct berval new_dn = {0, NULL}, new_ndn = {0, NULL}; + Entry *e = NULL; + Entry *p = NULL; + /* LDAP v2 supporting correct attribute handling. */ + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof textbuf; + MDB_txn *txn = NULL; + MDB_cursor *mc; + struct mdb_op_info opinfo = {{{ 0 }}}, *moi = &opinfo; + Entry dummy = {0}; + + Entry *np = NULL; /* newSuperior Entry */ + struct berval *np_dn = NULL; /* newSuperior dn */ + struct berval *np_ndn = NULL; /* newSuperior ndn */ + struct berval *new_parent_dn = NULL; /* np_dn, p_dn, or NULL */ + + int manageDSAit = get_manageDSAit( op ); + + ID nid, nsubs; + LDAPControl **preread_ctrl = NULL; + LDAPControl **postread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + + int parent_is_glue = 0; + int parent_is_leaf = 0; + +#ifdef LDAP_X_TXN + int settle = 0; +#endif + + Debug( LDAP_DEBUG_TRACE, "==>" LDAP_XSTRING(mdb_modrdn) "(%s,%s,%s)\n", + op->o_req_dn.bv_val,op->oq_modrdn.rs_newrdn.bv_val, + op->oq_modrdn.rs_newSup ? op->oq_modrdn.rs_newSup->bv_val : "NULL" ); + +#ifdef LDAP_X_TXN + if( op->o_txnSpec ) { + /* acquire connection lock */ + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + if( op->o_conn->c_txn == CONN_TXN_INACTIVE ) { + rs->sr_text = "invalid transaction identifier"; + rs->sr_err = LDAP_X_TXN_ID_INVALID; + goto txnReturn; + } else if( op->o_conn->c_txn == CONN_TXN_SETTLE ) { + settle=1; + goto txnReturn; + } + + if( op->o_conn->c_txn_backend == NULL ) { + op->o_conn->c_txn_backend = op->o_bd; + + } else if( op->o_conn->c_txn_backend != op->o_bd ) { + rs->sr_text = "transaction cannot span multiple database contexts"; + rs->sr_err = LDAP_AFFECTS_MULTIPLE_DSAS; + goto txnReturn; + } + + /* insert operation into transaction */ + + rs->sr_text = "transaction specified"; + rs->sr_err = LDAP_X_TXN_SPECIFY_OKAY; + +txnReturn: + /* release connection lock */ + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + + if( !settle ) { + send_ldap_result( op, rs ); + return rs->sr_err; + } + } +#endif + + ctrls[num_ctrls] = NULL; + + /* begin transaction */ + rs->sr_err = mdb_opinfo_get( op, mdb, 0, &moi ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) ": txn_begin failed: " + "%s (%d)\n", mdb_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + txn = moi->moi_txn; + + slap_mods_opattrs( op, &op->orr_modlist, 1 ); + + if ( be_issuffix( op->o_bd, &op->o_req_ndn ) ) { +#ifdef MDB_MULTIPLE_SUFFIXES + /* Allow renaming one suffix entry to another */ + p_ndn = slap_empty_bv; +#else + /* There can only be one suffix entry */ + rs->sr_err = LDAP_NAMING_VIOLATION; + rs->sr_text = "cannot rename suffix entry"; + goto return_results; +#endif + } else { + dnParent( &op->o_req_ndn, &p_ndn ); + } + np_ndn = &p_ndn; + /* Make sure parent entry exist and we can write its + * children. + */ + rs->sr_err = mdb_cursor_open( txn, mdb->mi_dn2id, &mc ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_modrdn) + ": cursor_open failed: %s (%d)\n", + mdb_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "DN cursor_open failed"; + goto return_results; + } + rs->sr_err = mdb_dn2entry( op, txn, mc, &p_ndn, &p, NULL, 0 ); + switch( rs->sr_err ) { + case MDB_NOTFOUND: + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(mdb_modrdn) + ": parent does not exist\n", 0, 0, 0); + rs->sr_ref = referral_rewrite( default_referral, NULL, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + rs->sr_err = LDAP_REFERRAL; + + send_ldap_result( op, rs ); + + ber_bvarray_free( rs->sr_ref ); + goto done; + case 0: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + /* check parent for "children" acl */ + rs->sr_err = access_allowed( op, p, + children, NULL, + op->oq_modrdn.rs_newSup == NULL ? + ACL_WRITE : ACL_WDEL, + NULL ); + + if ( ! rs->sr_err ) { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + Debug( LDAP_DEBUG_TRACE, "no access to parent\n", 0, + 0, 0 ); + rs->sr_text = "no write access to parent's children"; + goto return_results; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) ": wr to children " + "of entry %s OK\n", p_ndn.bv_val, 0, 0 ); + + if ( p_ndn.bv_val == slap_empty_bv.bv_val ) { + p_dn = slap_empty_bv; + } else { + dnParent( &op->o_req_dn, &p_dn ); + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) ": parent dn=%s\n", + p_dn.bv_val, 0, 0 ); + + /* get entry */ + rs->sr_err = mdb_dn2entry( op, txn, mc, &op->o_req_ndn, &e, &nsubs, 0 ); + switch( rs->sr_err ) { + case MDB_NOTFOUND: + e = p; + p = NULL; + case 0: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + /* FIXME: dn2entry() should return non-glue entry */ + if (( rs->sr_err == MDB_NOTFOUND ) || + ( !manageDSAit && e && is_entry_glue( e ))) + { + if( e != NULL ) { + rs->sr_matched = ch_strdup( e->e_dn ); + if ( is_entry_referral( e )) { + BerVarray ref = get_entry_referrals( op, e ); + rs->sr_ref = referral_rewrite( ref, &e->e_name, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + ber_bvarray_free( ref ); + } else { + rs->sr_ref = NULL; + } + mdb_entry_return( op, e ); + e = NULL; + + } else { + rs->sr_ref = referral_rewrite( default_referral, NULL, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + } + + rs->sr_err = LDAP_REFERRAL; + send_ldap_result( op, rs ); + + ber_bvarray_free( rs->sr_ref ); + free( (char *)rs->sr_matched ); + rs->sr_ref = NULL; + rs->sr_matched = NULL; + + goto done; + } + + if ( get_assert( op ) && + ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto return_results; + } + + /* check write on old entry */ + rs->sr_err = access_allowed( op, e, entry, NULL, ACL_WRITE, NULL ); + if ( ! rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, "no access to entry\n", 0, + 0, 0 ); + rs->sr_text = "no write access to old entry"; + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto return_results; + } + + if (!manageDSAit && is_entry_referral( e ) ) { + /* entry is a referral, don't allow rename */ + rs->sr_ref = get_entry_referrals( op, e ); + + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(mdb_modrdn) + ": entry %s is referral\n", e->e_dn, 0, 0 ); + + rs->sr_err = LDAP_REFERRAL, + rs->sr_matched = e->e_name.bv_val; + send_ldap_result( op, rs ); + + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + rs->sr_matched = NULL; + goto done; + } + + new_parent_dn = &p_dn; /* New Parent unless newSuperior given */ + + if ( op->oq_modrdn.rs_newSup != NULL ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) + ": new parent \"%s\" requested...\n", + op->oq_modrdn.rs_newSup->bv_val, 0, 0 ); + + /* newSuperior == oldParent? */ + if( dn_match( &p_ndn, op->oq_modrdn.rs_nnewSup ) ) { + Debug( LDAP_DEBUG_TRACE, "mdb_back_modrdn: " + "new parent \"%s\" same as the old parent \"%s\"\n", + op->oq_modrdn.rs_newSup->bv_val, p_dn.bv_val, 0 ); + op->oq_modrdn.rs_newSup = NULL; /* ignore newSuperior */ + } + } + + /* There's a MDB_MULTIPLE_SUFFIXES case here that this code doesn't + * support. E.g., two suffixes dc=foo,dc=com and dc=bar,dc=net. + * We do not allow modDN + * dc=foo,dc=com + * newrdn dc=bar + * newsup dc=net + * and we probably should. But since MULTIPLE_SUFFIXES is deprecated + * I'm ignoring this problem for now. + */ + if ( op->oq_modrdn.rs_newSup != NULL ) { + if ( op->oq_modrdn.rs_newSup->bv_len ) { + np_dn = op->oq_modrdn.rs_newSup; + np_ndn = op->oq_modrdn.rs_nnewSup; + + /* newSuperior == oldParent? - checked above */ + /* newSuperior == entry being moved?, if so ==> ERROR */ + if ( dnIsSuffix( np_ndn, &e->e_nname )) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = "new superior not found"; + goto return_results; + } + /* Get Entry with dn=newSuperior. Does newSuperior exist? */ + rs->sr_err = mdb_dn2entry( op, txn, NULL, np_ndn, &np, NULL, 0 ); + + switch( rs->sr_err ) { + case 0: + break; + case MDB_NOTFOUND: + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) + ": newSup(ndn=%s) not here!\n", + np_ndn->bv_val, 0, 0); + rs->sr_text = "new superior not found"; + rs->sr_err = LDAP_NO_SUCH_OBJECT; + goto return_results; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + /* check newSuperior for "children" acl */ + rs->sr_err = access_allowed( op, np, children, + NULL, ACL_WADD, NULL ); + + if( ! rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) + ": no wr to newSup children\n", + 0, 0, 0 ); + rs->sr_text = "no write access to new superior's children"; + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto return_results; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) + ": wr to new parent OK np=%p, id=%ld\n", + (void *) np, (long) np->e_id, 0 ); + + if ( is_entry_alias( np ) ) { + /* parent is an alias, don't allow add */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) + ": entry is alias\n", + 0, 0, 0 ); + rs->sr_text = "new superior is an alias"; + rs->sr_err = LDAP_ALIAS_PROBLEM; + goto return_results; + } + + if ( is_entry_referral( np ) ) { + /* parent is a referral, don't allow add */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) + ": entry is referral\n", + 0, 0, 0 ); + rs->sr_text = "new superior is a referral"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + np_dn = &np->e_name; + + } else { + np_dn = NULL; + + /* no parent, modrdn entry directly under root */ + if ( be_issuffix( op->o_bd, (struct berval *)&slap_empty_bv ) + || be_isupdate( op ) ) { + np = (Entry *)&slap_entry_root; + + /* check parent for "children" acl */ + rs->sr_err = access_allowed( op, np, + children, NULL, ACL_WADD, NULL ); + + np = NULL; + + if ( ! rs->sr_err ) { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + Debug( LDAP_DEBUG_TRACE, + "no access to new superior\n", + 0, 0, 0 ); + rs->sr_text = + "no write access to new superior's children"; + goto return_results; + } + } + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) + ": wr to new parent's children OK\n", + 0, 0, 0 ); + + new_parent_dn = np_dn; + } + + /* Build target dn and make sure target entry doesn't exist already. */ + if (!new_dn.bv_val) { + build_new_dn( &new_dn, new_parent_dn, &op->oq_modrdn.rs_newrdn, op->o_tmpmemctx ); + } + + if (!new_ndn.bv_val) { + dnNormalize( 0, NULL, NULL, &new_dn, &new_ndn, op->o_tmpmemctx ); + } + + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(mdb_modrdn) ": new ndn=%s\n", + new_ndn.bv_val, 0, 0 ); + + /* Shortcut the search */ + rs->sr_err = mdb_dn2id ( op, txn, NULL, &new_ndn, &nid, NULL, NULL, NULL ); + switch( rs->sr_err ) { + case MDB_NOTFOUND: + break; + case 0: + /* Allow rename to same DN */ + if ( nid == e->e_id ) + break; + rs->sr_err = LDAP_ALREADY_EXISTS; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + assert( op->orr_modlist != NULL ); + + if( op->o_preread ) { + if( preread_ctrl == NULL ) { + preread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if( slap_read_controls( op, rs, e, + &slap_pre_read_bv, preread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_modrdn) + ": pre-read failed!\n", 0, 0, 0 ); + if ( op->o_preread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + /* delete old DN + * If moving to a new parent, must delete current subtree count, + * otherwise leave it unchanged since we'll be adding it right back. + */ + rs->sr_err = mdb_dn2id_delete( op, mc, e->e_id, np ? nsubs : 0 ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_modrdn) + ": dn2id del failed: %s (%d)\n", + mdb_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "DN index delete fail"; + goto return_results; + } + + /* copy the entry, then override some fields */ + dummy = *e; + dummy.e_name = new_dn; + dummy.e_nname = new_ndn; + dummy.e_attrs = NULL; + + /* add new DN */ + rs->sr_err = mdb_dn2id_add( op, mc, mc, np ? np->e_id : p->e_id, + nsubs, np != NULL, &dummy ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_modrdn) + ": dn2id add failed: %s (%d)\n", + mdb_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "DN index add failed"; + goto return_results; + } + + dummy.e_attrs = e->e_attrs; + + /* modify entry */ + rs->sr_err = mdb_modify_internal( op, txn, op->orr_modlist, &dummy, + &rs->sr_text, textbuf, textlen ); + if( rs->sr_err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_modrdn) + ": modify failed: %s (%d)\n", + mdb_strerror(rs->sr_err), rs->sr_err, 0 ); + if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL; + goto return_results; + } + + /* id2entry index */ + rs->sr_err = mdb_id2entry_update( op, txn, NULL, &dummy ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_modrdn) + ": id2entry failed: %s (%d)\n", + mdb_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "entry update failed"; + goto return_results; + } + + if ( p_ndn.bv_len != 0 ) { + if ((parent_is_glue = is_entry_glue(p))) { + rs->sr_err = mdb_dn2id_children( op, txn, p ); + if ( rs->sr_err != MDB_NOTFOUND ) { + switch( rs->sr_err ) { + case 0: + break; + default: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(mdb_modrdn) + ": has_children failed: %s (%d)\n", + mdb_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + } else { + parent_is_leaf = 1; + } + } + mdb_entry_return( op, p ); + p = NULL; + } + + if( op->o_postread ) { + if( postread_ctrl == NULL ) { + postread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if( slap_read_controls( op, rs, &dummy, + &slap_post_read_bv, postread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_modrdn) + ": post-read failed!\n", 0, 0, 0 ); + if ( op->o_postread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + if( moi == &opinfo ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next ); + opinfo.moi_oe.oe_key = NULL; + if( op->o_noop ) { + mdb_txn_abort( txn ); + rs->sr_err = LDAP_X_NO_OPERATION; + txn = NULL; + /* Only free attrs if they were dup'd. */ + if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL; + goto return_results; + + } else { + if(( rs->sr_err=mdb_txn_commit( txn )) != 0 ) { + rs->sr_text = "txn_commit failed"; + } else { + rs->sr_err = LDAP_SUCCESS; + } + txn = NULL; + } + } + + if( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_modrdn) ": %s : %s (%d)\n", + rs->sr_text, mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + + goto return_results; + } + + Debug(LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) + ": rdn modified%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + dummy.e_id, op->o_req_dn.bv_val ); + rs->sr_text = NULL; + if( num_ctrls ) rs->sr_ctrls = ctrls; + +return_results: + if ( dummy.e_attrs ) { + attrs_free( dummy.e_attrs ); + } + send_ldap_result( op, rs ); + +#if 0 + if( rs->sr_err == LDAP_SUCCESS && mdb->bi_txn_cp_kbyte ) { + TXN_CHECKPOINT( mdb->bi_dbenv, + mdb->bi_txn_cp_kbyte, mdb->bi_txn_cp_min, 0 ); + } +#endif + + if ( rs->sr_err == LDAP_SUCCESS && parent_is_glue && parent_is_leaf ) { + op->o_delete_glue_parent = 1; + } + +done: + slap_graduate_commit_csn( op ); + + if( new_ndn.bv_val != NULL ) op->o_tmpfree( new_ndn.bv_val, op->o_tmpmemctx ); + if( new_dn.bv_val != NULL ) op->o_tmpfree( new_dn.bv_val, op->o_tmpmemctx ); + + /* LDAP v3 Support */ + if( np != NULL ) { + /* free new parent */ + mdb_entry_return( op, np ); + } + + if( p != NULL ) { + /* free parent */ + mdb_entry_return( op, p ); + } + + /* free entry */ + if( e != NULL ) { + mdb_entry_return( op, e ); + } + + if( moi == &opinfo ) { + if( txn != NULL ) { + mdb_txn_abort( txn ); + } + if ( opinfo.moi_oe.oe_key ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next ); + } + } else { + moi->moi_ref--; + } + + if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) { + slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *preread_ctrl, op->o_tmpmemctx ); + } + if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) { + slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *postread_ctrl, op->o_tmpmemctx ); + } + return rs->sr_err; +} diff --git a/servers/slapd/back-mdb/monitor.c b/servers/slapd/back-mdb/monitor.c new file mode 100644 index 0000000..c472441 --- /dev/null +++ b/servers/slapd/back-mdb/monitor.c @@ -0,0 +1,807 @@ +/* monitor.c - monitor mdb backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/unistd.h> +#include <ac/stdlib.h> +#include <ac/errno.h> +#include <sys/stat.h> +#include "lutil.h" +#include "back-mdb.h" + +#include "../back-monitor/back-monitor.h" + +#include "config.h" + +static ObjectClass *oc_olmMDBDatabase; + +static AttributeDescription *ad_olmDbDirectory; + +#ifdef MDB_MONITOR_IDX +static int +mdb_monitor_idx_entry_add( + struct mdb_info *mdb, + Entry *e ); + +static AttributeDescription *ad_olmDbNotIndexed; +#endif /* MDB_MONITOR_IDX */ + +static AttributeDescription *ad_olmMDBPagesMax, + *ad_olmMDBPagesUsed, *ad_olmMDBPagesFree; + +static AttributeDescription *ad_olmMDBReadersMax, + *ad_olmMDBReadersUsed; + +static AttributeDescription *ad_olmMDBEntries; + +/* + * NOTE: there's some confusion in monitor OID arc; + * by now, let's consider: + * + * Subsystems monitor attributes 1.3.6.1.4.1.4203.666.1.55.0 + * Databases monitor attributes 1.3.6.1.4.1.4203.666.1.55.0.1 + * MDB database monitor attributes 1.3.6.1.4.1.4203.666.1.55.0.1.3 + * + * Subsystems monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0 + * Databases monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0.1 + * MDB database monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0.1.3 + */ + +static struct { + char *name; + char *oid; +} s_oid[] = { + { "olmMDBAttributes", "olmDatabaseAttributes:1" }, + { "olmMDBObjectClasses", "olmDatabaseObjectClasses:1" }, + + { NULL } +}; + +static struct { + char *desc; + AttributeDescription **ad; +} s_at[] = { + { "( olmDatabaseAttributes:1 " + "NAME ( 'olmDbDirectory' ) " + "DESC 'Path name of the directory " + "where the database environment resides' " + "SUP monitoredInfo " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmDbDirectory }, + +#ifdef MDB_MONITOR_IDX + { "( olmDatabaseAttributes:2 " + "NAME ( 'olmDbNotIndexed' ) " + "DESC 'Missing indexes resulting from candidate selection' " + "SUP monitoredInfo " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmDbNotIndexed }, +#endif /* MDB_MONITOR_IDX */ + + { "( olmMDBAttributes:1 " + "NAME ( 'olmMDBPagesMax' ) " + "DESC 'Maximum number of pages' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmMDBPagesMax }, + + { "( olmMDBAttributes:2 " + "NAME ( 'olmMDBPagesUsed' ) " + "DESC 'Number of pages in use' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmMDBPagesUsed }, + + { "( olmMDBAttributes:3 " + "NAME ( 'olmMDBPagesFree' ) " + "DESC 'Number of free pages' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmMDBPagesFree }, + + { "( olmMDBAttributes:4 " + "NAME ( 'olmMDBReadersMax' ) " + "DESC 'Maximum number of readers' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmMDBReadersMax }, + + { "( olmMDBAttributes:5 " + "NAME ( 'olmMDBReadersUsed' ) " + "DESC 'Number of readers in use' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmMDBReadersUsed }, + + { "( olmMDBAttributes:6 " + "NAME ( 'olmMDBEntries' ) " + "DESC 'Number of entries in DB' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmMDBEntries }, + { NULL } +}; + +static struct { + char *desc; + ObjectClass **oc; +} s_oc[] = { + /* augments an existing object, so it must be AUXILIARY + * FIXME: derive from some ABSTRACT "monitoredEntity"? */ + { "( olmMDBObjectClasses:2 " + "NAME ( 'olmMDBDatabase' ) " + "SUP top AUXILIARY " + "MAY ( " + "olmDbDirectory " +#ifdef MDB_MONITOR_IDX + "$ olmDbNotIndexed " +#endif /* MDB_MONITOR_IDX */ + "$ olmMDBPagesMax $ olmMDBPagesUsed $ olmMDBPagesFree " + "$ olmMDBReadersMax $ olmMDBReadersUsed $ olmMDBEntries " + ") )", + &oc_olmMDBDatabase }, + + { NULL } +}; + +static int +mdb_monitor_update( + Operation *op, + SlapReply *rs, + Entry *e, + void *priv ) +{ + struct mdb_info *mdb = (struct mdb_info *) priv; + Attribute *a; + char buf[ BUFSIZ ]; + struct berval bv; + MDB_stat mst; + MDB_envinfo mei; + MDB_txn *txn; + int rc; + +#ifdef MDB_MONITOR_IDX + + mdb_monitor_idx_entry_add( mdb, e ); +#endif /* MDB_MONITOR_IDX */ + + mdb_env_stat( mdb->mi_dbenv, &mst ); + mdb_env_info( mdb->mi_dbenv, &mei ); + + a = attr_find( e->e_attrs, ad_olmMDBPagesMax ); + assert( a != NULL ); + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", mei.me_mapsize / mst.ms_psize ); + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + + a = attr_find( e->e_attrs, ad_olmMDBPagesUsed ); + assert( a != NULL ); + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", mei.me_last_pgno+1 ); + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + + a = attr_find( e->e_attrs, ad_olmMDBReadersMax ); + assert( a != NULL ); + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", mei.me_maxreaders ); + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + + a = attr_find( e->e_attrs, ad_olmMDBReadersUsed ); + assert( a != NULL ); + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", mei.me_numreaders ); + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, MDB_RDONLY, &txn ); + if ( !rc ) { + MDB_cursor *cursor; + MDB_val key, data; + size_t pages = 0, *iptr; + + rc = mdb_cursor_open( txn, 0, &cursor ); + if ( !rc ) { + while (( rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT )) == 0 ) { + iptr = data.mv_data; + pages += *iptr; + } + mdb_cursor_close( cursor ); + } + + mdb_stat( txn, mdb->mi_id2entry, &mst ); + a = attr_find( e->e_attrs, ad_olmMDBEntries ); + assert( a != NULL ); + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", mst.ms_entries ); + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + + mdb_txn_abort( txn ); + + a = attr_find( e->e_attrs, ad_olmMDBPagesFree ); + assert( a != NULL ); + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", pages ); + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + } + return SLAP_CB_CONTINUE; +} + +#if 0 /* uncomment if required */ +static int +mdb_monitor_modify( + Operation *op, + SlapReply *rs, + Entry *e, + void *priv ) +{ + return SLAP_CB_CONTINUE; +} +#endif + +static int +mdb_monitor_free( + Entry *e, + void **priv ) +{ + struct berval values[ 2 ]; + Modification mod = { 0 }; + + const char *text; + char textbuf[ SLAP_TEXT_BUFLEN ]; + + int i, rc; + + /* NOTE: if slap_shutdown != 0, priv might have already been freed */ + *priv = NULL; + + /* Remove objectClass */ + mod.sm_op = LDAP_MOD_DELETE; + mod.sm_desc = slap_schema.si_ad_objectClass; + mod.sm_values = values; + mod.sm_numvals = 1; + values[ 0 ] = oc_olmMDBDatabase->soc_cname; + BER_BVZERO( &values[ 1 ] ); + + rc = modify_delete_values( e, &mod, 1, &text, + textbuf, sizeof( textbuf ) ); + /* don't care too much about return code... */ + + /* remove attrs */ + mod.sm_values = NULL; + mod.sm_numvals = 0; + for ( i = 0; s_at[ i ].desc != NULL; i++ ) { + mod.sm_desc = *s_at[ i ].ad; + rc = modify_delete_values( e, &mod, 1, &text, + textbuf, sizeof( textbuf ) ); + /* don't care too much about return code... */ + } + + return SLAP_CB_CONTINUE; +} + +/* + * call from within mdb_initialize() + */ +static int +mdb_monitor_initialize( void ) +{ + int i, code; + ConfigArgs c; + char *argv[ 3 ]; + + static int mdb_monitor_initialized = 0; + + /* set to 0 when successfully initialized; otherwise, remember failure */ + static int mdb_monitor_initialized_failure = 1; + + if ( mdb_monitor_initialized++ ) { + return mdb_monitor_initialized_failure; + } + + if ( backend_info( "monitor" ) == NULL ) { + return -1; + } + + /* register schema here */ + + argv[ 0 ] = "back-mdb monitor"; + c.argv = argv; + c.argc = 3; + c.fname = argv[0]; + + for ( i = 0; s_oid[ i ].name; i++ ) { + c.lineno = i; + argv[ 1 ] = s_oid[ i ].name; + argv[ 2 ] = s_oid[ i ].oid; + + if ( parse_oidm( &c, 0, NULL ) != 0 ) { + Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(mdb_monitor_initialize) + ": unable to add " + "objectIdentifier \"%s=%s\"\n", + s_oid[ i ].name, s_oid[ i ].oid, 0 ); + return 2; + } + } + + for ( i = 0; s_at[ i ].desc != NULL; i++ ) { + code = register_at( s_at[ i ].desc, s_at[ i ].ad, 1 ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(mdb_monitor_initialize) + ": register_at failed for attributeType (%s)\n", + s_at[ i ].desc, 0, 0 ); + return 3; + + } else { + (*s_at[ i ].ad)->ad_type->sat_flags |= SLAP_AT_HIDE; + } + } + + for ( i = 0; s_oc[ i ].desc != NULL; i++ ) { + code = register_oc( s_oc[ i ].desc, s_oc[ i ].oc, 1 ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(mdb_monitor_initialize) + ": register_oc failed for objectClass (%s)\n", + s_oc[ i ].desc, 0, 0 ); + return 4; + + } else { + (*s_oc[ i ].oc)->soc_flags |= SLAP_OC_HIDE; + } + } + + return ( mdb_monitor_initialized_failure = LDAP_SUCCESS ); +} + +/* + * call from within mdb_db_init() + */ +int +mdb_monitor_db_init( BackendDB *be ) +{ +#ifdef MDB_MONITOR_IDX + struct mdb_info *mdb = (struct mdb_info *) be->be_private; +#endif /* MDB_MONITOR_IDX */ + + if ( mdb_monitor_initialize() == LDAP_SUCCESS ) { + /* monitoring in back-mdb is on by default */ + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_MONITORING; + } + +#ifdef MDB_MONITOR_IDX + mdb->mi_idx = NULL; + ldap_pvt_thread_mutex_init( &mdb->mi_idx_mutex ); +#endif /* MDB_MONITOR_IDX */ + + return 0; +} + +/* + * call from within mdb_db_open() + */ +int +mdb_monitor_db_open( BackendDB *be ) +{ + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + Attribute *a, *next; + monitor_callback_t *cb = NULL; + int rc = 0; + BackendInfo *mi; + monitor_extra_t *mbe; + + if ( !SLAP_DBMONITORING( be ) ) { + return 0; + } + + mi = backend_info( "monitor" ); + if ( !mi || !mi->bi_extra ) { + SLAP_DBFLAGS( be ) ^= SLAP_DBFLAG_MONITORING; + return 0; + } + mbe = mi->bi_extra; + + /* don't bother if monitor is not configured */ + if ( !mbe->is_configured() ) { + static int warning = 0; + + if ( warning++ == 0 ) { + Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(mdb_monitor_db_open) + ": monitoring disabled; " + "configure monitor database to enable\n", + 0, 0, 0 ); + } + + return 0; + } + + /* alloc as many as required (plus 1 for objectClass) */ + a = attrs_alloc( 1 + 7 ); + if ( a == NULL ) { + rc = 1; + goto cleanup; + } + + a->a_desc = slap_schema.si_ad_objectClass; + attr_valadd( a, &oc_olmMDBDatabase->soc_cname, NULL, 1 ); + next = a->a_next; + + { + struct berval bv = BER_BVC( "0" ); + + next->a_desc = ad_olmMDBPagesMax; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + + next->a_desc = ad_olmMDBPagesUsed; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + + next->a_desc = ad_olmMDBPagesFree; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + + next->a_desc = ad_olmMDBReadersMax; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + + next->a_desc = ad_olmMDBReadersUsed; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + + next->a_desc = ad_olmMDBEntries; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + } + + { + struct berval bv, nbv; + ber_len_t pathlen = 0, len = 0; + char path[ MAXPATHLEN ] = { '\0' }; + char *fname = mdb->mi_dbenv_home, + *ptr; + + len = strlen( fname ); + if ( fname[ 0 ] != '/' ) { + /* get full path name */ + getcwd( path, sizeof( path ) ); + pathlen = strlen( path ); + + if ( fname[ 0 ] == '.' && fname[ 1 ] == '/' ) { + fname += 2; + len -= 2; + } + } + + bv.bv_len = pathlen + STRLENOF( "/" ) + len; + ptr = bv.bv_val = ch_malloc( bv.bv_len + STRLENOF( "/" ) + 1 ); + if ( pathlen ) { + ptr = lutil_strncopy( ptr, path, pathlen ); + ptr[ 0 ] = '/'; + ptr++; + } + ptr = lutil_strncopy( ptr, fname, len ); + if ( ptr[ -1 ] != '/' ) { + ptr[ 0 ] = '/'; + ptr++; + } + ptr[ 0 ] = '\0'; + + attr_normalize_one( ad_olmDbDirectory, &bv, &nbv, NULL ); + + next->a_desc = ad_olmDbDirectory; + next->a_vals = ch_calloc( sizeof( struct berval ), 2 ); + next->a_vals[ 0 ] = bv; + next->a_numvals = 1; + + if ( BER_BVISNULL( &nbv ) ) { + next->a_nvals = next->a_vals; + + } else { + next->a_nvals = ch_calloc( sizeof( struct berval ), 2 ); + next->a_nvals[ 0 ] = nbv; + } + + next = next->a_next; + } + + cb = ch_calloc( sizeof( monitor_callback_t ), 1 ); + cb->mc_update = mdb_monitor_update; +#if 0 /* uncomment if required */ + cb->mc_modify = mdb_monitor_modify; +#endif + cb->mc_free = mdb_monitor_free; + cb->mc_private = (void *)mdb; + + /* make sure the database is registered; then add monitor attributes */ + rc = mbe->register_database( be, &mdb->mi_monitor.mdm_ndn ); + if ( rc == 0 ) { + rc = mbe->register_entry_attrs( &mdb->mi_monitor.mdm_ndn, a, cb, + NULL, -1, NULL ); + } + +cleanup:; + if ( rc != 0 ) { + if ( cb != NULL ) { + ch_free( cb ); + cb = NULL; + } + + if ( a != NULL ) { + attrs_free( a ); + a = NULL; + } + } + + /* store for cleanup */ + mdb->mi_monitor.mdm_cb = (void *)cb; + + /* we don't need to keep track of the attributes, because + * mdb_monitor_free() takes care of everything */ + if ( a != NULL ) { + attrs_free( a ); + } + + return rc; +} + +/* + * call from within mdb_db_close() + */ +int +mdb_monitor_db_close( BackendDB *be ) +{ + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + + if ( !BER_BVISNULL( &mdb->mi_monitor.mdm_ndn ) ) { + BackendInfo *mi = backend_info( "monitor" ); + monitor_extra_t *mbe; + + if ( mi && &mi->bi_extra ) { + mbe = mi->bi_extra; + mbe->unregister_entry_callback( &mdb->mi_monitor.mdm_ndn, + (monitor_callback_t *)mdb->mi_monitor.mdm_cb, + NULL, 0, NULL ); + } + + memset( &mdb->mi_monitor, 0, sizeof( mdb->mi_monitor ) ); + } + + return 0; +} + +/* + * call from within mdb_db_destroy() + */ +int +mdb_monitor_db_destroy( BackendDB *be ) +{ +#ifdef MDB_MONITOR_IDX + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + + /* TODO: free tree */ + ldap_pvt_thread_mutex_destroy( &mdb->mi_idx_mutex ); + avl_free( mdb->mi_idx, ch_free ); +#endif /* MDB_MONITOR_IDX */ + + return 0; +} + +#ifdef MDB_MONITOR_IDX + +#define MDB_MONITOR_IDX_TYPES (4) + +typedef struct monitor_idx_t monitor_idx_t; + +struct monitor_idx_t { + AttributeDescription *idx_ad; + unsigned long idx_count[MDB_MONITOR_IDX_TYPES]; +}; + +static int +mdb_monitor_bitmask2key( slap_mask_t bitmask ) +{ + int key; + + for ( key = 0; key < 8 * (int)sizeof(slap_mask_t) && !( bitmask & 0x1U ); + key++ ) + bitmask >>= 1; + + return key; +} + +static struct berval idxbv[] = { + BER_BVC( "present=" ), + BER_BVC( "equality=" ), + BER_BVC( "approx=" ), + BER_BVC( "substr=" ), + BER_BVNULL +}; + +static ber_len_t +mdb_monitor_idx2len( monitor_idx_t *idx ) +{ + int i; + ber_len_t len = 0; + + for ( i = 0; i < MDB_MONITOR_IDX_TYPES; i++ ) { + if ( idx->idx_count[ i ] != 0 ) { + len += idxbv[i].bv_len; + } + } + + return len; +} + +static int +monitor_idx_cmp( const void *p1, const void *p2 ) +{ + const monitor_idx_t *idx1 = (const monitor_idx_t *)p1; + const monitor_idx_t *idx2 = (const monitor_idx_t *)p2; + + return SLAP_PTRCMP( idx1->idx_ad, idx2->idx_ad ); +} + +static int +monitor_idx_dup( void *p1, void *p2 ) +{ + monitor_idx_t *idx1 = (monitor_idx_t *)p1; + monitor_idx_t *idx2 = (monitor_idx_t *)p2; + + return SLAP_PTRCMP( idx1->idx_ad, idx2->idx_ad ) == 0 ? -1 : 0; +} + +int +mdb_monitor_idx_add( + struct mdb_info *mdb, + AttributeDescription *desc, + slap_mask_t type ) +{ + monitor_idx_t idx_dummy = { 0 }, + *idx; + int rc = 0, key; + + idx_dummy.idx_ad = desc; + key = mdb_monitor_bitmask2key( type ) - 1; + if ( key >= MDB_MONITOR_IDX_TYPES ) { + /* invalid index type */ + return -1; + } + + ldap_pvt_thread_mutex_lock( &mdb->mi_idx_mutex ); + + idx = (monitor_idx_t *)avl_find( mdb->mi_idx, + (caddr_t)&idx_dummy, monitor_idx_cmp ); + if ( idx == NULL ) { + idx = (monitor_idx_t *)ch_calloc( sizeof( monitor_idx_t ), 1 ); + idx->idx_ad = desc; + idx->idx_count[ key ] = 1; + + switch ( avl_insert( &mdb->mi_idx, (caddr_t)idx, + monitor_idx_cmp, monitor_idx_dup ) ) + { + case 0: + break; + + default: + ch_free( idx ); + rc = -1; + } + + } else { + idx->idx_count[ key ]++; + } + + ldap_pvt_thread_mutex_unlock( &mdb->mi_idx_mutex ); + + return rc; +} + +static int +mdb_monitor_idx_apply( void *v_idx, void *v_valp ) +{ + monitor_idx_t *idx = (monitor_idx_t *)v_idx; + BerVarray *valp = (BerVarray *)v_valp; + + struct berval bv; + char *ptr; + char count_buf[ MDB_MONITOR_IDX_TYPES ][ SLAP_TEXT_BUFLEN ]; + ber_len_t count_len[ MDB_MONITOR_IDX_TYPES ], + idx_len; + int i, num = 0; + + idx_len = mdb_monitor_idx2len( idx ); + + bv.bv_len = 0; + for ( i = 0; i < MDB_MONITOR_IDX_TYPES; i++ ) { + if ( idx->idx_count[ i ] == 0 ) { + continue; + } + + count_len[ i ] = snprintf( count_buf[ i ], + sizeof( count_buf[ i ] ), "%lu", idx->idx_count[ i ] ); + bv.bv_len += count_len[ i ]; + num++; + } + + bv.bv_len += idx->idx_ad->ad_cname.bv_len + + num + + idx_len; + ptr = bv.bv_val = ch_malloc( bv.bv_len + 1 ); + ptr = lutil_strcopy( ptr, idx->idx_ad->ad_cname.bv_val ); + for ( i = 0; i < MDB_MONITOR_IDX_TYPES; i++ ) { + if ( idx->idx_count[ i ] == 0 ) { + continue; + } + + ptr[ 0 ] = '#'; + ++ptr; + ptr = lutil_strcopy( ptr, idxbv[ i ].bv_val ); + ptr = lutil_strcopy( ptr, count_buf[ i ] ); + } + + ber_bvarray_add( valp, &bv ); + + return 0; +} + +static int +mdb_monitor_idx_entry_add( + struct mdb_info *mdb, + Entry *e ) +{ + BerVarray vals = NULL; + Attribute *a; + + a = attr_find( e->e_attrs, ad_olmDbNotIndexed ); + + ldap_pvt_thread_mutex_lock( &mdb->mi_idx_mutex ); + + avl_apply( mdb->mi_idx, mdb_monitor_idx_apply, + &vals, -1, AVL_INORDER ); + + ldap_pvt_thread_mutex_unlock( &mdb->mi_idx_mutex ); + + if ( vals != NULL ) { + if ( a != NULL ) { + assert( a->a_nvals == a->a_vals ); + + ber_bvarray_free( a->a_vals ); + + } else { + Attribute **ap; + + for ( ap = &e->e_attrs; *ap != NULL; ap = &(*ap)->a_next ) + ; + *ap = attr_alloc( ad_olmDbNotIndexed ); + a = *ap; + } + a->a_vals = vals; + a->a_nvals = a->a_vals; + } + + return 0; +} + +#endif /* MDB_MONITOR_IDX */ diff --git a/servers/slapd/back-mdb/nextid.c b/servers/slapd/back-mdb/nextid.c new file mode 100644 index 0000000..2698571 --- /dev/null +++ b/servers/slapd/back-mdb/nextid.c @@ -0,0 +1,53 @@ +/* init.c - initialize mdb backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" + +int mdb_next_id( BackendDB *be, MDB_cursor *mc, ID *out ) +{ + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + int rc; + ID id = 0; + MDB_val key; + + rc = mdb_cursor_get(mc, &key, NULL, MDB_LAST); + + switch(rc) { + case MDB_NOTFOUND: + rc = 0; + *out = 1; + break; + case 0: + memcpy( &id, key.mv_data, sizeof( id )); + *out = ++id; + break; + + default: + Debug( LDAP_DEBUG_ANY, + "=> mdb_next_id: get failed: %s (%d)\n", + mdb_strerror(rc), rc, 0 ); + goto done; + } + mdb->mi_nextid = *out; + +done: + return rc; +} diff --git a/servers/slapd/back-mdb/operational.c b/servers/slapd/back-mdb/operational.c new file mode 100644 index 0000000..5fb0081 --- /dev/null +++ b/servers/slapd/back-mdb/operational.c @@ -0,0 +1,121 @@ +/* operational.c - mdb backend operational attributes function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-mdb.h" + +/* + * sets *hasSubordinates to LDAP_COMPARE_TRUE/LDAP_COMPARE_FALSE + * if the entry has children or not. + */ +int +mdb_hasSubordinates( + Operation *op, + Entry *e, + int *hasSubordinates ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_txn *rtxn; + mdb_op_info opinfo = {{{0}}}, *moi = &opinfo; + int rc; + + assert( e != NULL ); + + rc = mdb_opinfo_get(op, mdb, 1, &moi); + switch(rc) { + case 0: + break; + default: + rc = LDAP_OTHER; + goto done; + } + + rtxn = moi->moi_txn; + + rc = mdb_dn2id_children( op, rtxn, e ); + + switch( rc ) { + case 0: + *hasSubordinates = LDAP_COMPARE_TRUE; + break; + + case MDB_NOTFOUND: + *hasSubordinates = LDAP_COMPARE_FALSE; + rc = LDAP_SUCCESS; + break; + + default: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(mdb_hasSubordinates) + ": has_children failed: %s (%d)\n", + mdb_strerror(rc), rc, 0 ); + rc = LDAP_OTHER; + } + +done:; + if ( moi == &opinfo ) { + mdb_txn_reset( moi->moi_txn ); + LDAP_SLIST_REMOVE( &op->o_extra, &moi->moi_oe, OpExtra, oe_next ); + } else { + moi->moi_ref--; + } + return rc; +} + +/* + * sets the supported operational attributes (if required) + */ +int +mdb_operational( + Operation *op, + SlapReply *rs ) +{ + Attribute **ap; + + assert( rs->sr_entry != NULL ); + + for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next ) { + if ( (*ap)->a_desc == slap_schema.si_ad_hasSubordinates ) { + break; + } + } + + if ( *ap == NULL && + attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_hasSubordinates ) == NULL && + ( SLAP_OPATTRS( rs->sr_attr_flags ) || + ad_inlist( slap_schema.si_ad_hasSubordinates, rs->sr_attrs ) ) ) + { + int hasSubordinates, rc; + + rc = mdb_hasSubordinates( op, rs->sr_entry, &hasSubordinates ); + if ( rc == LDAP_SUCCESS ) { + *ap = slap_operational_hasSubordinate( hasSubordinates == LDAP_COMPARE_TRUE ); + assert( *ap != NULL ); + + ap = &(*ap)->a_next; + } + } + + return LDAP_SUCCESS; +} + diff --git a/servers/slapd/back-mdb/proto-mdb.h b/servers/slapd/back-mdb/proto-mdb.h new file mode 100644 index 0000000..5b20e47 --- /dev/null +++ b/servers/slapd/back-mdb/proto-mdb.h @@ -0,0 +1,394 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#ifndef _PROTO_MDB_H +#define _PROTO_MDB_H + +LDAP_BEGIN_DECL + +#define MDB_UCTYPE "MDB" + +/* + * attr.c + */ + +AttrInfo *mdb_attr_mask( struct mdb_info *mdb, + AttributeDescription *desc ); + +void mdb_attr_flush( struct mdb_info *mdb ); + +int mdb_attr_slot( struct mdb_info *mdb, + AttributeDescription *desc, int *insert ); + +int mdb_attr_dbs_open( BackendDB *be, MDB_txn *txn, struct config_reply_s *cr ); +void mdb_attr_dbs_close( struct mdb_info *mdb ); + +int mdb_attr_index_config LDAP_P(( struct mdb_info *mdb, + const char *fname, int lineno, + int argc, char **argv, struct config_reply_s *cr )); + +void mdb_attr_index_unparse LDAP_P(( struct mdb_info *mdb, BerVarray *bva )); +void mdb_attr_index_destroy LDAP_P(( struct mdb_info *mdb )); +void mdb_attr_index_free LDAP_P(( struct mdb_info *mdb, + AttributeDescription *ad )); + +void mdb_attr_info_free( AttrInfo *ai ); + +int mdb_ad_read( struct mdb_info *mdb, MDB_txn *txn ); +int mdb_ad_get( struct mdb_info *mdb, MDB_txn *txn, AttributeDescription *ad ); +void mdb_ad_unwind( struct mdb_info *mdb, int prev_ads ); + +/* + * config.c + */ + +int mdb_back_init_cf( BackendInfo *bi ); + +/* + * dn2entry.c + */ + +int mdb_dn2entry LDAP_P(( Operation *op, MDB_txn *tid, MDB_cursor *mc, + struct berval *dn, Entry **e, ID *nsubs, int matched )); + +/* + * dn2id.c + */ + +int mdb_dn2id( + Operation *op, + MDB_txn *txn, + MDB_cursor *mc, + struct berval *ndn, + ID *id, + ID *nsubs, + struct berval *matched, + struct berval *nmatched ); + +int mdb_dn2id_add( + Operation *op, + MDB_cursor *mcp, + MDB_cursor *mcd, + ID pid, + ID nsubs, + int upsub, + Entry *e ); + +int mdb_dn2id_delete( + Operation *op, + MDB_cursor *mc, + ID id, + ID nsubs ); + +int mdb_dn2id_children( + Operation *op, + MDB_txn *tid, + Entry *e ); + +int mdb_dn2sups ( + Operation *op, + MDB_txn *tid, + struct berval *dn, + ID *sups + ); + +int mdb_dn2idl( + Operation *op, + MDB_txn *txn, + struct berval *ndn, + ID eid, + ID *ids, + ID *stack ); + +int mdb_dn2id_parent( + Operation *op, + MDB_txn *txn, + ID eid, + ID *idp ); + +int mdb_id2name( + Operation *op, + MDB_txn *txn, + MDB_cursor **cursp, + ID eid, + struct berval *name, + struct berval *nname); + +int mdb_idscope( + Operation *op, + MDB_txn *txn, + ID base, + ID *ids, + ID *res ); + +struct IdScopes; + +int mdb_idscopes( + Operation *op, + struct IdScopes *isc ); + +int mdb_idscopechk( + Operation *op, + struct IdScopes *isc ); + +int mdb_dn2id_walk( + Operation *op, + struct IdScopes *isc ); + +void mdb_dn2id_wrestore( + Operation *op, + struct IdScopes *isc ); + +MDB_cmp_func mdb_dup_compare; + +/* + * filterentry.c + */ + +int mdb_filter_candidates( + Operation *op, + MDB_txn *txn, + Filter *f, + ID *ids, + ID *tmp, + ID *stack ); + +/* + * id2entry.c + */ + +int mdb_id2entry_add( + Operation *op, + MDB_txn *tid, + MDB_cursor *mc, + Entry *e ); + +int mdb_id2entry_update( + Operation *op, + MDB_txn *tid, + MDB_cursor *mc, + Entry *e ); + +int mdb_id2entry_delete( + BackendDB *be, + MDB_txn *tid, + Entry *e); + +int mdb_id2entry( + Operation *op, + MDB_cursor *mc, + ID id, + Entry **e); + +int mdb_id2edata( + Operation *op, + MDB_cursor *mc, + ID id, + MDB_val *data); + +int mdb_entry_return( Operation *op, Entry *e ); +BI_entry_release_rw mdb_entry_release; +BI_entry_get_rw mdb_entry_get; + +int mdb_entry_decode( Operation *op, MDB_txn *txn, MDB_val *data, Entry **e ); + +void mdb_reader_flush( MDB_env *env ); +int mdb_opinfo_get( Operation *op, struct mdb_info *mdb, int rdonly, mdb_op_info **moi ); + +/* + * idl.c + */ + +unsigned mdb_idl_search( ID *ids, ID id ); + +int mdb_idl_fetch_key( + BackendDB *be, + MDB_txn *txn, + MDB_dbi dbi, + MDB_val *key, + ID *ids, + MDB_cursor **saved_cursor, + int get_flag ); + +int mdb_idl_insert( ID *ids, ID id ); + +typedef int (mdb_idl_keyfunc)( + BackendDB *be, + MDB_cursor *mc, + struct berval *key, + ID id ); + +mdb_idl_keyfunc mdb_idl_insert_keys; +mdb_idl_keyfunc mdb_idl_delete_keys; + +int +mdb_idl_intersection( + ID *a, + ID *b ); + +int +mdb_idl_union( + ID *a, + ID *b ); + +ID mdb_idl_first( ID *ids, ID *cursor ); +ID mdb_idl_next( ID *ids, ID *cursor ); + +void mdb_idl_sort( ID *ids, ID *tmp ); +int mdb_idl_append( ID *a, ID *b ); +int mdb_idl_append_one( ID *ids, ID id ); + + +/* + * index.c + */ + +extern AttrInfo * +mdb_index_mask LDAP_P(( + Backend *be, + AttributeDescription *desc, + struct berval *name )); + +extern int +mdb_index_param LDAP_P(( + Backend *be, + AttributeDescription *desc, + int ftype, + MDB_dbi *dbi, + slap_mask_t *mask, + struct berval *prefix )); + +extern int +mdb_index_values LDAP_P(( + Operation *op, + MDB_txn *txn, + AttributeDescription *desc, + BerVarray vals, + ID id, + int opid )); + +extern int +mdb_index_recset LDAP_P(( + struct mdb_info *mdb, + Attribute *a, + AttributeType *type, + struct berval *tags, + IndexRec *ir )); + +extern int +mdb_index_recrun LDAP_P(( + Operation *op, + MDB_txn *txn, + struct mdb_info *mdb, + IndexRec *ir, + ID id, + int base )); + +int mdb_index_entry LDAP_P(( Operation *op, MDB_txn *t, int r, Entry *e )); + +#define mdb_index_entry_add(op,t,e) \ + mdb_index_entry((op),(t),SLAP_INDEX_ADD_OP,(e)) +#define mdb_index_entry_del(op,t,e) \ + mdb_index_entry((op),(t),SLAP_INDEX_DELETE_OP,(e)) + +/* + * key.c + */ + +extern int +mdb_key_read( + Backend *be, + MDB_txn *txn, + MDB_dbi dbi, + struct berval *k, + ID *ids, + MDB_cursor **saved_cursor, + int get_flags ); + +/* + * nextid.c + */ + +int mdb_next_id( BackendDB *be, MDB_cursor *mc, ID *id ); + +/* + * modify.c + */ + +int mdb_modify_internal( + Operation *op, + MDB_txn *tid, + Modifications *modlist, + Entry *e, + const char **text, + char *textbuf, + size_t textlen ); + +/* + * monitor.c + */ + +int mdb_monitor_db_init( BackendDB *be ); +int mdb_monitor_db_open( BackendDB *be ); +int mdb_monitor_db_close( BackendDB *be ); +int mdb_monitor_db_destroy( BackendDB *be ); + +#ifdef MDB_MONITOR_IDX +int +mdb_monitor_idx_add( + struct mdb_info *mdb, + AttributeDescription *desc, + slap_mask_t type ); +#endif /* MDB_MONITOR_IDX */ + +/* + * former external.h + */ + +extern BI_init mdb_back_initialize; + +extern BI_db_config mdb_db_config; + +extern BI_op_add mdb_add; +extern BI_op_bind mdb_bind; +extern BI_op_compare mdb_compare; +extern BI_op_delete mdb_delete; +extern BI_op_modify mdb_modify; +extern BI_op_modrdn mdb_modrdn; +extern BI_op_search mdb_search; +extern BI_op_extended mdb_extended; + +extern BI_chk_referrals mdb_referrals; + +extern BI_operational mdb_operational; + +extern BI_has_subordinates mdb_hasSubordinates; + +/* tools.c */ +extern BI_tool_entry_open mdb_tool_entry_open; +extern BI_tool_entry_close mdb_tool_entry_close; +extern BI_tool_entry_first_x mdb_tool_entry_first_x; +extern BI_tool_entry_next mdb_tool_entry_next; +extern BI_tool_entry_get mdb_tool_entry_get; +extern BI_tool_entry_put mdb_tool_entry_put; +extern BI_tool_entry_reindex mdb_tool_entry_reindex; +extern BI_tool_dn2id_get mdb_tool_dn2id_get; +extern BI_tool_entry_modify mdb_tool_entry_modify; + +extern mdb_idl_keyfunc mdb_tool_idl_add; + +LDAP_END_DECL + +#endif /* _PROTO_MDB_H */ diff --git a/servers/slapd/back-mdb/referral.c b/servers/slapd/back-mdb/referral.c new file mode 100644 index 0000000..ac2f662 --- /dev/null +++ b/servers/slapd/back-mdb/referral.c @@ -0,0 +1,151 @@ +/* referral.c - MDB backend referral handler */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" + +int +mdb_referrals( Operation *op, SlapReply *rs ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + Entry *e = NULL; + int rc = LDAP_SUCCESS; + + MDB_txn *rtxn; + mdb_op_info opinfo = {0}, *moi = &opinfo; + + if( op->o_tag == LDAP_REQ_SEARCH ) { + /* let search take care of itself */ + return rc; + } + + if( get_manageDSAit( op ) ) { + /* let op take care of DSA management */ + return rc; + } + + rc = mdb_opinfo_get(op, mdb, 1, &moi); + switch(rc) { + case 0: + break; + default: + return LDAP_OTHER; + } + + rtxn = moi->moi_txn; + + /* get entry */ + rc = mdb_dn2entry( op, rtxn, &op->o_req_ndn, &e, 1 ); + + switch(rc) { + case MDB_NOTFOUND: + case 0: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto done; + default: + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_referrals) + ": dn2entry failed: %s (%d)\n", + mdb_strerror(rc), rc, 0 ); + rs->sr_text = "internal error"; + rc = LDAP_OTHER; + goto done; + } + + if ( rc == MDB_NOTFOUND ) { + rc = LDAP_SUCCESS; + rs->sr_matched = NULL; + if ( e != NULL ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_referrals) + ": tag=%lu target=\"%s\" matched=\"%s\"\n", + (unsigned long)op->o_tag, op->o_req_dn.bv_val, e->e_name.bv_val ); + + if( is_entry_referral( e ) ) { + BerVarray ref = get_entry_referrals( op, e ); + rc = LDAP_OTHER; + rs->sr_ref = referral_rewrite( ref, &e->e_name, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + ber_bvarray_free( ref ); + if ( rs->sr_ref ) { + rs->sr_matched = ber_strdup_x( + e->e_name.bv_val, op->o_tmpmemctx ); + } + } + + mdb_entry_return( op, e ); + e = NULL; + } + + if( rs->sr_ref != NULL ) { + /* send referrals */ + rc = rs->sr_err = LDAP_REFERRAL; + send_ldap_result( op, rs ); + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } else if ( rc != LDAP_SUCCESS ) { + rs->sr_text = rs->sr_matched ? "bad referral object" : NULL; + } + + if (rs->sr_matched) { + op->o_tmpfree( (char *)rs->sr_matched, op->o_tmpmemctx ); + rs->sr_matched = NULL; + } + goto done; + } + + if ( is_entry_referral( e ) ) { + /* entry is a referral */ + BerVarray refs = get_entry_referrals( op, e ); + rs->sr_ref = referral_rewrite( + refs, &e->e_name, &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_referrals) + ": tag=%lu target=\"%s\" matched=\"%s\"\n", + (unsigned long)op->o_tag, op->o_req_dn.bv_val, e->e_name.bv_val ); + + rs->sr_matched = e->e_name.bv_val; + if( rs->sr_ref != NULL ) { + rc = rs->sr_err = LDAP_REFERRAL; + send_ldap_result( op, rs ); + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } else { + rc = LDAP_OTHER; + rs->sr_text = "bad referral object"; + } + + rs->sr_matched = NULL; + ber_bvarray_free( refs ); + } + +done: + if ( moi == &opinfo ) { + mdb_txn_reset( moi->moi_txn ); + LDAP_SLIST_REMOVE( &op->o_extra, &moi->moi_oe, OpExtra, oe_next ); + } else { + moi->moi_ref--; + } + if ( e ) + mdb_entry_return( op, e ); + return rc; +} diff --git a/servers/slapd/back-mdb/search.c b/servers/slapd/back-mdb/search.c new file mode 100644 index 0000000..1bc4ec0 --- /dev/null +++ b/servers/slapd/back-mdb/search.c @@ -0,0 +1,1512 @@ +/* search.c - search operation */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" +#include "idl.h" + +static int base_candidate( + BackendDB *be, + Entry *e, + ID *ids ); + +static int search_candidates( + Operation *op, + SlapReply *rs, + Entry *e, + IdScopes *isc, + MDB_cursor *mci, + ID *ids, + ID *stack ); + +static int parse_paged_cookie( Operation *op, SlapReply *rs ); + +static void send_paged_response( + Operation *op, + SlapReply *rs, + ID *lastid, + int tentries ); + +/* Dereference aliases for a single alias entry. Return the final + * dereferenced entry on success, NULL on any failure. + */ +static Entry * deref_base ( + Operation *op, + SlapReply *rs, + Entry *e, + Entry **matched, + MDB_txn *txn, + ID *tmp, + ID *visited ) +{ + struct berval ndn; + + rs->sr_err = LDAP_ALIAS_DEREF_PROBLEM; + rs->sr_text = "maximum deref depth exceeded"; + + for (;;) { + /* Remember the last entry we looked at, so we can + * report broken links + */ + *matched = e; + + if (MDB_IDL_N(tmp) >= op->o_bd->be_max_deref_depth) { + e = NULL; + break; + } + + /* If this is part of a subtree or onelevel search, + * have we seen this ID before? If so, quit. + */ + if ( visited && mdb_idl_insert( visited, e->e_id ) ) { + e = NULL; + break; + } + + /* If we've seen this ID during this deref iteration, + * we've hit a loop. + */ + if ( mdb_idl_insert( tmp, e->e_id ) ) { + rs->sr_err = LDAP_ALIAS_PROBLEM; + rs->sr_text = "circular alias"; + e = NULL; + break; + } + + /* If there was a problem getting the aliasedObjectName, + * get_alias_dn will have set the error status. + */ + if ( get_alias_dn(e, &ndn, &rs->sr_err, &rs->sr_text) ) { + e = NULL; + break; + } + + rs->sr_err = mdb_dn2entry( op, txn, NULL, &ndn, &e, NULL, 0 ); + if (rs->sr_err) { + rs->sr_err = LDAP_ALIAS_PROBLEM; + rs->sr_text = "aliasedObject not found"; + break; + } + + /* Free the previous entry, continue to work with the + * one we just retrieved. + */ + mdb_entry_return( op, *matched ); + + /* We found a regular entry. Return this to the caller. + */ + if (!is_entry_alias(e)) { + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + break; + } + } + return e; +} + +/* Look for and dereference all aliases within the search scope. + * Requires "stack" to be able to hold 6 levels of DB_SIZE IDLs. + * Of course we're hardcoded to require a minimum of 8 UM_SIZE + * IDLs so this is never a problem. + */ +static int search_aliases( + Operation *op, + SlapReply *rs, + ID e_id, + IdScopes *isc, + MDB_cursor *mci, + ID *stack ) +{ + ID *aliases, *curscop, *visited, *newsubs, *oldsubs, *tmp; + ID cursora, ida, cursoro, ido; + Entry *matched, *a; + struct berval bv_alias = BER_BVC( "alias" ); + AttributeAssertion aa_alias = ATTRIBUTEASSERTION_INIT; + Filter af; + + aliases = stack; /* IDL of all aliases in the database */ + curscop = aliases + MDB_IDL_DB_SIZE; /* Aliases in the current scope */ + visited = curscop + MDB_IDL_DB_SIZE; /* IDs we've seen in this search */ + newsubs = visited + MDB_IDL_DB_SIZE; /* New subtrees we've added */ + oldsubs = newsubs + MDB_IDL_DB_SIZE; /* Subtrees added previously */ + tmp = oldsubs + MDB_IDL_DB_SIZE; /* Scratch space for deref_base() */ + + af.f_choice = LDAP_FILTER_EQUALITY; + af.f_ava = &aa_alias; + af.f_av_desc = slap_schema.si_ad_objectClass; + af.f_av_value = bv_alias; + af.f_next = NULL; + + /* Find all aliases in database */ + MDB_IDL_ZERO( aliases ); + rs->sr_err = mdb_filter_candidates( op, isc->mt, &af, aliases, + curscop, visited ); + if (rs->sr_err != LDAP_SUCCESS || MDB_IDL_IS_ZERO( aliases )) { + return rs->sr_err; + } + if ( op->ors_limit /* isroot == FALSE */ && + op->ors_limit->lms_s_unchecked != -1 && + MDB_IDL_N( aliases ) > (unsigned) op->ors_limit->lms_s_unchecked ) + { + return LDAP_ADMINLIMIT_EXCEEDED; + } + oldsubs[0] = 1; + oldsubs[1] = e_id; + + MDB_IDL_ZERO( visited ); + MDB_IDL_ZERO( newsubs ); + + cursoro = 0; + ido = mdb_idl_first( oldsubs, &cursoro ); + + for (;;) { + /* Set curscop to only the aliases in the current scope. Start with + * all the aliases, then get the intersection with the scope. + */ + rs->sr_err = mdb_idscope( op, isc->mt, e_id, aliases, curscop ); + + /* Dereference all of the aliases in the current scope. */ + cursora = 0; + for (ida = mdb_idl_first(curscop, &cursora); ida != NOID; + ida = mdb_idl_next(curscop, &cursora)) + { + rs->sr_err = mdb_id2entry(op, mci, ida, &a); + if (rs->sr_err != LDAP_SUCCESS) { + continue; + } + + /* This should only happen if the curscop IDL has maxed out and + * turned into a range that spans IDs indiscriminately + */ + if (!is_entry_alias(a)) { + mdb_entry_return(op, a); + continue; + } + + /* Actually dereference the alias */ + MDB_IDL_ZERO(tmp); + a = deref_base( op, rs, a, &matched, isc->mt, + tmp, visited ); + if (a) { + /* If the target was not already in our current scopes, + * make note of it in the newsubs list. + */ + ID2 mid; + mid.mid = a->e_id; + mid.mval.mv_data = NULL; + if (op->ors_scope == LDAP_SCOPE_SUBTREE) { + isc->id = a->e_id; + /* if ID is a child of any of our current scopes, + * ignore it, it's already included. + */ + if (mdb_idscopechk(op, isc)) + goto skip; + } + if (mdb_id2l_insert(isc->scopes, &mid) == 0) { + mdb_idl_insert(newsubs, a->e_id); + } +skip: mdb_entry_return( op, a ); + + } else if (matched) { + /* Alias could not be dereferenced, or it deref'd to + * an ID we've already seen. Ignore it. + */ + mdb_entry_return( op, matched ); + rs->sr_text = NULL; + rs->sr_err = 0; + } + } + /* If this is a OneLevel search, we're done; oldsubs only had one + * ID in it. For a Subtree search, oldsubs may be a list of scope IDs. + */ + if ( op->ors_scope == LDAP_SCOPE_ONELEVEL ) break; +nextido: + ido = mdb_idl_next( oldsubs, &cursoro ); + + /* If we're done processing the old scopes, did we add any new + * scopes in this iteration? If so, go back and do those now. + */ + if (ido == NOID) { + if (MDB_IDL_IS_ZERO(newsubs)) break; + MDB_IDL_CPY(oldsubs, newsubs); + MDB_IDL_ZERO(newsubs); + cursoro = 0; + ido = mdb_idl_first( oldsubs, &cursoro ); + } + + /* Find the entry corresponding to the next scope. If it can't + * be found, ignore it and move on. This should never happen; + * we should never see the ID of an entry that doesn't exist. + */ + { + MDB_val edata; + rs->sr_err = mdb_id2edata(op, mci, ido, &edata); + if ( rs->sr_err != MDB_SUCCESS ) { + goto nextido; + } + e_id = ido; + } + } + return rs->sr_err; +} + +/* Get the next ID from the DB. Used if the candidate list is + * a range and simple iteration hits missing entryIDs + */ +static int +mdb_get_nextid(MDB_cursor *mci, ID *cursor) +{ + MDB_val key; + ID id; + int rc; + + id = *cursor + 1; + key.mv_data = &id; + key.mv_size = sizeof(ID); + rc = mdb_cursor_get( mci, &key, NULL, MDB_SET_RANGE ); + if ( rc ) + return rc; + memcpy( cursor, key.mv_data, sizeof(ID)); + return 0; +} + +static void scope_chunk_free( void *key, void *data ) +{ + ID2 *p1, *p2; + for (p1 = data; p1; p1 = p2) { + p2 = p1[0].mval.mv_data; + ber_memfree_x(p1, NULL); + } +} + +static ID2 *scope_chunk_get( Operation *op ) +{ + ID2 *ret = NULL; + + ldap_pvt_thread_pool_getkey( op->o_threadctx, (void *)scope_chunk_get, + (void *)&ret, NULL ); + if ( !ret ) { + ret = ch_malloc( MDB_IDL_UM_SIZE * sizeof( ID2 )); + } else { + void *r2 = ret[0].mval.mv_data; + ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)scope_chunk_get, + r2, scope_chunk_free, NULL, NULL ); + } + return ret; +} + +static void scope_chunk_ret( Operation *op, ID2 *scopes ) +{ + void *ret = NULL; + + ldap_pvt_thread_pool_getkey( op->o_threadctx, (void *)scope_chunk_get, + &ret, NULL ); + scopes[0].mval.mv_data = ret; + ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)scope_chunk_get, + (void *)scopes, scope_chunk_free, NULL, NULL ); +} + +static void *search_stack( Operation *op ); + +typedef struct ww_ctx { + MDB_txn *txn; + MDB_cursor *mcd; /* if set, save cursor context */ + ID key; + MDB_val data; + int flag; + unsigned nentries; +} ww_ctx; + +/* ITS#7904 if we get blocked while writing results to client, + * release the current reader txn and reacquire it after we + * unblock. + * Slight problem - if we're doing a scope-based walk (mdb_dn2id_walk) + * to return results, we need to remember the state of the mcd cursor. + * If the node that cursor was pointing to gets deleted while we're + * blocked, we may be unable to restore the cursor position. In that + * case return an LDAP_BUSY error - let the client know this search + * couldn't succeed, but might succeed on a retry. + */ +static void +mdb_rtxn_snap( Operation *op, ww_ctx *ww ) +{ + /* save cursor position and release read txn */ + if ( ww->mcd ) { + MDB_val key, data; + mdb_cursor_get( ww->mcd, &key, &data, MDB_GET_CURRENT ); + memcpy( &ww->key, key.mv_data, sizeof(ID) ); + ww->data.mv_size = data.mv_size; + ww->data.mv_data = op->o_tmpalloc( data.mv_size, op->o_tmpmemctx ); + memcpy(ww->data.mv_data, data.mv_data, data.mv_size); + } + mdb_txn_reset( ww->txn ); + ww->flag = 1; +} + +static void +mdb_writewait( Operation *op, slap_callback *sc ) +{ + ww_ctx *ww = sc->sc_private; + if ( !ww->flag ) { + mdb_rtxn_snap( op, ww ); + } +} + +static int +mdb_waitfixup( Operation *op, ww_ctx *ww, MDB_cursor *mci, MDB_cursor *mcd, IdScopes *isc ) +{ + MDB_val key; + int rc = 0; + ww->flag = 0; + mdb_txn_renew( ww->txn ); + mdb_cursor_renew( ww->txn, mci ); + mdb_cursor_renew( ww->txn, mcd ); + + key.mv_size = sizeof(ID); + if ( ww->mcd ) { /* scope-based search using dn2id_walk */ + MDB_val data; + + if ( isc->numrdns ) + mdb_dn2id_wrestore( op, isc ); + + key.mv_data = &ww->key; + data = ww->data; + rc = mdb_cursor_get( mcd, &key, &data, MDB_GET_BOTH ); + if ( rc == MDB_NOTFOUND ) { + data = ww->data; + rc = mdb_cursor_get( mcd, &key, &data, MDB_GET_BOTH_RANGE ); + /* the loop will skip this node using NEXT_DUP but we want it + * sent, so go back one space first + */ + if ( rc == MDB_SUCCESS ) + mdb_cursor_get( mcd, &key, &data, MDB_PREV_DUP ); + else + rc = LDAP_BUSY; + } else if ( rc ) { + rc = LDAP_OTHER; + } + op->o_tmpfree( ww->data.mv_data, op->o_tmpmemctx ); + ww->data.mv_data = NULL; + } else if ( isc->scopes[0].mid > 1 ) { /* candidate-based search */ + int i; + for ( i=1; i<isc->scopes[0].mid; i++ ) { + if ( !isc->scopes[i].mval.mv_data ) + continue; + key.mv_data = &isc->scopes[i].mid; + mdb_cursor_get( mcd, &key, &isc->scopes[i].mval, MDB_SET ); + } + } + return rc; +} + +int +mdb_search( Operation *op, SlapReply *rs ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + ID id, cursor, nsubs, ncand, cscope; + ID lastid = NOID; + ID candidates[MDB_IDL_UM_SIZE]; + ID iscopes[MDB_IDL_DB_SIZE]; + ID2 *scopes; + void *stack; + Entry *e = NULL, *base = NULL; + Entry *matched = NULL; + AttributeName *attrs; + slap_mask_t mask; + time_t stoptime; + int manageDSAit; + int tentries = 0; + IdScopes isc; + MDB_cursor *mci, *mcd; + ww_ctx wwctx; + slap_callback cb = { 0 }; + + mdb_op_info opinfo = {{{0}}}, *moi = &opinfo; + MDB_txn *ltid = NULL; + + Debug( LDAP_DEBUG_TRACE, "=> " LDAP_XSTRING(mdb_search) "\n", 0, 0, 0); + attrs = op->oq_search.rs_attrs; + + manageDSAit = get_manageDSAit( op ); + + rs->sr_err = mdb_opinfo_get( op, mdb, 1, &moi ); + switch(rs->sr_err) { + case 0: + break; + default: + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return rs->sr_err; + } + + ltid = moi->moi_txn; + + rs->sr_err = mdb_cursor_open( ltid, mdb->mi_id2entry, &mci ); + if ( rs->sr_err ) { + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return rs->sr_err; + } + + rs->sr_err = mdb_cursor_open( ltid, mdb->mi_dn2id, &mcd ); + if ( rs->sr_err ) { + mdb_cursor_close( mci ); + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return rs->sr_err; + } + + scopes = scope_chunk_get( op ); + stack = search_stack( op ); + isc.mt = ltid; + isc.mc = mcd; + isc.scopes = scopes; + isc.oscope = op->ors_scope; + isc.sctmp = stack; + + if ( op->ors_deref & LDAP_DEREF_FINDING ) { + MDB_IDL_ZERO(candidates); + } +dn2entry_retry: + /* get entry with reader lock */ + rs->sr_err = mdb_dn2entry( op, ltid, mcd, &op->o_req_ndn, &e, &nsubs, 1 ); + + switch(rs->sr_err) { + case MDB_NOTFOUND: + matched = e; + e = NULL; + break; + case 0: + break; + case LDAP_BUSY: + send_ldap_error( op, rs, LDAP_BUSY, "ldap server busy" ); + goto done; + default: + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + goto done; + } + + if ( op->ors_deref & LDAP_DEREF_FINDING ) { + if ( matched && is_entry_alias( matched )) { + struct berval stub; + + stub.bv_val = op->o_req_ndn.bv_val; + stub.bv_len = op->o_req_ndn.bv_len - matched->e_nname.bv_len - 1; + e = deref_base( op, rs, matched, &matched, ltid, + candidates, NULL ); + if ( e ) { + build_new_dn( &op->o_req_ndn, &e->e_nname, &stub, + op->o_tmpmemctx ); + mdb_entry_return(op, e); + matched = NULL; + goto dn2entry_retry; + } + } else if ( e && is_entry_alias( e )) { + e = deref_base( op, rs, e, &matched, ltid, + candidates, NULL ); + } + } + + if ( e == NULL ) { + struct berval matched_dn = BER_BVNULL; + + if ( matched != NULL ) { + BerVarray erefs = NULL; + + /* return referral only if "disclose" + * is granted on the object */ + if ( ! access_allowed( op, matched, + slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + + } else { + ber_dupbv( &matched_dn, &matched->e_name ); + + erefs = is_entry_referral( matched ) + ? get_entry_referrals( op, matched ) + : NULL; + if ( rs->sr_err == MDB_NOTFOUND ) + rs->sr_err = LDAP_REFERRAL; + rs->sr_matched = matched_dn.bv_val; + } + + mdb_entry_return(op, matched); + matched = NULL; + + if ( erefs ) { + rs->sr_ref = referral_rewrite( erefs, &matched_dn, + &op->o_req_dn, op->oq_search.rs_scope ); + ber_bvarray_free( erefs ); + } + + } else { + rs->sr_ref = referral_rewrite( default_referral, + NULL, &op->o_req_dn, op->oq_search.rs_scope ); + rs->sr_err = rs->sr_ref != NULL ? LDAP_REFERRAL : LDAP_NO_SUCH_OBJECT; + } + + send_ldap_result( op, rs ); + + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + if ( !BER_BVISNULL( &matched_dn ) ) { + ber_memfree( matched_dn.bv_val ); + rs->sr_matched = NULL; + } + goto done; + } + + /* NOTE: __NEW__ "search" access is required + * on searchBase object */ + if ( ! access_allowed_mask( op, e, slap_schema.si_ad_entry, + NULL, ACL_SEARCH, NULL, &mask ) ) + { + if ( !ACL_GRANT( mask, ACL_DISCLOSE ) ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } else { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + } + + mdb_entry_return( op,e); + send_ldap_result( op, rs ); + goto done; + } + + if ( !manageDSAit && is_entry_referral( e ) ) { + /* entry is a referral */ + struct berval matched_dn = BER_BVNULL; + BerVarray erefs = NULL; + + ber_dupbv( &matched_dn, &e->e_name ); + erefs = get_entry_referrals( op, e ); + + rs->sr_err = LDAP_REFERRAL; + + mdb_entry_return( op, e ); + e = NULL; + + if ( erefs ) { + rs->sr_ref = referral_rewrite( erefs, &matched_dn, + &op->o_req_dn, op->oq_search.rs_scope ); + ber_bvarray_free( erefs ); + + if ( !rs->sr_ref ) { + rs->sr_text = "bad_referral object"; + } + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_search) ": entry is referral\n", + 0, 0, 0 ); + + rs->sr_matched = matched_dn.bv_val; + send_ldap_result( op, rs ); + + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + ber_memfree( matched_dn.bv_val ); + rs->sr_matched = NULL; + goto done; + } + + if ( get_assert( op ) && + ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + mdb_entry_return( op,e); + send_ldap_result( op, rs ); + goto done; + } + + /* compute it anyway; root does not use it */ + stoptime = op->o_time + op->ors_tlimit; + + base = e; + + e = NULL; + + /* select candidates */ + if ( op->oq_search.rs_scope == LDAP_SCOPE_BASE ) { + rs->sr_err = base_candidate( op->o_bd, base, candidates ); + scopes[0].mid = 0; + ncand = 1; + } else { + if ( op->ors_scope == LDAP_SCOPE_ONELEVEL ) { + size_t nkids; + MDB_val key, data; + key.mv_data = &base->e_id; + key.mv_size = sizeof( ID ); + mdb_cursor_get( mcd, &key, &data, MDB_SET ); + mdb_cursor_count( mcd, &nkids ); + nsubs = nkids - 1; + } else if ( !base->e_id ) { + /* we don't maintain nsubs for entryID 0. + * just grab entry count from id2entry stat + */ + MDB_stat ms; + mdb_stat( ltid, mdb->mi_id2entry, &ms ); + nsubs = ms.ms_entries; + } + MDB_IDL_ZERO( candidates ); + scopes[0].mid = 1; + scopes[1].mid = base->e_id; + scopes[1].mval.mv_data = NULL; + rs->sr_err = search_candidates( op, rs, base, + &isc, mci, candidates, stack ); + + if ( rs->sr_err == LDAP_ADMINLIMIT_EXCEEDED ) + goto adminlimit; + + ncand = MDB_IDL_N( candidates ); + if ( !base->e_id || ncand == NOID ) { + /* grab entry count from id2entry stat + */ + MDB_stat ms; + mdb_stat( ltid, mdb->mi_id2entry, &ms ); + if ( !base->e_id ) + nsubs = ms.ms_entries; + if ( ncand == NOID ) + ncand = ms.ms_entries; + } + } + + /* start cursor at beginning of candidates. + */ + cursor = 0; + + if ( candidates[0] == 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_search) ": no candidates\n", + 0, 0, 0 ); + + goto nochange; + } + + /* if not root and candidates exceed to-be-checked entries, abort */ + if ( op->ors_limit /* isroot == FALSE */ && + op->ors_limit->lms_s_unchecked != -1 && + ncand > (unsigned) op->ors_limit->lms_s_unchecked ) + { + rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED; +adminlimit: + send_ldap_result( op, rs ); + rs->sr_err = LDAP_SUCCESS; + goto done; + } + + if ( op->ors_limit == NULL /* isroot == TRUE */ || + !op->ors_limit->lms_s_pr_hide ) + { + tentries = ncand; + } + + wwctx.flag = 0; + wwctx.nentries = 0; + /* If we're running in our own read txn */ + if ( moi == &opinfo ) { + cb.sc_writewait = mdb_writewait; + cb.sc_private = &wwctx; + wwctx.txn = ltid; + wwctx.mcd = NULL; + cb.sc_next = op->o_callback; + op->o_callback = &cb; + } + + if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) { + PagedResultsState *ps = op->o_pagedresults_state; + /* deferred cookie parsing */ + rs->sr_err = parse_paged_cookie( op, rs ); + if ( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto done; + } + + cursor = (ID) ps->ps_cookie; + if ( cursor && ps->ps_size == 0 ) { + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = "search abandoned by pagedResult size=0"; + send_ldap_result( op, rs ); + goto done; + } + id = mdb_idl_first( candidates, &cursor ); + if ( id == NOID ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_search) + ": no paged results candidates\n", + 0, 0, 0 ); + send_paged_response( op, rs, &lastid, 0 ); + + rs->sr_err = LDAP_OTHER; + goto done; + } + if ( id == (ID)ps->ps_cookie ) + id = mdb_idl_next( candidates, &cursor ); + nsubs = ncand; /* always bypass scope'd search */ + goto loop_begin; + } + if ( nsubs < ncand ) { + int rc; + /* Do scope-based search */ + + /* if any alias scopes were set, save them */ + if (scopes[0].mid > 1) { + cursor = 1; + for (cscope = 1; cscope <= scopes[0].mid; cscope++) { + /* Ignore the original base */ + if (scopes[cscope].mid == base->e_id) + continue; + iscopes[cursor++] = scopes[cscope].mid; + } + iscopes[0] = scopes[0].mid - 1; + } else { + iscopes[0] = 0; + } + + wwctx.mcd = mcd; + isc.id = base->e_id; + isc.numrdns = 0; + rc = mdb_dn2id_walk( op, &isc ); + if ( rc ) + id = NOID; + else + id = isc.id; + cscope = 0; + } else { + id = mdb_idl_first( candidates, &cursor ); + } + + while (id != NOID) + { + int scopeok; + MDB_val edata; + +loop_begin: + + /* check for abandon */ + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + send_ldap_result( op, rs ); + goto done; + } + + /* mostly needed by internal searches, + * e.g. related to syncrepl, for whom + * abandon does not get set... */ + if ( slapd_shutdown ) { + rs->sr_err = LDAP_UNAVAILABLE; + send_ldap_disconnect( op, rs ); + goto done; + } + + /* check time limit */ + if ( op->ors_tlimit != SLAP_NO_LIMIT + && slap_get_time() > stoptime ) + { + rs->sr_err = LDAP_TIMELIMIT_EXCEEDED; + rs->sr_ref = rs->sr_v2ref; + send_ldap_result( op, rs ); + rs->sr_err = LDAP_SUCCESS; + goto done; + } + + + if ( nsubs < ncand ) { + unsigned i; + /* Is this entry in the candidate list? */ + scopeok = 0; + if (MDB_IDL_IS_RANGE( candidates )) { + if ( id >= MDB_IDL_RANGE_FIRST( candidates ) && + id <= MDB_IDL_RANGE_LAST( candidates )) + scopeok = 1; + } else { + i = mdb_idl_search( candidates, id ); + if (i <= candidates[0] && candidates[i] == id ) + scopeok = 1; + } + if ( scopeok ) + goto scopeok; + goto loop_continue; + } + + /* Does this candidate actually satisfy the search scope? + */ + scopeok = 0; + isc.numrdns = 0; + switch( op->ors_scope ) { + case LDAP_SCOPE_BASE: + /* This is always true, yes? */ + if ( id == base->e_id ) scopeok = 1; + break; + +#ifdef LDAP_SCOPE_CHILDREN + case LDAP_SCOPE_CHILDREN: + if ( id == base->e_id ) break; + /* Fall-thru */ +#endif + case LDAP_SCOPE_SUBTREE: + if ( id == base->e_id ) { + scopeok = 1; + break; + } + /* Fall-thru */ + case LDAP_SCOPE_ONELEVEL: + if ( id == base->e_id ) break; + isc.id = id; + isc.nscope = 0; + rs->sr_err = mdb_idscopes( op, &isc ); + if ( rs->sr_err == MDB_SUCCESS ) { + if ( isc.nscope ) + scopeok = 1; + } else { + if ( rs->sr_err == MDB_NOTFOUND ) + goto notfound; + } + break; + } + + /* Not in scope, ignore it */ + if ( !scopeok ) + { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_search) + ": %ld scope not okay\n", + (long) id, 0, 0 ); + goto loop_continue; + } + +scopeok: + if ( id == base->e_id ) { + e = base; + } else { + + /* get the entry */ + rs->sr_err = mdb_id2edata( op, mci, id, &edata ); + if ( rs->sr_err == MDB_NOTFOUND ) { +notfound: + if( nsubs < ncand ) + goto loop_continue; + + if( !MDB_IDL_IS_RANGE(candidates) ) { + /* only complain for non-range IDLs */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_search) + ": candidate %ld not found\n", + (long) id, 0, 0 ); + } else { + /* get the next ID from the DB */ + rs->sr_err = mdb_get_nextid( mci, &cursor ); + if ( rs->sr_err == MDB_NOTFOUND ) { + break; + } + if ( rs->sr_err ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error in get_nextid"; + send_ldap_result( op, rs ); + goto done; + } + cursor--; + } + + goto loop_continue; + } else if ( rs->sr_err ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error in mdb_id2edata"; + send_ldap_result( op, rs ); + goto done; + } + + rs->sr_err = mdb_entry_decode( op, ltid, &edata, &e ); + if ( rs->sr_err ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error in mdb_entry_decode"; + send_ldap_result( op, rs ); + goto done; + } + e->e_id = id; + e->e_name.bv_val = NULL; + e->e_nname.bv_val = NULL; + } + + if ( is_entry_subentry( e ) ) { + if( op->oq_search.rs_scope != LDAP_SCOPE_BASE ) { + if(!get_subentries_visibility( op )) { + /* only subentries are visible */ + goto loop_continue; + } + + } else if ( get_subentries( op ) && + !get_subentries_visibility( op )) + { + /* only subentries are visible */ + goto loop_continue; + } + + } else if ( get_subentries_visibility( op )) { + /* only subentries are visible */ + goto loop_continue; + } + + /* aliases were already dereferenced in candidate list */ + if ( op->ors_deref & LDAP_DEREF_SEARCHING ) { + /* but if the search base is an alias, and we didn't + * deref it when finding, return it. + */ + if ( is_entry_alias(e) && + ((op->ors_deref & LDAP_DEREF_FINDING) || e != base )) + { + goto loop_continue; + } + } + + if ( !manageDSAit && is_entry_glue( e )) { + goto loop_continue; + } + + if (e != base) { + struct berval pdn, pndn; + char *d, *n; + int i; + + /* child of base, just append RDNs to base->e_name */ + if ( nsubs < ncand || isc.scopes[isc.nscope].mid == base->e_id ) { + pdn = base->e_name; + pndn = base->e_nname; + } else { + mdb_id2name( op, ltid, &isc.mc, scopes[isc.nscope].mid, &pdn, &pndn ); + } + e->e_name.bv_len = pdn.bv_len; + e->e_nname.bv_len = pndn.bv_len; + for (i=0; i<isc.numrdns; i++) { + e->e_name.bv_len += isc.rdns[i].bv_len + 1; + e->e_nname.bv_len += isc.nrdns[i].bv_len + 1; + } + e->e_name.bv_val = op->o_tmpalloc(e->e_name.bv_len + 1, op->o_tmpmemctx); + e->e_nname.bv_val = op->o_tmpalloc(e->e_nname.bv_len + 1, op->o_tmpmemctx); + d = e->e_name.bv_val; + n = e->e_nname.bv_val; + if (nsubs < ncand) { + /* RDNs are in top-down order */ + for (i=isc.numrdns-1; i>=0; i--) { + memcpy(d, isc.rdns[i].bv_val, isc.rdns[i].bv_len); + d += isc.rdns[i].bv_len; + *d++ = ','; + memcpy(n, isc.nrdns[i].bv_val, isc.nrdns[i].bv_len); + n += isc.nrdns[i].bv_len; + *n++ = ','; + } + } else { + /* RDNs are in bottom-up order */ + for (i=0; i<isc.numrdns; i++) { + memcpy(d, isc.rdns[i].bv_val, isc.rdns[i].bv_len); + d += isc.rdns[i].bv_len; + *d++ = ','; + memcpy(n, isc.nrdns[i].bv_val, isc.nrdns[i].bv_len); + n += isc.nrdns[i].bv_len; + *n++ = ','; + } + } + + if (pdn.bv_len) { + memcpy(d, pdn.bv_val, pdn.bv_len+1); + memcpy(n, pndn.bv_val, pndn.bv_len+1); + } else { + *--d = '\0'; + *--n = '\0'; + e->e_name.bv_len--; + e->e_nname.bv_len--; + } + if (pndn.bv_val != base->e_nname.bv_val) { + op->o_tmpfree(pndn.bv_val, op->o_tmpmemctx); + op->o_tmpfree(pdn.bv_val, op->o_tmpmemctx); + } + } + + /* + * if it's a referral, add it to the list of referrals. only do + * this for non-base searches, and don't check the filter + * explicitly here since it's only a candidate anyway. + */ + if ( !manageDSAit && op->oq_search.rs_scope != LDAP_SCOPE_BASE + && is_entry_referral( e ) ) + { + BerVarray erefs = get_entry_referrals( op, e ); + rs->sr_ref = referral_rewrite( erefs, &e->e_name, NULL, + op->oq_search.rs_scope == LDAP_SCOPE_ONELEVEL + ? LDAP_SCOPE_BASE : LDAP_SCOPE_SUBTREE ); + + rs->sr_entry = e; + rs->sr_flags = 0; + + send_search_reference( op, rs ); + + if (e != base) + mdb_entry_return( op, e ); + rs->sr_entry = NULL; + e = NULL; + + ber_bvarray_free( rs->sr_ref ); + ber_bvarray_free( erefs ); + rs->sr_ref = NULL; + + goto loop_continue; + } + + /* if it matches the filter and scope, send it */ + rs->sr_err = test_filter( op, e, op->oq_search.rs_filter ); + + if ( rs->sr_err == LDAP_COMPARE_TRUE ) { + /* check size limit */ + if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) { + if ( rs->sr_nentries >= ((PagedResultsState *)op->o_pagedresults_state)->ps_size ) { + if (e != base) + mdb_entry_return( op, e ); + e = NULL; + send_paged_response( op, rs, &lastid, tentries ); + goto done; + } + lastid = id; + } + + if (e) { + /* safe default */ + rs->sr_attrs = op->oq_search.rs_attrs; + rs->sr_operational_attrs = NULL; + rs->sr_ctrls = NULL; + rs->sr_entry = e; + RS_ASSERT( e->e_private != NULL ); + rs->sr_flags = 0; + rs->sr_err = LDAP_SUCCESS; + rs->sr_err = send_search_entry( op, rs ); + rs->sr_attrs = NULL; + rs->sr_entry = NULL; + if (e != base) + mdb_entry_return( op, e ); + e = NULL; + + switch ( rs->sr_err ) { + case LDAP_SUCCESS: /* entry sent ok */ + break; + default: /* entry not sent */ + break; + case LDAP_BUSY: + send_ldap_result( op, rs ); + goto done; + case LDAP_UNAVAILABLE: + case LDAP_SIZELIMIT_EXCEEDED: + if ( rs->sr_err == LDAP_SIZELIMIT_EXCEEDED ) { + rs->sr_ref = rs->sr_v2ref; + send_ldap_result( op, rs ); + rs->sr_err = LDAP_SUCCESS; + + } else { + rs->sr_err = LDAP_OTHER; + } + goto done; + } + } + + } else { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_search) + ": %ld does not match filter\n", + (long) id, 0, 0 ); + } + +loop_continue: + if ( moi == &opinfo && !wwctx.flag && mdb->mi_rtxn_size ) { + wwctx.nentries++; + if ( wwctx.nentries >= mdb->mi_rtxn_size ) { + MDB_envinfo ei; + wwctx.nentries = 0; + mdb_env_info(mdb->mi_dbenv, &ei); + if ( ei.me_last_txnid > mdb_txn_id( ltid )) + mdb_rtxn_snap( op, &wwctx ); + } + } + if ( wwctx.flag ) { + rs->sr_err = mdb_waitfixup( op, &wwctx, mci, mcd, &isc ); + if ( rs->sr_err ) { + send_ldap_result( op, rs ); + goto done; + } + } + + if( e != NULL ) { + if ( e != base ) + mdb_entry_return( op, e ); + RS_ASSERT( rs->sr_entry == NULL ); + e = NULL; + rs->sr_entry = NULL; + } + + if ( nsubs < ncand ) { + int rc = mdb_dn2id_walk( op, &isc ); + if (rc) { + id = NOID; + /* We got to the end of a subtree. If there are any + * alias scopes left, search them too. + */ + while (iscopes[0] && cscope < iscopes[0]) { + cscope++; + isc.id = iscopes[cscope]; + if ( base ) + mdb_entry_return( op, base ); + rs->sr_err = mdb_id2entry(op, mci, isc.id, &base); + if ( !rs->sr_err ) { + mdb_id2name( op, ltid, &isc.mc, isc.id, &base->e_name, &base->e_nname ); + isc.numrdns = 0; + if (isc.oscope == LDAP_SCOPE_ONELEVEL) + isc.oscope = LDAP_SCOPE_BASE; + rc = mdb_dn2id_walk( op, &isc ); + if ( !rc ) { + id = isc.id; + break; + } + } + } + } else + id = isc.id; + } else { + id = mdb_idl_next( candidates, &cursor ); + } + } + +nochange: + rs->sr_ctrls = NULL; + rs->sr_ref = rs->sr_v2ref; + rs->sr_err = (rs->sr_v2ref == NULL) ? LDAP_SUCCESS : LDAP_REFERRAL; + rs->sr_rspoid = NULL; + if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) { + send_paged_response( op, rs, NULL, 0 ); + } else { + send_ldap_result( op, rs ); + } + + rs->sr_err = LDAP_SUCCESS; + +done: + if ( cb.sc_private ) { + /* remove our writewait callback */ + slap_callback **scp = &op->o_callback; + while ( *scp ) { + if ( *scp == &cb ) { + *scp = cb.sc_next; + cb.sc_private = NULL; + break; + } + } + } + mdb_cursor_close( mcd ); + mdb_cursor_close( mci ); + if ( moi == &opinfo ) { + mdb_txn_reset( moi->moi_txn ); + LDAP_SLIST_REMOVE( &op->o_extra, &moi->moi_oe, OpExtra, oe_next ); + } else { + moi->moi_ref--; + } + if( rs->sr_v2ref ) { + ber_bvarray_free( rs->sr_v2ref ); + rs->sr_v2ref = NULL; + } + if (base) + mdb_entry_return( op, base ); + scope_chunk_ret( op, scopes ); + + return rs->sr_err; +} + + +static int base_candidate( + BackendDB *be, + Entry *e, + ID *ids ) +{ + Debug(LDAP_DEBUG_ARGS, "base_candidates: base: \"%s\" (0x%08lx)\n", + e->e_nname.bv_val, (long) e->e_id, 0); + + ids[0] = 1; + ids[1] = e->e_id; + return 0; +} + +/* Look for "objectClass Present" in this filter. + * Also count depth of filter tree while we're at it. + */ +static int oc_filter( + Filter *f, + int cur, + int *max ) +{ + int rc = 0; + + assert( f != NULL ); + + if( cur > *max ) *max = cur; + + switch( f->f_choice ) { + case LDAP_FILTER_PRESENT: + if (f->f_desc == slap_schema.si_ad_objectClass) { + rc = 1; + } + break; + + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + cur++; + for ( f=f->f_and; f; f=f->f_next ) { + (void) oc_filter(f, cur, max); + } + break; + + default: + break; + } + return rc; +} + +static void search_stack_free( void *key, void *data ) +{ + ber_memfree_x(data, NULL); +} + +static void *search_stack( Operation *op ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + void *ret = NULL; + + if ( op->o_threadctx ) { + ldap_pvt_thread_pool_getkey( op->o_threadctx, (void *)search_stack, + &ret, NULL ); + } else { + ret = mdb->mi_search_stack; + } + + if ( !ret ) { + ret = ch_malloc( mdb->mi_search_stack_depth * MDB_IDL_UM_SIZE + * sizeof( ID ) ); + if ( op->o_threadctx ) { + ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)search_stack, + ret, search_stack_free, NULL, NULL ); + } else { + mdb->mi_search_stack = ret; + } + } + return ret; +} + +static int search_candidates( + Operation *op, + SlapReply *rs, + Entry *e, + IdScopes *isc, + MDB_cursor *mci, + ID *ids, + ID *stack ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + int rc, depth = 1; + Filter *f, rf, xf, nf, sf; + AttributeAssertion aa_ref = ATTRIBUTEASSERTION_INIT; + AttributeAssertion aa_subentry = ATTRIBUTEASSERTION_INIT; + + /* + * This routine takes as input a filter (user-filter) + * and rewrites it as follows: + * (&(scope=DN)[(objectClass=subentry)] + * (|[(objectClass=referral)](user-filter)) + */ + + Debug(LDAP_DEBUG_TRACE, + "search_candidates: base=\"%s\" (0x%08lx) scope=%d\n", + e->e_nname.bv_val, (long) e->e_id, op->oq_search.rs_scope ); + + f = op->oq_search.rs_filter; + + /* If the user's filter uses objectClass=*, + * these clauses are redundant. + */ + if (!oc_filter(op->oq_search.rs_filter, 1, &depth) + && !get_subentries_visibility(op)) { + if( !get_manageDSAit(op) && !get_domainScope(op) ) { + /* match referral objects */ + struct berval bv_ref = BER_BVC( "referral" ); + rf.f_choice = LDAP_FILTER_EQUALITY; + rf.f_ava = &aa_ref; + rf.f_av_desc = slap_schema.si_ad_objectClass; + rf.f_av_value = bv_ref; + rf.f_next = f; + xf.f_or = &rf; + xf.f_choice = LDAP_FILTER_OR; + xf.f_next = NULL; + f = &xf; + depth++; + } + } + + if( get_subentries_visibility( op ) ) { + struct berval bv_subentry = BER_BVC( "subentry" ); + sf.f_choice = LDAP_FILTER_EQUALITY; + sf.f_ava = &aa_subentry; + sf.f_av_desc = slap_schema.si_ad_objectClass; + sf.f_av_value = bv_subentry; + sf.f_next = f; + nf.f_choice = LDAP_FILTER_AND; + nf.f_and = &sf; + nf.f_next = NULL; + f = &nf; + depth++; + } + + /* Allocate IDL stack, plus 1 more for former tmp */ + if ( depth+1 > mdb->mi_search_stack_depth ) { + stack = ch_malloc( (depth + 1) * MDB_IDL_UM_SIZE * sizeof( ID ) ); + } + + if( op->ors_deref & LDAP_DEREF_SEARCHING ) { + rc = search_aliases( op, rs, e->e_id, isc, mci, stack ); + } else { + rc = LDAP_SUCCESS; + } + + if ( rc == LDAP_SUCCESS ) { + rc = mdb_filter_candidates( op, isc->mt, f, ids, + stack, stack+MDB_IDL_UM_SIZE ); + } + + if ( depth+1 > mdb->mi_search_stack_depth ) { + ch_free( stack ); + } + + if( rc ) { + Debug(LDAP_DEBUG_TRACE, + "mdb_search_candidates: failed (rc=%d)\n", + rc, NULL, NULL ); + + } else { + Debug(LDAP_DEBUG_TRACE, + "mdb_search_candidates: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) MDB_IDL_FIRST(ids), + (long) MDB_IDL_LAST(ids) ); + } + + return rc; +} + +static int +parse_paged_cookie( Operation *op, SlapReply *rs ) +{ + int rc = LDAP_SUCCESS; + PagedResultsState *ps = op->o_pagedresults_state; + + /* this function must be invoked only if the pagedResults + * control has been detected, parsed and partially checked + * by the frontend */ + assert( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ); + + /* cookie decoding/checks deferred to backend... */ + if ( ps->ps_cookieval.bv_len ) { + PagedResultsCookie reqcookie; + if( ps->ps_cookieval.bv_len != sizeof( reqcookie ) ) { + /* bad cookie */ + rs->sr_text = "paged results cookie is invalid"; + rc = LDAP_PROTOCOL_ERROR; + goto done; + } + + AC_MEMCPY( &reqcookie, ps->ps_cookieval.bv_val, sizeof( reqcookie )); + + if ( reqcookie > ps->ps_cookie ) { + /* bad cookie */ + rs->sr_text = "paged results cookie is invalid"; + rc = LDAP_PROTOCOL_ERROR; + goto done; + + } else if ( reqcookie < ps->ps_cookie ) { + rs->sr_text = "paged results cookie is invalid or old"; + rc = LDAP_UNWILLING_TO_PERFORM; + goto done; + } + + } else { + /* we're going to use ps_cookie */ + op->o_conn->c_pagedresults_state.ps_cookie = 0; + } + +done:; + + return rc; +} + +static void +send_paged_response( + Operation *op, + SlapReply *rs, + ID *lastid, + int tentries ) +{ + LDAPControl *ctrls[2]; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + PagedResultsCookie respcookie; + struct berval cookie; + + Debug(LDAP_DEBUG_ARGS, + "send_paged_response: lastid=0x%08lx nentries=%d\n", + lastid ? *lastid : 0, rs->sr_nentries, NULL ); + + ctrls[1] = NULL; + + ber_init2( ber, NULL, LBER_USE_DER ); + + if ( lastid ) { + respcookie = ( PagedResultsCookie )(*lastid); + cookie.bv_len = sizeof( respcookie ); + cookie.bv_val = (char *)&respcookie; + + } else { + respcookie = ( PagedResultsCookie )0; + BER_BVSTR( &cookie, "" ); + } + + op->o_conn->c_pagedresults_state.ps_cookie = respcookie; + op->o_conn->c_pagedresults_state.ps_count = + ((PagedResultsState *)op->o_pagedresults_state)->ps_count + + rs->sr_nentries; + + /* return size of 0 -- no estimate */ + ber_printf( ber, "{iO}", 0, &cookie ); + + ctrls[0] = op->o_tmpalloc( sizeof(LDAPControl), op->o_tmpmemctx ); + if ( ber_flatten2( ber, &ctrls[0]->ldctl_value, 0 ) == -1 ) { + goto done; + } + + ctrls[0]->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS; + ctrls[0]->ldctl_iscritical = 0; + + slap_add_ctrls( op, rs, ctrls ); + rs->sr_err = LDAP_SUCCESS; + send_ldap_result( op, rs ); + +done: + (void) ber_free_buf( ber ); +} diff --git a/servers/slapd/back-mdb/tools.c b/servers/slapd/back-mdb/tools.c new file mode 100644 index 0000000..5fa67b0 --- /dev/null +++ b/servers/slapd/back-mdb/tools.c @@ -0,0 +1,1512 @@ +/* tools.c - tools for slap tools */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2011-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/errno.h> + +#define AVL_INTERNAL +#include "back-mdb.h" +#include "idl.h" + +#ifdef MDB_TOOL_IDL_CACHING +static int mdb_tool_idl_flush( BackendDB *be, MDB_txn *txn ); + +#define IDBLOCK 1024 + +typedef struct mdb_tool_idl_cache_entry { + struct mdb_tool_idl_cache_entry *next; + ID ids[IDBLOCK]; +} mdb_tool_idl_cache_entry; + +typedef struct mdb_tool_idl_cache { + struct berval kstr; + mdb_tool_idl_cache_entry *head, *tail; + ID first, last; + int count; + short offset; + short flags; +} mdb_tool_idl_cache; +#define WAS_FOUND 0x01 +#define WAS_RANGE 0x02 + +#define MDB_TOOL_IDL_FLUSH(be, txn) mdb_tool_idl_flush(be, txn) +#else +#define MDB_TOOL_IDL_FLUSH(be, txn) +#endif /* MDB_TOOL_IDL_CACHING */ + +MDB_txn *mdb_tool_txn = NULL; + +static MDB_txn *txi = NULL; +static MDB_cursor *cursor = NULL, *idcursor = NULL; +static MDB_cursor *mcp = NULL, *mcd = NULL; +static MDB_val key, data; +static ID previd = NOID; + +typedef struct dn_id { + ID id; + struct berval dn; +} dn_id; + +#define HOLE_SIZE 4096 +static dn_id hbuf[HOLE_SIZE], *holes = hbuf; +static unsigned nhmax = HOLE_SIZE; +static unsigned nholes; + +static struct berval *tool_base; +static int tool_scope; +static Filter *tool_filter; +static Entry *tool_next_entry; + +static ID mdb_tool_ix_id; +static Operation *mdb_tool_ix_op; +static MDB_txn *mdb_tool_ix_txn; +static int mdb_tool_index_tcount, mdb_tool_threads; +static IndexRec *mdb_tool_index_rec; +static struct mdb_info *mdb_tool_info; +static ldap_pvt_thread_mutex_t mdb_tool_index_mutex; +static ldap_pvt_thread_cond_t mdb_tool_index_cond_main; +static ldap_pvt_thread_cond_t mdb_tool_index_cond_work; +static void * mdb_tool_index_task( void *ctx, void *ptr ); + +static int mdb_writes, mdb_writes_per_commit; + +/* Number of ops per commit in Quick mode. + * Batching speeds writes overall, but too large a + * batch will fail with MDB_TXN_FULL. + */ +#ifndef MDB_WRITES_PER_COMMIT +#define MDB_WRITES_PER_COMMIT 500 +#endif + +static int +mdb_tool_entry_get_int( BackendDB *be, ID id, Entry **ep ); + +int mdb_tool_entry_open( + BackendDB *be, int mode ) +{ + /* In Quick mode, commit once per 500 entries */ + mdb_writes = 0; + if ( slapMode & SLAP_TOOL_QUICK ) + mdb_writes_per_commit = MDB_WRITES_PER_COMMIT; + else + mdb_writes_per_commit = 1; + + /* Set up for threaded slapindex */ + if (( slapMode & (SLAP_TOOL_QUICK|SLAP_TOOL_READONLY)) == SLAP_TOOL_QUICK ) { + if ( !mdb_tool_info ) { + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + ldap_pvt_thread_mutex_init( &mdb_tool_index_mutex ); + ldap_pvt_thread_cond_init( &mdb_tool_index_cond_main ); + ldap_pvt_thread_cond_init( &mdb_tool_index_cond_work ); + if ( mdb->mi_nattrs ) { + int i; +#if 0 /* threaded indexing has no performance advantage */ + mdb_tool_threads = slap_tool_thread_max - 1; +#endif + if ( mdb_tool_threads > 1 ) { + mdb_tool_index_rec = ch_calloc( mdb->mi_nattrs, sizeof( IndexRec )); + mdb_tool_index_tcount = mdb_tool_threads - 1; + for (i=1; i<mdb_tool_threads; i++) { + int *ptr = ch_malloc( sizeof( int )); + *ptr = i; + ldap_pvt_thread_pool_submit( &connection_pool, + mdb_tool_index_task, ptr ); + } + mdb_tool_info = mdb; + } + } + } + } + + return 0; +} + +int mdb_tool_entry_close( + BackendDB *be ) +{ + if ( mdb_tool_info ) { + slapd_shutdown = 1; + ldap_pvt_thread_mutex_lock( &mdb_tool_index_mutex ); + + /* There might still be some threads starting */ + while ( mdb_tool_index_tcount > 0 ) { + ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_main, + &mdb_tool_index_mutex ); + } + + mdb_tool_index_tcount = mdb_tool_threads - 1; + ldap_pvt_thread_cond_broadcast( &mdb_tool_index_cond_work ); + + /* Make sure all threads are stopped */ + while ( mdb_tool_index_tcount > 0 ) { + ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_main, + &mdb_tool_index_mutex ); + } + ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex ); + + mdb_tool_info = NULL; + slapd_shutdown = 0; + ch_free( mdb_tool_index_rec ); + mdb_tool_index_tcount = mdb_tool_threads - 1; + } + + if( idcursor ) { + mdb_cursor_close( idcursor ); + idcursor = NULL; + } + if( cursor ) { + mdb_cursor_close( cursor ); + cursor = NULL; + } + { + struct mdb_info *mdb = be->be_private; + if ( mdb ) { + int i; + for (i=0; i<mdb->mi_nattrs; i++) + mdb->mi_attrs[i]->ai_cursor = NULL; + } + } + if( mdb_tool_txn ) { + int rc; + MDB_TOOL_IDL_FLUSH( be, mdb_tool_txn ); + if (( rc = mdb_txn_commit( mdb_tool_txn ))) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_tool_entry_close) ": database %s: " + "txn_commit failed: %s (%d)\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + return -1; + } + mdb_tool_txn = NULL; + } + if( txi ) { + int rc; + if (( rc = mdb_txn_commit( txi ))) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_tool_entry_close) ": database %s: " + "txn_commit failed: %s (%d)\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + return -1; + } + txi = NULL; + } + + if( nholes ) { + unsigned i; + fprintf( stderr, "Error, entries missing!\n"); + for (i=0; i<nholes; i++) { + fprintf(stderr, " entry %ld: %s\n", + holes[i].id, holes[i].dn.bv_val); + } + nholes = 0; + return -1; + } + + return 0; +} + +ID +mdb_tool_entry_first_x( + BackendDB *be, + struct berval *base, + int scope, + Filter *f ) +{ + tool_base = base; + tool_scope = scope; + tool_filter = f; + + return mdb_tool_entry_next( be ); +} + +ID mdb_tool_entry_next( + BackendDB *be ) +{ + int rc; + ID id; + struct mdb_info *mdb; + + assert( be != NULL ); + assert( slapMode & SLAP_TOOL_MODE ); + + mdb = (struct mdb_info *) be->be_private; + assert( mdb != NULL ); + + if ( !mdb_tool_txn ) { + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, MDB_RDONLY, &mdb_tool_txn ); + if ( rc ) + return NOID; + rc = mdb_cursor_open( mdb_tool_txn, mdb->mi_id2entry, &cursor ); + if ( rc ) { + mdb_txn_abort( mdb_tool_txn ); + return NOID; + } + } + +next:; + rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT ); + + if( rc ) { + return NOID; + } + + previd = *(ID *)key.mv_data; + id = previd; + + if ( !data.mv_size ) + goto next; + + if ( tool_filter || tool_base ) { + static Operation op = {0}; + static Opheader ohdr = {0}; + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + if ( tool_next_entry ) { + mdb_entry_release( &op, tool_next_entry, 0 ); + tool_next_entry = NULL; + } + + rc = mdb_tool_entry_get_int( be, id, &tool_next_entry ); + if ( rc == LDAP_NO_SUCH_OBJECT ) { + goto next; + } + + assert( tool_next_entry != NULL ); + + if ( tool_filter && test_filter( NULL, tool_next_entry, tool_filter ) != LDAP_COMPARE_TRUE ) + { + mdb_entry_release( &op, tool_next_entry, 0 ); + tool_next_entry = NULL; + goto next; + } + } + + return id; +} + +ID mdb_tool_dn2id_get( + Backend *be, + struct berval *dn +) +{ + struct mdb_info *mdb; + Operation op = {0}; + Opheader ohdr = {0}; + ID id; + int rc; + + if ( BER_BVISEMPTY(dn) ) + return 0; + + mdb = (struct mdb_info *) be->be_private; + + if ( !mdb_tool_txn ) { + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, (slapMode & SLAP_TOOL_READONLY) != 0 ? + MDB_RDONLY : 0, &mdb_tool_txn ); + if ( rc ) + return NOID; + } + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + rc = mdb_dn2id( &op, mdb_tool_txn, NULL, dn, &id, NULL, NULL, NULL ); + if ( rc == MDB_NOTFOUND ) + return NOID; + + return id; +} + +static int +mdb_tool_entry_get_int( BackendDB *be, ID id, Entry **ep ) +{ + Operation op = {0}; + Opheader ohdr = {0}; + + Entry *e = NULL; + struct berval dn = BER_BVNULL, ndn = BER_BVNULL; + int rc; + + assert( be != NULL ); + assert( slapMode & SLAP_TOOL_MODE ); + + if ( ( tool_filter || tool_base ) && id == previd && tool_next_entry != NULL ) { + *ep = tool_next_entry; + tool_next_entry = NULL; + return LDAP_SUCCESS; + } + + if ( id != previd ) { + key.mv_size = sizeof(ID); + key.mv_data = &id; + rc = mdb_cursor_get( cursor, &key, &data, MDB_SET ); + if ( rc ) { + rc = LDAP_OTHER; + goto done; + } + } + if ( !data.mv_size ) { + rc = LDAP_NO_SUCH_OBJECT; + goto done; + } + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + if ( slapMode & SLAP_TOOL_READONLY ) { + rc = mdb_id2name( &op, mdb_tool_txn, &idcursor, id, &dn, &ndn ); + if ( rc ) { + rc = LDAP_OTHER; + goto done; + } + if ( tool_base != NULL ) { + if ( !dnIsSuffixScope( &ndn, tool_base, tool_scope ) ) { + ch_free( dn.bv_val ); + ch_free( ndn.bv_val ); + rc = LDAP_NO_SUCH_OBJECT; + goto done; + } + } + } + rc = mdb_entry_decode( &op, mdb_tool_txn, &data, &e ); + e->e_id = id; + if ( !BER_BVISNULL( &dn )) { + e->e_name = dn; + e->e_nname = ndn; + } else { + e->e_name.bv_val = NULL; + e->e_nname.bv_val = NULL; + } + +done: + if ( e != NULL ) { + *ep = e; + } + + return rc; +} + +Entry* +mdb_tool_entry_get( BackendDB *be, ID id ) +{ + Entry *e = NULL; + int rc; + + if ( !mdb_tool_txn ) { + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, + (slapMode & SLAP_TOOL_READONLY) ? MDB_RDONLY : 0, &mdb_tool_txn ); + if ( rc ) + return NULL; + } + if ( !cursor ) { + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + rc = mdb_cursor_open( mdb_tool_txn, mdb->mi_id2entry, &cursor ); + if ( rc ) { + mdb_txn_abort( mdb_tool_txn ); + mdb_tool_txn = NULL; + return NULL; + } + } + (void)mdb_tool_entry_get_int( be, id, &e ); + return e; +} + +static int mdb_tool_next_id( + Operation *op, + MDB_txn *tid, + Entry *e, + struct berval *text, + int hole ) +{ + struct berval dn = e->e_name; + struct berval ndn = e->e_nname; + struct berval pdn, npdn, nmatched; + ID id, pid = 0; + int rc; + + if (ndn.bv_len == 0) { + e->e_id = 0; + return 0; + } + + rc = mdb_dn2id( op, tid, mcp, &ndn, &id, NULL, NULL, &nmatched ); + if ( rc == MDB_NOTFOUND ) { + if ( !be_issuffix( op->o_bd, &ndn ) ) { + ID eid = e->e_id; + dnParent( &ndn, &npdn ); + if ( nmatched.bv_len != npdn.bv_len ) { + dnParent( &dn, &pdn ); + e->e_name = pdn; + e->e_nname = npdn; + rc = mdb_tool_next_id( op, tid, e, text, 1 ); + e->e_name = dn; + e->e_nname = ndn; + if ( rc ) { + return rc; + } + /* If parent didn't exist, it was created just now + * and its ID is now in e->e_id. Make sure the current + * entry gets added under the new parent ID. + */ + if ( eid != e->e_id ) { + pid = e->e_id; + } + } else { + pid = id; + } + } + rc = mdb_next_id( op->o_bd, idcursor, &e->e_id ); + if ( rc ) { + snprintf( text->bv_val, text->bv_len, + "next_id failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> mdb_tool_next_id: %s\n", text->bv_val, 0, 0 ); + return rc; + } + rc = mdb_dn2id_add( op, mcp, mcd, pid, 1, 1, e ); + if ( rc ) { + snprintf( text->bv_val, text->bv_len, + "dn2id_add failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> mdb_tool_next_id: %s\n", text->bv_val, 0, 0 ); + } else if ( hole ) { + MDB_val key, data; + if ( nholes == nhmax - 1 ) { + if ( holes == hbuf ) { + holes = ch_malloc( nhmax * sizeof(dn_id) * 2 ); + AC_MEMCPY( holes, hbuf, sizeof(hbuf) ); + } else { + holes = ch_realloc( holes, nhmax * sizeof(dn_id) * 2 ); + } + nhmax *= 2; + } + ber_dupbv( &holes[nholes].dn, &ndn ); + holes[nholes++].id = e->e_id; + key.mv_size = sizeof(ID); + key.mv_data = &e->e_id; + data.mv_size = 0; + data.mv_data = NULL; + rc = mdb_cursor_put( idcursor, &key, &data, MDB_NOOVERWRITE ); + if ( rc == MDB_KEYEXIST ) + rc = 0; + if ( rc ) { + snprintf( text->bv_val, text->bv_len, + "dummy id2entry add failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> mdb_tool_next_id: %s\n", text->bv_val, 0, 0 ); + } + } + } else if ( !hole ) { + unsigned i, j; + + e->e_id = id; + + for ( i=0; i<nholes; i++) { + if ( holes[i].id == e->e_id ) { + free(holes[i].dn.bv_val); + for (j=i;j<nholes;j++) holes[j] = holes[j+1]; + holes[j].id = 0; + nholes--; + break; + } else if ( holes[i].id > e->e_id ) { + break; + } + } + } + return rc; +} + +static int +mdb_tool_index_add( + Operation *op, + MDB_txn *txn, + Entry *e ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + + if ( !mdb->mi_nattrs ) + return 0; + + if ( mdb_tool_threads > 1 ) { + IndexRec *ir; + int i, rc; + Attribute *a; + + ir = mdb_tool_index_rec; + for (i=0; i<mdb->mi_nattrs; i++) + ir[i].ir_attrs = NULL; + + for ( a = e->e_attrs; a != NULL; a = a->a_next ) { + rc = mdb_index_recset( mdb, a, a->a_desc->ad_type, + &a->a_desc->ad_tags, ir ); + if ( rc ) + return rc; + } + for (i=0; i<mdb->mi_nattrs; i++) { + if ( !ir[i].ir_ai ) + break; + rc = mdb_cursor_open( txn, ir[i].ir_ai->ai_dbi, + &ir[i].ir_ai->ai_cursor ); + if ( rc ) + return rc; + } + mdb_tool_ix_id = e->e_id; + mdb_tool_ix_op = op; + mdb_tool_ix_txn = txn; + ldap_pvt_thread_mutex_lock( &mdb_tool_index_mutex ); + /* Wait for all threads to be ready */ + while ( mdb_tool_index_tcount ) { + ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_main, + &mdb_tool_index_mutex ); + } + + for ( i=1; i<mdb_tool_threads; i++ ) + mdb_tool_index_rec[i].ir_i = LDAP_BUSY; + mdb_tool_index_tcount = mdb_tool_threads - 1; + ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex ); + ldap_pvt_thread_cond_broadcast( &mdb_tool_index_cond_work ); + + rc = mdb_index_recrun( op, txn, mdb, ir, e->e_id, 0 ); + if ( rc ) + return rc; + ldap_pvt_thread_mutex_lock( &mdb_tool_index_mutex ); + for ( i=1; i<mdb_tool_threads; i++ ) { + if ( mdb_tool_index_rec[i].ir_i == LDAP_BUSY ) { + ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_main, + &mdb_tool_index_mutex ); + i--; + continue; + } + if ( mdb_tool_index_rec[i].ir_i ) { + rc = mdb_tool_index_rec[i].ir_i; + break; + } + } + ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex ); + return rc; + } else + { + return mdb_index_entry_add( op, txn, e ); + } +} + +ID mdb_tool_entry_put( + BackendDB *be, + Entry *e, + struct berval *text ) +{ + int rc; + struct mdb_info *mdb; + Operation op = {0}; + Opheader ohdr = {0}; + + assert( be != NULL ); + assert( slapMode & SLAP_TOOL_MODE ); + + assert( text != NULL ); + assert( text->bv_val != NULL ); + assert( text->bv_val[0] == '\0' ); /* overconservative? */ + + Debug( LDAP_DEBUG_TRACE, "=> " LDAP_XSTRING(mdb_tool_entry_put) + "( %ld, \"%s\" )\n", (long) e->e_id, e->e_dn, 0 ); + + mdb = (struct mdb_info *) be->be_private; + + if ( !mdb_tool_txn ) { + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, 0, &mdb_tool_txn ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "txn_begin failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + return NOID; + } + rc = mdb_cursor_open( mdb_tool_txn, mdb->mi_id2entry, &idcursor ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "cursor_open failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + return NOID; + } + if ( !mdb->mi_nextid ) { + ID dummy; + mdb_next_id( be, idcursor, &dummy ); + } + rc = mdb_cursor_open( mdb_tool_txn, mdb->mi_dn2id, &mcp ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "cursor_open failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + return NOID; + } + rc = mdb_cursor_open( mdb_tool_txn, mdb->mi_dn2id, &mcd ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "cursor_open failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + return NOID; + } + } + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + /* add dn2id indices */ + rc = mdb_tool_next_id( &op, mdb_tool_txn, e, text, 0 ); + if( rc != 0 ) { + goto done; + } + + rc = mdb_tool_index_add( &op, mdb_tool_txn, e ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "index_entry_add failed: err=%d", rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + goto done; + } + + + /* id2entry index */ + rc = mdb_id2entry_add( &op, mdb_tool_txn, idcursor, e ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "id2entry_add failed: err=%d", rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + goto done; + } + +done: + if( rc == 0 ) { + mdb_writes++; + if ( mdb_writes >= mdb_writes_per_commit ) { + unsigned i; + MDB_TOOL_IDL_FLUSH( be, mdb_tool_txn ); + rc = mdb_txn_commit( mdb_tool_txn ); + for ( i=0; i<mdb->mi_nattrs; i++ ) + mdb->mi_attrs[i]->ai_cursor = NULL; + mdb_writes = 0; + mdb_tool_txn = NULL; + idcursor = NULL; + if( rc != 0 ) { + mdb->mi_numads = 0; + snprintf( text->bv_val, text->bv_len, + "txn_commit failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + e->e_id = NOID; + } + } + + } else { + unsigned i; + mdb_txn_abort( mdb_tool_txn ); + mdb_tool_txn = NULL; + idcursor = NULL; + for ( i=0; i<mdb->mi_nattrs; i++ ) + mdb->mi_attrs[i]->ai_cursor = NULL; + mdb_writes = 0; + snprintf( text->bv_val, text->bv_len, + "txn_aborted! %s (%d)", + rc == LDAP_OTHER ? "Internal error" : + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + e->e_id = NOID; + } + + return e->e_id; +} + +static int mdb_dn2id_upgrade( BackendDB *be ); + +int mdb_tool_entry_reindex( + BackendDB *be, + ID id, + AttributeDescription **adv ) +{ + struct mdb_info *mi = (struct mdb_info *) be->be_private; + int rc; + Entry *e; + Operation op = {0}; + Opheader ohdr = {0}; + + Debug( LDAP_DEBUG_ARGS, + "=> " LDAP_XSTRING(mdb_tool_entry_reindex) "( %ld )\n", + (long) id, 0, 0 ); + assert( tool_base == NULL ); + assert( tool_filter == NULL ); + + /* Special: do a dn2id upgrade */ + if ( adv && adv[0] == slap_schema.si_ad_entryDN ) { + /* short-circuit tool_entry_next() */ + mdb_cursor_get( cursor, &key, &data, MDB_LAST ); + return mdb_dn2id_upgrade( be ); + } + + /* No indexes configured, nothing to do. Could return an + * error here to shortcut things. + */ + if (!mi->mi_attrs) { + return 0; + } + + /* Check for explicit list of attrs to index */ + if ( adv ) { + int i, j, n; + + if ( mi->mi_attrs[0]->ai_desc != adv[0] ) { + /* count */ + for ( n = 0; adv[n]; n++ ) ; + + /* insertion sort */ + for ( i = 0; i < n; i++ ) { + AttributeDescription *ad = adv[i]; + for ( j = i-1; j>=0; j--) { + if ( SLAP_PTRCMP( adv[j], ad ) <= 0 ) break; + adv[j+1] = adv[j]; + } + adv[j+1] = ad; + } + } + + for ( i = 0; adv[i]; i++ ) { + if ( mi->mi_attrs[i]->ai_desc != adv[i] ) { + for ( j = i+1; j < mi->mi_nattrs; j++ ) { + if ( mi->mi_attrs[j]->ai_desc == adv[i] ) { + AttrInfo *ai = mi->mi_attrs[i]; + mi->mi_attrs[i] = mi->mi_attrs[j]; + mi->mi_attrs[j] = ai; + break; + } + } + if ( j == mi->mi_nattrs ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_tool_entry_reindex) + ": no index configured for %s\n", + adv[i]->ad_cname.bv_val, 0, 0 ); + return -1; + } + } + } + mi->mi_nattrs = i; + } + + e = mdb_tool_entry_get( be, id ); + + if( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_tool_entry_reindex) + ": could not locate id=%ld\n", + (long) id, 0, 0 ); + return -1; + } + + if ( !txi ) { + rc = mdb_txn_begin( mi->mi_dbenv, NULL, 0, &txi ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_reindex) ": " + "txn_begin failed: %s (%d)\n", + mdb_strerror(rc), rc, 0 ); + goto done; + } + } + + if ( slapMode & SLAP_TRUNCATE_MODE ) { + int i; + for ( i=0; i < mi->mi_nattrs; i++ ) { + rc = mdb_drop( txi, mi->mi_attrs[i]->ai_dbi, 0 ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_tool_entry_reindex) + ": (Truncate) mdb_drop(%s) failed: %s (%d)\n", + mi->mi_attrs[i]->ai_desc->ad_type->sat_cname.bv_val, + mdb_strerror(rc), rc ); + return -1; + } + } + slapMode ^= SLAP_TRUNCATE_MODE; + } + + /* + * just (re)add them for now + * Use truncate mode to empty/reset index databases + */ + + Debug( LDAP_DEBUG_TRACE, + "=> " LDAP_XSTRING(mdb_tool_entry_reindex) "( %ld )\n", + (long) id, 0, 0 ); + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + rc = mdb_tool_index_add( &op, txi, e ); + +done: + if( rc == 0 ) { + mdb_writes++; + if ( mdb_writes >= mdb_writes_per_commit ) { + MDB_val key; + unsigned i; + MDB_TOOL_IDL_FLUSH( be, txi ); + rc = mdb_txn_commit( txi ); + mdb_writes = 0; + for ( i=0; i<mi->mi_nattrs; i++ ) + mi->mi_attrs[i]->ai_cursor = NULL; + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_reindex) + ": txn_commit failed: %s (%d)\n", + mdb_strerror(rc), rc, 0 ); + e->e_id = NOID; + } + mdb_cursor_close( cursor ); + txi = NULL; + /* Must close the read txn to allow old pages to be reclaimed. */ + mdb_txn_abort( mdb_tool_txn ); + /* and then reopen it so that tool_entry_next still works. */ + mdb_txn_begin( mi->mi_dbenv, NULL, MDB_RDONLY, &mdb_tool_txn ); + mdb_cursor_open( mdb_tool_txn, mi->mi_id2entry, &cursor ); + key.mv_data = &id; + key.mv_size = sizeof(ID); + mdb_cursor_get( cursor, &key, NULL, MDB_SET ); + } + + } else { + unsigned i; + mdb_writes = 0; + mdb_cursor_close( cursor ); + cursor = NULL; + mdb_txn_abort( txi ); + for ( i=0; i<mi->mi_nattrs; i++ ) + mi->mi_attrs[i]->ai_cursor = NULL; + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_reindex) + ": txn_aborted! err=%d\n", + rc, 0, 0 ); + e->e_id = NOID; + txi = NULL; + } + mdb_entry_release( &op, e, 0 ); + + return rc; +} + +ID mdb_tool_entry_modify( + BackendDB *be, + Entry *e, + struct berval *text ) +{ + int rc; + struct mdb_info *mdb; + Operation op = {0}; + Opheader ohdr = {0}; + + assert( be != NULL ); + assert( slapMode & SLAP_TOOL_MODE ); + + assert( text != NULL ); + assert( text->bv_val != NULL ); + assert( text->bv_val[0] == '\0' ); /* overconservative? */ + + assert ( e->e_id != NOID ); + + Debug( LDAP_DEBUG_TRACE, + "=> " LDAP_XSTRING(mdb_tool_entry_modify) "( %ld, \"%s\" )\n", + (long) e->e_id, e->e_dn, 0 ); + + mdb = (struct mdb_info *) be->be_private; + + if( cursor ) { + mdb_cursor_close( cursor ); + cursor = NULL; + } + if ( !mdb_tool_txn ) { + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, 0, &mdb_tool_txn ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "txn_begin failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_modify) ": %s\n", + text->bv_val, 0, 0 ); + return NOID; + } + } + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + /* id2entry index */ + rc = mdb_id2entry_update( &op, mdb_tool_txn, NULL, e ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "id2entry_update failed: err=%d", rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_modify) ": %s\n", + text->bv_val, 0, 0 ); + goto done; + } + +done: + if( rc == 0 ) { + rc = mdb_txn_commit( mdb_tool_txn ); + if( rc != 0 ) { + mdb->mi_numads = 0; + snprintf( text->bv_val, text->bv_len, + "txn_commit failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_modify) ": " + "%s\n", text->bv_val, 0, 0 ); + e->e_id = NOID; + } + + } else { + mdb_txn_abort( mdb_tool_txn ); + snprintf( text->bv_val, text->bv_len, + "txn_aborted! %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_modify) ": %s\n", + text->bv_val, 0, 0 ); + e->e_id = NOID; + } + mdb_tool_txn = NULL; + idcursor = NULL; + + return e->e_id; +} + +static void * +mdb_tool_index_task( void *ctx, void *ptr ) +{ + int base = *(int *)ptr; + + free( ptr ); + while ( 1 ) { + ldap_pvt_thread_mutex_lock( &mdb_tool_index_mutex ); + mdb_tool_index_tcount--; + if ( !mdb_tool_index_tcount ) + ldap_pvt_thread_cond_signal( &mdb_tool_index_cond_main ); + ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_work, + &mdb_tool_index_mutex ); + if ( slapd_shutdown ) { + mdb_tool_index_tcount--; + if ( !mdb_tool_index_tcount ) + ldap_pvt_thread_cond_signal( &mdb_tool_index_cond_main ); + ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex ); + break; + } + ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex ); + mdb_tool_index_rec[base].ir_i = mdb_index_recrun( mdb_tool_ix_op, + mdb_tool_ix_txn, + mdb_tool_info, mdb_tool_index_rec, mdb_tool_ix_id, base ); + } + + return NULL; +} + +#ifdef MDB_TOOL_IDL_CACHING +static int +mdb_tool_idl_cmp( const void *v1, const void *v2 ) +{ + const mdb_tool_idl_cache *c1 = v1, *c2 = v2; + int rc; + + if (( rc = c1->kstr.bv_len - c2->kstr.bv_len )) return rc; + return memcmp( c1->kstr.bv_val, c2->kstr.bv_val, c1->kstr.bv_len ); +} + +static int +mdb_tool_idl_flush_one( MDB_cursor *mc, AttrInfo *ai, mdb_tool_idl_cache *ic ) +{ + mdb_tool_idl_cache_entry *ice; + MDB_val key, data[2]; + int i, rc; + ID id, nid; + + /* Freshly allocated, ignore it */ + if ( !ic->head && ic->count <= MDB_IDL_DB_SIZE ) { + return 0; + } + + key.mv_data = ic->kstr.bv_val; + key.mv_size = ic->kstr.bv_len; + + if ( ic->count > MDB_IDL_DB_SIZE ) { + while ( ic->flags & WAS_FOUND ) { + rc = mdb_cursor_get( mc, &key, data, MDB_SET ); + if ( rc ) { + /* FIXME: find out why this happens */ + ic->flags = 0; + break; + } + if ( ic->flags & WAS_RANGE ) { + /* Skip lo */ + rc = mdb_cursor_get( mc, &key, data, MDB_NEXT_DUP ); + + /* Get hi */ + rc = mdb_cursor_get( mc, &key, data, MDB_NEXT_DUP ); + + /* Store range hi */ + data[0].mv_data = &ic->last; + rc = mdb_cursor_put( mc, &key, data, MDB_CURRENT ); + } else { + /* Delete old data, replace with range */ + ic->first = *(ID *)data[0].mv_data; + mdb_cursor_del( mc, MDB_NODUPDATA ); + } + break; + } + if ( !(ic->flags & WAS_RANGE)) { + /* range, didn't exist before */ + nid = 0; + data[0].mv_size = sizeof(ID); + data[0].mv_data = &nid; + rc = mdb_cursor_put( mc, &key, data, 0 ); + if ( rc == 0 ) { + data[0].mv_data = &ic->first; + rc = mdb_cursor_put( mc, &key, data, 0 ); + if ( rc == 0 ) { + data[0].mv_data = &ic->last; + rc = mdb_cursor_put( mc, &key, data, 0 ); + } + } + if ( rc ) { + rc = -1; + } + } + } else { + /* Normal write */ + int n; + + data[0].mv_size = sizeof(ID); + rc = 0; + i = ic->offset; + for ( ice = ic->head, n=0; ice; ice = ice->next, n++ ) { + int end; + if ( ice->next ) { + end = IDBLOCK; + } else { + end = ic->count & (IDBLOCK-1); + if ( !end ) + end = IDBLOCK; + } + data[1].mv_size = end - i; + data[0].mv_data = &ice->ids[i]; + i = 0; + rc = mdb_cursor_put( mc, &key, data, MDB_NODUPDATA|MDB_APPEND|MDB_MULTIPLE ); + if ( rc ) { + if ( rc == MDB_KEYEXIST ) { + rc = 0; + continue; + } + rc = -1; + break; + } + } + if ( ic->head ) { + ic->tail->next = ai->ai_flist; + ai->ai_flist = ic->head; + } + } + ic->head = ai->ai_clist; + ai->ai_clist = ic; + return rc; +} + +static int +mdb_tool_idl_flush_db( MDB_txn *txn, AttrInfo *ai ) +{ + MDB_cursor *mc; + Avlnode *root; + int rc; + + mdb_cursor_open( txn, ai->ai_dbi, &mc ); + root = tavl_end( ai->ai_root, TAVL_DIR_LEFT ); + do { + rc = mdb_tool_idl_flush_one( mc, ai, root->avl_data ); + if ( rc != -1 ) + rc = 0; + } while ((root = tavl_next(root, TAVL_DIR_RIGHT))); + mdb_cursor_close( mc ); + + return rc; +} + +static int +mdb_tool_idl_flush( BackendDB *be, MDB_txn *txn ) +{ + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + int rc = 0; + unsigned int i, dbi; + + for ( i=0; i < mdb->mi_nattrs; i++ ) { + if ( !mdb->mi_attrs[i]->ai_root ) continue; + rc = mdb_tool_idl_flush_db( txn, mdb->mi_attrs[i] ); + tavl_free(mdb->mi_attrs[i]->ai_root, NULL); + mdb->mi_attrs[i]->ai_root = NULL; + if ( rc ) + break; + } + return rc; +} + +int mdb_tool_idl_add( + BackendDB *be, + MDB_cursor *mc, + struct berval *keys, + ID id ) +{ + MDB_dbi dbi; + mdb_tool_idl_cache *ic, itmp; + mdb_tool_idl_cache_entry *ice; + int i, rc, lcount; + AttrInfo *ai = (AttrInfo *)mc; + mc = ai->ai_cursor; + + dbi = ai->ai_dbi; + for (i=0; keys[i].bv_val; i++) { + itmp.kstr = keys[i]; + ic = tavl_find( (Avlnode *)ai->ai_root, &itmp, mdb_tool_idl_cmp ); + + /* No entry yet, create one */ + if ( !ic ) { + MDB_val key, data; + ID nid; + int rc; + + if ( ai->ai_clist ) { + ic = ai->ai_clist; + ai->ai_clist = ic->head; + } else { + ic = ch_malloc( sizeof( mdb_tool_idl_cache ) + itmp.kstr.bv_len + 4 ); + } + ic->kstr.bv_len = itmp.kstr.bv_len; + ic->kstr.bv_val = (char *)(ic+1); + memcpy( ic->kstr.bv_val, itmp.kstr.bv_val, ic->kstr.bv_len ); + ic->head = ic->tail = NULL; + ic->last = 0; + ic->count = 0; + ic->offset = 0; + ic->flags = 0; + tavl_insert( (Avlnode **)&ai->ai_root, ic, mdb_tool_idl_cmp, + avl_dup_error ); + + /* load existing key count here */ + key.mv_size = keys[i].bv_len; + key.mv_data = keys[i].bv_val; + rc = mdb_cursor_get( mc, &key, &data, MDB_SET ); + if ( rc == 0 ) { + ic->flags |= WAS_FOUND; + nid = *(ID *)data.mv_data; + if ( nid == 0 ) { + ic->count = MDB_IDL_DB_SIZE+1; + ic->flags |= WAS_RANGE; + } else { + size_t count; + + mdb_cursor_count( mc, &count ); + ic->count = count; + ic->first = nid; + ic->offset = count & (IDBLOCK-1); + } + } + } + /* are we a range already? */ + if ( ic->count > MDB_IDL_DB_SIZE ) { + ic->last = id; + continue; + /* Are we at the limit, and converting to a range? */ + } else if ( ic->count == MDB_IDL_DB_SIZE ) { + if ( ic->head ) { + ic->tail->next = ai->ai_flist; + ai->ai_flist = ic->head; + } + ic->head = ic->tail = NULL; + ic->last = id; + ic->count++; + continue; + } + /* No free block, create that too */ + lcount = ic->count & (IDBLOCK-1); + if ( !ic->tail || lcount == 0) { + if ( ai->ai_flist ) { + ice = ai->ai_flist; + ai->ai_flist = ice->next; + } else { + ice = ch_malloc( sizeof( mdb_tool_idl_cache_entry )); + } + ice->next = NULL; + if ( !ic->head ) { + ic->head = ice; + } else { + ic->tail->next = ice; + } + ic->tail = ice; + if ( lcount ) + ice->ids[lcount-1] = 0; + if ( !ic->count ) + ic->first = id; + } + ice = ic->tail; + if (!lcount || ice->ids[lcount-1] != id) + ice->ids[lcount] = id; + ic->count++; + } + + return 0; +} +#endif /* MDB_TOOL_IDL_CACHING */ + +/* Upgrade from pre 2.4.34 dn2id format */ + +#include <ac/unistd.h> +#include <lutil_meter.h> + +#define STACKSIZ 2048 + +typedef struct rec { + ID id; + size_t len; + char rdn[512]; +} rec; + +static int +mdb_dn2id_upgrade( BackendDB *be ) { + struct mdb_info *mi = (struct mdb_info *) be->be_private; + MDB_txn *mt; + MDB_cursor *mc = NULL; + MDB_val key, data; + int rc, writes=0, depth=0; + int enable_meter = 0; + ID id = 0, *num, count = 0; + rec *stack; + lutil_meter_t meter; + + if (!(mi->mi_flags & MDB_NEED_UPGRADE)) { + Debug( LDAP_DEBUG_ANY, "database %s: No upgrade needed.\n", + be->be_suffix[0].bv_val, 0, 0 ); + return 0; + } + + { + MDB_stat st; + + mdb_stat(mdb_cursor_txn(cursor), mi->mi_dbis[MDB_ID2ENTRY], &st); + if (!st.ms_entries) { + /* Empty DB, nothing to upgrade? */ + return 0; + } + if (isatty(2)) + enable_meter = !lutil_meter_open(&meter, + &lutil_meter_text_display, + &lutil_meter_linear_estimator, + st.ms_entries); + } + + num = ch_malloc(STACKSIZ * (sizeof(ID) + sizeof(rec))); + stack = (rec *)(num + STACKSIZ); + + rc = mdb_txn_begin(mi->mi_dbenv, NULL, 0, &mt); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_txn_begin failed, %s (%d)\n", + mdb_strerror(rc), rc, 0 ); + goto leave; + } + rc = mdb_cursor_open(mt, mi->mi_dbis[MDB_DN2ID], &mc); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_open failed, %s (%d)\n", + mdb_strerror(rc), rc, 0 ); + goto leave; + } + + key.mv_size = sizeof(ID); + /* post-order depth-first update */ + for(;;) { + size_t dkids; + unsigned char *ptr; + + /* visit */ + key.mv_data = &id; + stack[depth].id = id; + rc = mdb_cursor_get(mc, &key, &data, MDB_SET); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_get failed, %s (%d)\n", + mdb_strerror(rc), rc, 0 ); + goto leave; + } + num[depth] = 1; + + rc = mdb_cursor_count(mc, &dkids); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_count failed, %s (%d)\n", + mdb_strerror(rc), rc, 0 ); + goto leave; + } + if (dkids > 1) { + rc = mdb_cursor_get(mc, &key, &data, MDB_NEXT_DUP); +down: + ptr = (unsigned char *)data.mv_data + data.mv_size - sizeof(ID); + memcpy(&id, ptr, sizeof(ID)); + depth++; + memcpy(stack[depth].rdn, data.mv_data, data.mv_size); + stack[depth].len = data.mv_size; + continue; + } + + + /* pop: write updated count, advance to next node */ +pop: + /* update superior counts */ + if (depth) + num[depth-1] += num[depth]; + + key.mv_data = &id; + id = stack[depth-1].id; + data.mv_data = stack[depth].rdn; + data.mv_size = stack[depth].len; + rc = mdb_cursor_get(mc, &key, &data, MDB_GET_BOTH); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_get(BOTH) failed, %s (%d)\n", + mdb_strerror(rc), rc, 0 ); + goto leave; + } + data.mv_data = stack[depth].rdn; + ptr = (unsigned char *)data.mv_data + data.mv_size; + memcpy(ptr, &num[depth], sizeof(ID)); + data.mv_size += sizeof(ID); + rc = mdb_cursor_del(mc, 0); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_del failed, %s (%d)\n", + mdb_strerror(rc), rc, 0 ); + goto leave; + } + rc = mdb_cursor_put(mc, &key, &data, 0); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_put failed, %s (%d)\n", + mdb_strerror(rc), rc, 0 ); + goto leave; + } + count++; +#if 1 + if (enable_meter) + lutil_meter_update(&meter, count, 0); +#else + { + int len; + ptr = data.mv_data; + len = (ptr[0] & 0x7f) << 8 | ptr[1]; + printf("ID: %zu, %zu, %.*s\n", stack[depth].id, num[depth], len, ptr+2); + } +#endif + writes++; + if (writes == 1000) { + mdb_cursor_close(mc); + rc = mdb_txn_commit(mt); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_txn_commit failed, %s (%d)\n", + mdb_strerror(rc), rc, 0 ); + goto leave; + } + rc = mdb_txn_begin(mi->mi_dbenv, NULL, 0, &mt); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_txn_begin(2) failed, %s (%d)\n", + mdb_strerror(rc), rc, 0 ); + goto leave; + } + rc = mdb_cursor_open(mt, mi->mi_dbis[MDB_DN2ID], &mc); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_open(2) failed, %s (%d)\n", + mdb_strerror(rc), rc, 0 ); + goto leave; + } + rc = mdb_cursor_get(mc, &key, &data, MDB_GET_BOTH); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_get(2) failed, %s (%d)\n", + mdb_strerror(rc), rc, 0 ); + goto leave; + } + writes = 0; + } + depth--; + + rc = mdb_cursor_get(mc, &key, &data, MDB_NEXT_DUP); + if (rc == 0) + goto down; + rc = 0; + if (depth) + goto pop; + else + break; + } +leave: + mdb_cursor_close(mc); + if (mt) { + int r2; + r2 = mdb_txn_commit(mt); + if (r2) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_txn_commit(2) failed, %s (%d)\n", + mdb_strerror(r2), r2, 0 ); + if (!rc) + rc = r2; + } + } + ch_free(num); + if (enable_meter) { + lutil_meter_update(&meter, count, 1); + lutil_meter_close(&meter); + } + return rc; +} diff --git a/servers/slapd/back-meta/Makefile.in b/servers/slapd/back-meta/Makefile.in new file mode 100644 index 0000000..12974fb --- /dev/null +++ b/servers/slapd/back-meta/Makefile.in @@ -0,0 +1,45 @@ +# Makefile.in for back-meta +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +SRCS = init.c config.c search.c bind.c unbind.c add.c compare.c \ + delete.c modify.c modrdn.c suffixmassage.c map.c \ + conn.c candidates.c dncache.c +OBJS = init.lo config.lo search.lo bind.lo unbind.lo add.lo compare.lo \ + delete.lo modify.lo modrdn.lo suffixmassage.lo map.lo \ + conn.lo candidates.lo dncache.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-meta" +BUILD_MOD = @BUILD_META@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_META@_DEFS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_R_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBBASE = back_meta + +XINCPATH = -I.. -I$(srcdir)/.. +XDEFS = $(MODULES_CPPFLAGS) + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ + diff --git a/servers/slapd/back-meta/add.c b/servers/slapd/back-meta/add.c new file mode 100644 index 0000000..745bc1e --- /dev/null +++ b/servers/slapd/back-meta/add.c @@ -0,0 +1,211 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +int +meta_back_add( Operation *op, SlapReply *rs ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt; + metaconn_t *mc; + int i, candidate = -1; + int isupdate; + Attribute *a; + LDAPMod **attrs; + struct berval mdn = BER_BVNULL, mapped; + dncookie dc; + int msgid; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + LDAPControl **ctrls = NULL; + + Debug(LDAP_DEBUG_ARGS, "==> meta_back_add: %s\n", + op->o_req_dn.bv_val, 0, 0 ); + + /* + * get the current connection + */ + mc = meta_back_getconn( op, rs, &candidate, LDAP_BACK_SENDERR ); + if ( !mc || !meta_back_dobind( op, rs, mc, LDAP_BACK_SENDERR ) ) { + return rs->sr_err; + } + + assert( mc->mc_conns[ candidate ].msc_ld != NULL ); + + /* + * Rewrite the add dn, if needed + */ + mt = mi->mi_targets[ candidate ]; + dc.target = mt; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "addDN"; + + if ( ldap_back_dn_massage( &dc, &op->o_req_dn, &mdn ) ) { + send_ldap_result( op, rs ); + goto done; + } + + /* Count number of attributes in entry ( +1 ) */ + for ( i = 1, a = op->ora_e->e_attrs; a; i++, a = a->a_next ); + + /* Create array of LDAPMods for ldap_add() */ + attrs = ch_malloc( sizeof( LDAPMod * )*i ); + + dc.ctx = "addAttrDN"; + isupdate = be_shadow_update( op ); + for ( i = 0, a = op->ora_e->e_attrs; a; a = a->a_next ) { + int j, is_oc = 0; + + if ( !isupdate && !get_relax( op ) && a->a_desc->ad_type->sat_no_user_mod ) + { + continue; + } + + if ( a->a_desc == slap_schema.si_ad_objectClass + || a->a_desc == slap_schema.si_ad_structuralObjectClass ) + { + is_oc = 1; + mapped = a->a_desc->ad_cname; + + } else { + ldap_back_map( &mt->mt_rwmap.rwm_at, + &a->a_desc->ad_cname, &mapped, BACKLDAP_MAP ); + if ( BER_BVISNULL( &mapped ) || BER_BVISEMPTY( &mapped ) ) { + continue; + } + } + + attrs[ i ] = ch_malloc( sizeof( LDAPMod ) ); + if ( attrs[ i ] == NULL ) { + continue; + } + attrs[ i ]->mod_op = LDAP_MOD_BVALUES; + attrs[ i ]->mod_type = mapped.bv_val; + + if ( is_oc ) { + for ( j = 0; !BER_BVISNULL( &a->a_vals[ j ] ); j++ ) + ; + + attrs[ i ]->mod_bvalues = + (struct berval **)ch_malloc( ( j + 1 ) * + sizeof( struct berval * ) ); + + for ( j = 0; !BER_BVISNULL( &a->a_vals[ j ] ); ) { + struct ldapmapping *mapping; + + ldap_back_mapping( &mt->mt_rwmap.rwm_oc, + &a->a_vals[ j ], &mapping, BACKLDAP_MAP ); + + if ( mapping == NULL ) { + if ( mt->mt_rwmap.rwm_oc.drop_missing ) { + continue; + } + attrs[ i ]->mod_bvalues[ j ] = &a->a_vals[ j ]; + + } else { + attrs[ i ]->mod_bvalues[ j ] = &mapping->dst; + } + j++; + } + attrs[ i ]->mod_bvalues[ j ] = NULL; + + } else { + /* + * FIXME: dn-valued attrs should be rewritten + * to allow their use in ACLs at the back-ldap + * level. + */ + if ( a->a_desc->ad_type->sat_syntax == + slap_schema.si_syn_distinguishedName ) + { + (void)ldap_dnattr_rewrite( &dc, a->a_vals ); + if ( a->a_vals == NULL ) { + continue; + } + } + + for ( j = 0; !BER_BVISNULL( &a->a_vals[ j ] ); j++ ) + ; + + attrs[ i ]->mod_bvalues = ch_malloc( ( j + 1 ) * sizeof( struct berval * ) ); + for ( j = 0; !BER_BVISNULL( &a->a_vals[ j ] ); j++ ) { + attrs[ i ]->mod_bvalues[ j ] = &a->a_vals[ j ]; + } + attrs[ i ]->mod_bvalues[ j ] = NULL; + } + i++; + } + attrs[ i ] = NULL; + +retry:; + ctrls = op->o_ctrls; + if ( meta_back_controls_add( op, rs, mc, candidate, &ctrls ) != LDAP_SUCCESS ) + { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = ldap_add_ext( mc->mc_conns[ candidate ].msc_ld, mdn.bv_val, + attrs, ctrls, NULL, &msgid ); + rs->sr_err = meta_back_op_result( mc, op, rs, candidate, msgid, + mt->mt_timeout[ SLAP_OP_ADD ], ( LDAP_BACK_SENDRESULT | retrying ) ); + if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( meta_back_retry( op, rs, &mc, candidate, LDAP_BACK_SENDERR ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + goto retry; + } + } + +cleanup:; + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + + for ( --i; i >= 0; --i ) { + free( attrs[ i ]->mod_bvalues ); + free( attrs[ i ] ); + } + free( attrs ); + if ( mdn.bv_val != op->ora_e->e_dn ) { + free( mdn.bv_val ); + BER_BVZERO( &mdn ); + } + +done:; + if ( mc ) { + meta_back_release_conn( mi, mc ); + } + + return rs->sr_err; +} + diff --git a/servers/slapd/back-meta/back-meta.h b/servers/slapd/back-meta/back-meta.h new file mode 100644 index 0000000..087111d --- /dev/null +++ b/servers/slapd/back-meta/back-meta.h @@ -0,0 +1,705 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#ifndef SLAPD_LDAP_H +#error "include servers/slapd/back-ldap/back-ldap.h before this file!" +#endif /* SLAPD_LDAP_H */ + +#ifndef SLAPD_META_H +#define SLAPD_META_H + +#ifdef LDAP_DEVEL +#define SLAPD_META_CLIENT_PR 1 +#endif /* LDAP_DEVEL */ + +#include "proto-meta.h" + +/* String rewrite library */ +#include "rewrite.h" + +LDAP_BEGIN_DECL + +/* + * Set META_BACK_PRINT_CONNTREE larger than 0 to dump the connection tree (debug only) + */ +#ifndef META_BACK_PRINT_CONNTREE +#define META_BACK_PRINT_CONNTREE 0 +#endif /* !META_BACK_PRINT_CONNTREE */ + +/* from back-ldap.h before rwm removal */ +struct ldapmap { + int drop_missing; + + Avlnode *map; + Avlnode *remap; +}; + +struct ldapmapping { + struct berval src; + struct berval dst; +}; + +struct ldaprwmap { + /* + * DN rewriting + */ +#ifdef ENABLE_REWRITE + struct rewrite_info *rwm_rw; +#else /* !ENABLE_REWRITE */ + /* some time the suffix massaging without librewrite + * will be disabled */ + BerVarray rwm_suffix_massage; +#endif /* !ENABLE_REWRITE */ + BerVarray rwm_bva_rewrite; + + /* + * Attribute/objectClass mapping + */ + struct ldapmap rwm_oc; + struct ldapmap rwm_at; + BerVarray rwm_bva_map; +}; + +/* Whatever context ldap_back_dn_massage needs... */ +typedef struct dncookie { + struct metatarget_t *target; + +#ifdef ENABLE_REWRITE + Connection *conn; + char *ctx; + SlapReply *rs; +#else + int normalized; + int tofrom; +#endif +} dncookie; + +int ldap_back_dn_massage(dncookie *dc, struct berval *dn, + struct berval *res); + +extern int ldap_back_conn_cmp( const void *c1, const void *c2); +extern int ldap_back_conn_dup( void *c1, void *c2 ); +extern void ldap_back_conn_free( void *c ); + +/* attributeType/objectClass mapping */ +int mapping_cmp (const void *, const void *); +int mapping_dup (void *, void *); + +void ldap_back_map_init ( struct ldapmap *lm, struct ldapmapping ** ); +int ldap_back_mapping ( struct ldapmap *map, struct berval *s, + struct ldapmapping **m, int remap ); +void ldap_back_map ( struct ldapmap *map, struct berval *s, struct berval *m, + int remap ); +#define BACKLDAP_MAP 0 +#define BACKLDAP_REMAP 1 +char * +ldap_back_map_filter( + struct ldapmap *at_map, + struct ldapmap *oc_map, + struct berval *f, + int remap ); + +int +ldap_back_map_attrs( + Operation *op, + struct ldapmap *at_map, + AttributeName *a, + int remap, + char ***mapped_attrs ); + +extern int +ldap_back_filter_map_rewrite( + dncookie *dc, + Filter *f, + struct berval *fstr, + int remap, + void *memctx ); + +/* suffix massaging by means of librewrite */ +#ifdef ENABLE_REWRITE +extern int +suffix_massage_config( struct rewrite_info *info, + struct berval *pvnc, + struct berval *nvnc, + struct berval *prnc, + struct berval *nrnc ); +#endif /* ENABLE_REWRITE */ +extern int +ldap_back_referral_result_rewrite( + dncookie *dc, + BerVarray a_vals, + void *memctx ); +extern int +ldap_dnattr_rewrite( + dncookie *dc, + BerVarray a_vals ); +extern int +ldap_dnattr_result_rewrite( + dncookie *dc, + BerVarray a_vals ); + +/* (end of) from back-ldap.h before rwm removal */ + +/* + * A metasingleconn_t can be in the following, mutually exclusive states: + * + * - none (0x0U) + * - creating META_BACK_FCONN_CREATING + * - initialized META_BACK_FCONN_INITED + * - binding LDAP_BACK_FCONN_BINDING + * - bound/anonymous LDAP_BACK_FCONN_ISBOUND/LDAP_BACK_FCONN_ISANON + * + * possible modifiers are: + * + * - privileged LDAP_BACK_FCONN_ISPRIV + * - privileged, TLS LDAP_BACK_FCONN_ISTLS + * - subjected to idassert LDAP_BACK_FCONN_ISIDASR + * - tainted LDAP_BACK_FCONN_TAINTED + */ + +#define META_BACK_FCONN_INITED (0x00100000U) +#define META_BACK_FCONN_CREATING (0x00200000U) + +#define META_BACK_CONN_INITED(lc) LDAP_BACK_CONN_ISSET((lc), META_BACK_FCONN_INITED) +#define META_BACK_CONN_INITED_SET(lc) LDAP_BACK_CONN_SET((lc), META_BACK_FCONN_INITED) +#define META_BACK_CONN_INITED_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), META_BACK_FCONN_INITED) +#define META_BACK_CONN_INITED_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), META_BACK_FCONN_INITED, (mlc)) +#define META_BACK_CONN_CREATING(lc) LDAP_BACK_CONN_ISSET((lc), META_BACK_FCONN_CREATING) +#define META_BACK_CONN_CREATING_SET(lc) LDAP_BACK_CONN_SET((lc), META_BACK_FCONN_CREATING) +#define META_BACK_CONN_CREATING_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), META_BACK_FCONN_CREATING) +#define META_BACK_CONN_CREATING_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), META_BACK_FCONN_CREATING, (mlc)) + +struct metainfo_t; + +#define META_NOT_CANDIDATE ((ber_tag_t)0x0) +#define META_CANDIDATE ((ber_tag_t)0x1) +#define META_BINDING ((ber_tag_t)0x2) +#define META_RETRYING ((ber_tag_t)0x4) + +typedef struct metasingleconn_t { +#define META_CND_ISSET(rs,f) ( ( (rs)->sr_tag & (f) ) == (f) ) +#define META_CND_SET(rs,f) ( (rs)->sr_tag |= (f) ) +#define META_CND_CLEAR(rs,f) ( (rs)->sr_tag &= ~(f) ) + +#define META_CANDIDATE_RESET(rs) ( (rs)->sr_tag = 0 ) +#define META_IS_CANDIDATE(rs) META_CND_ISSET( (rs), META_CANDIDATE ) +#define META_CANDIDATE_SET(rs) META_CND_SET( (rs), META_CANDIDATE ) +#define META_CANDIDATE_CLEAR(rs) META_CND_CLEAR( (rs), META_CANDIDATE ) +#define META_IS_BINDING(rs) META_CND_ISSET( (rs), META_BINDING ) +#define META_BINDING_SET(rs) META_CND_SET( (rs), META_BINDING ) +#define META_BINDING_CLEAR(rs) META_CND_CLEAR( (rs), META_BINDING ) +#define META_IS_RETRYING(rs) META_CND_ISSET( (rs), META_RETRYING ) +#define META_RETRYING_SET(rs) META_CND_SET( (rs), META_RETRYING ) +#define META_RETRYING_CLEAR(rs) META_CND_CLEAR( (rs), META_RETRYING ) + + LDAP *msc_ld; + time_t msc_time; + struct berval msc_bound_ndn; + struct berval msc_cred; + unsigned msc_mscflags; + /* NOTE: lc_lcflags is redefined to msc_mscflags to reuse the macros + * defined for back-ldap */ +#define lc_lcflags msc_mscflags +} metasingleconn_t; + +typedef struct metaconn_t { + ldapconn_base_t lc_base; +#define mc_base lc_base +#define mc_conn mc_base.lcb_conn +#define mc_local_ndn mc_base.lcb_local_ndn +#define mc_refcnt mc_base.lcb_refcnt +#define mc_create_time mc_base.lcb_create_time +#define mc_time mc_base.lcb_time + + LDAP_TAILQ_ENTRY(metaconn_t) mc_q; + + /* NOTE: msc_mscflags is used to recycle the #define + * in metasingleconn_t */ + unsigned msc_mscflags; + + /* + * means that the connection is bound; + * of course only one target actually is ... + */ + int mc_authz_target; +#define META_BOUND_NONE (-1) +#define META_BOUND_ALL (-2) + + struct metainfo_t *mc_info; + + /* supersedes the connection stuff */ + metasingleconn_t mc_conns[ 1 ]; + /* NOTE: mc_conns must be last, because + * the required number of conns is malloc'ed + * in one block with the metaconn_t structure */ +} metaconn_t; + +typedef enum meta_st_t { +#if 0 /* todo */ + META_ST_EXACT = LDAP_SCOPE_BASE, +#endif + META_ST_SUBTREE = LDAP_SCOPE_SUBTREE, + META_ST_SUBORDINATE = LDAP_SCOPE_SUBORDINATE, + META_ST_REGEX /* last + 1 */ +} meta_st_t; + +typedef struct metasubtree_t { + meta_st_t ms_type; + union { + struct berval msu_dn; + struct { + struct berval msr_regex_pattern; + regex_t msr_regex; + } msu_regex; + } ms_un; +#define ms_dn ms_un.msu_dn +#define ms_regex ms_un.msu_regex.msr_regex +#define ms_regex_pattern ms_un.msu_regex.msr_regex_pattern + + struct metasubtree_t *ms_next; +} metasubtree_t; + +typedef struct metafilter_t { + struct metafilter_t *mf_next; + struct berval mf_regex_pattern; + regex_t mf_regex; +} metafilter_t; + +typedef struct metacommon_t { + int mc_version; + int mc_nretries; +#define META_RETRY_UNDEFINED (-2) +#define META_RETRY_FOREVER (-1) +#define META_RETRY_NEVER (0) +#define META_RETRY_DEFAULT (10) + + unsigned mc_flags; +#define META_BACK_CMN_ISSET(mc,f) ( ( (mc)->mc_flags & (f) ) == (f) ) +#define META_BACK_CMN_QUARANTINE(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_QUARANTINE ) +#define META_BACK_CMN_CHASE_REFERRALS(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_CHASE_REFERRALS ) +#define META_BACK_CMN_NOREFS(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_NOREFS ) +#define META_BACK_CMN_NOUNDEFFILTER(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_NOUNDEFFILTER ) +#define META_BACK_CMN_SAVECRED(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_SAVECRED ) +#define META_BACK_CMN_ST_REQUEST(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_ST_REQUEST ) + +#ifdef SLAPD_META_CLIENT_PR + /* + * client-side paged results: + * -1: accept unsolicited paged results responses + * 0: off + * >0: always request paged results with size == mt_ps + */ +#define META_CLIENT_PR_DISABLE (0) +#define META_CLIENT_PR_ACCEPT_UNSOLICITED (-1) + ber_int_t mc_ps; +#endif /* SLAPD_META_CLIENT_PR */ + + slap_retry_info_t mc_quarantine; + time_t mc_network_timeout; + struct timeval mc_bind_timeout; +#define META_BIND_TIMEOUT LDAP_BACK_RESULT_UTIMEOUT + time_t mc_timeout[ SLAP_OP_LAST ]; +} metacommon_t; + +typedef struct metatarget_t { + char *mt_uri; + ldap_pvt_thread_mutex_t mt_uri_mutex; + + /* TODO: we might want to enable different strategies + * for different targets */ + LDAP_REBIND_PROC *mt_rebind_f; + LDAP_URLLIST_PROC *mt_urllist_f; + void *mt_urllist_p; + + metafilter_t *mt_filter; + metasubtree_t *mt_subtree; + /* F: subtree-include; T: subtree-exclude */ + int mt_subtree_exclude; + + int mt_scope; + + struct berval mt_psuffix; /* pretty suffix */ + struct berval mt_nsuffix; /* normalized suffix */ + + struct berval mt_binddn; + struct berval mt_bindpw; + + /* we only care about the TLS options here */ + slap_bindconf mt_tls; + + slap_idassert_t mt_idassert; +#define mt_idassert_mode mt_idassert.si_mode +#define mt_idassert_authcID mt_idassert.si_bc.sb_authcId +#define mt_idassert_authcDN mt_idassert.si_bc.sb_binddn +#define mt_idassert_passwd mt_idassert.si_bc.sb_cred +#define mt_idassert_authzID mt_idassert.si_bc.sb_authzId +#define mt_idassert_authmethod mt_idassert.si_bc.sb_method +#define mt_idassert_sasl_mech mt_idassert.si_bc.sb_saslmech +#define mt_idassert_sasl_realm mt_idassert.si_bc.sb_realm +#define mt_idassert_secprops mt_idassert.si_bc.sb_secprops +#define mt_idassert_tls mt_idassert.si_bc.sb_tls +#define mt_idassert_flags mt_idassert.si_flags +#define mt_idassert_authz mt_idassert.si_authz + + struct ldaprwmap mt_rwmap; + + sig_atomic_t mt_isquarantined; + ldap_pvt_thread_mutex_t mt_quarantine_mutex; + + metacommon_t mt_mc; +#define mt_nretries mt_mc.mc_nretries +#define mt_flags mt_mc.mc_flags +#define mt_version mt_mc.mc_version +#define mt_ps mt_mc.mc_ps +#define mt_network_timeout mt_mc.mc_network_timeout +#define mt_bind_timeout mt_mc.mc_bind_timeout +#define mt_timeout mt_mc.mc_timeout +#define mt_quarantine mt_mc.mc_quarantine + +#define META_BACK_TGT_ISSET(mt,f) ( ( (mt)->mt_flags & (f) ) == (f) ) +#define META_BACK_TGT_ISMASK(mt,m,f) ( ( (mt)->mt_flags & (m) ) == (f) ) + +#define META_BACK_TGT_SAVECRED(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_SAVECRED ) + +#define META_BACK_TGT_USE_TLS(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_USE_TLS ) +#define META_BACK_TGT_PROPAGATE_TLS(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_PROPAGATE_TLS ) +#define META_BACK_TGT_TLS_CRITICAL(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_TLS_CRITICAL ) + +#define META_BACK_TGT_CHASE_REFERRALS(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_CHASE_REFERRALS ) + +#define META_BACK_TGT_T_F(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_T_F_MASK, LDAP_BACK_F_T_F ) +#define META_BACK_TGT_T_F_DISCOVER(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_T_F_MASK2, LDAP_BACK_F_T_F_DISCOVER ) + +#define META_BACK_TGT_ABANDON(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_ABANDON ) +#define META_BACK_TGT_IGNORE(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_IGNORE ) +#define META_BACK_TGT_CANCEL(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_EXOP ) +#define META_BACK_TGT_CANCEL_DISCOVER(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_CANCEL_MASK2, LDAP_BACK_F_CANCEL_EXOP_DISCOVER ) +#define META_BACK_TGT_QUARANTINE(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_QUARANTINE ) + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING +#define META_BACK_TGT_ST_REQUEST(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_ST_REQUEST ) +#define META_BACK_TGT_ST_RESPONSE(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_ST_RESPONSE ) +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + +#define META_BACK_TGT_NOREFS(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_NOREFS ) +#define META_BACK_TGT_NOUNDEFFILTER(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_NOUNDEFFILTER ) + + slap_mask_t mt_rep_flags; + +} metatarget_t; + +typedef struct metadncache_t { + ldap_pvt_thread_mutex_t mutex; + Avlnode *tree; + +#define META_DNCACHE_DISABLED (0) +#define META_DNCACHE_FOREVER ((time_t)(-1)) + time_t ttl; /* seconds; 0: no cache, -1: no expiry */ +} metadncache_t; + +typedef struct metacandidates_t { + int mc_ntargets; + SlapReply *mc_candidates; +} metacandidates_t; + +/* + * Hook to allow mucking with metainfo_t/metatarget_t when quarantine is over + */ +typedef int (*meta_back_quarantine_f)( struct metainfo_t *, int target, void * ); + +typedef struct metainfo_t { + int mi_ntargets; + int mi_defaulttarget; +#define META_DEFAULT_TARGET_NONE (-1) + +#define mi_nretries mi_mc.mc_nretries +#define mi_flags mi_mc.mc_flags +#define mi_version mi_mc.mc_version +#define mi_ps mi_mc.mc_ps +#define mi_network_timeout mi_mc.mc_network_timeout +#define mi_bind_timeout mi_mc.mc_bind_timeout +#define mi_timeout mi_mc.mc_timeout +#define mi_quarantine mi_mc.mc_quarantine + + metatarget_t **mi_targets; + metacandidates_t *mi_candidates; + + LDAP_REBIND_PROC *mi_rebind_f; + LDAP_URLLIST_PROC *mi_urllist_f; + + metadncache_t mi_cache; + + /* cached connections; + * special conns are in tailq rather than in tree */ + ldap_avl_info_t mi_conninfo; + struct { + int mic_num; + LDAP_TAILQ_HEAD(mc_conn_priv_q, metaconn_t) mic_priv; + } mi_conn_priv[ LDAP_BACK_PCONN_LAST ]; + int mi_conn_priv_max; + + /* NOTE: quarantine uses the connection mutex */ + meta_back_quarantine_f mi_quarantine_f; + void *mi_quarantine_p; + +#define li_flags mi_flags +/* uses flags as defined in <back-ldap/back-ldap.h> */ +#define META_BACK_F_ONERR_STOP LDAP_BACK_F_ONERR_STOP +#define META_BACK_F_ONERR_REPORT (0x02000000U) +#define META_BACK_F_ONERR_MASK (META_BACK_F_ONERR_STOP|META_BACK_F_ONERR_REPORT) +#define META_BACK_F_DEFER_ROOTDN_BIND (0x04000000U) +#define META_BACK_F_PROXYAUTHZ_ALWAYS (0x08000000U) /* users always proxyauthz */ +#define META_BACK_F_PROXYAUTHZ_ANON (0x10000000U) /* anonymous always proxyauthz */ +#define META_BACK_F_PROXYAUTHZ_NOANON (0x20000000U) /* anonymous remains anonymous */ + +#define META_BACK_ONERR_STOP(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_STOP ) +#define META_BACK_ONERR_REPORT(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_REPORT ) +#define META_BACK_ONERR_CONTINUE(mi) ( !LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_MASK ) ) + +#define META_BACK_DEFER_ROOTDN_BIND(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_DEFER_ROOTDN_BIND ) +#define META_BACK_PROXYAUTHZ_ALWAYS(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_ALWAYS ) +#define META_BACK_PROXYAUTHZ_ANON(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_ANON ) +#define META_BACK_PROXYAUTHZ_NOANON(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_NOANON ) + +#define META_BACK_QUARANTINE(mi) LDAP_BACK_ISSET( (mi), LDAP_BACK_F_QUARANTINE ) + + time_t mi_conn_ttl; + time_t mi_idle_timeout; + + metacommon_t mi_mc; + ldap_extra_t *mi_ldap_extra; + +} metainfo_t; + +typedef enum meta_op_type { + META_OP_ALLOW_MULTIPLE = 0, + META_OP_REQUIRE_SINGLE, + META_OP_REQUIRE_ALL +} meta_op_type; + +SlapReply * +meta_back_candidates_get( Operation *op ); + +extern metaconn_t * +meta_back_getconn( + Operation *op, + SlapReply *rs, + int *candidate, + ldap_back_send_t sendok ); + +extern void +meta_back_release_conn_lock( + metainfo_t *mi, + metaconn_t *mc, + int dolock ); +#define meta_back_release_conn(mi, mc) meta_back_release_conn_lock( (mi), (mc), 1 ) + +extern int +meta_back_retry( + Operation *op, + SlapReply *rs, + metaconn_t **mcp, + int candidate, + ldap_back_send_t sendok ); + +extern void +meta_back_conn_free( + void *v_mc ); + +#if META_BACK_PRINT_CONNTREE > 0 +extern void +meta_back_print_conntree( + metainfo_t *mi, + char *msg ); +#endif + +extern int +meta_back_init_one_conn( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + int candidate, + int ispriv, + ldap_back_send_t sendok, + int dolock ); + +extern void +meta_back_quarantine( + Operation *op, + SlapReply *rs, + int candidate ); + +extern int +meta_back_dobind( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + ldap_back_send_t sendok ); + +extern int +meta_back_single_dobind( + Operation *op, + SlapReply *rs, + metaconn_t **mcp, + int candidate, + ldap_back_send_t sendok, + int retries, + int dolock ); + +extern int +meta_back_proxy_authz_cred( + metaconn_t *mc, + int candidate, + Operation *op, + SlapReply *rs, + ldap_back_send_t sendok, + struct berval *binddn, + struct berval *bindcred, + int *method ); + +extern int +meta_back_cancel( + metaconn_t *mc, + Operation *op, + SlapReply *rs, + ber_int_t msgid, + int candidate, + ldap_back_send_t sendok ); + +extern int +meta_back_op_result( + metaconn_t *mc, + Operation *op, + SlapReply *rs, + int candidate, + ber_int_t msgid, + time_t timeout, + ldap_back_send_t sendok ); + +extern int +meta_back_controls_add( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + int candidate, + LDAPControl ***pctrls ); + +extern int +back_meta_LTX_init_module( + int argc, + char *argv[] ); + +extern int +meta_back_conn_cmp( + const void *c1, + const void *c2 ); + +extern int +meta_back_conndn_cmp( + const void *c1, + const void *c2 ); + +extern int +meta_back_conndn_dup( + void *c1, + void *c2 ); + +/* + * Candidate stuff + */ +extern int +meta_back_is_candidate( + metatarget_t *mt, + struct berval *ndn, + int scope ); + +extern int +meta_back_select_unique_candidate( + metainfo_t *mi, + struct berval *ndn ); + +extern int +meta_clear_unused_candidates( + Operation *op, + int candidate ); + +extern int +meta_clear_one_candidate( + Operation *op, + metaconn_t *mc, + int candidate ); + +/* + * Dn cache stuff (experimental) + */ +extern int +meta_dncache_cmp( + const void *c1, + const void *c2 ); + +extern int +meta_dncache_dup( + void *c1, + void *c2 ); + +#define META_TARGET_NONE (-1) +#define META_TARGET_MULTIPLE (-2) +extern int +meta_dncache_get_target( + metadncache_t *cache, + struct berval *ndn ); + +extern int +meta_dncache_update_entry( + metadncache_t *cache, + struct berval *ndn, + int target ); + +extern int +meta_dncache_delete_entry( + metadncache_t *cache, + struct berval *ndn ); + +extern void +meta_dncache_free( void *entry ); + +extern void +meta_back_map_free( struct ldapmap *lm ); + +extern int +meta_subtree_destroy( metasubtree_t *ms ); + +extern void +meta_filter_destroy( metafilter_t *mf ); + +extern int +meta_target_finish( metainfo_t *mi, metatarget_t *mt, + const char *log, char *msg, size_t msize +); + +extern LDAP_REBIND_PROC meta_back_default_rebind; +extern LDAP_URLLIST_PROC meta_back_default_urllist; + +LDAP_END_DECL + +#endif /* SLAPD_META_H */ + diff --git a/servers/slapd/back-meta/bind.c b/servers/slapd/back-meta/bind.c new file mode 100644 index 0000000..5b87e89 --- /dev/null +++ b/servers/slapd/back-meta/bind.c @@ -0,0 +1,1771 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> + + +#define AVL_INTERNAL +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +#include "lutil_ldap.h" + +static int +meta_back_proxy_authz_bind( + metaconn_t *mc, + int candidate, + Operation *op, + SlapReply *rs, + ldap_back_send_t sendok, + int dolock ); + +static int +meta_back_single_bind( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + int candidate ); + +int +meta_back_bind( Operation *op, SlapReply *rs ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metaconn_t *mc = NULL; + + int rc = LDAP_OTHER, + i, + gotit = 0, + isroot = 0; + + SlapReply *candidates; + + rs->sr_err = LDAP_SUCCESS; + + Debug( LDAP_DEBUG_ARGS, "%s meta_back_bind: dn=\"%s\".\n", + op->o_log_prefix, op->o_req_dn.bv_val, 0 ); + + /* the test on the bind method should be superfluous */ + switch ( be_rootdn_bind( op, rs ) ) { + case LDAP_SUCCESS: + if ( META_BACK_DEFER_ROOTDN_BIND( mi ) ) { + /* frontend will return success */ + return rs->sr_err; + } + + isroot = 1; + /* fallthru */ + + case SLAP_CB_CONTINUE: + break; + + default: + /* be_rootdn_bind() sent result */ + return rs->sr_err; + } + + /* we need meta_back_getconn() not send result even on error, + * because we want to intercept the error and make it + * invalidCredentials */ + mc = meta_back_getconn( op, rs, NULL, LDAP_BACK_BIND_DONTSEND ); + if ( !mc ) { + if ( LogTest( LDAP_DEBUG_ANY ) ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "meta_back_bind: no target " + "for dn \"%s\" (%d%s%s).", + op->o_req_dn.bv_val, rs->sr_err, + rs->sr_text ? ". " : "", + rs->sr_text ? rs->sr_text : "" ); + Debug( LDAP_DEBUG_ANY, + "%s %s\n", + op->o_log_prefix, buf, 0 ); + } + + /* FIXME: there might be cases where we don't want + * to map the error onto invalidCredentials */ + switch ( rs->sr_err ) { + case LDAP_NO_SUCH_OBJECT: + case LDAP_UNWILLING_TO_PERFORM: + rs->sr_err = LDAP_INVALID_CREDENTIALS; + rs->sr_text = NULL; + break; + } + send_ldap_result( op, rs ); + return rs->sr_err; + } + + candidates = meta_back_candidates_get( op ); + + /* + * Each target is scanned ... + */ + mc->mc_authz_target = META_BOUND_NONE; + for ( i = 0; i < mi->mi_ntargets; i++ ) { + metatarget_t *mt = mi->mi_targets[ i ]; + int lerr; + + /* + * Skip non-candidates + */ + if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) { + continue; + } + + if ( gotit == 0 ) { + /* set rc to LDAP_SUCCESS only if at least + * one candidate has been tried */ + rc = LDAP_SUCCESS; + gotit = 1; + + } else if ( !isroot ) { + /* + * A bind operation is expected to have + * ONE CANDIDATE ONLY! + */ + Debug( LDAP_DEBUG_ANY, + "### %s meta_back_bind: more than one" + " candidate selected...\n", + op->o_log_prefix, 0, 0 ); + } + + if ( isroot ) { + if ( mt->mt_idassert_authmethod == LDAP_AUTH_NONE + || BER_BVISNULL( &mt->mt_idassert_authcDN ) ) + { + metasingleconn_t *msc = &mc->mc_conns[ i ]; + + /* skip the target if no pseudorootdn is provided */ + if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) { + ch_free( msc->msc_bound_ndn.bv_val ); + BER_BVZERO( &msc->msc_bound_ndn ); + } + + if ( !BER_BVISNULL( &msc->msc_cred ) ) { + /* destroy sensitive data */ + memset( msc->msc_cred.bv_val, 0, + msc->msc_cred.bv_len ); + ch_free( msc->msc_cred.bv_val ); + BER_BVZERO( &msc->msc_cred ); + } + + continue; + } + + + (void)meta_back_proxy_authz_bind( mc, i, op, rs, LDAP_BACK_DONTSEND, 1 ); + lerr = rs->sr_err; + + } else { + lerr = meta_back_single_bind( op, rs, mc, i ); + } + + if ( lerr != LDAP_SUCCESS ) { + rc = rs->sr_err = lerr; + + /* FIXME: in some cases (e.g. unavailable) + * do not assume it's not candidate; rather + * mark this as an error to be eventually + * reported to client */ + META_CANDIDATE_CLEAR( &candidates[ i ] ); + break; + } + } + + /* must re-insert if local DN changed as result of bind */ + if ( rc == LDAP_SUCCESS ) { + if ( isroot ) { + mc->mc_authz_target = META_BOUND_ALL; + } + + if ( !LDAP_BACK_PCONN_ISPRIV( mc ) + && !dn_match( &op->o_req_ndn, &mc->mc_local_ndn ) ) + { + int lerr; + + /* wait for all other ops to release the connection */ + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + assert( mc->mc_refcnt == 1 ); +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, ">>> meta_back_bind" ); +#endif /* META_BACK_PRINT_CONNTREE */ + + /* delete all cached connections with the current connection */ + if ( LDAP_BACK_SINGLECONN( mi ) ) { + metaconn_t *tmpmc; + + while ( ( tmpmc = avl_delete( &mi->mi_conninfo.lai_tree, (caddr_t)mc, meta_back_conn_cmp ) ) != NULL ) + { + assert( !LDAP_BACK_PCONN_ISPRIV( mc ) ); + Debug( LDAP_DEBUG_TRACE, + "=>meta_back_bind: destroying conn %lu (refcnt=%u)\n", + mc->mc_conn->c_connid, mc->mc_refcnt, 0 ); + + if ( tmpmc->mc_refcnt != 0 ) { + /* taint it */ + LDAP_BACK_CONN_TAINTED_SET( tmpmc ); + + } else { + /* + * Needs a test because the handler may be corrupted, + * and calling ldap_unbind on a corrupted header results + * in a segmentation fault + */ + meta_back_conn_free( tmpmc ); + } + } + } + + ber_bvreplace( &mc->mc_local_ndn, &op->o_req_ndn ); + lerr = avl_insert( &mi->mi_conninfo.lai_tree, (caddr_t)mc, + meta_back_conndn_cmp, meta_back_conndn_dup ); +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, "<<< meta_back_bind" ); +#endif /* META_BACK_PRINT_CONNTREE */ + if ( lerr == 0 ) { +#if 0 + /* NOTE: a connection cannot be privileged + * and be in the avl tree at the same time + */ + if ( isroot ) { + LDAP_BACK_CONN_ISPRIV_SET( mc ); + LDAP_BACK_PCONN_SET( mc, op ); + } +#endif + LDAP_BACK_CONN_CACHED_SET( mc ); + + } else { + LDAP_BACK_CONN_CACHED_CLEAR( mc ); + } + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + } + + if ( mc != NULL ) { + meta_back_release_conn( mi, mc ); + } + + /* + * rc is LDAP_SUCCESS if at least one bind succeeded, + * err is the last error that occurred during a bind; + * if at least (and at most?) one bind succeeds, fine. + */ + if ( rc != LDAP_SUCCESS ) { + + /* + * deal with bind failure ... + */ + + /* + * no target was found within the naming context, + * so bind must fail with invalid credentials + */ + if ( rs->sr_err == LDAP_SUCCESS && gotit == 0 ) { + rs->sr_err = LDAP_INVALID_CREDENTIALS; + } else { + rs->sr_err = slap_map_api2result( rs ); + } + send_ldap_result( op, rs ); + return rs->sr_err; + + } + + return LDAP_SUCCESS; +} + +static int +meta_back_bind_op_result( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + int candidate, + int msgid, + ldap_back_send_t sendok, + int dolock ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + LDAPMessage *res; + struct timeval tv; + int rc; + int nretries = mt->mt_nretries; + char buf[ SLAP_TEXT_BUFLEN ]; + + Debug( LDAP_DEBUG_TRACE, + ">>> %s meta_back_bind_op_result[%d]\n", + op->o_log_prefix, candidate, 0 ); + + /* make sure this is clean */ + assert( rs->sr_ctrls == NULL ); + + if ( rs->sr_err == LDAP_SUCCESS ) { + time_t stoptime = (time_t)(-1), + timeout; + int timeout_err = op->o_protocol >= LDAP_VERSION3 ? + LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER; + const char *timeout_text = "Operation timed out"; + slap_op_t opidx = slap_req2op( op->o_tag ); + + /* since timeout is not specified, compute and use + * the one specific to the ongoing operation */ + if ( opidx == LDAP_REQ_SEARCH ) { + if ( op->ors_tlimit <= 0 ) { + timeout = 0; + + } else { + timeout = op->ors_tlimit; + timeout_err = LDAP_TIMELIMIT_EXCEEDED; + timeout_text = NULL; + } + + } else { + timeout = mt->mt_timeout[ opidx ]; + } + + /* better than nothing :) */ + if ( timeout == 0 ) { + if ( mi->mi_idle_timeout ) { + timeout = mi->mi_idle_timeout; + + } else if ( mi->mi_conn_ttl ) { + timeout = mi->mi_conn_ttl; + } + } + + if ( timeout ) { + stoptime = op->o_time + timeout; + } + + LDAP_BACK_TV_SET( &tv ); + + /* + * handle response!!! + */ +retry:; + rc = ldap_result( msc->msc_ld, msgid, LDAP_MSG_ALL, &tv, &res ); + switch ( rc ) { + case 0: + if ( nretries != META_RETRY_NEVER + || ( timeout && slap_get_time() <= stoptime ) ) + { + ldap_pvt_thread_yield(); + if ( nretries > 0 ) { + nretries--; + } + tv = mt->mt_bind_timeout; + goto retry; + } + + /* don't let anyone else use this handler, + * because there's a pending bind that will not + * be acknowledged */ + if ( dolock) { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + } + assert( LDAP_BACK_CONN_BINDING( msc ) ); + +#ifdef DEBUG_205 + Debug( LDAP_DEBUG_ANY, "### %s meta_back_bind_op_result ldap_unbind_ext[%d] ld=%p\n", + op->o_log_prefix, candidate, (void *)msc->msc_ld ); +#endif /* DEBUG_205 */ + + meta_clear_one_candidate( op, mc, candidate ); + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + + rs->sr_err = timeout_err; + rs->sr_text = timeout_text; + break; + + case -1: + ldap_get_option( msc->msc_ld, LDAP_OPT_ERROR_NUMBER, + &rs->sr_err ); + + snprintf( buf, sizeof( buf ), + "err=%d (%s) nretries=%d", + rs->sr_err, ldap_err2string( rs->sr_err ), nretries ); + Debug( LDAP_DEBUG_ANY, + "### %s meta_back_bind_op_result[%d]: %s.\n", + op->o_log_prefix, candidate, buf ); + break; + + default: + /* only touch when activity actually took place... */ + if ( mi->mi_idle_timeout != 0 && msc->msc_time < op->o_time ) { + msc->msc_time = op->o_time; + } + + /* FIXME: matched? referrals? response controls? */ + rc = ldap_parse_result( msc->msc_ld, res, &rs->sr_err, + NULL, NULL, NULL, NULL, 1 ); + if ( rc != LDAP_SUCCESS ) { + rs->sr_err = rc; + } + rs->sr_err = slap_map_api2result( rs ); + break; + } + } + + rs->sr_err = slap_map_api2result( rs ); + + Debug( LDAP_DEBUG_TRACE, + "<<< %s meta_back_bind_op_result[%d] err=%d\n", + op->o_log_prefix, candidate, rs->sr_err ); + + return rs->sr_err; +} + +/* + * meta_back_single_bind + * + * attempts to perform a bind with creds + */ +static int +meta_back_single_bind( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + int candidate ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + struct berval mdn = BER_BVNULL; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + int msgid; + dncookie dc; + struct berval save_o_dn; + int save_o_do_not_cache; + LDAPControl **ctrls = NULL; + + if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) { + ch_free( msc->msc_bound_ndn.bv_val ); + BER_BVZERO( &msc->msc_bound_ndn ); + } + + if ( !BER_BVISNULL( &msc->msc_cred ) ) { + /* destroy sensitive data */ + memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len ); + ch_free( msc->msc_cred.bv_val ); + BER_BVZERO( &msc->msc_cred ); + } + + /* + * Rewrite the bind dn if needed + */ + dc.target = mt; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "bindDN"; + + if ( ldap_back_dn_massage( &dc, &op->o_req_dn, &mdn ) ) { + rs->sr_text = "DN rewrite error"; + rs->sr_err = LDAP_OTHER; + return rs->sr_err; + } + + /* don't add proxyAuthz; set the bindDN */ + save_o_dn = op->o_dn; + save_o_do_not_cache = op->o_do_not_cache; + op->o_do_not_cache = 1; + op->o_dn = op->o_req_dn; + + ctrls = op->o_ctrls; + rs->sr_err = meta_back_controls_add( op, rs, mc, candidate, &ctrls ); + op->o_dn = save_o_dn; + op->o_do_not_cache = save_o_do_not_cache; + if ( rs->sr_err != LDAP_SUCCESS ) { + goto return_results; + } + + /* FIXME: this fixes the bind problem right now; we need + * to use the asynchronous version to get the "matched" + * and more in case of failure ... */ + /* FIXME: should we check if at least some of the op->o_ctrls + * can/should be passed? */ + for (;;) { + rs->sr_err = ldap_sasl_bind( msc->msc_ld, mdn.bv_val, + LDAP_SASL_SIMPLE, &op->orb_cred, + ctrls, NULL, &msgid ); + if ( rs->sr_err != LDAP_X_CONNECTING ) { + break; + } + ldap_pvt_thread_yield(); + } + + mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + + meta_back_bind_op_result( op, rs, mc, candidate, msgid, LDAP_BACK_DONTSEND, 1 ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto return_results; + } + + /* If defined, proxyAuthz will be used also when + * back-ldap is the authorizing backend; for this + * purpose, a successful bind is followed by a + * bind with the configured identity assertion */ + /* NOTE: use with care */ + if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) { + meta_back_proxy_authz_bind( mc, candidate, op, rs, LDAP_BACK_SENDERR, 1 ); + if ( !LDAP_BACK_CONN_ISBOUND( msc ) ) { + goto return_results; + } + goto cache_refresh; + } + + ber_bvreplace( &msc->msc_bound_ndn, &op->o_req_ndn ); + LDAP_BACK_CONN_ISBOUND_SET( msc ); + mc->mc_authz_target = candidate; + + if ( META_BACK_TGT_SAVECRED( mt ) ) { + if ( !BER_BVISNULL( &msc->msc_cred ) ) { + memset( msc->msc_cred.bv_val, 0, + msc->msc_cred.bv_len ); + } + ber_bvreplace( &msc->msc_cred, &op->orb_cred ); + ldap_set_rebind_proc( msc->msc_ld, mt->mt_rebind_f, msc ); + } + +cache_refresh:; + if ( mi->mi_cache.ttl != META_DNCACHE_DISABLED + && !BER_BVISEMPTY( &op->o_req_ndn ) ) + { + ( void )meta_dncache_update_entry( &mi->mi_cache, + &op->o_req_ndn, candidate ); + } + +return_results:; + if ( mdn.bv_val != op->o_req_dn.bv_val ) { + free( mdn.bv_val ); + } + + if ( META_BACK_TGT_QUARANTINE( mt ) ) { + meta_back_quarantine( op, rs, candidate ); + } + + return rs->sr_err; +} + +/* + * meta_back_single_dobind + */ +int +meta_back_single_dobind( + Operation *op, + SlapReply *rs, + metaconn_t **mcp, + int candidate, + ldap_back_send_t sendok, + int nretries, + int dolock ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + metaconn_t *mc = *mcp; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + int msgid; + + assert( !LDAP_BACK_CONN_ISBOUND( msc ) ); + + /* NOTE: this obsoletes pseudorootdn */ + if ( op->o_conn != NULL && + !op->o_do_not_cache && + ( BER_BVISNULL( &msc->msc_bound_ndn ) || + BER_BVISEMPTY( &msc->msc_bound_ndn ) || + ( LDAP_BACK_CONN_ISPRIV( mc ) && dn_match( &msc->msc_bound_ndn, &mt->mt_idassert_authcDN ) ) || + ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) ) ) + { + (void)meta_back_proxy_authz_bind( mc, candidate, op, rs, sendok, dolock ); + + } else { + char *binddn = ""; + struct berval cred = BER_BVC( "" ); + + /* use credentials if available */ + if ( !BER_BVISNULL( &msc->msc_bound_ndn ) + && !BER_BVISNULL( &msc->msc_cred ) ) + { + binddn = msc->msc_bound_ndn.bv_val; + cred = msc->msc_cred; + } + + /* FIXME: should we check if at least some of the op->o_ctrls + * can/should be passed? */ + if(!dolock) { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + + for (;;) { + rs->sr_err = ldap_sasl_bind( msc->msc_ld, + binddn, LDAP_SASL_SIMPLE, &cred, + NULL, NULL, &msgid ); + if ( rs->sr_err != LDAP_X_CONNECTING ) { + break; + } + ldap_pvt_thread_yield(); + } + + if(!dolock) { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + } + + rs->sr_err = meta_back_bind_op_result( op, rs, mc, candidate, msgid, sendok, dolock ); + + /* if bind succeeded, but anonymous, clear msc_bound_ndn */ + if ( rs->sr_err != LDAP_SUCCESS || binddn[0] == '\0' ) { + if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) { + ber_memfree( msc->msc_bound_ndn.bv_val ); + BER_BVZERO( &msc->msc_bound_ndn ); + } + + if ( !BER_BVISNULL( &msc->msc_cred ) ) { + memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len ); + ber_memfree( msc->msc_cred.bv_val ); + BER_BVZERO( &msc->msc_cred ); + } + } + } + + if ( rs->sr_err != LDAP_SUCCESS ) { + if ( dolock ) { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + } + LDAP_BACK_CONN_BINDING_CLEAR( msc ); + if ( META_BACK_ONERR_STOP( mi ) ) { + LDAP_BACK_CONN_TAINTED_SET( mc ); + meta_back_release_conn_lock( mi, mc, 0 ); + *mcp = NULL; + } + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + } + + if ( META_BACK_TGT_QUARANTINE( mt ) ) { + meta_back_quarantine( op, rs, candidate ); + } + + return rs->sr_err; +} + +/* + * meta_back_dobind + */ +int +meta_back_dobind( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + ldap_back_send_t sendok ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + + int bound = 0, + i, + isroot = 0; + + SlapReply *candidates; + + if ( be_isroot( op ) ) { + isroot = 1; + } + + if ( LogTest( LDAP_DEBUG_TRACE ) ) { + char buf[STRLENOF("4294967295U") + 1] = { 0 }; + mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) ); + + Debug( LDAP_DEBUG_TRACE, + "%s meta_back_dobind: conn=%s%s\n", + op->o_log_prefix, buf, + isroot ? " (isroot)" : "" ); + } + + /* + * all the targets are bound as pseudoroot + */ + if ( mc->mc_authz_target == META_BOUND_ALL ) { + bound = 1; + goto done; + } + + candidates = meta_back_candidates_get( op ); + + for ( i = 0; i < mi->mi_ntargets; i++ ) { + metatarget_t *mt = mi->mi_targets[ i ]; + metasingleconn_t *msc = &mc->mc_conns[ i ]; + int rc; + + /* + * Not a candidate + */ + if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) { + continue; + } + + assert( msc->msc_ld != NULL ); + + /* + * If the target is already bound it is skipped + */ + +retry_binding:; + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + if ( LDAP_BACK_CONN_ISBOUND( msc ) + || ( LDAP_BACK_CONN_ISANON( msc ) + && mt->mt_idassert_authmethod == LDAP_AUTH_NONE ) ) + { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + ++bound; + continue; + + } else if ( META_BACK_CONN_CREATING( msc ) || LDAP_BACK_CONN_BINDING( msc ) ) + { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + ldap_pvt_thread_yield(); + goto retry_binding; + + } + + LDAP_BACK_CONN_BINDING_SET( msc ); + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + + rc = meta_back_single_dobind( op, rs, &mc, i, + LDAP_BACK_DONTSEND, mt->mt_nretries, 1 ); + /* + * NOTE: meta_back_single_dobind() already retries; + * in case of failure, it resets mc... + */ + if ( rc != LDAP_SUCCESS ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + if ( mc == NULL ) { + /* meta_back_single_dobind() already sent + * response and released connection */ + goto send_err; + } + + + if ( rc == LDAP_UNAVAILABLE ) { + /* FIXME: meta_back_retry() already re-calls + * meta_back_single_dobind() */ + if ( meta_back_retry( op, rs, &mc, i, sendok ) ) { + goto retry_ok; + } + + if ( mc != NULL ) { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + LDAP_BACK_CONN_BINDING_CLEAR( msc ); + meta_back_release_conn_lock( mi, mc, 0 ); + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + + return 0; + } + + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + LDAP_BACK_CONN_BINDING_CLEAR( msc ); + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + + snprintf( buf, sizeof( buf ), + "meta_back_dobind[%d]: (%s) err=%d (%s).", + i, isroot ? op->o_bd->be_rootdn.bv_val : "anonymous", + rc, ldap_err2string( rc ) ); + Debug( LDAP_DEBUG_ANY, + "%s %s\n", + op->o_log_prefix, buf, 0 ); + + /* + * null cred bind should always succeed + * as anonymous, so a failure means + * the target is no longer candidate possibly + * due to technical reasons (remote host down?) + * so better clear the handle + */ + /* leave the target candidate, but record the error for later use */ + candidates[ i ].sr_err = rc; + if ( META_BACK_ONERR_STOP( mi ) ) { + bound = 0; + goto done; + } + + continue; + } /* else */ + +retry_ok:; + Debug( LDAP_DEBUG_TRACE, + "%s meta_back_dobind[%d]: " + "(%s)\n", + op->o_log_prefix, i, + isroot ? op->o_bd->be_rootdn.bv_val : "anonymous" ); + + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + LDAP_BACK_CONN_BINDING_CLEAR( msc ); + if ( isroot ) { + LDAP_BACK_CONN_ISBOUND_SET( msc ); + } else { + LDAP_BACK_CONN_ISANON_SET( msc ); + } + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + ++bound; + } + +done:; + if ( LogTest( LDAP_DEBUG_TRACE ) ) { + char buf[STRLENOF("4294967295U") + 1] = { 0 }; + mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) ); + + Debug( LDAP_DEBUG_TRACE, + "%s meta_back_dobind: conn=%s bound=%d\n", + op->o_log_prefix, buf, bound ); + } + + if ( bound == 0 ) { + meta_back_release_conn( mi, mc ); + +send_err:; + if ( sendok & LDAP_BACK_SENDERR ) { + if ( rs->sr_err == LDAP_SUCCESS ) { + rs->sr_err = LDAP_BUSY; + } + send_ldap_result( op, rs ); + } + + return 0; + } + + return ( bound > 0 ); +} + +/* + * meta_back_default_rebind + * + * This is a callback used for chasing referrals using the same + * credentials as the original user on this session. + */ +int +meta_back_default_rebind( + LDAP *ld, + LDAP_CONST char *url, + ber_tag_t request, + ber_int_t msgid, + void *params ) +{ + metasingleconn_t *msc = ( metasingleconn_t * )params; + + return ldap_sasl_bind_s( ld, msc->msc_bound_ndn.bv_val, + LDAP_SASL_SIMPLE, &msc->msc_cred, + NULL, NULL, NULL ); +} + +/* + * meta_back_default_urllist + * + * This is a callback used for mucking with the urllist + */ +int +meta_back_default_urllist( + LDAP *ld, + LDAPURLDesc **urllist, + LDAPURLDesc **url, + void *params ) +{ + metatarget_t *mt = (metatarget_t *)params; + LDAPURLDesc **urltail; + + if ( urllist == url ) { + return LDAP_SUCCESS; + } + + for ( urltail = &(*url)->lud_next; *urltail; urltail = &(*urltail)->lud_next ) + /* count */ ; + + *urltail = *urllist; + *urllist = *url; + *url = NULL; + + ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex ); + if ( mt->mt_uri ) { + ch_free( mt->mt_uri ); + } + + ldap_get_option( ld, LDAP_OPT_URI, (void *)&mt->mt_uri ); + ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex ); + + return LDAP_SUCCESS; +} + +int +meta_back_cancel( + metaconn_t *mc, + Operation *op, + SlapReply *rs, + ber_int_t msgid, + int candidate, + ldap_back_send_t sendok ) +{ + metainfo_t *mi = (metainfo_t *)op->o_bd->be_private; + + metatarget_t *mt = mi->mi_targets[ candidate ]; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + + int rc = LDAP_OTHER; + + Debug( LDAP_DEBUG_TRACE, ">>> %s meta_back_cancel[%d] msgid=%d\n", + op->o_log_prefix, candidate, msgid ); + + /* default behavior */ + if ( META_BACK_TGT_ABANDON( mt ) ) { + rc = ldap_abandon_ext( msc->msc_ld, msgid, NULL, NULL ); + + } else if ( META_BACK_TGT_IGNORE( mt ) ) { + rc = ldap_pvt_discard( msc->msc_ld, msgid ); + + } else if ( META_BACK_TGT_CANCEL( mt ) ) { + rc = ldap_cancel_s( msc->msc_ld, msgid, NULL, NULL ); + + } else { + assert( 0 ); + } + + Debug( LDAP_DEBUG_TRACE, "<<< %s meta_back_cancel[%d] err=%d\n", + op->o_log_prefix, candidate, rc ); + + return rc; +} + + + +/* + * FIXME: error return must be handled in a cleaner way ... + */ +int +meta_back_op_result( + metaconn_t *mc, + Operation *op, + SlapReply *rs, + int candidate, + ber_int_t msgid, + time_t timeout, + ldap_back_send_t sendok ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + + const char *save_text = rs->sr_text, + *save_matched = rs->sr_matched; + BerVarray save_ref = rs->sr_ref; + LDAPControl **save_ctrls = rs->sr_ctrls; + void *matched_ctx = NULL; + + char *matched = NULL; + char *text = NULL; + char **refs = NULL; + LDAPControl **ctrls = NULL; + + assert( mc != NULL ); + + rs->sr_text = NULL; + rs->sr_matched = NULL; + rs->sr_ref = NULL; + rs->sr_ctrls = NULL; + + if ( candidate != META_TARGET_NONE ) { + metatarget_t *mt = mi->mi_targets[ candidate ]; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + + if ( LDAP_ERR_OK( rs->sr_err ) ) { + int rc; + struct timeval tv; + LDAPMessage *res = NULL; + time_t stoptime = (time_t)(-1); + int timeout_err = op->o_protocol >= LDAP_VERSION3 ? + LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER; + const char *timeout_text = "Operation timed out"; + + /* if timeout is not specified, compute and use + * the one specific to the ongoing operation */ + if ( timeout == (time_t)(-1) ) { + slap_op_t opidx = slap_req2op( op->o_tag ); + + if ( opidx == SLAP_OP_SEARCH ) { + if ( op->ors_tlimit <= 0 ) { + timeout = 0; + + } else { + timeout = op->ors_tlimit; + timeout_err = LDAP_TIMELIMIT_EXCEEDED; + timeout_text = NULL; + } + + } else { + timeout = mt->mt_timeout[ opidx ]; + } + } + + /* better than nothing :) */ + if ( timeout == 0 ) { + if ( mi->mi_idle_timeout ) { + timeout = mi->mi_idle_timeout; + + } else if ( mi->mi_conn_ttl ) { + timeout = mi->mi_conn_ttl; + } + } + + if ( timeout ) { + stoptime = op->o_time + timeout; + } + + LDAP_BACK_TV_SET( &tv ); + +retry:; + rc = ldap_result( msc->msc_ld, msgid, LDAP_MSG_ALL, &tv, &res ); + switch ( rc ) { + case 0: + if ( timeout && slap_get_time() > stoptime ) { + (void)meta_back_cancel( mc, op, rs, msgid, candidate, sendok ); + rs->sr_err = timeout_err; + rs->sr_text = timeout_text; + break; + } + + LDAP_BACK_TV_SET( &tv ); + ldap_pvt_thread_yield(); + goto retry; + + case -1: + ldap_get_option( msc->msc_ld, LDAP_OPT_RESULT_CODE, + &rs->sr_err ); + break; + + + /* otherwise get the result; if it is not + * LDAP_SUCCESS, record it in the reply + * structure (this includes + * LDAP_COMPARE_{TRUE|FALSE}) */ + default: + /* only touch when activity actually took place... */ + if ( mi->mi_idle_timeout != 0 && msc->msc_time < op->o_time ) { + msc->msc_time = op->o_time; + } + + rc = ldap_parse_result( msc->msc_ld, res, &rs->sr_err, + &matched, &text, &refs, &ctrls, 1 ); + res = NULL; + if ( rc == LDAP_SUCCESS ) { + rs->sr_text = text; + } else { + rs->sr_err = rc; + } + rs->sr_err = slap_map_api2result( rs ); + + /* RFC 4511: referrals can only appear + * if result code is LDAP_REFERRAL */ + if ( refs != NULL + && refs[ 0 ] != NULL + && refs[ 0 ][ 0 ] != '\0' ) + { + if ( rs->sr_err != LDAP_REFERRAL ) { + Debug( LDAP_DEBUG_ANY, + "%s meta_back_op_result[%d]: " + "got referrals with err=%d\n", + op->o_log_prefix, + candidate, rs->sr_err ); + + } else { + int i; + + for ( i = 0; refs[ i ] != NULL; i++ ) + /* count */ ; + rs->sr_ref = op->o_tmpalloc( sizeof( struct berval ) * ( i + 1 ), + op->o_tmpmemctx ); + for ( i = 0; refs[ i ] != NULL; i++ ) { + ber_str2bv( refs[ i ], 0, 0, &rs->sr_ref[ i ] ); + } + BER_BVZERO( &rs->sr_ref[ i ] ); + } + + } else if ( rs->sr_err == LDAP_REFERRAL ) { + Debug( LDAP_DEBUG_ANY, + "%s meta_back_op_result[%d]: " + "got err=%d with null " + "or empty referrals\n", + op->o_log_prefix, + candidate, rs->sr_err ); + + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } + + if ( ctrls != NULL ) { + rs->sr_ctrls = ctrls; + } + } + + assert( res == NULL ); + } + + /* if the error in the reply structure is not + * LDAP_SUCCESS, try to map it from client + * to server error */ + if ( !LDAP_ERR_OK( rs->sr_err ) ) { + rs->sr_err = slap_map_api2result( rs ); + + /* internal ops ( op->o_conn == NULL ) + * must not reply to client */ + if ( op->o_conn && !op->o_do_not_cache && matched ) { + + /* record the (massaged) matched + * DN into the reply structure */ + rs->sr_matched = matched; + } + } + + if ( META_BACK_TGT_QUARANTINE( mt ) ) { + meta_back_quarantine( op, rs, candidate ); + } + + } else { + int i, + err = rs->sr_err; + + for ( i = 0; i < mi->mi_ntargets; i++ ) { + metasingleconn_t *msc = &mc->mc_conns[ i ]; + char *xtext = NULL; + char *xmatched = NULL; + + if ( msc->msc_ld == NULL ) { + continue; + } + + rs->sr_err = LDAP_SUCCESS; + + ldap_get_option( msc->msc_ld, LDAP_OPT_RESULT_CODE, &rs->sr_err ); + if ( rs->sr_err != LDAP_SUCCESS ) { + /* + * better check the type of error. In some cases + * (search ?) it might be better to return a + * success if at least one of the targets gave + * positive result ... + */ + ldap_get_option( msc->msc_ld, + LDAP_OPT_DIAGNOSTIC_MESSAGE, &xtext ); + if ( xtext != NULL && xtext [ 0 ] == '\0' ) { + ldap_memfree( xtext ); + xtext = NULL; + } + + ldap_get_option( msc->msc_ld, + LDAP_OPT_MATCHED_DN, &xmatched ); + if ( xmatched != NULL && xmatched[ 0 ] == '\0' ) { + ldap_memfree( xmatched ); + xmatched = NULL; + } + + rs->sr_err = slap_map_api2result( rs ); + + if ( LogTest( LDAP_DEBUG_ANY ) ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "meta_back_op_result[%d] " + "err=%d text=\"%s\" matched=\"%s\"", + i, rs->sr_err, + ( xtext ? xtext : "" ), + ( xmatched ? xmatched : "" ) ); + Debug( LDAP_DEBUG_ANY, "%s %s.\n", + op->o_log_prefix, buf, 0 ); + } + + /* + * FIXME: need to rewrite "match" (need rwinfo) + */ + switch ( rs->sr_err ) { + default: + err = rs->sr_err; + if ( xtext != NULL ) { + if ( text ) { + ldap_memfree( text ); + } + text = xtext; + xtext = NULL; + } + if ( xmatched != NULL ) { + if ( matched ) { + ldap_memfree( matched ); + } + matched = xmatched; + xmatched = NULL; + } + break; + } + + if ( xtext ) { + ldap_memfree( xtext ); + } + + if ( xmatched ) { + ldap_memfree( xmatched ); + } + } + + if ( META_BACK_TGT_QUARANTINE( mi->mi_targets[ i ] ) ) { + meta_back_quarantine( op, rs, i ); + } + } + + if ( err != LDAP_SUCCESS ) { + rs->sr_err = err; + } + } + + if ( matched != NULL ) { + struct berval dn, pdn; + + ber_str2bv( matched, 0, 0, &dn ); + if ( dnPretty( NULL, &dn, &pdn, op->o_tmpmemctx ) == LDAP_SUCCESS ) { + ldap_memfree( matched ); + matched_ctx = op->o_tmpmemctx; + matched = pdn.bv_val; + } + rs->sr_matched = matched; + } + + if ( rs->sr_err == LDAP_UNAVAILABLE ) { + if ( !( sendok & LDAP_BACK_RETRYING ) ) { + if ( op->o_conn && ( sendok & LDAP_BACK_SENDERR ) ) { + if ( rs->sr_text == NULL ) rs->sr_text = "Proxy operation retry failed"; + send_ldap_result( op, rs ); + } + } + + } else if ( op->o_conn && + ( ( ( sendok & LDAP_BACK_SENDOK ) && LDAP_ERR_OK( rs->sr_err ) ) + || ( ( sendok & LDAP_BACK_SENDERR ) && !LDAP_ERR_OK( rs->sr_err ) ) ) ) + { + send_ldap_result( op, rs ); + } + if ( matched ) { + op->o_tmpfree( (char *)rs->sr_matched, matched_ctx ); + } + if ( text ) { + ldap_memfree( text ); + } + if ( rs->sr_ref ) { + op->o_tmpfree( rs->sr_ref, op->o_tmpmemctx ); + rs->sr_ref = NULL; + } + if ( refs ) { + ber_memvfree( (void **)refs ); + } + if ( ctrls ) { + assert( rs->sr_ctrls != NULL ); + ldap_controls_free( ctrls ); + } + + rs->sr_text = save_text; + rs->sr_matched = save_matched; + rs->sr_ref = save_ref; + rs->sr_ctrls = save_ctrls; + + return( LDAP_ERR_OK( rs->sr_err ) ? LDAP_SUCCESS : rs->sr_err ); +} + +/* + * meta_back_proxy_authz_cred() + * + * prepares credentials & method for meta_back_proxy_authz_bind(); + * or, if method is SASL, performs the SASL bind directly. + */ +int +meta_back_proxy_authz_cred( + metaconn_t *mc, + int candidate, + Operation *op, + SlapReply *rs, + ldap_back_send_t sendok, + struct berval *binddn, + struct berval *bindcred, + int *method ) +{ + metainfo_t *mi = (metainfo_t *)op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + struct berval ndn; + int dobind = 0; + + /* don't proxyAuthz if protocol is not LDAPv3 */ + switch ( mt->mt_version ) { + case LDAP_VERSION3: + break; + + case 0: + if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) { + break; + } + /* fall thru */ + + default: + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + LDAP_BACK_CONN_ISBOUND_CLEAR( msc ); + goto done; + } + + if ( op->o_tag == LDAP_REQ_BIND ) { + ndn = op->o_req_ndn; + + } else if ( !BER_BVISNULL( &op->o_conn->c_ndn ) ) { + ndn = op->o_conn->c_ndn; + + } else { + ndn = op->o_ndn; + } + rs->sr_err = LDAP_SUCCESS; + + /* + * FIXME: we need to let clients use proxyAuthz + * otherwise we cannot do symmetric pools of servers; + * we have to live with the fact that a user can + * authorize itself as any ID that is allowed + * by the authzTo directive of the "proxyauthzdn". + */ + /* + * NOTE: current Proxy Authorization specification + * and implementation do not allow proxy authorization + * control to be provided with Bind requests + */ + /* + * if no bind took place yet, but the connection is bound + * and the "proxyauthzdn" is set, then bind as + * "proxyauthzdn" and explicitly add the proxyAuthz + * control to every operation with the dn bound + * to the connection as control value. + */ + + /* bind as proxyauthzdn only if no idassert mode + * is requested, or if the client's identity + * is authorized */ + switch ( mt->mt_idassert_mode ) { + case LDAP_BACK_IDASSERT_LEGACY: + if ( !BER_BVISNULL( &ndn ) && !BER_BVISEMPTY( &ndn ) ) { + if ( !BER_BVISNULL( &mt->mt_idassert_authcDN ) && !BER_BVISEMPTY( &mt->mt_idassert_authcDN ) ) + { + *binddn = mt->mt_idassert_authcDN; + *bindcred = mt->mt_idassert_passwd; + dobind = 1; + } + } + break; + + default: + /* NOTE: rootdn can always idassert */ + if ( BER_BVISNULL( &ndn ) + && mt->mt_idassert_authz == NULL + && !( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) ) + { + if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) { + rs->sr_err = LDAP_INAPPROPRIATE_AUTH; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + LDAP_BACK_CONN_ISBOUND_CLEAR( msc ); + goto done; + + } + + rs->sr_err = LDAP_SUCCESS; + *binddn = slap_empty_bv; + *bindcred = slap_empty_bv; + break; + + } else if ( mt->mt_idassert_authz && !be_isroot( op ) ) { + struct berval authcDN; + + if ( BER_BVISNULL( &ndn ) ) { + authcDN = slap_empty_bv; + + } else { + authcDN = ndn; + } + rs->sr_err = slap_sasl_matches( op, mt->mt_idassert_authz, + &authcDN, &authcDN ); + if ( rs->sr_err != LDAP_SUCCESS ) { + if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) { + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + LDAP_BACK_CONN_ISBOUND_CLEAR( msc ); + goto done; + } + + rs->sr_err = LDAP_SUCCESS; + *binddn = slap_empty_bv; + *bindcred = slap_empty_bv; + break; + } + } + + *binddn = mt->mt_idassert_authcDN; + *bindcred = mt->mt_idassert_passwd; + dobind = 1; + break; + } + + if ( dobind && mt->mt_idassert_authmethod == LDAP_AUTH_SASL ) { +#ifdef HAVE_CYRUS_SASL + void *defaults = NULL; + struct berval authzID = BER_BVNULL; + int freeauthz = 0; + + /* if SASL supports native authz, prepare for it */ + if ( ( !op->o_do_not_cache || !op->o_is_auth_check ) && + ( mt->mt_idassert_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) ) + { + switch ( mt->mt_idassert_mode ) { + case LDAP_BACK_IDASSERT_OTHERID: + case LDAP_BACK_IDASSERT_OTHERDN: + authzID = mt->mt_idassert_authzID; + break; + + case LDAP_BACK_IDASSERT_ANONYMOUS: + BER_BVSTR( &authzID, "dn:" ); + break; + + case LDAP_BACK_IDASSERT_SELF: + if ( BER_BVISNULL( &ndn ) ) { + /* connection is not authc'd, so don't idassert */ + BER_BVSTR( &authzID, "dn:" ); + break; + } + authzID.bv_len = STRLENOF( "dn:" ) + ndn.bv_len; + authzID.bv_val = slap_sl_malloc( authzID.bv_len + 1, op->o_tmpmemctx ); + AC_MEMCPY( authzID.bv_val, "dn:", STRLENOF( "dn:" ) ); + AC_MEMCPY( authzID.bv_val + STRLENOF( "dn:" ), + ndn.bv_val, ndn.bv_len + 1 ); + freeauthz = 1; + break; + + default: + break; + } + } + + if ( mt->mt_idassert_secprops != NULL ) { + rs->sr_err = ldap_set_option( msc->msc_ld, + LDAP_OPT_X_SASL_SECPROPS, + (void *)mt->mt_idassert_secprops ); + + if ( rs->sr_err != LDAP_OPT_SUCCESS ) { + rs->sr_err = LDAP_OTHER; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + LDAP_BACK_CONN_ISBOUND_CLEAR( msc ); + goto done; + } + } + + defaults = lutil_sasl_defaults( msc->msc_ld, + mt->mt_idassert_sasl_mech.bv_val, + mt->mt_idassert_sasl_realm.bv_val, + mt->mt_idassert_authcID.bv_val, + mt->mt_idassert_passwd.bv_val, + authzID.bv_val ); + if ( defaults == NULL ) { + rs->sr_err = LDAP_OTHER; + LDAP_BACK_CONN_ISBOUND_CLEAR( msc ); + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + goto done; + } + + rs->sr_err = ldap_sasl_interactive_bind_s( msc->msc_ld, binddn->bv_val, + mt->mt_idassert_sasl_mech.bv_val, NULL, NULL, + LDAP_SASL_QUIET, lutil_sasl_interact, + defaults ); + + rs->sr_err = slap_map_api2result( rs ); + if ( rs->sr_err != LDAP_SUCCESS ) { + LDAP_BACK_CONN_ISBOUND_CLEAR( msc ); + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + + } else { + LDAP_BACK_CONN_ISBOUND_SET( msc ); + } + + lutil_sasl_freedefs( defaults ); + if ( freeauthz ) { + slap_sl_free( authzID.bv_val, op->o_tmpmemctx ); + } + + goto done; +#endif /* HAVE_CYRUS_SASL */ + } + + *method = mt->mt_idassert_authmethod; + switch ( mt->mt_idassert_authmethod ) { + case LDAP_AUTH_NONE: + BER_BVSTR( binddn, "" ); + BER_BVSTR( bindcred, "" ); + /* fallthru */ + + case LDAP_AUTH_SIMPLE: + break; + + default: + /* unsupported! */ + LDAP_BACK_CONN_ISBOUND_CLEAR( msc ); + rs->sr_err = LDAP_AUTH_METHOD_NOT_SUPPORTED; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + break; + } + +done:; + + if ( !BER_BVISEMPTY( binddn ) ) { + LDAP_BACK_CONN_ISIDASSERT_SET( msc ); + } + + return rs->sr_err; +} + +static int +meta_back_proxy_authz_bind( + metaconn_t *mc, + int candidate, + Operation *op, + SlapReply *rs, + ldap_back_send_t sendok, + int dolock ) +{ + metainfo_t *mi = (metainfo_t *)op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + struct berval binddn = BER_BVC( "" ), + cred = BER_BVC( "" ); + int method = LDAP_AUTH_NONE, + rc; + + rc = meta_back_proxy_authz_cred( mc, candidate, op, rs, sendok, &binddn, &cred, &method ); + if ( rc == LDAP_SUCCESS && !LDAP_BACK_CONN_ISBOUND( msc ) ) { + int msgid; + + switch ( method ) { + case LDAP_AUTH_NONE: + case LDAP_AUTH_SIMPLE: + + if(!dolock) { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + + for (;;) { + rs->sr_err = ldap_sasl_bind( msc->msc_ld, + binddn.bv_val, LDAP_SASL_SIMPLE, + &cred, NULL, NULL, &msgid ); + if ( rs->sr_err != LDAP_X_CONNECTING ) { + break; + } + ldap_pvt_thread_yield(); + } + + if(!dolock) { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + } + + rc = meta_back_bind_op_result( op, rs, mc, candidate, msgid, sendok, dolock ); + if ( rc == LDAP_SUCCESS ) { + /* set rebind stuff in case of successful proxyAuthz bind, + * so that referral chasing is attempted using the right + * identity */ + LDAP_BACK_CONN_ISBOUND_SET( msc ); + ber_bvreplace( &msc->msc_bound_ndn, &binddn ); + + if ( META_BACK_TGT_SAVECRED( mt ) ) { + if ( !BER_BVISNULL( &msc->msc_cred ) ) { + memset( msc->msc_cred.bv_val, 0, + msc->msc_cred.bv_len ); + } + ber_bvreplace( &msc->msc_cred, &cred ); + ldap_set_rebind_proc( msc->msc_ld, mt->mt_rebind_f, msc ); + } + } + break; + + default: + assert( 0 ); + break; + } + } + + return LDAP_BACK_CONN_ISBOUND( msc ); +} + +/* + * Add controls; + * + * if any needs to be added, it is prepended to existing ones, + * in a newly allocated array. The companion function + * mi->mi_ldap_extra->controls_free() must be used to restore the original + * status of op->o_ctrls. + */ +int +meta_back_controls_add( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + int candidate, + LDAPControl ***pctrls ) +{ + metainfo_t *mi = (metainfo_t *)op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + + LDAPControl **ctrls = NULL; + /* set to the maximum number of controls this backend can add */ + LDAPControl c[ 2 ] = {{ 0 }}; + int n = 0, i, j1 = 0, j2 = 0; + + *pctrls = NULL; + + rs->sr_err = LDAP_SUCCESS; + + /* don't add controls if protocol is not LDAPv3 */ + switch ( mt->mt_version ) { + case LDAP_VERSION3: + break; + + case 0: + if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) { + break; + } + /* fall thru */ + + default: + goto done; + } + + /* put controls that go __before__ existing ones here */ + + /* proxyAuthz for identity assertion */ + switch ( mi->mi_ldap_extra->proxy_authz_ctrl( op, rs, &msc->msc_bound_ndn, + mt->mt_version, &mt->mt_idassert, &c[ j1 ] ) ) + { + case SLAP_CB_CONTINUE: + break; + + case LDAP_SUCCESS: + j1++; + break; + + default: + goto done; + } + + /* put controls that go __after__ existing ones here */ + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + /* session tracking */ + if ( META_BACK_TGT_ST_REQUEST( mt ) ) { + switch ( slap_ctrl_session_tracking_request_add( op, rs, &c[ j1 + j2 ] ) ) { + case SLAP_CB_CONTINUE: + break; + + case LDAP_SUCCESS: + j2++; + break; + + default: + goto done; + } + } +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + + if ( rs->sr_err == SLAP_CB_CONTINUE ) { + rs->sr_err = LDAP_SUCCESS; + } + + /* if nothing to do, just bail out */ + if ( j1 == 0 && j2 == 0 ) { + goto done; + } + + assert( j1 + j2 <= (int) (sizeof( c )/sizeof( c[0] )) ); + + if ( op->o_ctrls ) { + for ( n = 0; op->o_ctrls[ n ]; n++ ) + /* just count ctrls */ ; + } + + ctrls = op->o_tmpalloc( (n + j1 + j2 + 1) * sizeof( LDAPControl * ) + ( j1 + j2 ) * sizeof( LDAPControl ), + op->o_tmpmemctx ); + if ( j1 ) { + ctrls[ 0 ] = (LDAPControl *)&ctrls[ n + j1 + j2 + 1 ]; + *ctrls[ 0 ] = c[ 0 ]; + for ( i = 1; i < j1; i++ ) { + ctrls[ i ] = &ctrls[ 0 ][ i ]; + *ctrls[ i ] = c[ i ]; + } + } + + i = 0; + if ( op->o_ctrls ) { + for ( i = 0; op->o_ctrls[ i ]; i++ ) { + ctrls[ i + j1 ] = op->o_ctrls[ i ]; + } + } + + n += j1; + if ( j2 ) { + ctrls[ n ] = (LDAPControl *)&ctrls[ n + j2 + 1 ] + j1; + *ctrls[ n ] = c[ j1 ]; + for ( i = 1; i < j2; i++ ) { + ctrls[ n + i ] = &ctrls[ n ][ i ]; + *ctrls[ n + i ] = c[ i ]; + } + } + + ctrls[ n + j2 ] = NULL; + +done:; + if ( ctrls == NULL ) { + ctrls = op->o_ctrls; + } + + *pctrls = ctrls; + + return rs->sr_err; +} + diff --git a/servers/slapd/back-meta/candidates.c b/servers/slapd/back-meta/candidates.c new file mode 100644 index 0000000..98f0a36 --- /dev/null +++ b/servers/slapd/back-meta/candidates.c @@ -0,0 +1,284 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include "ac/string.h" + +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +/* + * The meta-directory has one suffix, called <suffix>. + * It handles a pool of target servers, each with a branch suffix + * of the form <branch X>,<suffix>, where <branch X> may be empty. + * + * When the meta-directory receives a request with a request DN that belongs + * to a branch, the corresponding target is invoked. When the request DN + * does not belong to a specific branch, all the targets that + * are compatible with the request DN are selected as candidates, and + * the request is spawned to all the candidate targets + * + * A request is characterized by a request DN. The following cases are + * handled: + * - the request DN is the suffix: <dn> == <suffix>, + * all the targets are candidates (search ...) + * - the request DN is a branch suffix: <dn> == <branch X>,<suffix>, or + * - the request DN is a subtree of a branch suffix: + * <dn> == <rdn>,<branch X>,<suffix>, + * the target is the only candidate. + * + * A possible extension will include the handling of multiple suffixes + */ + +static metasubtree_t * +meta_subtree_match( metatarget_t *mt, struct berval *ndn, int scope ) +{ + metasubtree_t *ms = mt->mt_subtree; + + for ( ms = mt->mt_subtree; ms; ms = ms->ms_next ) { + switch ( ms->ms_type ) { + case META_ST_SUBTREE: + if ( dnIsSuffix( ndn, &ms->ms_dn ) ) { + return ms; + } + break; + + case META_ST_SUBORDINATE: + if ( dnIsSuffix( ndn, &ms->ms_dn ) && + ( ndn->bv_len > ms->ms_dn.bv_len || scope != LDAP_SCOPE_BASE ) ) + { + return ms; + } + break; + + case META_ST_REGEX: + /* NOTE: cannot handle scope */ + if ( regexec( &ms->ms_regex, ndn->bv_val, 0, NULL, 0 ) == 0 ) { + return ms; + } + break; + } + } + + return NULL; +} + +/* + * returns 1 if suffix is candidate for dn, otherwise 0 + * + * Note: this function should never be called if dn is the <suffix>. + */ +int +meta_back_is_candidate( + metatarget_t *mt, + struct berval *ndn, + int scope ) +{ + struct berval rdn; + int d = ndn->bv_len - mt->mt_nsuffix.bv_len; + + if ( d >= 0 ) { + if ( !dnIsSuffix( ndn, &mt->mt_nsuffix ) ) { + return META_NOT_CANDIDATE; + } + + /* + * | match | exclude | + * +---------+---------+-------------------+ + * | T | T | not candidate | + * | F | T | continue checking | + * +---------+---------+-------------------+ + * | T | F | candidate | + * | F | F | not candidate | + * +---------+---------+-------------------+ + */ + + if ( mt->mt_subtree ) { + int match = ( meta_subtree_match( mt, ndn, scope ) != NULL ); + + if ( !mt->mt_subtree_exclude ) { + return match ? META_CANDIDATE : META_NOT_CANDIDATE; + } + + if ( match /* && mt->mt_subtree_exclude */ ) { + return META_NOT_CANDIDATE; + } + } + + switch ( mt->mt_scope ) { + case LDAP_SCOPE_SUBTREE: + default: + return META_CANDIDATE; + + case LDAP_SCOPE_SUBORDINATE: + if ( d > 0 ) { + return META_CANDIDATE; + } + break; + + /* nearly useless; not allowed by config */ + case LDAP_SCOPE_ONELEVEL: + if ( d > 0 ) { + rdn.bv_val = ndn->bv_val; + rdn.bv_len = (ber_len_t)d - STRLENOF( "," ); + if ( dnIsOneLevelRDN( &rdn ) ) { + return META_CANDIDATE; + } + } + break; + + /* nearly useless; not allowed by config */ + case LDAP_SCOPE_BASE: + if ( d == 0 ) { + return META_CANDIDATE; + } + break; + } + + } else /* if ( d < 0 ) */ { + if ( !dnIsSuffix( &mt->mt_nsuffix, ndn ) ) { + return META_NOT_CANDIDATE; + } + + switch ( scope ) { + case LDAP_SCOPE_SUBTREE: + case LDAP_SCOPE_SUBORDINATE: + /* + * suffix longer than dn, but common part matches + */ + return META_CANDIDATE; + + case LDAP_SCOPE_ONELEVEL: + rdn.bv_val = mt->mt_nsuffix.bv_val; + rdn.bv_len = (ber_len_t)(-d) - STRLENOF( "," ); + if ( dnIsOneLevelRDN( &rdn ) ) { + return META_CANDIDATE; + } + break; + } + } + + return META_NOT_CANDIDATE; +} + +/* + * meta_back_select_unique_candidate + * + * returns the index of the candidate in case it is unique, otherwise + * META_TARGET_NONE if none matches, or + * META_TARGET_MULTIPLE if more than one matches + * Note: ndn MUST be normalized. + */ +int +meta_back_select_unique_candidate( + metainfo_t *mi, + struct berval *ndn ) +{ + int i, candidate = META_TARGET_NONE; + + for ( i = 0; i < mi->mi_ntargets; i++ ) { + metatarget_t *mt = mi->mi_targets[ i ]; + + if ( meta_back_is_candidate( mt, ndn, LDAP_SCOPE_BASE ) ) { + if ( candidate == META_TARGET_NONE ) { + candidate = i; + + } else { + return META_TARGET_MULTIPLE; + } + } + } + + return candidate; +} + +/* + * meta_clear_unused_candidates + * + * clears all candidates except candidate + */ +int +meta_clear_unused_candidates( + Operation *op, + int candidate ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + int i; + SlapReply *candidates = meta_back_candidates_get( op ); + + for ( i = 0; i < mi->mi_ntargets; ++i ) { + if ( i == candidate ) { + continue; + } + META_CANDIDATE_RESET( &candidates[ i ] ); + } + + return 0; +} + +/* + * meta_clear_one_candidate + * + * clears the selected candidate + */ +int +meta_clear_one_candidate( + Operation *op, + metaconn_t *mc, + int candidate ) +{ + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + + if ( msc->msc_ld != NULL ) { + +#ifdef DEBUG_205 + char buf[ BUFSIZ ]; + + snprintf( buf, sizeof( buf ), "meta_clear_one_candidate ldap_unbind_ext[%d] mc=%p ld=%p", + candidate, (void *)mc, (void *)msc->msc_ld ); + Debug( LDAP_DEBUG_ANY, "### %s %s\n", + op ? op->o_log_prefix : "", buf, 0 ); +#endif /* DEBUG_205 */ + + ldap_unbind_ext( msc->msc_ld, NULL, NULL ); + msc->msc_ld = NULL; + } + + if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) { + ber_memfree_x( msc->msc_bound_ndn.bv_val, NULL ); + BER_BVZERO( &msc->msc_bound_ndn ); + } + + if ( !BER_BVISNULL( &msc->msc_cred ) ) { + memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len ); + ber_memfree_x( msc->msc_cred.bv_val, NULL ); + BER_BVZERO( &msc->msc_cred ); + } + + msc->msc_mscflags = 0; + + return 0; +} + diff --git a/servers/slapd/back-meta/compare.c b/servers/slapd/back-meta/compare.c new file mode 100644 index 0000000..b2c584f --- /dev/null +++ b/servers/slapd/back-meta/compare.c @@ -0,0 +1,154 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +int +meta_back_compare( Operation *op, SlapReply *rs ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt; + metaconn_t *mc; + int rc = 0; + int candidate = -1; + struct berval mdn = BER_BVNULL; + dncookie dc; + struct berval mapped_attr = op->orc_ava->aa_desc->ad_cname; + struct berval mapped_value = op->orc_ava->aa_value; + int msgid; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + LDAPControl **ctrls = NULL; + + mc = meta_back_getconn( op, rs, &candidate, LDAP_BACK_SENDERR ); + if ( !mc || !meta_back_dobind( op, rs, mc, LDAP_BACK_SENDERR ) ) { + return rs->sr_err; + } + + assert( mc->mc_conns[ candidate ].msc_ld != NULL ); + + /* + * Rewrite the modify dn, if needed + */ + mt = mi->mi_targets[ candidate ]; + dc.target = mt; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "compareDN"; + + switch ( ldap_back_dn_massage( &dc, &op->o_req_dn, &mdn ) ) { + case LDAP_UNWILLING_TO_PERFORM: + rc = 1; + goto cleanup; + + default: + break; + } + + /* + * if attr is objectClass, try to remap the value + */ + if ( op->orc_ava->aa_desc == slap_schema.si_ad_objectClass ) { + ldap_back_map( &mt->mt_rwmap.rwm_oc, + &op->orc_ava->aa_value, + &mapped_value, BACKLDAP_MAP ); + + if ( BER_BVISNULL( &mapped_value ) || BER_BVISEMPTY( &mapped_value ) ) { + goto cleanup; + } + + /* + * else try to remap the attribute + */ + } else { + ldap_back_map( &mt->mt_rwmap.rwm_at, + &op->orc_ava->aa_desc->ad_cname, + &mapped_attr, BACKLDAP_MAP ); + if ( BER_BVISNULL( &mapped_attr ) || BER_BVISEMPTY( &mapped_attr ) ) { + goto cleanup; + } + + if ( op->orc_ava->aa_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) + { + dc.ctx = "compareAttrDN"; + + switch ( ldap_back_dn_massage( &dc, &op->orc_ava->aa_value, &mapped_value ) ) + { + case LDAP_UNWILLING_TO_PERFORM: + rc = 1; + goto cleanup; + + default: + break; + } + } + } + +retry:; + ctrls = op->o_ctrls; + rc = meta_back_controls_add( op, rs, mc, candidate, &ctrls ); + if ( rc != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = ldap_compare_ext( mc->mc_conns[ candidate ].msc_ld, mdn.bv_val, + mapped_attr.bv_val, &mapped_value, + ctrls, NULL, &msgid ); + + rs->sr_err = meta_back_op_result( mc, op, rs, candidate, msgid, + mt->mt_timeout[ SLAP_OP_COMPARE ], ( LDAP_BACK_SENDRESULT | retrying ) ); + if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( meta_back_retry( op, rs, &mc, candidate, LDAP_BACK_SENDERR ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + goto retry; + } + } + +cleanup:; + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + + if ( mdn.bv_val != op->o_req_dn.bv_val ) { + free( mdn.bv_val ); + } + + if ( op->orc_ava->aa_value.bv_val != mapped_value.bv_val ) { + free( mapped_value.bv_val ); + } + + if ( mc ) { + meta_back_release_conn( mi, mc ); + } + + return rs->sr_err; +} + diff --git a/servers/slapd/back-meta/config.c b/servers/slapd/back-meta/config.c new file mode 100644 index 0000000..03bc762 --- /dev/null +++ b/servers/slapd/back-meta/config.c @@ -0,0 +1,3385 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ctype.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "config.h" +#include "lutil.h" +#include "ldif.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +#ifdef LDAP_DEVEL +#define SLAP_AUTH_DN 1 +#endif + +static ConfigDriver meta_back_cf_gen; +static ConfigLDAPadd meta_ldadd; +static ConfigCfAdd meta_cfadd; + +static int ldap_back_map_config( + ConfigArgs *c, + struct ldapmap *oc_map, + struct ldapmap *at_map ); + +/* Three sets of enums: + * 1) attrs that are only valid in the base config + * 2) attrs that are valid in base or target + * 3) attrs that are only valid in a target + */ + +/* Base attrs */ +enum { + LDAP_BACK_CFG_CONN_TTL = 1, + LDAP_BACK_CFG_DNCACHE_TTL, + LDAP_BACK_CFG_IDLE_TIMEOUT, + LDAP_BACK_CFG_ONERR, + LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER, + LDAP_BACK_CFG_SINGLECONN, + LDAP_BACK_CFG_USETEMP, + LDAP_BACK_CFG_CONNPOOLMAX, + LDAP_BACK_CFG_LAST_BASE +}; + +/* Base or target */ +enum { + LDAP_BACK_CFG_BIND_TIMEOUT = LDAP_BACK_CFG_LAST_BASE, + LDAP_BACK_CFG_CANCEL, + LDAP_BACK_CFG_CHASE, + LDAP_BACK_CFG_CLIENT_PR, + LDAP_BACK_CFG_DEFAULT_T, + LDAP_BACK_CFG_NETWORK_TIMEOUT, + LDAP_BACK_CFG_NOREFS, + LDAP_BACK_CFG_NOUNDEFFILTER, + LDAP_BACK_CFG_NRETRIES, + LDAP_BACK_CFG_QUARANTINE, + LDAP_BACK_CFG_REBIND, + LDAP_BACK_CFG_TIMEOUT, + LDAP_BACK_CFG_VERSION, + LDAP_BACK_CFG_ST_REQUEST, + LDAP_BACK_CFG_T_F, + LDAP_BACK_CFG_TLS, + LDAP_BACK_CFG_LAST_BOTH +}; + +/* Target attrs */ +enum { + LDAP_BACK_CFG_URI = LDAP_BACK_CFG_LAST_BOTH, + LDAP_BACK_CFG_ACL_AUTHCDN, + LDAP_BACK_CFG_ACL_PASSWD, + LDAP_BACK_CFG_IDASSERT_AUTHZFROM, + LDAP_BACK_CFG_IDASSERT_BIND, + LDAP_BACK_CFG_REWRITE, + LDAP_BACK_CFG_SUFFIXM, + LDAP_BACK_CFG_MAP, + LDAP_BACK_CFG_SUBTREE_EX, + LDAP_BACK_CFG_SUBTREE_IN, + LDAP_BACK_CFG_PSEUDOROOTDN, + LDAP_BACK_CFG_PSEUDOROOTPW, + LDAP_BACK_CFG_KEEPALIVE, + LDAP_BACK_CFG_FILTER, + + LDAP_BACK_CFG_LAST +}; + +static ConfigTable metacfg[] = { + { "uri", "uri", 2, 0, 0, + ARG_MAGIC|LDAP_BACK_CFG_URI, + meta_back_cf_gen, "( OLcfgDbAt:0.14 " + "NAME 'olcDbURI' " + "DESC 'URI (list) for remote DSA' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "tls", "what", 2, 0, 0, + ARG_MAGIC|LDAP_BACK_CFG_TLS, + meta_back_cf_gen, "( OLcfgDbAt:3.1 " + "NAME 'olcDbStartTLS' " + "DESC 'StartTLS' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "acl-authcDN", "DN", 2, 2, 0, + ARG_DN|ARG_MAGIC|LDAP_BACK_CFG_ACL_AUTHCDN, + meta_back_cf_gen, "( OLcfgDbAt:3.2 " + "NAME 'olcDbACLAuthcDn' " + "DESC 'Remote ACL administrative identity' " + "OBSOLETE " + "SYNTAX OMsDN " + "SINGLE-VALUE )", + NULL, NULL }, + /* deprecated, will be removed; aliases "acl-authcDN" */ + { "binddn", "DN", 2, 2, 0, + ARG_DN|ARG_MAGIC|LDAP_BACK_CFG_ACL_AUTHCDN, + meta_back_cf_gen, NULL, NULL, NULL }, + { "acl-passwd", "cred", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_ACL_PASSWD, + meta_back_cf_gen, "( OLcfgDbAt:3.3 " + "NAME 'olcDbACLPasswd' " + "DESC 'Remote ACL administrative identity credentials' " + "OBSOLETE " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + /* deprecated, will be removed; aliases "acl-passwd" */ + { "bindpw", "cred", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_ACL_PASSWD, + meta_back_cf_gen, NULL, NULL, NULL }, + { "idassert-bind", "args", 2, 0, 0, + ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_BIND, + meta_back_cf_gen, "( OLcfgDbAt:3.7 " + "NAME 'olcDbIDAssertBind' " + "DESC 'Remote Identity Assertion administrative identity auth bind configuration' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "idassert-authzFrom", "authzRule", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_AUTHZFROM, + meta_back_cf_gen, "( OLcfgDbAt:3.9 " + "NAME 'olcDbIDAssertAuthzFrom' " + "DESC 'Remote Identity Assertion authz rules' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "X-ORDERED 'VALUES' )", + NULL, NULL }, + { "rebind-as-user", "true|FALSE", 1, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_REBIND, + meta_back_cf_gen, "( OLcfgDbAt:3.10 " + "NAME 'olcDbRebindAsUser' " + "DESC 'Rebind as user' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "chase-referrals", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_CHASE, + meta_back_cf_gen, "( OLcfgDbAt:3.11 " + "NAME 'olcDbChaseReferrals' " + "DESC 'Chase referrals' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "t-f-support", "true|FALSE|discover", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_T_F, + meta_back_cf_gen, "( OLcfgDbAt:3.12 " + "NAME 'olcDbTFSupport' " + "DESC 'Absolute filters support' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "timeout", "timeout(list)", 2, 0, 0, + ARG_MAGIC|LDAP_BACK_CFG_TIMEOUT, + meta_back_cf_gen, "( OLcfgDbAt:3.14 " + "NAME 'olcDbTimeout' " + "DESC 'Per-operation timeouts' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "idle-timeout", "timeout", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_IDLE_TIMEOUT, + meta_back_cf_gen, "( OLcfgDbAt:3.15 " + "NAME 'olcDbIdleTimeout' " + "DESC 'connection idle timeout' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "conn-ttl", "ttl", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_CONN_TTL, + meta_back_cf_gen, "( OLcfgDbAt:3.16 " + "NAME 'olcDbConnTtl' " + "DESC 'connection ttl' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "network-timeout", "timeout", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_NETWORK_TIMEOUT, + meta_back_cf_gen, "( OLcfgDbAt:3.17 " + "NAME 'olcDbNetworkTimeout' " + "DESC 'connection network timeout' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "protocol-version", "version", 2, 2, 0, + ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_VERSION, + meta_back_cf_gen, "( OLcfgDbAt:3.18 " + "NAME 'olcDbProtocolVersion' " + "DESC 'protocol version' " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", + NULL, NULL }, + { "single-conn", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_SINGLECONN, + meta_back_cf_gen, "( OLcfgDbAt:3.19 " + "NAME 'olcDbSingleConn' " + "DESC 'cache a single connection per identity' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "cancel", "ABANDON|ignore|exop", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_CANCEL, + meta_back_cf_gen, "( OLcfgDbAt:3.20 " + "NAME 'olcDbCancel' " + "DESC 'abandon/ignore/exop operations when appropriate' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "quarantine", "retrylist", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_QUARANTINE, + meta_back_cf_gen, "( OLcfgDbAt:3.21 " + "NAME 'olcDbQuarantine' " + "DESC 'Quarantine database if connection fails and retry according to rule' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "use-temporary-conn", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_USETEMP, + meta_back_cf_gen, "( OLcfgDbAt:3.22 " + "NAME 'olcDbUseTemporaryConn' " + "DESC 'Use temporary connections if the cached one is busy' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "conn-pool-max", "<n>", 2, 2, 0, + ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_CONNPOOLMAX, + meta_back_cf_gen, "( OLcfgDbAt:3.23 " + "NAME 'olcDbConnectionPoolMax' " + "DESC 'Max size of privileged connections pool' " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", + NULL, NULL }, +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + { "session-tracking-request", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_ST_REQUEST, + meta_back_cf_gen, "( OLcfgDbAt:3.24 " + "NAME 'olcDbSessionTrackingRequest' " + "DESC 'Add session tracking control to proxied requests' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + { "norefs", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_NOREFS, + meta_back_cf_gen, "( OLcfgDbAt:3.25 " + "NAME 'olcDbNoRefs' " + "DESC 'Do not return search reference responses' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "noundeffilter", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_NOUNDEFFILTER, + meta_back_cf_gen, "( OLcfgDbAt:3.26 " + "NAME 'olcDbNoUndefFilter' " + "DESC 'Do not propagate undefined search filters' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + + { "rewrite", "arglist", 2, 0, STRLENOF( "rewrite" ), + ARG_MAGIC|LDAP_BACK_CFG_REWRITE, + meta_back_cf_gen, "( OLcfgDbAt:3.101 " + "NAME 'olcDbRewrite' " + "DESC 'DN rewriting rules' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "X-ORDERED 'VALUES' )", + NULL, NULL }, + { "suffixmassage", "virtual> <real", 2, 3, 0, + ARG_MAGIC|LDAP_BACK_CFG_SUFFIXM, + meta_back_cf_gen, NULL, NULL, NULL }, + + { "map", "attribute|objectClass> [*|<local>] *|<remote", 3, 4, 0, + ARG_MAGIC|LDAP_BACK_CFG_MAP, + meta_back_cf_gen, "( OLcfgDbAt:3.102 " + "NAME 'olcDbMap' " + "DESC 'Map attribute and objectclass names' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "X-ORDERED 'VALUES' )", + NULL, NULL }, + + { "subtree-exclude", "pattern", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_SUBTREE_EX, + meta_back_cf_gen, "( OLcfgDbAt:3.103 " + "NAME 'olcDbSubtreeExclude' " + "DESC 'DN of subtree to exclude from target' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", + NULL, NULL }, + { "subtree-include", "pattern", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_SUBTREE_IN, + meta_back_cf_gen, "( OLcfgDbAt:3.104 " + "NAME 'olcDbSubtreeInclude' " + "DESC 'DN of subtree to include in target' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", + NULL, NULL }, + { "default-target", "[none|<target ID>]", 1, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_DEFAULT_T, + meta_back_cf_gen, "( OLcfgDbAt:3.105 " + "NAME 'olcDbDefaultTarget' " + "DESC 'Specify the default target' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "dncache-ttl", "ttl", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_DNCACHE_TTL, + meta_back_cf_gen, "( OLcfgDbAt:3.106 " + "NAME 'olcDbDnCacheTtl' " + "DESC 'dncache ttl' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "bind-timeout", "microseconds", 2, 2, 0, + ARG_MAGIC|ARG_ULONG|LDAP_BACK_CFG_BIND_TIMEOUT, + meta_back_cf_gen, "( OLcfgDbAt:3.107 " + "NAME 'olcDbBindTimeout' " + "DESC 'bind timeout' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "onerr", "CONTINUE|report|stop", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_ONERR, + meta_back_cf_gen, "( OLcfgDbAt:3.108 " + "NAME 'olcDbOnErr' " + "DESC 'error handling' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "pseudoroot-bind-defer", "TRUE|false", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER, + meta_back_cf_gen, "( OLcfgDbAt:3.109 " + "NAME 'olcDbPseudoRootBindDefer' " + "DESC 'error handling' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "root-bind-defer", "TRUE|false", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER, + meta_back_cf_gen, NULL, NULL, NULL }, + { "pseudorootdn", "dn", 2, 2, 0, + ARG_MAGIC|ARG_DN|LDAP_BACK_CFG_PSEUDOROOTDN, + meta_back_cf_gen, NULL, NULL, NULL }, + { "pseudorootpw", "password", 2, 2, 0, + ARG_MAGIC|ARG_STRING|LDAP_BACK_CFG_PSEUDOROOTDN, + meta_back_cf_gen, NULL, NULL, NULL }, + { "nretries", "NEVER|forever|<number>", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_NRETRIES, + meta_back_cf_gen, "( OLcfgDbAt:3.110 " + "NAME 'olcDbNretries' " + "DESC 'retry handling' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "client-pr", "accept-unsolicited|disable|<size>", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_CLIENT_PR, + meta_back_cf_gen, "( OLcfgDbAt:3.111 " + "NAME 'olcDbClientPr' " + "DESC 'PagedResults handling' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + + { "", "", 0, 0, 0, ARG_IGNORED, + NULL, "( OLcfgDbAt:3.100 NAME 'olcMetaSub' " + "DESC 'Placeholder to name a Target entry' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE X-ORDERED 'SIBLINGS' )", NULL, NULL }, + + { "keepalive", "keepalive", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_KEEPALIVE, + meta_back_cf_gen, "( OLcfgDbAt:3.29 " + "NAME 'olcDbKeepalive' " + "DESC 'TCP keepalive' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + + { "filter", "pattern", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_FILTER, + meta_back_cf_gen, "( OLcfgDbAt:3.112 " + "NAME 'olcDbFilter' " + "DESC 'Filter regex pattern to include in target' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString )", + NULL, NULL }, + + { NULL, NULL, 0, 0, 0, ARG_IGNORED, + NULL, NULL, NULL, NULL } +}; + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING +#define ST_ATTR "$ olcDbSessionTrackingRequest " +#else +#define ST_ATTR "" +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + +#define COMMON_ATTRS \ + "$ olcDbBindTimeout " \ + "$ olcDbCancel " \ + "$ olcDbChaseReferrals " \ + "$ olcDbClientPr " \ + "$ olcDbDefaultTarget " \ + "$ olcDbNetworkTimeout " \ + "$ olcDbNoRefs " \ + "$ olcDbNoUndefFilter " \ + "$ olcDbNretries " \ + "$ olcDbProtocolVersion " \ + "$ olcDbQuarantine " \ + "$ olcDbRebindAsUser " \ + ST_ATTR \ + "$ olcDbStartTLS " \ + "$ olcDbTFSupport " + +static ConfigOCs metaocs[] = { + { "( OLcfgDbOc:3.2 " + "NAME 'olcMetaConfig' " + "DESC 'Meta backend configuration' " + "SUP olcDatabaseConfig " + "MAY ( olcDbConnTtl " + "$ olcDbDnCacheTtl " + "$ olcDbIdleTimeout " + "$ olcDbOnErr " + "$ olcDbPseudoRootBindDefer " + "$ olcDbSingleConn " + "$ olcDbUseTemporaryConn " + "$ olcDbConnectionPoolMax " + + /* defaults, may be overridden per-target */ + COMMON_ATTRS + ") )", + Cft_Database, metacfg, NULL, meta_cfadd }, + { "( OLcfgDbOc:3.3 " + "NAME 'olcMetaTargetConfig' " + "DESC 'Meta target configuration' " + "SUP olcConfig STRUCTURAL " + "MUST ( olcMetaSub $ olcDbURI ) " + "MAY ( olcDbACLAuthcDn " + "$ olcDbACLPasswd " + "$ olcDbIDAssertAuthzFrom " + "$ olcDbIDAssertBind " + "$ olcDbMap " + "$ olcDbRewrite " + "$ olcDbSubtreeExclude " + "$ olcDbSubtreeInclude " + "$ olcDbTimeout " + "$ olcDbKeepalive " + "$ olcDbFilter " + + /* defaults may be inherited */ + COMMON_ATTRS + ") )", + Cft_Misc, metacfg, meta_ldadd }, + { NULL, 0, NULL } +}; + +static int +meta_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *c ) +{ + if ( p->ce_type != Cft_Database || !p->ce_be || + p->ce_be->be_cf_ocs != metaocs ) + return LDAP_CONSTRAINT_VIOLATION; + + c->be = p->ce_be; + return LDAP_SUCCESS; +} + +static int +meta_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *c ) +{ + metainfo_t *mi = ( metainfo_t * )c->be->be_private; + struct berval bv; + int i; + + bv.bv_val = c->cr_msg; + for ( i=0; i<mi->mi_ntargets; i++ ) { + bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), + "olcMetaSub=" SLAP_X_ORDERED_FMT "uri", i ); + c->ca_private = mi->mi_targets[i]; + c->valx = i; + config_build_entry( op, rs, p->e_private, c, + &bv, &metaocs[1], NULL ); + } + + return LDAP_SUCCESS; +} + +static int +meta_rwi_init( struct rewrite_info **rwm_rw ) +{ + char *rargv[ 3 ]; + + *rwm_rw = rewrite_info_init( REWRITE_MODE_USE_DEFAULT ); + if ( *rwm_rw == NULL ) { + return -1; + } + /* + * the filter rewrite as a string must be disabled + * by default; it can be re-enabled by adding rules; + * this creates an empty rewriteContext + */ + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "searchFilter"; + rargv[ 2 ] = NULL; + rewrite_parse( *rwm_rw, "<suffix massage>", 1, 2, rargv ); + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "default"; + rargv[ 2 ] = NULL; + rewrite_parse( *rwm_rw, "<suffix massage>", 1, 2, rargv ); + + return 0; +} + +static int +meta_back_new_target( + metatarget_t **mtp ) +{ + metatarget_t *mt; + + *mtp = NULL; + + mt = ch_calloc( sizeof( metatarget_t ), 1 ); + + if ( meta_rwi_init( &mt->mt_rwmap.rwm_rw )) { + ch_free( mt ); + return -1; + } + + ldap_pvt_thread_mutex_init( &mt->mt_uri_mutex ); + + mt->mt_idassert_mode = LDAP_BACK_IDASSERT_LEGACY; + mt->mt_idassert_authmethod = LDAP_AUTH_NONE; + mt->mt_idassert_tls = SB_TLS_DEFAULT; + + /* by default, use proxyAuthz control on each operation */ + mt->mt_idassert_flags = LDAP_BACK_AUTH_PRESCRIPTIVE; + + *mtp = mt; + + return 0; +} + +/* Validation for suffixmassage_config */ +static int +meta_suffixm_config( + ConfigArgs *c, + int argc, + char **argv, + metatarget_t *mt +) +{ + BackendDB *tmp_bd; + struct berval dn, nvnc, pvnc, nrnc, prnc; + int j, rc; + + /* + * syntax: + * + * suffixmassage <suffix> <massaged suffix> + * + * the <suffix> field must be defined as a valid suffix + * (or suffixAlias?) for the current database; + * the <massaged suffix> shouldn't have already been + * defined as a valid suffix or suffixAlias for the + * current server + */ + + ber_str2bv( argv[ 1 ], 0, 0, &dn ); + if ( dnPrettyNormal( NULL, &dn, &pvnc, &nvnc, NULL ) != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "suffix \"%s\" is invalid", + argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + for ( j = 0; !BER_BVISNULL( &c->be->be_nsuffix[ j ] ); j++ ) { + if ( dnIsSuffix( &nvnc, &c->be->be_nsuffix[ 0 ] ) ) { + break; + } + } + + if ( BER_BVISNULL( &c->be->be_nsuffix[ j ] ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "suffix \"%s\" must be within the database naming context", + argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + free( pvnc.bv_val ); + free( nvnc.bv_val ); + return 1; + } + + ber_str2bv( argv[ 2 ], 0, 0, &dn ); + if ( dnPrettyNormal( NULL, &dn, &prnc, &nrnc, NULL ) != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "massaged suffix \"%s\" is invalid", + argv[2] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + free( pvnc.bv_val ); + free( nvnc.bv_val ); + return 1; + } + + tmp_bd = select_backend( &nrnc, 0 ); + if ( tmp_bd != NULL && tmp_bd->be_private == c->be->be_private ) { + Debug( LDAP_DEBUG_ANY, + "%s: warning: <massaged suffix> \"%s\" resolves to this database, in " + "\"suffixMassage <suffix> <massaged suffix>\"\n", + c->log, prnc.bv_val, 0 ); + } + + /* + * The suffix massaging is emulated by means of the + * rewrite capabilities + */ + rc = suffix_massage_config( mt->mt_rwmap.rwm_rw, + &pvnc, &nvnc, &prnc, &nrnc ); + + free( pvnc.bv_val ); + free( nvnc.bv_val ); + free( prnc.bv_val ); + free( nrnc.bv_val ); + + return rc; +} + +static int +slap_bv_x_ordered_unparse( BerVarray in, BerVarray *out ) +{ + int i; + BerVarray bva = NULL; + char ibuf[32], *ptr; + struct berval idx; + + assert( in != NULL ); + + for ( i = 0; !BER_BVISNULL( &in[i] ); i++ ) + /* count'em */ ; + + if ( i == 0 ) { + return 1; + } + + idx.bv_val = ibuf; + + bva = ch_malloc( ( i + 1 ) * sizeof(struct berval) ); + BER_BVZERO( &bva[ 0 ] ); + + for ( i = 0; !BER_BVISNULL( &in[i] ); i++ ) { + idx.bv_len = snprintf( idx.bv_val, sizeof( ibuf ), SLAP_X_ORDERED_FMT, i ); + if ( idx.bv_len >= sizeof( ibuf ) ) { + ber_bvarray_free( bva ); + return 1; + } + + bva[i].bv_len = idx.bv_len + in[i].bv_len; + bva[i].bv_val = ch_malloc( bva[i].bv_len + 1 ); + ptr = lutil_strcopy( bva[i].bv_val, ibuf ); + ptr = lutil_strcopy( ptr, in[i].bv_val ); + *ptr = '\0'; + BER_BVZERO( &bva[ i + 1 ] ); + } + + *out = bva; + return 0; +} + +int +meta_subtree_free( metasubtree_t *ms ) +{ + switch ( ms->ms_type ) { + case META_ST_SUBTREE: + case META_ST_SUBORDINATE: + ber_memfree( ms->ms_dn.bv_val ); + break; + + case META_ST_REGEX: + regfree( &ms->ms_regex ); + ber_memfree( ms->ms_regex_pattern.bv_val ); + break; + + default: + return -1; + } + + ch_free( ms ); + return 0; +} + +int +meta_subtree_destroy( metasubtree_t *ms ) +{ + if ( ms->ms_next ) { + meta_subtree_destroy( ms->ms_next ); + } + + return meta_subtree_free( ms ); +} + +static void +meta_filter_free( metafilter_t *mf ) +{ + regfree( &mf->mf_regex ); + ber_memfree( mf->mf_regex_pattern.bv_val ); + ch_free( mf ); +} + +void +meta_filter_destroy( metafilter_t *mf ) +{ + if ( mf->mf_next ) + meta_filter_destroy( mf->mf_next ); + meta_filter_free( mf ); +} + +static struct berval st_styles[] = { + BER_BVC("subtree"), + BER_BVC("children"), + BER_BVC("regex") +}; + +static int +meta_subtree_unparse( + ConfigArgs *c, + metatarget_t *mt ) +{ + metasubtree_t *ms; + struct berval bv, *style; + + if ( !mt->mt_subtree ) + return 1; + + /* can only be one of exclude or include */ + if (( c->type == LDAP_BACK_CFG_SUBTREE_EX ) ^ mt->mt_subtree_exclude ) + return 1; + + bv.bv_val = c->cr_msg; + for ( ms=mt->mt_subtree; ms; ms=ms->ms_next ) { + if (ms->ms_type == META_ST_SUBTREE) + style = &st_styles[0]; + else if ( ms->ms_type == META_ST_SUBORDINATE ) + style = &st_styles[1]; + else if ( ms->ms_type == META_ST_REGEX ) + style = &st_styles[2]; + else { + assert(0); + continue; + } + bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), + "dn.%s:%s", style->bv_val, ms->ms_dn.bv_val ); + value_add_one( &c->rvalue_vals, &bv ); + } + return 0; +} + +static int +meta_subtree_config( + metatarget_t *mt, + ConfigArgs *c ) +{ + meta_st_t type = META_ST_SUBTREE; + char *pattern; + struct berval ndn = BER_BVNULL; + metasubtree_t *ms = NULL; + + if ( c->type == LDAP_BACK_CFG_SUBTREE_EX ) { + if ( mt->mt_subtree && !mt->mt_subtree_exclude ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "\"subtree-exclude\" incompatible with previous \"subtree-include\" directives" ); + return 1; + } + + mt->mt_subtree_exclude = 1; + + } else { + if ( mt->mt_subtree && mt->mt_subtree_exclude ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "\"subtree-include\" incompatible with previous \"subtree-exclude\" directives" ); + return 1; + } + } + + pattern = c->argv[1]; + if ( strncasecmp( pattern, "dn", STRLENOF( "dn" ) ) == 0 ) { + char *style; + + pattern = &pattern[STRLENOF( "dn")]; + + if ( pattern[0] == '.' ) { + style = &pattern[1]; + + if ( strncasecmp( style, "subtree", STRLENOF( "subtree" ) ) == 0 ) { + type = META_ST_SUBTREE; + pattern = &style[STRLENOF( "subtree" )]; + + } else if ( strncasecmp( style, "children", STRLENOF( "children" ) ) == 0 ) { + type = META_ST_SUBORDINATE; + pattern = &style[STRLENOF( "children" )]; + + } else if ( strncasecmp( style, "sub", STRLENOF( "sub" ) ) == 0 ) { + type = META_ST_SUBTREE; + pattern = &style[STRLENOF( "sub" )]; + + } else if ( strncasecmp( style, "regex", STRLENOF( "regex" ) ) == 0 ) { + type = META_ST_REGEX; + pattern = &style[STRLENOF( "regex" )]; + + } else { + snprintf( c->cr_msg, sizeof(c->cr_msg), "unknown style in \"dn.<style>\"" ); + return 1; + } + } + + if ( pattern[0] != ':' ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), "missing colon after \"dn.<style>\"" ); + return 1; + } + pattern++; + } + + switch ( type ) { + case META_ST_SUBTREE: + case META_ST_SUBORDINATE: { + struct berval dn; + + ber_str2bv( pattern, 0, 0, &dn ); + if ( dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL ) + != LDAP_SUCCESS ) + { + snprintf( c->cr_msg, sizeof(c->cr_msg), "DN=\"%s\" is invalid", pattern ); + return 1; + } + + if ( !dnIsSuffix( &ndn, &mt->mt_nsuffix ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "DN=\"%s\" is not a subtree of target \"%s\"", + pattern, mt->mt_nsuffix.bv_val ); + ber_memfree( ndn.bv_val ); + return( 1 ); + } + } break; + + default: + /* silence warnings */ + break; + } + + ms = ch_calloc( sizeof( metasubtree_t ), 1 ); + ms->ms_type = type; + + switch ( ms->ms_type ) { + case META_ST_SUBTREE: + case META_ST_SUBORDINATE: + ms->ms_dn = ndn; + break; + + case META_ST_REGEX: { + int rc; + + rc = regcomp( &ms->ms_regex, pattern, REG_EXTENDED|REG_ICASE ); + if ( rc != 0 ) { + char regerr[ SLAP_TEXT_BUFLEN ]; + + regerror( rc, &ms->ms_regex, regerr, sizeof(regerr) ); + + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "regular expression \"%s\" bad because of %s", + pattern, regerr ); + ch_free( ms ); + return 1; + } + ber_str2bv( pattern, 0, 1, &ms->ms_regex_pattern ); + } break; + } + + if ( mt->mt_subtree == NULL ) { + mt->mt_subtree = ms; + + } else { + metasubtree_t **msp; + + for ( msp = &mt->mt_subtree; *msp; ) { + switch ( ms->ms_type ) { + case META_ST_SUBTREE: + switch ( (*msp)->ms_type ) { + case META_ST_SUBTREE: + if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) { + metasubtree_t *tmp = *msp; + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.subtree:%s\" is contained in rule \"dn.subtree:%s\" (replaced)\n", + c->log, pattern, (*msp)->ms_dn.bv_val ); + *msp = (*msp)->ms_next; + tmp->ms_next = NULL; + meta_subtree_destroy( tmp ); + continue; + + } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) ) { + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.subtree:%s\" contains rule \"dn.subtree:%s\" (ignored)\n", + c->log, (*msp)->ms_dn.bv_val, pattern ); + meta_subtree_destroy( ms ); + ms = NULL; + return( 0 ); + } + break; + + case META_ST_SUBORDINATE: + if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) { + metasubtree_t *tmp = *msp; + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.children:%s\" is contained in rule \"dn.subtree:%s\" (replaced)\n", + c->log, pattern, (*msp)->ms_dn.bv_val ); + *msp = (*msp)->ms_next; + tmp->ms_next = NULL; + meta_subtree_destroy( tmp ); + continue; + + } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) && ms->ms_dn.bv_len > (*msp)->ms_dn.bv_len ) { + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.children:%s\" contains rule \"dn.subtree:%s\" (ignored)\n", + c->log, (*msp)->ms_dn.bv_val, pattern ); + meta_subtree_destroy( ms ); + ms = NULL; + return( 0 ); + } + break; + + case META_ST_REGEX: + if ( regexec( &(*msp)->ms_regex, ms->ms_dn.bv_val, 0, NULL, 0 ) == 0 ) { + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.regex:%s\" may contain rule \"dn.subtree:%s\"\n", + c->log, (*msp)->ms_regex_pattern.bv_val, ms->ms_dn.bv_val ); + } + break; + } + break; + + case META_ST_SUBORDINATE: + switch ( (*msp)->ms_type ) { + case META_ST_SUBTREE: + if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) { + metasubtree_t *tmp = *msp; + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.children:%s\" is contained in rule \"dn.subtree:%s\" (replaced)\n", + c->log, pattern, (*msp)->ms_dn.bv_val ); + *msp = (*msp)->ms_next; + tmp->ms_next = NULL; + meta_subtree_destroy( tmp ); + continue; + + } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) && ms->ms_dn.bv_len > (*msp)->ms_dn.bv_len ) { + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.children:%s\" contains rule \"dn.subtree:%s\" (ignored)\n", + c->log, (*msp)->ms_dn.bv_val, pattern ); + meta_subtree_destroy( ms ); + ms = NULL; + return( 0 ); + } + break; + + case META_ST_SUBORDINATE: + if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) { + metasubtree_t *tmp = *msp; + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.children:%s\" is contained in rule \"dn.children:%s\" (replaced)\n", + c->log, pattern, (*msp)->ms_dn.bv_val ); + *msp = (*msp)->ms_next; + tmp->ms_next = NULL; + meta_subtree_destroy( tmp ); + continue; + + } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) ) { + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.children:%s\" contains rule \"dn.children:%s\" (ignored)\n", + c->log, (*msp)->ms_dn.bv_val, pattern ); + meta_subtree_destroy( ms ); + ms = NULL; + return( 0 ); + } + break; + + case META_ST_REGEX: + if ( regexec( &(*msp)->ms_regex, ms->ms_dn.bv_val, 0, NULL, 0 ) == 0 ) { + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.regex:%s\" may contain rule \"dn.subtree:%s\"\n", + c->log, (*msp)->ms_regex_pattern.bv_val, ms->ms_dn.bv_val ); + } + break; + } + break; + + case META_ST_REGEX: + switch ( (*msp)->ms_type ) { + case META_ST_SUBTREE: + case META_ST_SUBORDINATE: + if ( regexec( &ms->ms_regex, (*msp)->ms_dn.bv_val, 0, NULL, 0 ) == 0 ) { + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.subtree:%s\" may be contained in rule \"dn.regex:%s\"\n", + c->log, (*msp)->ms_dn.bv_val, ms->ms_regex_pattern.bv_val ); + } + break; + + case META_ST_REGEX: + /* no check possible */ + break; + } + break; + } + + msp = &(*msp)->ms_next; + } + + *msp = ms; + } + + return 0; +} + +static slap_verbmasks idassert_mode[] = { + { BER_BVC("self"), LDAP_BACK_IDASSERT_SELF }, + { BER_BVC("anonymous"), LDAP_BACK_IDASSERT_ANONYMOUS }, + { BER_BVC("none"), LDAP_BACK_IDASSERT_NOASSERT }, + { BER_BVC("legacy"), LDAP_BACK_IDASSERT_LEGACY }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks tls_mode[] = { + { BER_BVC( "propagate" ), LDAP_BACK_F_TLS_PROPAGATE_MASK }, + { BER_BVC( "try-propagate" ), LDAP_BACK_F_PROPAGATE_TLS }, + { BER_BVC( "start" ), LDAP_BACK_F_TLS_USE_MASK }, + { BER_BVC( "try-start" ), LDAP_BACK_F_USE_TLS }, + { BER_BVC( "ldaps" ), LDAP_BACK_F_TLS_LDAPS }, + { BER_BVC( "none" ), LDAP_BACK_F_NONE }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks t_f_mode[] = { + { BER_BVC( "yes" ), LDAP_BACK_F_T_F }, + { BER_BVC( "discover" ), LDAP_BACK_F_T_F_DISCOVER }, + { BER_BVC( "no" ), LDAP_BACK_F_NONE }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks cancel_mode[] = { + { BER_BVC( "ignore" ), LDAP_BACK_F_CANCEL_IGNORE }, + { BER_BVC( "exop" ), LDAP_BACK_F_CANCEL_EXOP }, + { BER_BVC( "exop-discover" ), LDAP_BACK_F_CANCEL_EXOP_DISCOVER }, + { BER_BVC( "abandon" ), LDAP_BACK_F_CANCEL_ABANDON }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks onerr_mode[] = { + { BER_BVC( "stop" ), META_BACK_F_ONERR_STOP }, + { BER_BVC( "report" ), META_BACK_F_ONERR_REPORT }, + { BER_BVC( "continue" ), LDAP_BACK_F_NONE }, + { BER_BVNULL, 0 } +}; + +/* see enum in slap.h */ +static slap_cf_aux_table timeout_table[] = { + { BER_BVC("bind="), SLAP_OP_BIND * sizeof( time_t ), 'u', 0, NULL }, + /* unbind makes no sense */ + { BER_BVC("add="), SLAP_OP_ADD * sizeof( time_t ), 'u', 0, NULL }, + { BER_BVC("delete="), SLAP_OP_DELETE * sizeof( time_t ), 'u', 0, NULL }, + { BER_BVC("modrdn="), SLAP_OP_MODRDN * sizeof( time_t ), 'u', 0, NULL }, + { BER_BVC("modify="), SLAP_OP_MODIFY * sizeof( time_t ), 'u', 0, NULL }, + { BER_BVC("compare="), SLAP_OP_COMPARE * sizeof( time_t ), 'u', 0, NULL }, + { BER_BVC("search="), SLAP_OP_SEARCH * sizeof( time_t ), 'u', 0, NULL }, + /* abandon makes little sense */ +#if 0 /* not implemented yet */ + { BER_BVC("extended="), SLAP_OP_EXTENDED * sizeof( time_t ), 'u', 0, NULL }, +#endif + { BER_BVNULL, 0, 0, 0, NULL } +}; + +static int +meta_cf_cleanup( ConfigArgs *c ) +{ + metainfo_t *mi = ( metainfo_t * )c->be->be_private; + metatarget_t *mt = c->ca_private; + + return meta_target_finish( mi, mt, c->log, c->cr_msg, sizeof( c->cr_msg )); +} + +static int +meta_back_cf_gen( ConfigArgs *c ) +{ + metainfo_t *mi = ( metainfo_t * )c->be->be_private; + metatarget_t *mt; + metacommon_t *mc; + + int i, rc = 0; + + assert( mi != NULL ); + + if ( c->op == SLAP_CONFIG_EMIT || c->op == LDAP_MOD_DELETE ) { + if ( !mi ) + return 1; + + if ( c->table == Cft_Database ) { + mt = NULL; + mc = &mi->mi_mc; + } else { + mt = c->ca_private; + mc = &mt->mt_mc; + } + } + + if ( c->op == SLAP_CONFIG_EMIT ) { + struct berval bv = BER_BVNULL; + + switch( c->type ) { + /* Base attrs */ + case LDAP_BACK_CFG_CONN_TTL: + if ( mi->mi_conn_ttl == 0 ) { + return 1; + } else { + char buf[ SLAP_TEXT_BUFLEN ]; + + lutil_unparse_time( buf, sizeof( buf ), mi->mi_conn_ttl ); + ber_str2bv( buf, 0, 0, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + } + break; + + case LDAP_BACK_CFG_DNCACHE_TTL: + if ( mi->mi_cache.ttl == META_DNCACHE_DISABLED ) { + return 1; + } else if ( mi->mi_cache.ttl == META_DNCACHE_FOREVER ) { + BER_BVSTR( &bv, "forever" ); + } else { + char buf[ SLAP_TEXT_BUFLEN ]; + + lutil_unparse_time( buf, sizeof( buf ), mi->mi_cache.ttl ); + ber_str2bv( buf, 0, 0, &bv ); + } + value_add_one( &c->rvalue_vals, &bv ); + break; + + case LDAP_BACK_CFG_IDLE_TIMEOUT: + if ( mi->mi_idle_timeout == 0 ) { + return 1; + } else { + char buf[ SLAP_TEXT_BUFLEN ]; + + lutil_unparse_time( buf, sizeof( buf ), mi->mi_idle_timeout ); + ber_str2bv( buf, 0, 0, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + } + break; + + case LDAP_BACK_CFG_ONERR: + enum_to_verb( onerr_mode, mi->mi_flags & META_BACK_F_ONERR_MASK, &bv ); + if ( BER_BVISNULL( &bv )) { + rc = 1; + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + break; + + case LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER: + c->value_int = META_BACK_DEFER_ROOTDN_BIND( mi ); + break; + + case LDAP_BACK_CFG_SINGLECONN: + c->value_int = LDAP_BACK_SINGLECONN( mi ); + break; + + case LDAP_BACK_CFG_USETEMP: + c->value_int = LDAP_BACK_USE_TEMPORARIES( mi ); + break; + + case LDAP_BACK_CFG_CONNPOOLMAX: + c->value_int = mi->mi_conn_priv_max; + break; + + /* common attrs */ + case LDAP_BACK_CFG_BIND_TIMEOUT: + if ( mc->mc_bind_timeout.tv_sec == 0 && + mc->mc_bind_timeout.tv_usec == 0 ) { + return 1; + } else { + c->value_ulong = mc->mc_bind_timeout.tv_sec * 1000000UL + + mc->mc_bind_timeout.tv_usec; + } + break; + + case LDAP_BACK_CFG_CANCEL: { + slap_mask_t mask = LDAP_BACK_F_CANCEL_MASK2; + + if ( mt && META_BACK_TGT_CANCEL_DISCOVER( mt ) ) { + mask &= ~LDAP_BACK_F_CANCEL_EXOP; + } + enum_to_verb( cancel_mode, (mc->mc_flags & mask), &bv ); + if ( BER_BVISNULL( &bv ) ) { + /* there's something wrong... */ + assert( 0 ); + rc = 1; + + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + } break; + + case LDAP_BACK_CFG_CHASE: + c->value_int = META_BACK_CMN_CHASE_REFERRALS(mc); + break; + +#ifdef SLAPD_META_CLIENT_PR + case LDAP_BACK_CFG_CLIENT_PR: + if ( mc->mc_ps == META_CLIENT_PR_DISABLE ) { + return 1; + } else if ( mc->mc_ps == META_CLIENT_PR_ACCEPT_UNSOLICITED ) { + BER_BVSTR( &bv, "accept-unsolicited" ); + } else { + bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%d", mc->mc_ps ); + bv.bv_val = c->cr_msg; + } + value_add_one( &c->rvalue_vals, &bv ); + break; +#endif /* SLAPD_META_CLIENT_PR */ + + case LDAP_BACK_CFG_DEFAULT_T: + if ( mt || mi->mi_defaulttarget == META_DEFAULT_TARGET_NONE ) + return 1; + bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%d", mi->mi_defaulttarget ); + bv.bv_val = c->cr_msg; + value_add_one( &c->rvalue_vals, &bv ); + break; + + case LDAP_BACK_CFG_NETWORK_TIMEOUT: + if ( mc->mc_network_timeout == 0 ) { + return 1; + } + bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%ld", + mc->mc_network_timeout ); + bv.bv_val = c->cr_msg; + value_add_one( &c->rvalue_vals, &bv ); + break; + + case LDAP_BACK_CFG_NOREFS: + c->value_int = META_BACK_CMN_NOREFS(mc); + break; + + case LDAP_BACK_CFG_NOUNDEFFILTER: + c->value_int = META_BACK_CMN_NOUNDEFFILTER(mc); + break; + + case LDAP_BACK_CFG_NRETRIES: + if ( mc->mc_nretries == META_RETRY_FOREVER ) { + BER_BVSTR( &bv, "forever" ); + } else if ( mc->mc_nretries == META_RETRY_NEVER ) { + BER_BVSTR( &bv, "never" ); + } else { + bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%d", + mc->mc_nretries ); + bv.bv_val = c->cr_msg; + } + value_add_one( &c->rvalue_vals, &bv ); + break; + + case LDAP_BACK_CFG_QUARANTINE: + if ( !META_BACK_CMN_QUARANTINE( mc )) { + rc = 1; + break; + } + rc = mi->mi_ldap_extra->retry_info_unparse( &mc->mc_quarantine, &bv ); + if ( rc == 0 ) { + ber_bvarray_add( &c->rvalue_vals, &bv ); + } + break; + + case LDAP_BACK_CFG_REBIND: + c->value_int = META_BACK_CMN_SAVECRED(mc); + break; + + case LDAP_BACK_CFG_TIMEOUT: + for ( i = 0; i < SLAP_OP_LAST; i++ ) { + if ( mc->mc_timeout[ i ] != 0 ) { + break; + } + } + + if ( i == SLAP_OP_LAST ) { + return 1; + } + + BER_BVZERO( &bv ); + slap_cf_aux_table_unparse( mc->mc_timeout, &bv, timeout_table ); + + if ( BER_BVISNULL( &bv ) ) { + return 1; + } + + for ( i = 0; isspace( (unsigned char) bv.bv_val[ i ] ); i++ ) + /* count spaces */ ; + + if ( i ) { + bv.bv_len -= i; + AC_MEMCPY( bv.bv_val, &bv.bv_val[ i ], + bv.bv_len + 1 ); + } + + ber_bvarray_add( &c->rvalue_vals, &bv ); + break; + + case LDAP_BACK_CFG_VERSION: + if ( mc->mc_version == 0 ) + return 1; + c->value_int = mc->mc_version; + break; + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + case LDAP_BACK_CFG_ST_REQUEST: + c->value_int = META_BACK_CMN_ST_REQUEST( mc ); + break; +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + + case LDAP_BACK_CFG_T_F: + enum_to_verb( t_f_mode, (mc->mc_flags & LDAP_BACK_F_T_F_MASK2), &bv ); + if ( BER_BVISNULL( &bv ) ) { + /* there's something wrong... */ + assert( 0 ); + rc = 1; + + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + break; + + case LDAP_BACK_CFG_TLS: { + struct berval bc = BER_BVNULL, bv2; + + if (( mc->mc_flags & LDAP_BACK_F_TLS_MASK ) == LDAP_BACK_F_NONE ) { + rc = 1; + break; + } + enum_to_verb( tls_mode, ( mc->mc_flags & LDAP_BACK_F_TLS_MASK ), &bv ); + assert( !BER_BVISNULL( &bv ) ); + + if ( mt ) { + bindconf_tls_unparse( &mt->mt_tls, &bc ); + } + + if ( !BER_BVISEMPTY( &bc )) { + bv2.bv_len = bv.bv_len + bc.bv_len + 1; + bv2.bv_val = ch_malloc( bv2.bv_len + 1 ); + strcpy( bv2.bv_val, bv.bv_val ); + bv2.bv_val[bv.bv_len] = ' '; + strcpy( &bv2.bv_val[bv.bv_len + 1], bc.bv_val ); + ber_memfree( bc.bv_val ); + ber_bvarray_add( &c->rvalue_vals, &bv2 ); + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + } break; + + /* target attrs */ + case LDAP_BACK_CFG_URI: { + char *p2, *p1 = strchr( mt->mt_uri, ' ' ); + bv.bv_len = strlen( mt->mt_uri ) + 3 + mt->mt_psuffix.bv_len; + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + p2 = bv.bv_val; + *p2++ = '"'; + if ( p1 ) { + p2 = lutil_strncopy( p2, mt->mt_uri, p1 - mt->mt_uri ); + } else { + p2 = lutil_strcopy( p2, mt->mt_uri ); + } + *p2++ = '/'; + p2 = lutil_strcopy( p2, mt->mt_psuffix.bv_val ); + *p2++ = '"'; + if ( p1 ) { + strcpy( p2, p1 ); + } + ber_bvarray_add( &c->rvalue_vals, &bv ); + } break; + + case LDAP_BACK_CFG_ACL_AUTHCDN: + case LDAP_BACK_CFG_ACL_PASSWD: + /* FIXME no point here, there is no code implementing + * their features. Was this supposed to implement + * acl-bind like back-ldap? + */ + rc = 1; + break; + + case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: { + BerVarray *bvp; + int i; + struct berval bv = BER_BVNULL; + char buf[SLAP_TEXT_BUFLEN]; + + bvp = &mt->mt_idassert_authz; + if ( *bvp == NULL ) { + if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) + { + BER_BVSTR( &bv, "*" ); + value_add_one( &c->rvalue_vals, &bv ); + + } else { + rc = 1; + } + break; + } + + for ( i = 0; !BER_BVISNULL( &((*bvp)[ i ]) ); i++ ) { + char *ptr; + int len = snprintf( buf, sizeof( buf ), SLAP_X_ORDERED_FMT, i ); + bv.bv_len = ((*bvp)[ i ]).bv_len + len; + bv.bv_val = ber_memrealloc( bv.bv_val, bv.bv_len + 1 ); + ptr = bv.bv_val; + ptr = lutil_strcopy( ptr, buf ); + ptr = lutil_strncopy( ptr, ((*bvp)[ i ]).bv_val, ((*bvp)[ i ]).bv_len ); + value_add_one( &c->rvalue_vals, &bv ); + } + if ( bv.bv_val ) { + ber_memfree( bv.bv_val ); + } + break; + } + + case LDAP_BACK_CFG_IDASSERT_BIND: { + int i; + struct berval bc = BER_BVNULL; + char *ptr; + + if ( mt->mt_idassert_authmethod == LDAP_AUTH_NONE ) { + return 1; + } else { + ber_len_t len; + + switch ( mt->mt_idassert_mode ) { + case LDAP_BACK_IDASSERT_OTHERID: + case LDAP_BACK_IDASSERT_OTHERDN: + break; + + default: { + struct berval mode = BER_BVNULL; + + enum_to_verb( idassert_mode, mt->mt_idassert_mode, &mode ); + if ( BER_BVISNULL( &mode ) ) { + /* there's something wrong... */ + assert( 0 ); + rc = 1; + + } else { + bv.bv_len = STRLENOF( "mode=" ) + mode.bv_len; + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + + ptr = lutil_strcopy( bv.bv_val, "mode=" ); + ptr = lutil_strcopy( ptr, mode.bv_val ); + } + break; + } + } + + if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) { + len = bv.bv_len + STRLENOF( "authz=native" ); + + if ( !BER_BVISEMPTY( &bv ) ) { + len += STRLENOF( " " ); + } + + bv.bv_val = ch_realloc( bv.bv_val, len + 1 ); + + ptr = &bv.bv_val[ bv.bv_len ]; + + if ( !BER_BVISEMPTY( &bv ) ) { + ptr = lutil_strcopy( ptr, " " ); + } + + (void)lutil_strcopy( ptr, "authz=native" ); + } + + len = bv.bv_len + STRLENOF( "flags=non-prescriptive,override,obsolete-encoding-workaround,proxy-authz-non-critical,dn-authzid" ); + /* flags */ + if ( !BER_BVISEMPTY( &bv ) ) { + len += STRLENOF( " " ); + } + + bv.bv_val = ch_realloc( bv.bv_val, len + 1 ); + + ptr = &bv.bv_val[ bv.bv_len ]; + + if ( !BER_BVISEMPTY( &bv ) ) { + ptr = lutil_strcopy( ptr, " " ); + } + + ptr = lutil_strcopy( ptr, "flags=" ); + + if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) { + ptr = lutil_strcopy( ptr, "prescriptive" ); + } else { + ptr = lutil_strcopy( ptr, "non-prescriptive" ); + } + + if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) { + ptr = lutil_strcopy( ptr, ",override" ); + } + + if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ ) { + ptr = lutil_strcopy( ptr, ",obsolete-proxy-authz" ); + + } else if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND ) { + ptr = lutil_strcopy( ptr, ",obsolete-encoding-workaround" ); + } + + if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL ) { + ptr = lutil_strcopy( ptr, ",proxy-authz-critical" ); + + } else { + ptr = lutil_strcopy( ptr, ",proxy-authz-non-critical" ); + } + +#ifdef SLAP_AUTH_DN + switch ( mt->mt_idassert_flags & LDAP_BACK_AUTH_DN_MASK ) { + case LDAP_BACK_AUTH_DN_AUTHZID: + ptr = lutil_strcopy( ptr, ",dn-authzid" ); + break; + + case LDAP_BACK_AUTH_DN_WHOAMI: + ptr = lutil_strcopy( ptr, ",dn-whoami" ); + break; + + default: +#if 0 /* implicit */ + ptr = lutil_strcopy( ptr, ",dn-none" ); +#endif + break; + } +#endif + + bv.bv_len = ( ptr - bv.bv_val ); + /* end-of-flags */ + } + + bindconf_unparse( &mt->mt_idassert.si_bc, &bc ); + + if ( !BER_BVISNULL( &bv ) ) { + ber_len_t len = bv.bv_len + bc.bv_len; + + bv.bv_val = ch_realloc( bv.bv_val, len + 1 ); + + assert( bc.bv_val[ 0 ] == ' ' ); + + ptr = lutil_strcopy( &bv.bv_val[ bv.bv_len ], bc.bv_val ); + free( bc.bv_val ); + bv.bv_len = ptr - bv.bv_val; + + } else { + for ( i = 0; isspace( (unsigned char) bc.bv_val[ i ] ); i++ ) + /* count spaces */ ; + + if ( i ) { + bc.bv_len -= i; + AC_MEMCPY( bc.bv_val, &bc.bv_val[ i ], bc.bv_len + 1 ); + } + + bv = bc; + } + + ber_bvarray_add( &c->rvalue_vals, &bv ); + + break; + } + + case LDAP_BACK_CFG_SUFFIXM: /* unused */ + case LDAP_BACK_CFG_REWRITE: + if ( mt->mt_rwmap.rwm_bva_rewrite == NULL ) { + rc = 1; + } else { + rc = slap_bv_x_ordered_unparse( mt->mt_rwmap.rwm_bva_rewrite, &c->rvalue_vals ); + } + break; + + case LDAP_BACK_CFG_MAP: + if ( mt->mt_rwmap.rwm_bva_map == NULL ) { + rc = 1; + } else { + rc = slap_bv_x_ordered_unparse( mt->mt_rwmap.rwm_bva_map, &c->rvalue_vals ); + } + break; + + case LDAP_BACK_CFG_SUBTREE_EX: + case LDAP_BACK_CFG_SUBTREE_IN: + rc = meta_subtree_unparse( c, mt ); + break; + + case LDAP_BACK_CFG_FILTER: + if ( mt->mt_filter == NULL ) { + rc = 1; + } else { + metafilter_t *mf; + for ( mf = mt->mt_filter; mf; mf = mf->mf_next ) + value_add_one( &c->rvalue_vals, &mf->mf_regex_pattern ); + } + break; + + /* replaced by idassert */ + case LDAP_BACK_CFG_PSEUDOROOTDN: + case LDAP_BACK_CFG_PSEUDOROOTPW: + rc = 1; + break; + + case LDAP_BACK_CFG_KEEPALIVE: { + struct berval bv; + char buf[AC_LINE_MAX]; + bv.bv_len = AC_LINE_MAX; + bv.bv_val = &buf[0]; + slap_keepalive_parse(&bv, &mt->mt_tls.sb_keepalive, 0, 0, 1); + value_add_one( &c->rvalue_vals, &bv ); + break; + } + + default: + rc = 1; + } + return rc; + } else if ( c->op == LDAP_MOD_DELETE ) { + switch( c->type ) { + /* Base attrs */ + case LDAP_BACK_CFG_CONN_TTL: + mi->mi_conn_ttl = 0; + break; + + case LDAP_BACK_CFG_DNCACHE_TTL: + mi->mi_cache.ttl = META_DNCACHE_DISABLED; + break; + + case LDAP_BACK_CFG_IDLE_TIMEOUT: + mi->mi_idle_timeout = 0; + break; + + case LDAP_BACK_CFG_ONERR: + mi->mi_flags &= ~META_BACK_F_ONERR_MASK; + break; + + case LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER: + mi->mi_flags &= ~META_BACK_F_DEFER_ROOTDN_BIND; + break; + + case LDAP_BACK_CFG_SINGLECONN: + mi->mi_flags &= ~LDAP_BACK_F_SINGLECONN; + break; + + case LDAP_BACK_CFG_USETEMP: + mi->mi_flags &= ~LDAP_BACK_F_USE_TEMPORARIES; + break; + + case LDAP_BACK_CFG_CONNPOOLMAX: + mi->mi_conn_priv_max = LDAP_BACK_CONN_PRIV_MIN; + break; + + /* common attrs */ + case LDAP_BACK_CFG_BIND_TIMEOUT: + mc->mc_bind_timeout.tv_sec = 0; + mc->mc_bind_timeout.tv_usec = 0; + break; + + case LDAP_BACK_CFG_CANCEL: + mc->mc_flags &= ~LDAP_BACK_F_CANCEL_MASK2; + break; + + case LDAP_BACK_CFG_CHASE: + mc->mc_flags &= ~LDAP_BACK_F_CHASE_REFERRALS; + break; + +#ifdef SLAPD_META_CLIENT_PR + case LDAP_BACK_CFG_CLIENT_PR: + mc->mc_ps = META_CLIENT_PR_DISABLE; + break; +#endif /* SLAPD_META_CLIENT_PR */ + + case LDAP_BACK_CFG_DEFAULT_T: + mi->mi_defaulttarget = META_DEFAULT_TARGET_NONE; + break; + + case LDAP_BACK_CFG_NETWORK_TIMEOUT: + mc->mc_network_timeout = 0; + break; + + case LDAP_BACK_CFG_NOREFS: + mc->mc_flags &= ~LDAP_BACK_F_NOREFS; + break; + + case LDAP_BACK_CFG_NOUNDEFFILTER: + mc->mc_flags &= ~LDAP_BACK_F_NOUNDEFFILTER; + break; + + case LDAP_BACK_CFG_NRETRIES: + mc->mc_nretries = META_RETRY_DEFAULT; + break; + + case LDAP_BACK_CFG_QUARANTINE: + if ( META_BACK_CMN_QUARANTINE( mc )) { + mi->mi_ldap_extra->retry_info_destroy( &mc->mc_quarantine ); + mc->mc_flags &= ~LDAP_BACK_F_QUARANTINE; + if ( mc == &mt->mt_mc ) { + ldap_pvt_thread_mutex_destroy( &mt->mt_quarantine_mutex ); + mt->mt_isquarantined = 0; + } + } + break; + + case LDAP_BACK_CFG_REBIND: + mc->mc_flags &= ~LDAP_BACK_F_SAVECRED; + break; + + case LDAP_BACK_CFG_TIMEOUT: + for ( i = 0; i < SLAP_OP_LAST; i++ ) { + mc->mc_timeout[ i ] = 0; + } + break; + + case LDAP_BACK_CFG_VERSION: + mc->mc_version = 0; + break; + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + case LDAP_BACK_CFG_ST_REQUEST: + mc->mc_flags &= ~LDAP_BACK_F_ST_REQUEST; + break; +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + + case LDAP_BACK_CFG_T_F: + mc->mc_flags &= ~LDAP_BACK_F_T_F_MASK2; + break; + + case LDAP_BACK_CFG_TLS: + mc->mc_flags &= ~LDAP_BACK_F_TLS_MASK; + if ( mt ) + bindconf_free( &mt->mt_tls ); + break; + + /* target attrs */ + case LDAP_BACK_CFG_URI: + if ( mt->mt_uri ) { + ch_free( mt->mt_uri ); + mt->mt_uri = NULL; + } + /* FIXME: should have a way to close all cached + * connections associated with this target. + */ + break; + + case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: { + BerVarray *bvp; + + bvp = &mt->mt_idassert_authz; + if ( c->valx < 0 ) { + if ( *bvp != NULL ) { + ber_bvarray_free( *bvp ); + *bvp = NULL; + } + + } else { + if ( *bvp == NULL ) { + rc = 1; + break; + } + + for ( i = 0; !BER_BVISNULL( &((*bvp)[ i ]) ); i++ ) + ; + + if ( i >= c->valx ) { + rc = 1; + break; + } + ber_memfree( ((*bvp)[ c->valx ]).bv_val ); + for ( i = c->valx; !BER_BVISNULL( &((*bvp)[ i + 1 ]) ); i++ ) { + (*bvp)[ i ] = (*bvp)[ i + 1 ]; + } + BER_BVZERO( &((*bvp)[ i ]) ); + } + } break; + + case LDAP_BACK_CFG_IDASSERT_BIND: + bindconf_free( &mt->mt_idassert.si_bc ); + memset( &mt->mt_idassert, 0, sizeof( slap_idassert_t ) ); + break; + + case LDAP_BACK_CFG_SUFFIXM: /* unused */ + case LDAP_BACK_CFG_REWRITE: + { + if ( c->valx >= 0 ) { + int i; + + for ( i = 0; !BER_BVISNULL( &mt->mt_rwmap.rwm_bva_rewrite[ i ] ); i++ ); + + if ( c->valx >= i ) { + rc = 1; + break; + } + + ber_memfree( mt->mt_rwmap.rwm_bva_rewrite[ c->valx ].bv_val ); + for ( i = c->valx; !BER_BVISNULL( &mt->mt_rwmap.rwm_bva_rewrite[ i + 1 ] ); i++ ) + { + mt->mt_rwmap.rwm_bva_rewrite[ i ] = mt->mt_rwmap.rwm_bva_rewrite[ i + 1 ]; + } + BER_BVZERO( &mt->mt_rwmap.rwm_bva_rewrite[ i ] ); + + rewrite_info_delete( &mt->mt_rwmap.rwm_rw ); + assert( mt->mt_rwmap.rwm_rw == NULL ); + + rc = meta_rwi_init( &mt->mt_rwmap.rwm_rw ); + + for ( i = 0; !BER_BVISNULL( &mt->mt_rwmap.rwm_bva_rewrite[ i ] ); i++ ) + { + ConfigArgs ca = { 0 }; + + ca.line = mt->mt_rwmap.rwm_bva_rewrite[ i ].bv_val; + init_config_argv( &ca ); + config_parse_ldif( &ca ); + + if ( !strcasecmp( ca.argv[0], "suffixmassage" )) { + rc = meta_suffixm_config( &ca, ca.argc, ca.argv, mt ); + } else { + rc = rewrite_parse( mt->mt_rwmap.rwm_rw, + c->fname, c->lineno, ca.argc, ca.argv ); + } + + + ch_free( ca.tline ); + ch_free( ca.argv ); + + assert( rc == 0 ); + } + + } else if ( mt->mt_rwmap.rwm_rw != NULL ) { + if ( mt->mt_rwmap.rwm_bva_rewrite ) { + ber_bvarray_free( mt->mt_rwmap.rwm_bva_rewrite ); + mt->mt_rwmap.rwm_bva_rewrite = NULL; + } + if ( mt->mt_rwmap.rwm_rw ) + rewrite_info_delete( &mt->mt_rwmap.rwm_rw ); + + meta_rwi_init( &mt->mt_rwmap.rwm_rw ); + } + } + break; + + case LDAP_BACK_CFG_MAP: + if ( mt->mt_rwmap.rwm_bva_map ) { + ber_bvarray_free( mt->mt_rwmap.rwm_bva_map ); + mt->mt_rwmap.rwm_bva_map = NULL; + } + meta_back_map_free( &mt->mt_rwmap.rwm_oc ); + meta_back_map_free( &mt->mt_rwmap.rwm_at ); + mt->mt_rwmap.rwm_oc.drop_missing = 0; + mt->mt_rwmap.rwm_at.drop_missing = 0; + break; + + case LDAP_BACK_CFG_SUBTREE_EX: + case LDAP_BACK_CFG_SUBTREE_IN: + /* can only be one of exclude or include */ + if (( c->type == LDAP_BACK_CFG_SUBTREE_EX ) ^ mt->mt_subtree_exclude ) { + rc = 1; + break; + } + if ( c->valx < 0 ) { + meta_subtree_destroy( mt->mt_subtree ); + mt->mt_subtree = NULL; + } else { + metasubtree_t *ms, **mprev; + for (i=0, mprev = &mt->mt_subtree, ms = *mprev; ms; ms = *mprev) { + if ( i == c->valx ) { + *mprev = ms->ms_next; + meta_subtree_free( ms ); + break; + } + i++; + mprev = &ms->ms_next; + } + if ( i != c->valx ) + rc = 1; + } + break; + + case LDAP_BACK_CFG_FILTER: + if ( c->valx < 0 ) { + meta_filter_destroy( mt->mt_filter ); + mt->mt_filter = NULL; + } else { + metafilter_t *mf, **mprev; + for (i=0, mprev = &mt->mt_filter, mf = *mprev; mf; mf = *mprev) { + if ( i == c->valx ) { + *mprev = mf->mf_next; + meta_filter_free( mf ); + break; + } + i++; + mprev = &mf->mf_next; + } + if ( i != c->valx ) + rc = 1; + } + break; + + case LDAP_BACK_CFG_KEEPALIVE: + mt->mt_tls.sb_keepalive.sk_idle = 0; + mt->mt_tls.sb_keepalive.sk_probes = 0; + mt->mt_tls.sb_keepalive.sk_interval = 0; + break; + + default: + rc = 1; + break; + } + + return rc; + } + + if ( c->op == SLAP_CONFIG_ADD ) { + if ( c->type >= LDAP_BACK_CFG_LAST_BASE ) { + /* exclude CFG_URI from this check */ + if ( c->type > LDAP_BACK_CFG_LAST_BOTH ) { + if ( !mi->mi_ntargets ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "need \"uri\" directive first" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + } + if ( mi->mi_ntargets ) { + mt = mi->mi_targets[ mi->mi_ntargets-1 ]; + mc = &mt->mt_mc; + } else { + mt = NULL; + mc = &mi->mi_mc; + } + } + } else { + if ( c->table == Cft_Database ) { + mt = NULL; + mc = &mi->mi_mc; + } else { + mt = c->ca_private; + if ( mt ) + mc = &mt->mt_mc; + else + mc = NULL; + } + } + + switch( c->type ) { + case LDAP_BACK_CFG_URI: { + LDAPURLDesc *ludp; + struct berval dn; + int j; + + char **uris = NULL; + + if ( c->be->be_nsuffix == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "the suffix must be defined before any target" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + i = mi->mi_ntargets++; + + mi->mi_targets = ( metatarget_t ** )ch_realloc( mi->mi_targets, + sizeof( metatarget_t * ) * mi->mi_ntargets ); + if ( mi->mi_targets == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "out of memory while storing server name" + " in \"%s <protocol>://<server>[:port]/<naming context>\"", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + if ( meta_back_new_target( &mi->mi_targets[ i ] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to init server" + " in \"%s <protocol>://<server>[:port]/<naming context>\"", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + mt = mi->mi_targets[ i ]; + + mt->mt_rebind_f = mi->mi_rebind_f; + mt->mt_urllist_f = mi->mi_urllist_f; + mt->mt_urllist_p = mt; + + if ( META_BACK_QUARANTINE( mi ) ) { + ldap_pvt_thread_mutex_init( &mt->mt_quarantine_mutex ); + } + mt->mt_mc = mi->mi_mc; + + for ( j = 1; j < c->argc; j++ ) { + char **tmpuris = ldap_str2charray( c->argv[ j ], "\t" ); + + if ( tmpuris == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse URIs #%d" + " in \"%s <protocol>://<server>[:port]/<naming context>\"", + j-1, c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + if ( j == 1 ) { + uris = tmpuris; + + } else { + ldap_charray_merge( &uris, tmpuris ); + ldap_charray_free( tmpuris ); + } + } + + for ( j = 0; uris[ j ] != NULL; j++ ) { + char *tmpuri = NULL; + + /* + * uri MUST be legal! + */ + if ( ldap_url_parselist_ext( &ludp, uris[ j ], "\t", + LDAP_PVT_URL_PARSE_NONE ) != LDAP_SUCCESS + || ludp->lud_next != NULL ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse URI #%d" + " in \"%s <protocol>://<server>[:port]/<naming context>\"", + j-1, c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + ldap_charray_free( uris ); + return 1; + } + + if ( j == 0 ) { + + /* + * uri MUST have the <dn> part! + */ + if ( ludp->lud_dn == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "missing <naming context> " + " in \"%s <protocol>://<server>[:port]/<naming context>\"", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + ldap_free_urllist( ludp ); + ldap_charray_free( uris ); + return 1; + } + + /* + * copies and stores uri and suffix + */ + ber_str2bv( ludp->lud_dn, 0, 0, &dn ); + rc = dnPrettyNormal( NULL, &dn, &mt->mt_psuffix, + &mt->mt_nsuffix, NULL ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "target DN is invalid \"%s\"", + c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + ldap_free_urllist( ludp ); + ldap_charray_free( uris ); + return( 1 ); + } + + ludp->lud_dn[ 0 ] = '\0'; + + switch ( ludp->lud_scope ) { + case LDAP_SCOPE_DEFAULT: + mt->mt_scope = LDAP_SCOPE_SUBTREE; + break; + + case LDAP_SCOPE_SUBTREE: + case LDAP_SCOPE_SUBORDINATE: + mt->mt_scope = ludp->lud_scope; + break; + + default: + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "invalid scope for target \"%s\"", + c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + ldap_free_urllist( ludp ); + ldap_charray_free( uris ); + return( 1 ); + } + + } else { + /* check all, to apply the scope check on the first one */ + if ( ludp->lud_dn != NULL && ludp->lud_dn[ 0 ] != '\0' ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "multiple URIs must have no DN part" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + ldap_free_urllist( ludp ); + ldap_charray_free( uris ); + return( 1 ); + + } + } + + tmpuri = ldap_url_list2urls( ludp ); + ldap_free_urllist( ludp ); + if ( tmpuri == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "no memory?" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + ldap_charray_free( uris ); + return( 1 ); + } + ldap_memfree( uris[ j ] ); + uris[ j ] = tmpuri; + } + + mt->mt_uri = ldap_charray2str( uris, " " ); + ldap_charray_free( uris ); + if ( mt->mt_uri == NULL) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "no memory?" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + + /* + * uri MUST be a branch of suffix! + */ + for ( j = 0; !BER_BVISNULL( &c->be->be_nsuffix[ j ] ); j++ ) { + if ( dnIsSuffix( &mt->mt_nsuffix, &c->be->be_nsuffix[ j ] ) ) { + break; + } + } + + if ( BER_BVISNULL( &c->be->be_nsuffix[ j ] ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<naming context> of URI must be within the naming context of this database." ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + c->ca_private = mt; + c->cleanup = meta_cf_cleanup; + } break; + case LDAP_BACK_CFG_SUBTREE_EX: + case LDAP_BACK_CFG_SUBTREE_IN: + /* subtree-exclude */ + if ( meta_subtree_config( mt, c )) { + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + break; + + case LDAP_BACK_CFG_FILTER: { + metafilter_t *mf, **m2; + mf = ch_calloc( 1, sizeof( metafilter_t )); + rc = regcomp( &mf->mf_regex, c->argv[1], REG_EXTENDED ); + if ( rc ) { + char regerr[ SLAP_TEXT_BUFLEN ]; + regerror( rc, &mf->mf_regex, regerr, sizeof(regerr) ); + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "regular expression \"%s\" bad because of %s", + c->argv[1], regerr ); + ch_free( mf ); + return 1; + } + ber_str2bv( c->argv[1], 0, 1, &mf->mf_regex_pattern ); + for ( m2 = &mt->mt_filter; *m2; m2 = &(*m2)->mf_next ) + ; + *m2 = mf; + } break; + + case LDAP_BACK_CFG_DEFAULT_T: + /* default target directive */ + i = mi->mi_ntargets - 1; + + if ( c->argc == 1 ) { + if ( i < 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s\" alone must be inside a \"uri\" directive", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + mi->mi_defaulttarget = i; + + } else { + if ( strcasecmp( c->argv[ 1 ], "none" ) == 0 ) { + if ( i >= 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s none\" should go before uri definitions", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + } + mi->mi_defaulttarget = META_DEFAULT_TARGET_NONE; + + } else { + + if ( lutil_atoi( &mi->mi_defaulttarget, c->argv[ 1 ] ) != 0 + || mi->mi_defaulttarget < 0 + || mi->mi_defaulttarget >= i - 1 ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "illegal target number %d", + mi->mi_defaulttarget ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + } + } + break; + + case LDAP_BACK_CFG_DNCACHE_TTL: + /* ttl of dn cache */ + if ( strcasecmp( c->argv[ 1 ], "forever" ) == 0 ) { + mi->mi_cache.ttl = META_DNCACHE_FOREVER; + + } else if ( strcasecmp( c->argv[ 1 ], "disabled" ) == 0 ) { + mi->mi_cache.ttl = META_DNCACHE_DISABLED; + + } else { + unsigned long t; + + if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse dncache ttl \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + mi->mi_cache.ttl = (time_t)t; + } + break; + + case LDAP_BACK_CFG_NETWORK_TIMEOUT: { + /* network timeout when connecting to ldap servers */ + unsigned long t; + + if ( lutil_parse_time( c->argv[ 1 ], &t ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse network timeout \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + mc->mc_network_timeout = (time_t)t; + } break; + + case LDAP_BACK_CFG_IDLE_TIMEOUT: { + /* idle timeout when connecting to ldap servers */ + unsigned long t; + + if ( lutil_parse_time( c->argv[ 1 ], &t ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse idle timeout \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + + } + mi->mi_idle_timeout = (time_t)t; + } break; + + case LDAP_BACK_CFG_CONN_TTL: { + /* conn ttl */ + unsigned long t; + + if ( lutil_parse_time( c->argv[ 1 ], &t ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse conn ttl \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + + } + mi->mi_conn_ttl = (time_t)t; + } break; + + case LDAP_BACK_CFG_BIND_TIMEOUT: + /* bind timeout when connecting to ldap servers */ + mc->mc_bind_timeout.tv_sec = c->value_ulong/1000000; + mc->mc_bind_timeout.tv_usec = c->value_ulong%1000000; + break; + + case LDAP_BACK_CFG_ACL_AUTHCDN: + /* name to use for meta_back_group */ + if ( strcasecmp( c->argv[ 0 ], "binddn" ) == 0 ) { + Debug( LDAP_DEBUG_ANY, "%s: " + "\"binddn\" statement is deprecated; " + "use \"acl-authcDN\" instead\n", + c->log, 0, 0 ); + /* FIXME: some day we'll need to throw an error */ + } + + ber_memfree_x( c->value_dn.bv_val, NULL ); + mt->mt_binddn = c->value_ndn; + BER_BVZERO( &c->value_dn ); + BER_BVZERO( &c->value_ndn ); + break; + + case LDAP_BACK_CFG_ACL_PASSWD: + /* password to use for meta_back_group */ + if ( strcasecmp( c->argv[ 0 ], "bindpw" ) == 0 ) { + Debug( LDAP_DEBUG_ANY, "%s " + "\"bindpw\" statement is deprecated; " + "use \"acl-passwd\" instead\n", + c->log, 0, 0 ); + /* FIXME: some day we'll need to throw an error */ + } + + ber_str2bv( c->argv[ 1 ], 0L, 1, &mt->mt_bindpw ); + break; + + case LDAP_BACK_CFG_REBIND: + /* save bind creds for referral rebinds? */ + if ( c->argc == 1 || c->value_int ) { + mc->mc_flags |= LDAP_BACK_F_SAVECRED; + } else { + mc->mc_flags &= ~LDAP_BACK_F_SAVECRED; + } + break; + + case LDAP_BACK_CFG_CHASE: + if ( c->argc == 1 || c->value_int ) { + mc->mc_flags |= LDAP_BACK_F_CHASE_REFERRALS; + } else { + mc->mc_flags &= ~LDAP_BACK_F_CHASE_REFERRALS; + } + break; + + case LDAP_BACK_CFG_TLS: + i = verb_to_mask( c->argv[1], tls_mode ); + if ( BER_BVISNULL( &tls_mode[i].word ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s unknown argument \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + mc->mc_flags &= ~LDAP_BACK_F_TLS_MASK; + mc->mc_flags |= tls_mode[i].mask; + + if ( c->argc > 2 ) { + if ( c->op == SLAP_CONFIG_ADD && mi->mi_ntargets == 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "need \"uri\" directive first" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + for ( i = 2; i < c->argc; i++ ) { + if ( bindconf_tls_parse( c->argv[i], &mt->mt_tls )) + return 1; + } + bindconf_tls_defaults( &mt->mt_tls ); + } + break; + + case LDAP_BACK_CFG_T_F: + i = verb_to_mask( c->argv[1], t_f_mode ); + if ( BER_BVISNULL( &t_f_mode[i].word ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s unknown argument \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + mc->mc_flags &= ~LDAP_BACK_F_T_F_MASK2; + mc->mc_flags |= t_f_mode[i].mask; + break; + + case LDAP_BACK_CFG_ONERR: + /* onerr? */ + i = verb_to_mask( c->argv[1], onerr_mode ); + if ( BER_BVISNULL( &onerr_mode[i].word ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s unknown argument \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + mi->mi_flags &= ~META_BACK_F_ONERR_MASK; + mi->mi_flags |= onerr_mode[i].mask; + break; + + case LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER: + /* bind-defer? */ + if ( c->argc == 1 || c->value_int ) { + mi->mi_flags |= META_BACK_F_DEFER_ROOTDN_BIND; + } else { + mi->mi_flags &= ~META_BACK_F_DEFER_ROOTDN_BIND; + } + break; + + case LDAP_BACK_CFG_SINGLECONN: + /* single-conn? */ + if ( mi->mi_ntargets > 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s\" must appear before target definitions", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + if ( c->value_int ) { + mi->mi_flags |= LDAP_BACK_F_SINGLECONN; + } else { + mi->mi_flags &= ~LDAP_BACK_F_SINGLECONN; + } + break; + + case LDAP_BACK_CFG_USETEMP: + /* use-temporaries? */ + if ( mi->mi_ntargets > 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s\" must appear before target definitions", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + if ( c->value_int ) { + mi->mi_flags |= LDAP_BACK_F_USE_TEMPORARIES; + } else { + mi->mi_flags &= ~LDAP_BACK_F_USE_TEMPORARIES; + } + break; + + case LDAP_BACK_CFG_CONNPOOLMAX: + /* privileged connections pool max size ? */ + if ( mi->mi_ntargets > 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s\" must appear before target definitions", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + + if ( c->value_int < LDAP_BACK_CONN_PRIV_MIN + || c->value_int > LDAP_BACK_CONN_PRIV_MAX ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "invalid max size " "of privileged " + "connections pool \"%s\" " + "in \"conn-pool-max <n> " + "(must be between %d and %d)\"", + c->argv[ 1 ], + LDAP_BACK_CONN_PRIV_MIN, + LDAP_BACK_CONN_PRIV_MAX ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + mi->mi_conn_priv_max = c->value_int; + break; + + case LDAP_BACK_CFG_CANCEL: + i = verb_to_mask( c->argv[1], cancel_mode ); + if ( BER_BVISNULL( &cancel_mode[i].word ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s unknown argument \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + mc->mc_flags &= ~LDAP_BACK_F_CANCEL_MASK2; + mc->mc_flags |= cancel_mode[i].mask; + break; + + case LDAP_BACK_CFG_TIMEOUT: + for ( i = 1; i < c->argc; i++ ) { + if ( isdigit( (unsigned char) c->argv[ i ][ 0 ] ) ) { + int j; + unsigned u; + + if ( lutil_atoux( &u, c->argv[ i ], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "unable to parse timeout \"%s\"", + c->argv[ i ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + for ( j = 0; j < SLAP_OP_LAST; j++ ) { + mc->mc_timeout[ j ] = u; + } + + continue; + } + + if ( slap_cf_aux_table_parse( c->argv[ i ], mc->mc_timeout, timeout_table, "slapd-meta timeout" ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "unable to parse timeout \"%s\"", + c->argv[ i ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + } + break; + + case LDAP_BACK_CFG_PSEUDOROOTDN: + /* name to use as pseudo-root dn */ + /* + * exact replacement: + * + +idassert-bind bindmethod=simple + binddn=<pseudorootdn> + credentials=<pseudorootpw> + mode=none + flags=non-prescriptive +idassert-authzFrom "dn:<rootdn>" + + * so that only when authc'd as <rootdn> the proxying occurs + * rebinding as the <pseudorootdn> without proxyAuthz. + */ + + Debug( LDAP_DEBUG_ANY, + "%s: \"pseudorootdn\", \"pseudorootpw\" are no longer supported; " + "use \"idassert-bind\" and \"idassert-authzFrom\" instead.\n", + c->log, 0, 0 ); + + { + char binddn[ SLAP_TEXT_BUFLEN ]; + char *cargv[] = { + "idassert-bind", + "bindmethod=simple", + NULL, + "mode=none", + "flags=non-prescriptive", + NULL + }; + char **oargv; + int oargc; + int cargc = 5; + int rc; + + + if ( BER_BVISNULL( &c->be->be_rootndn ) ) { + Debug( LDAP_DEBUG_ANY, "%s: \"pseudorootpw\": \"rootdn\" must be defined first.\n", + c->log, 0, 0 ); + return 1; + } + + if ( sizeof( binddn ) <= (unsigned) snprintf( binddn, + sizeof( binddn ), "binddn=%s", c->argv[ 1 ] )) + { + Debug( LDAP_DEBUG_ANY, "%s: \"pseudorootdn\" too long.\n", + c->log, 0, 0 ); + return 1; + } + cargv[ 2 ] = binddn; + + oargv = c->argv; + oargc = c->argc; + c->argv = cargv; + c->argc = cargc; + rc = mi->mi_ldap_extra->idassert_parse( c, &mt->mt_idassert ); + c->argv = oargv; + c->argc = oargc; + if ( rc == 0 ) { + struct berval bv; + + if ( mt->mt_idassert_authz != NULL ) { + Debug( LDAP_DEBUG_ANY, "%s: \"idassert-authzFrom\" already defined (discarded).\n", + c->log, 0, 0 ); + ber_bvarray_free( mt->mt_idassert_authz ); + mt->mt_idassert_authz = NULL; + } + + assert( !BER_BVISNULL( &mt->mt_idassert_authcDN ) ); + + bv.bv_len = STRLENOF( "dn:" ) + c->be->be_rootndn.bv_len; + bv.bv_val = ber_memalloc( bv.bv_len + 1 ); + AC_MEMCPY( bv.bv_val, "dn:", STRLENOF( "dn:" ) ); + AC_MEMCPY( &bv.bv_val[ STRLENOF( "dn:" ) ], c->be->be_rootndn.bv_val, c->be->be_rootndn.bv_len + 1 ); + + ber_bvarray_add( &mt->mt_idassert_authz, &bv ); + } + + return rc; + } + break; + + case LDAP_BACK_CFG_PSEUDOROOTPW: + /* password to use as pseudo-root */ + Debug( LDAP_DEBUG_ANY, + "%s: \"pseudorootdn\", \"pseudorootpw\" are no longer supported; " + "use \"idassert-bind\" and \"idassert-authzFrom\" instead.\n", + c->log, 0, 0 ); + + if ( BER_BVISNULL( &mt->mt_idassert_authcDN ) ) { + Debug( LDAP_DEBUG_ANY, "%s: \"pseudorootpw\": \"pseudorootdn\" must be defined first.\n", + c->log, 0, 0 ); + return 1; + } + + if ( !BER_BVISNULL( &mt->mt_idassert_passwd ) ) { + memset( mt->mt_idassert_passwd.bv_val, 0, + mt->mt_idassert_passwd.bv_len ); + ber_memfree( mt->mt_idassert_passwd.bv_val ); + } + ber_str2bv( c->argv[ 1 ], 0, 1, &mt->mt_idassert_passwd ); + break; + + case LDAP_BACK_CFG_IDASSERT_BIND: + /* idassert-bind */ + rc = mi->mi_ldap_extra->idassert_parse( c, &mt->mt_idassert ); + break; + + case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: + /* idassert-authzFrom */ + rc = mi->mi_ldap_extra->idassert_authzfrom_parse( c, &mt->mt_idassert ); + break; + + case LDAP_BACK_CFG_QUARANTINE: + /* quarantine */ + if ( META_BACK_CMN_QUARANTINE( mc ) ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "quarantine already defined" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + if ( mt ) { + mc->mc_quarantine.ri_interval = NULL; + mc->mc_quarantine.ri_num = NULL; + if ( !META_BACK_QUARANTINE( mi ) ) { + ldap_pvt_thread_mutex_init( &mt->mt_quarantine_mutex ); + } + } + + if ( mi->mi_ldap_extra->retry_info_parse( c->argv[ 1 ], &mc->mc_quarantine, c->cr_msg, sizeof( c->cr_msg ) ) ) { + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + mc->mc_flags |= LDAP_BACK_F_QUARANTINE; + break; + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + case LDAP_BACK_CFG_ST_REQUEST: + /* session tracking request */ + if ( c->value_int ) { + mc->mc_flags |= LDAP_BACK_F_ST_REQUEST; + } else { + mc->mc_flags &= ~LDAP_BACK_F_ST_REQUEST; + } + break; +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + + case LDAP_BACK_CFG_SUFFIXM: /* FALLTHRU */ + case LDAP_BACK_CFG_REWRITE: { + /* rewrite stuff ... */ + ConfigArgs ca = { 0 }; + char *line, **argv; + struct rewrite_info *rwi; + int cnt = 0, argc, ix = c->valx; + + if ( mt->mt_rwmap.rwm_bva_rewrite ) { + for ( ; !BER_BVISNULL( &mt->mt_rwmap.rwm_bva_rewrite[ cnt ] ); cnt++ ) + /* count */ ; + } + + if ( ix >= cnt || ix < 0 ) { + ix = cnt; + } else { + rwi = mt->mt_rwmap.rwm_rw; + + mt->mt_rwmap.rwm_rw = NULL; + rc = meta_rwi_init( &mt->mt_rwmap.rwm_rw ); + + /* re-parse all rewrite rules, up to the one + * that needs to be added */ + ca.be = c->be; + ca.fname = c->fname; + ca.lineno = c->lineno; + for ( i = 0; i < ix; i++ ) { + ca.line = mt->mt_rwmap.rwm_bva_rewrite[ i ].bv_val; + ca.argc = 0; + config_fp_parse_line( &ca ); + + if ( !strcasecmp( ca.argv[0], "suffixmassage" )) { + rc = meta_suffixm_config( &ca, ca.argc, ca.argv, mt ); + } else { + rc = rewrite_parse( mt->mt_rwmap.rwm_rw, + c->fname, c->lineno, ca.argc, ca.argv ); + } + assert( rc == 0 ); + ch_free( ca.argv ); + ch_free( ca.tline ); + } + } + argc = c->argc; + argv = c->argv; + if ( c->op != SLAP_CONFIG_ADD ) { + argc--; + argv++; + } + /* add the new rule */ + if ( !strcasecmp( argv[0], "suffixmassage" )) { + rc = meta_suffixm_config( c, argc, argv, mt ); + } else { + rc = rewrite_parse( mt->mt_rwmap.rwm_rw, + c->fname, c->lineno, argc, argv ); + } + if ( rc ) { + if ( ix < cnt ) { + rewrite_info_delete( &mt->mt_rwmap.rwm_rw ); + mt->mt_rwmap.rwm_rw = rwi; + } + return 1; + } + if ( ix < cnt ) { + for ( ; i < cnt; i++ ) { + ca.line = mt->mt_rwmap.rwm_bva_rewrite[ i ].bv_val; + ca.argc = 0; + config_fp_parse_line( &ca ); + + if ( !strcasecmp( ca.argv[0], "suffixmassage" )) { + rc = meta_suffixm_config( &ca, ca.argc, ca.argv, mt ); + } else { + rc = rewrite_parse( mt->mt_rwmap.rwm_rw, + c->fname, c->lineno, ca.argc, argv ); + } + assert( rc == 0 ); + ch_free( ca.argv ); + ch_free( ca.tline ); + } + } + + /* save the rule info */ + line = ldap_charray2str( argv, "\" \"" ); + if ( line != NULL ) { + struct berval bv; + int len = strlen( argv[ 0 ] ); + + ber_str2bv( line, 0, 0, &bv ); + AC_MEMCPY( &bv.bv_val[ len ], &bv.bv_val[ len + 1 ], + bv.bv_len - ( len + 1 )); + bv.bv_val[ bv.bv_len - 1] = '"'; + ber_bvarray_add( &mt->mt_rwmap.rwm_bva_rewrite, &bv ); + /* move it to the right slot */ + if ( ix < cnt ) { + for ( i=cnt; i>ix; i-- ) + mt->mt_rwmap.rwm_bva_rewrite[i+1] = mt->mt_rwmap.rwm_bva_rewrite[i]; + mt->mt_rwmap.rwm_bva_rewrite[i] = bv; + + /* destroy old rules */ + rewrite_info_delete( &rwi ); + } + } + } break; + + case LDAP_BACK_CFG_MAP: { + /* objectclass/attribute mapping */ + ConfigArgs ca = { 0 }; + char *argv[5]; + struct ldapmap rwm_oc; + struct ldapmap rwm_at; + int cnt = 0, ix = c->valx; + + if ( mt->mt_rwmap.rwm_bva_map ) { + for ( ; !BER_BVISNULL( &mt->mt_rwmap.rwm_bva_map[ cnt ] ); cnt++ ) + /* count */ ; + } + + if ( ix >= cnt || ix < 0 ) { + ix = cnt; + } else { + rwm_oc = mt->mt_rwmap.rwm_oc; + rwm_at = mt->mt_rwmap.rwm_at; + + memset( &mt->mt_rwmap.rwm_oc, 0, sizeof( mt->mt_rwmap.rwm_oc ) ); + memset( &mt->mt_rwmap.rwm_at, 0, sizeof( mt->mt_rwmap.rwm_at ) ); + + /* re-parse all mappings, up to the one + * that needs to be added */ + argv[0] = c->argv[0]; + ca.fname = c->fname; + ca.lineno = c->lineno; + for ( i = 0; i < ix; i++ ) { + ca.line = mt->mt_rwmap.rwm_bva_map[ i ].bv_val; + ca.argc = 0; + config_fp_parse_line( &ca ); + + argv[1] = ca.argv[0]; + argv[2] = ca.argv[1]; + argv[3] = ca.argv[2]; + argv[4] = ca.argv[3]; + ch_free( ca.argv ); + ca.argv = argv; + ca.argc++; + rc = ldap_back_map_config( &ca, &mt->mt_rwmap.rwm_oc, + &mt->mt_rwmap.rwm_at ); + + ch_free( ca.tline ); + ca.tline = NULL; + ca.argv = NULL; + + /* in case of failure, restore + * the existing mapping */ + if ( rc ) { + goto map_fail; + } + } + } + /* add the new mapping */ + rc = ldap_back_map_config( c, &mt->mt_rwmap.rwm_oc, + &mt->mt_rwmap.rwm_at ); + if ( rc ) { + goto map_fail; + } + + if ( ix < cnt ) { + for ( ; i<cnt ; cnt++ ) { + ca.line = mt->mt_rwmap.rwm_bva_map[ i ].bv_val; + ca.argc = 0; + config_fp_parse_line( &ca ); + + argv[1] = ca.argv[0]; + argv[2] = ca.argv[1]; + argv[3] = ca.argv[2]; + argv[4] = ca.argv[3]; + + ch_free( ca.argv ); + ca.argv = argv; + ca.argc++; + rc = ldap_back_map_config( &ca, &mt->mt_rwmap.rwm_oc, + &mt->mt_rwmap.rwm_at ); + + ch_free( ca.tline ); + ca.tline = NULL; + ca.argv = NULL; + + /* in case of failure, restore + * the existing mapping */ + if ( rc ) { + goto map_fail; + } + } + } + + /* save the map info */ + argv[0] = ldap_charray2str( &c->argv[ 1 ], " " ); + if ( argv[0] != NULL ) { + struct berval bv; + ber_str2bv( argv[0], 0, 0, &bv ); + ber_bvarray_add( &mt->mt_rwmap.rwm_bva_map, &bv ); + /* move it to the right slot */ + if ( ix < cnt ) { + for ( i=cnt; i>ix; i-- ) + mt->mt_rwmap.rwm_bva_map[i+1] = mt->mt_rwmap.rwm_bva_map[i]; + mt->mt_rwmap.rwm_bva_map[i] = bv; + + /* destroy old mapping */ + meta_back_map_free( &rwm_oc ); + meta_back_map_free( &rwm_at ); + } + } + break; + +map_fail:; + if ( ix < cnt ) { + meta_back_map_free( &mt->mt_rwmap.rwm_oc ); + meta_back_map_free( &mt->mt_rwmap.rwm_at ); + mt->mt_rwmap.rwm_oc = rwm_oc; + mt->mt_rwmap.rwm_at = rwm_at; + } + } break; + + case LDAP_BACK_CFG_NRETRIES: { + int nretries = META_RETRY_UNDEFINED; + + if ( strcasecmp( c->argv[ 1 ], "forever" ) == 0 ) { + nretries = META_RETRY_FOREVER; + + } else if ( strcasecmp( c->argv[ 1 ], "never" ) == 0 ) { + nretries = META_RETRY_NEVER; + + } else { + if ( lutil_atoi( &nretries, c->argv[ 1 ] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse nretries {never|forever|<retries>}: \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + } + + mc->mc_nretries = nretries; + } break; + + case LDAP_BACK_CFG_VERSION: + if ( c->value_int != 0 && ( c->value_int < LDAP_VERSION_MIN || c->value_int > LDAP_VERSION_MAX ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unsupported protocol version \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + mc->mc_version = c->value_int; + break; + + case LDAP_BACK_CFG_NOREFS: + /* do not return search references */ + if ( c->value_int ) { + mc->mc_flags |= LDAP_BACK_F_NOREFS; + } else { + mc->mc_flags &= ~LDAP_BACK_F_NOREFS; + } + break; + + case LDAP_BACK_CFG_NOUNDEFFILTER: + /* do not propagate undefined search filters */ + if ( c->value_int ) { + mc->mc_flags |= LDAP_BACK_F_NOUNDEFFILTER; + } else { + mc->mc_flags &= ~LDAP_BACK_F_NOUNDEFFILTER; + } + break; + +#ifdef SLAPD_META_CLIENT_PR + case LDAP_BACK_CFG_CLIENT_PR: + if ( strcasecmp( c->argv[ 1 ], "accept-unsolicited" ) == 0 ) { + mc->mc_ps = META_CLIENT_PR_ACCEPT_UNSOLICITED; + + } else if ( strcasecmp( c->argv[ 1 ], "disable" ) == 0 ) { + mc->mc_ps = META_CLIENT_PR_DISABLE; + + } else if ( lutil_atoi( &mc->mc_ps, c->argv[ 1 ] ) || mc->mc_ps < -1 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse client-pr {accept-unsolicited|disable|<size>}: \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + break; +#endif /* SLAPD_META_CLIENT_PR */ + + case LDAP_BACK_CFG_KEEPALIVE: + slap_keepalive_parse( ber_bvstrdup(c->argv[1]), + &mt->mt_tls.sb_keepalive, 0, 0, 0); + break; + + /* anything else */ + default: + return SLAP_CONF_UNKNOWN; + } + + return rc; +} + +int +meta_back_init_cf( BackendInfo *bi ) +{ + int rc; + AttributeDescription *ad = NULL; + const char *text; + + /* Make sure we don't exceed the bits reserved for userland */ + config_check_userland( LDAP_BACK_CFG_LAST ); + + bi->bi_cf_ocs = metaocs; + + rc = config_register_schema( metacfg, metaocs ); + if ( rc ) { + return rc; + } + + /* setup olcDbAclPasswd and olcDbIDAssertPasswd + * to be base64-encoded when written in LDIF form; + * basically, we don't care if it fails */ + rc = slap_str2ad( "olcDbACLPasswd", &ad, &text ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, "config_back_initialize: " + "warning, unable to get \"olcDbACLPasswd\" " + "attribute description: %d: %s\n", + rc, text, 0 ); + } else { + (void)ldif_must_b64_encode_register( ad->ad_cname.bv_val, + ad->ad_type->sat_oid ); + } + + ad = NULL; + rc = slap_str2ad( "olcDbIDAssertPasswd", &ad, &text ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, "config_back_initialize: " + "warning, unable to get \"olcDbIDAssertPasswd\" " + "attribute description: %d: %s\n", + rc, text, 0 ); + } else { + (void)ldif_must_b64_encode_register( ad->ad_cname.bv_val, + ad->ad_type->sat_oid ); + } + + return 0; +} + +static int +ldap_back_map_config( + ConfigArgs *c, + struct ldapmap *oc_map, + struct ldapmap *at_map ) +{ + struct ldapmap *map; + struct ldapmapping *mapping; + char *src, *dst; + int is_oc = 0; + + if ( strcasecmp( c->argv[ 1 ], "objectclass" ) == 0 ) { + map = oc_map; + is_oc = 1; + + } else if ( strcasecmp( c->argv[ 1 ], "attribute" ) == 0 ) { + map = at_map; + + } else { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "%s unknown argument \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + if ( !is_oc && map->map == NULL ) { + /* only init if required */ + ldap_back_map_init( map, &mapping ); + } + + if ( strcmp( c->argv[ 2 ], "*" ) == 0 ) { + if ( c->argc < 4 || strcmp( c->argv[ 3 ], "*" ) == 0 ) { + map->drop_missing = ( c->argc < 4 ); + goto success_return; + } + src = dst = c->argv[ 3 ]; + + } else if ( c->argc < 4 ) { + src = ""; + dst = c->argv[ 2 ]; + + } else { + src = c->argv[ 2 ]; + dst = ( strcmp( c->argv[ 3 ], "*" ) == 0 ? src : c->argv[ 3 ] ); + } + + if ( ( map == at_map ) + && ( strcasecmp( src, "objectclass" ) == 0 + || strcasecmp( dst, "objectclass" ) == 0 ) ) + { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "objectclass attribute cannot be mapped" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + mapping = (struct ldapmapping *)ch_calloc( 2, + sizeof(struct ldapmapping) ); + if ( mapping == NULL ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "out of memory" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + ber_str2bv( src, 0, 1, &mapping[ 0 ].src ); + ber_str2bv( dst, 0, 1, &mapping[ 0 ].dst ); + mapping[ 1 ].src = mapping[ 0 ].dst; + mapping[ 1 ].dst = mapping[ 0 ].src; + + /* + * schema check + */ + if ( is_oc ) { + if ( src[ 0 ] != '\0' ) { + if ( oc_bvfind( &mapping[ 0 ].src ) == NULL ) { + Debug( LDAP_DEBUG_ANY, + "warning, source objectClass '%s' should be defined in schema\n", + c->log, src, 0 ); + + /* + * FIXME: this should become an err + */ + goto error_return; + } + } + + if ( oc_bvfind( &mapping[ 0 ].dst ) == NULL ) { + Debug( LDAP_DEBUG_ANY, + "warning, destination objectClass '%s' is not defined in schema\n", + c->log, dst, 0 ); + } + } else { + int rc; + const char *text = NULL; + AttributeDescription *ad = NULL; + + if ( src[ 0 ] != '\0' ) { + rc = slap_bv2ad( &mapping[ 0 ].src, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "warning, source attributeType '%s' should be defined in schema\n", + c->log, src, 0 ); + + /* + * FIXME: this should become an err + */ + /* + * we create a fake "proxied" ad + * and add it here. + */ + + rc = slap_bv2undef_ad( &mapping[ 0 ].src, + &ad, &text, SLAP_AD_PROXIED ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "source attributeType \"%s\": %d (%s)", + src, rc, text ? text : "" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + goto error_return; + } + } + + ad = NULL; + } + + rc = slap_bv2ad( &mapping[ 0 ].dst, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "warning, destination attributeType '%s' is not defined in schema\n", + c->log, dst, 0 ); + + /* + * we create a fake "proxied" ad + * and add it here. + */ + + rc = slap_bv2undef_ad( &mapping[ 0 ].dst, + &ad, &text, SLAP_AD_PROXIED ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "destination attributeType \"%s\": %d (%s)\n", + dst, rc, text ? text : "" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + } + } + + if ( (src[ 0 ] != '\0' && avl_find( map->map, (caddr_t)&mapping[ 0 ], mapping_cmp ) != NULL) + || avl_find( map->remap, (caddr_t)&mapping[ 1 ], mapping_cmp ) != NULL) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "duplicate mapping found." ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + goto error_return; + } + + if ( src[ 0 ] != '\0' ) { + avl_insert( &map->map, (caddr_t)&mapping[ 0 ], + mapping_cmp, mapping_dup ); + } + avl_insert( &map->remap, (caddr_t)&mapping[ 1 ], + mapping_cmp, mapping_dup ); + +success_return:; + return 0; + +error_return:; + if ( mapping ) { + ch_free( mapping[ 0 ].src.bv_val ); + ch_free( mapping[ 0 ].dst.bv_val ); + ch_free( mapping ); + } + + return 1; +} + + +#ifdef ENABLE_REWRITE +static char * +suffix_massage_regexize( const char *s ) +{ + char *res, *ptr; + const char *p, *r; + int i; + + if ( s[ 0 ] == '\0' ) { + return ch_strdup( "^(.+)$" ); + } + + for ( i = 0, p = s; + ( r = strchr( p, ',' ) ) != NULL; + p = r + 1, i++ ) + ; + + res = ch_calloc( sizeof( char ), + strlen( s ) + + STRLENOF( "((.+),)?" ) + + STRLENOF( "[ ]?" ) * i + + STRLENOF( "$" ) + 1 ); + + ptr = lutil_strcopy( res, "((.+),)?" ); + for ( i = 0, p = s; + ( r = strchr( p, ',' ) ) != NULL; + p = r + 1 , i++ ) { + ptr = lutil_strncopy( ptr, p, r - p + 1 ); + ptr = lutil_strcopy( ptr, "[ ]?" ); + + if ( r[ 1 ] == ' ' ) { + r++; + } + } + ptr = lutil_strcopy( ptr, p ); + ptr[ 0 ] = '$'; + ptr++; + ptr[ 0 ] = '\0'; + + return res; +} + +static char * +suffix_massage_patternize( const char *s, const char *p ) +{ + ber_len_t len; + char *res, *ptr; + + len = strlen( p ); + + if ( s[ 0 ] == '\0' ) { + len++; + } + + res = ch_calloc( sizeof( char ), len + STRLENOF( "%1" ) + 1 ); + if ( res == NULL ) { + return NULL; + } + + ptr = lutil_strcopy( res, ( p[ 0 ] == '\0' ? "%2" : "%1" ) ); + if ( s[ 0 ] == '\0' ) { + ptr[ 0 ] = ','; + ptr++; + } + lutil_strcopy( ptr, p ); + + return res; +} + +int +suffix_massage_config( + struct rewrite_info *info, + struct berval *pvnc, + struct berval *nvnc, + struct berval *prnc, + struct berval *nrnc +) +{ + char *rargv[ 5 ]; + int line = 0; + + rargv[ 0 ] = "rewriteEngine"; + rargv[ 1 ] = "on"; + rargv[ 2 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 2, rargv ); + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "default"; + rargv[ 2 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 2, rargv ); + + rargv[ 0 ] = "rewriteRule"; + rargv[ 1 ] = suffix_massage_regexize( pvnc->bv_val ); + rargv[ 2 ] = suffix_massage_patternize( pvnc->bv_val, prnc->bv_val ); + rargv[ 3 ] = ":"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + ch_free( rargv[ 1 ] ); + ch_free( rargv[ 2 ] ); + + if ( BER_BVISEMPTY( pvnc ) ) { + rargv[ 0 ] = "rewriteRule"; + rargv[ 1 ] = "^$"; + rargv[ 2 ] = prnc->bv_val; + rargv[ 3 ] = ":"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + } + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "searchEntryDN"; + rargv[ 2 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 2, rargv ); + + rargv[ 0 ] = "rewriteRule"; + rargv[ 1 ] = suffix_massage_regexize( prnc->bv_val ); + rargv[ 2 ] = suffix_massage_patternize( prnc->bv_val, pvnc->bv_val ); + rargv[ 3 ] = ":"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + ch_free( rargv[ 1 ] ); + ch_free( rargv[ 2 ] ); + + if ( BER_BVISEMPTY( prnc ) ) { + rargv[ 0 ] = "rewriteRule"; + rargv[ 1 ] = "^$"; + rargv[ 2 ] = pvnc->bv_val; + rargv[ 3 ] = ":"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + } + + /* backward compatibility */ + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "searchResult"; + rargv[ 2 ] = "alias"; + rargv[ 3 ] = "searchEntryDN"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "matchedDN"; + rargv[ 2 ] = "alias"; + rargv[ 3 ] = "searchEntryDN"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "searchAttrDN"; + rargv[ 2 ] = "alias"; + rargv[ 3 ] = "searchEntryDN"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + + /* NOTE: this corresponds to #undef'ining RWM_REFERRAL_REWRITE; + * see servers/slapd/overlays/rwm.h for details */ + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "referralAttrDN"; + rargv[ 2 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 2, rargv ); + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "referralDN"; + rargv[ 2 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 2, rargv ); + + return 0; +} +#endif /* ENABLE_REWRITE */ + diff --git a/servers/slapd/back-meta/conn.c b/servers/slapd/back-meta/conn.c new file mode 100644 index 0000000..ba87d01 --- /dev/null +++ b/servers/slapd/back-meta/conn.c @@ -0,0 +1,1910 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> + + +#define AVL_INTERNAL +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +/* + * meta_back_conndn_cmp + * + * compares two struct metaconn based on the value of the conn pointer + * and of the local DN; used by avl stuff + */ +int +meta_back_conndn_cmp( + const void *c1, + const void *c2 ) +{ + metaconn_t *mc1 = ( metaconn_t * )c1; + metaconn_t *mc2 = ( metaconn_t * )c2; + int rc; + + /* If local DNs don't match, it is definitely not a match */ + /* For shared sessions, conn is NULL. Only explicitly + * bound sessions will have non-NULL conn. + */ + rc = SLAP_PTRCMP( mc1->mc_conn, mc2->mc_conn ); + if ( rc == 0 ) { + rc = ber_bvcmp( &mc1->mc_local_ndn, &mc2->mc_local_ndn ); + } + + return rc; +} + +/* + * meta_back_conndnmc_cmp + * + * compares two struct metaconn based on the value of the conn pointer, + * the local DN and the struct pointer; used by avl stuff + */ +static int +meta_back_conndnmc_cmp( + const void *c1, + const void *c2 ) +{ + metaconn_t *mc1 = ( metaconn_t * )c1; + metaconn_t *mc2 = ( metaconn_t * )c2; + int rc; + + /* If local DNs don't match, it is definitely not a match */ + /* For shared sessions, conn is NULL. Only explicitly + * bound sessions will have non-NULL conn. + */ + rc = SLAP_PTRCMP( mc1->mc_conn, mc2->mc_conn ); + if ( rc == 0 ) { + rc = ber_bvcmp( &mc1->mc_local_ndn, &mc2->mc_local_ndn ); + if ( rc == 0 ) { + rc = SLAP_PTRCMP( mc1, mc2 ); + } + } + + return rc; +} + +/* + * meta_back_conn_cmp + * + * compares two struct metaconn based on the value of the conn pointer; + * used by avl stuff + */ +int +meta_back_conn_cmp( + const void *c1, + const void *c2 ) +{ + metaconn_t *mc1 = ( metaconn_t * )c1; + metaconn_t *mc2 = ( metaconn_t * )c2; + + /* For shared sessions, conn is NULL. Only explicitly + * bound sessions will have non-NULL conn. + */ + return SLAP_PTRCMP( mc1->mc_conn, mc2->mc_conn ); +} + +/* + * meta_back_conndn_dup + * + * returns -1 in case a duplicate struct metaconn has been inserted; + * used by avl stuff + */ +int +meta_back_conndn_dup( + void *c1, + void *c2 ) +{ + metaconn_t *mc1 = ( metaconn_t * )c1; + metaconn_t *mc2 = ( metaconn_t * )c2; + + /* Cannot have more than one shared session with same DN */ + if ( mc1->mc_conn == mc2->mc_conn && + dn_match( &mc1->mc_local_ndn, &mc2->mc_local_ndn ) ) + { + return -1; + } + + return 0; +} + +/* + * Debug stuff (got it from libavl) + */ +#if META_BACK_PRINT_CONNTREE > 0 +static void +meta_back_print( metaconn_t *mc, char *avlstr ) +{ + int i; + + fputs( "targets=[", stderr ); + for ( i = 0; i < mc->mc_info->mi_ntargets; i++ ) { + fputc( mc->mc_conns[ i ].msc_ld ? '*' : 'o', stderr); + } + fputc( ']', stderr ); + + fprintf( stderr, " mc=%p local=\"%s\" conn=%p refcnt=%d%s %s\n", + (void *)mc, + mc->mc_local_ndn.bv_val ? mc->mc_local_ndn.bv_val : "", + (void *)mc->mc_conn, + mc->mc_refcnt, + LDAP_BACK_CONN_TAINTED( mc ) ? " tainted" : "", + avlstr ); +} + +static void +meta_back_ravl_print( Avlnode *root, int depth ) +{ + int i; + + if ( root == 0 ) { + return; + } + + meta_back_ravl_print( root->avl_right, depth + 1 ); + + for ( i = 0; i < depth; i++ ) { + fprintf( stderr, "-" ); + } + fputc( ' ', stderr ); + + meta_back_print( (metaconn_t *)root->avl_data, + avl_bf2str( root->avl_bf ) ); + + meta_back_ravl_print( root->avl_left, depth + 1 ); +} + +/* NOTE: duplicate from back-ldap/bind.c */ +static char* priv2str[] = { + "privileged", + "privileged/TLS", + "anonymous", + "anonymous/TLS", + "bind", + "bind/TLS", + NULL +}; + +void +meta_back_print_conntree( metainfo_t *mi, char *msg ) +{ + int c; + + fprintf( stderr, "========> %s\n", msg ); + + for ( c = LDAP_BACK_PCONN_FIRST; c < LDAP_BACK_PCONN_LAST; c++ ) { + int i = 0; + metaconn_t *mc; + + fprintf( stderr, " %s[%d]\n", priv2str[ c ], mi->mi_conn_priv[ c ].mic_num ); + + LDAP_TAILQ_FOREACH( mc, &mi->mi_conn_priv[ c ].mic_priv, mc_q ) + { + fprintf( stderr, " [%d] ", i ); + meta_back_print( mc, "" ); + i++; + } + } + + if ( mi->mi_conninfo.lai_tree == NULL ) { + fprintf( stderr, "\t(empty)\n" ); + + } else { + meta_back_ravl_print( mi->mi_conninfo.lai_tree, 0 ); + } + + fprintf( stderr, "<======== %s\n", msg ); +} +#endif /* META_BACK_PRINT_CONNTREE */ +/* + * End of debug stuff + */ + +/* + * metaconn_alloc + * + * Allocates a connection structure, making room for all the referenced targets + */ +static metaconn_t * +metaconn_alloc( + Operation *op ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metaconn_t *mc; + int ntargets = mi->mi_ntargets; + + assert( ntargets > 0 ); + + /* malloc all in one */ + mc = ( metaconn_t * )ch_calloc( 1, sizeof( metaconn_t ) + + sizeof( metasingleconn_t ) * ( ntargets - 1 ) ); + if ( mc == NULL ) { + return NULL; + } + + mc->mc_info = mi; + + mc->mc_authz_target = META_BOUND_NONE; + mc->mc_refcnt = 1; + + return mc; +} + +/* + * meta_back_init_one_conn + * + * Initializes one connection + */ +int +meta_back_init_one_conn( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + int candidate, + int ispriv, + ldap_back_send_t sendok, + int dolock ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + int version; + dncookie dc; + int isauthz = ( candidate == mc->mc_authz_target ); + int do_return = 0; +#ifdef HAVE_TLS + int is_ldaps = 0; + int do_start_tls = 0; +#endif /* HAVE_TLS */ + + /* if the server is quarantined, and + * - the current interval did not expire yet, or + * - no more retries should occur, + * don't return the connection */ + if ( mt->mt_isquarantined ) { + slap_retry_info_t *ri = &mt->mt_quarantine; + int dont_retry = 0; + + if ( mt->mt_quarantine.ri_interval ) { + ldap_pvt_thread_mutex_lock( &mt->mt_quarantine_mutex ); + dont_retry = ( mt->mt_isquarantined > LDAP_BACK_FQ_NO ); + if ( dont_retry ) { + dont_retry = ( ri->ri_num[ ri->ri_idx ] == SLAP_RETRYNUM_TAIL + || slap_get_time() < ri->ri_last + ri->ri_interval[ ri->ri_idx ] ); + if ( !dont_retry ) { + if ( LogTest( LDAP_DEBUG_ANY ) ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "meta_back_init_one_conn[%d]: quarantine " + "retry block #%d try #%d", + candidate, ri->ri_idx, ri->ri_count ); + Debug( LDAP_DEBUG_ANY, "%s %s.\n", + op->o_log_prefix, buf, 0 ); + } + + mt->mt_isquarantined = LDAP_BACK_FQ_RETRYING; + } + + } + ldap_pvt_thread_mutex_unlock( &mt->mt_quarantine_mutex ); + } + + if ( dont_retry ) { + rs->sr_err = LDAP_UNAVAILABLE; + if ( op->o_conn && ( sendok & LDAP_BACK_SENDERR ) ) { + rs->sr_text = "Target is quarantined"; + send_ldap_result( op, rs ); + } + return rs->sr_err; + } + } + +retry_lock:; + if ( dolock ) { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + } + + /* + * Already init'ed + */ + if ( LDAP_BACK_CONN_ISBOUND( msc ) + || LDAP_BACK_CONN_ISANON( msc ) ) + { + assert( msc->msc_ld != NULL ); + rs->sr_err = LDAP_SUCCESS; + do_return = 1; + + } else if ( META_BACK_CONN_CREATING( msc ) + || LDAP_BACK_CONN_BINDING( msc ) ) + { + if ( !LDAP_BACK_USE_TEMPORARIES( mi ) ) { + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + + ldap_pvt_thread_yield(); + goto retry_lock; + } + + /* sounds more appropriate */ + rs->sr_err = LDAP_BUSY; + rs->sr_text = "No connections to target are available"; + do_return = 1; + + } else if ( META_BACK_CONN_INITED( msc ) ) { + assert( msc->msc_ld != NULL ); + rs->sr_err = LDAP_SUCCESS; + do_return = 1; + + } else { + /* + * creating... + */ + META_BACK_CONN_CREATING_SET( msc ); + } + + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + + if ( do_return ) { + if ( rs->sr_err != LDAP_SUCCESS + && op->o_conn + && ( sendok & LDAP_BACK_SENDERR ) ) + { + send_ldap_result( op, rs ); + } + + return rs->sr_err; + } + + assert( msc->msc_ld == NULL ); + + /* + * Attempts to initialize the connection to the target ds + */ + ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex ); + rs->sr_err = ldap_initialize( &msc->msc_ld, mt->mt_uri ); +#ifdef HAVE_TLS + is_ldaps = ldap_is_ldaps_url( mt->mt_uri ); +#endif /* HAVE_TLS */ + ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto error_return; + } + + /* + * Set LDAP version. This will always succeed: If the client + * bound with a particular version, then so can we. + */ + if ( mt->mt_version != 0 ) { + version = mt->mt_version; + + } else if ( op->o_conn->c_protocol != 0 ) { + version = op->o_conn->c_protocol; + + } else { + version = LDAP_VERSION3; + } + ldap_set_option( msc->msc_ld, LDAP_OPT_PROTOCOL_VERSION, &version ); + ldap_set_urllist_proc( msc->msc_ld, mt->mt_urllist_f, mt->mt_urllist_p ); + + /* automatically chase referrals ("chase-referrals [{yes|no}]" statement) */ + ldap_set_option( msc->msc_ld, LDAP_OPT_REFERRALS, + META_BACK_TGT_CHASE_REFERRALS( mt ) ? LDAP_OPT_ON : LDAP_OPT_OFF ); + + slap_client_keepalive(msc->msc_ld, &mt->mt_tls.sb_keepalive); + +#ifdef HAVE_TLS + { + slap_bindconf *sb = NULL; + + if ( ispriv ) { + sb = &mt->mt_idassert.si_bc; + } else { + sb = &mt->mt_tls; + } + + if ( sb->sb_tls_do_init ) { + bindconf_tls_set( sb, msc->msc_ld ); + } else if ( sb->sb_tls_ctx ) { + ldap_set_option( msc->msc_ld, LDAP_OPT_X_TLS_CTX, sb->sb_tls_ctx ); + } + + if ( !is_ldaps ) { + if ( sb == &mt->mt_idassert.si_bc && sb->sb_tls_ctx ) { + do_start_tls = 1; + + } else if ( META_BACK_TGT_USE_TLS( mt ) + || ( op->o_conn->c_is_tls && META_BACK_TGT_PROPAGATE_TLS( mt ) ) ) + { + do_start_tls = 1; + } + } + } + + /* start TLS ("tls [try-]{start|propagate}" statement) */ + if ( do_start_tls ) { +#ifdef SLAP_STARTTLS_ASYNCHRONOUS + /* + * use asynchronous StartTLS; in case, chase referral + * FIXME: OpenLDAP does not return referral on StartTLS yet + */ + int msgid; + + rs->sr_err = ldap_start_tls( msc->msc_ld, NULL, NULL, &msgid ); + if ( rs->sr_err == LDAP_SUCCESS ) { + LDAPMessage *res = NULL; + int rc, nretries = mt->mt_nretries; + struct timeval tv; + + LDAP_BACK_TV_SET( &tv ); + +retry:; + rc = ldap_result( msc->msc_ld, msgid, LDAP_MSG_ALL, &tv, &res ); + switch ( rc ) { + case -1: + rs->sr_err = LDAP_UNAVAILABLE; + rs->sr_text = "Remote server down"; + break; + + case 0: + if ( nretries != 0 ) { + if ( nretries > 0 ) { + nretries--; + } + LDAP_BACK_TV_SET( &tv ); + goto retry; + } + rs->sr_err = LDAP_OTHER; + rs->sr_text = "Timeout, no more retries"; + break; + + default: + /* only touch when activity actually took place... */ + if ( mi->mi_idle_timeout != 0 && msc->msc_time < op->o_time ) { + msc->msc_time = op->o_time; + } + break; + } + + if ( rc == LDAP_RES_EXTENDED ) { + struct berval *data = NULL; + + /* NOTE: right now, data is unused, so don't get it */ + rs->sr_err = ldap_parse_extended_result( msc->msc_ld, + res, NULL, NULL /* &data */ , 0 ); + if ( rs->sr_err == LDAP_SUCCESS ) { + int err; + + /* FIXME: matched? referrals? response controls? */ + rs->sr_err = ldap_parse_result( msc->msc_ld, + res, &err, NULL, NULL, NULL, NULL, 1 ); + res = NULL; + + if ( rs->sr_err == LDAP_SUCCESS ) { + rs->sr_err = err; + } + rs->sr_err = slap_map_api2result( rs ); + + /* FIXME: in case a referral + * is returned, should we try + * using it instead of the + * configured URI? */ + if ( rs->sr_err == LDAP_SUCCESS ) { + rs->sr_err = ldap_install_tls( msc->msc_ld ); + + } else if ( rs->sr_err == LDAP_REFERRAL ) { + /* FIXME: LDAP_OPERATIONS_ERROR? */ + rs->sr_err = LDAP_OTHER; + rs->sr_text = "Unwilling to chase referral " + "returned by Start TLS exop"; + } + + if ( data ) { + ber_bvfree( data ); + } + } + + } else { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "Unknown response to StartTLS request :" + " an ExtendedResponse is expected"; + } + + if ( res != NULL ) { + ldap_msgfree( res ); + } + } +#else /* ! SLAP_STARTTLS_ASYNCHRONOUS */ + /* + * use synchronous StartTLS + */ + rs->sr_err = ldap_start_tls_s( msc->msc_ld, NULL, NULL ); +#endif /* ! SLAP_STARTTLS_ASYNCHRONOUS */ + + /* if StartTLS is requested, only attempt it if the URL + * is not "ldaps://"; this may occur not only in case + * of misconfiguration, but also when used in the chain + * overlay, where the "uri" can be parsed out of a referral */ + if ( rs->sr_err == LDAP_SERVER_DOWN + || ( rs->sr_err != LDAP_SUCCESS + && META_BACK_TGT_TLS_CRITICAL( mt ) ) ) + { + +#ifdef DEBUG_205 + Debug( LDAP_DEBUG_ANY, + "### %s meta_back_init_one_conn(TLS) " + "ldap_unbind_ext[%d] ld=%p\n", + op->o_log_prefix, candidate, + (void *)msc->msc_ld ); +#endif /* DEBUG_205 */ + + /* need to trash a failed Start TLS */ + meta_clear_one_candidate( op, mc, candidate ); + goto error_return; + } + } +#endif /* HAVE_TLS */ + + /* + * Set the network timeout if set + */ + if ( mt->mt_network_timeout != 0 ) { + struct timeval network_timeout; + + network_timeout.tv_usec = 0; + network_timeout.tv_sec = mt->mt_network_timeout; + + ldap_set_option( msc->msc_ld, LDAP_OPT_NETWORK_TIMEOUT, + (void *)&network_timeout ); + } + + /* + * If the connection DN is not null, an attempt to rewrite it is made + */ + + if ( ispriv ) { + if ( !BER_BVISNULL( &mt->mt_idassert_authcDN ) ) { + ber_bvreplace( &msc->msc_bound_ndn, &mt->mt_idassert_authcDN ); + if ( !BER_BVISNULL( &mt->mt_idassert_passwd ) ) { + if ( !BER_BVISNULL( &msc->msc_cred ) ) { + memset( msc->msc_cred.bv_val, 0, + msc->msc_cred.bv_len ); + } + ber_bvreplace( &msc->msc_cred, &mt->mt_idassert_passwd ); + } + LDAP_BACK_CONN_ISIDASSERT_SET( msc ); + + } else { + ber_bvreplace( &msc->msc_bound_ndn, &slap_empty_bv ); + } + + } else { + if ( !BER_BVISNULL( &msc->msc_cred ) ) { + memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len ); + ber_memfree_x( msc->msc_cred.bv_val, NULL ); + BER_BVZERO( &msc->msc_cred ); + } + if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) { + ber_memfree_x( msc->msc_bound_ndn.bv_val, NULL ); + BER_BVZERO( &msc->msc_bound_ndn ); + } + if ( !BER_BVISEMPTY( &op->o_ndn ) + && SLAP_IS_AUTHZ_BACKEND( op ) + && isauthz ) + { + dc.target = mt; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "bindDN"; + + /* + * Rewrite the bind dn if needed + */ + if ( ldap_back_dn_massage( &dc, &op->o_conn->c_dn, + &msc->msc_bound_ndn ) ) + { + +#ifdef DEBUG_205 + Debug( LDAP_DEBUG_ANY, + "### %s meta_back_init_one_conn(rewrite) " + "ldap_unbind_ext[%d] ld=%p\n", + op->o_log_prefix, candidate, + (void *)msc->msc_ld ); +#endif /* DEBUG_205 */ + + /* need to trash a connection not fully established */ + meta_clear_one_candidate( op, mc, candidate ); + goto error_return; + } + + /* copy the DN if needed */ + if ( msc->msc_bound_ndn.bv_val == op->o_conn->c_dn.bv_val ) { + ber_dupbv( &msc->msc_bound_ndn, &op->o_conn->c_dn ); + } + + assert( !BER_BVISNULL( &msc->msc_bound_ndn ) ); + + } else { + ber_dupbv( &msc->msc_bound_ndn, (struct berval *)&slap_empty_bv ); + } + } + + assert( !BER_BVISNULL( &msc->msc_bound_ndn ) ); + +error_return:; + if ( dolock ) { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + } + META_BACK_CONN_CREATING_CLEAR( msc ); + if ( rs->sr_err == LDAP_SUCCESS ) { + /* + * Sets a cookie for the rewrite session + */ + ( void )rewrite_session_init( mt->mt_rwmap.rwm_rw, op->o_conn ); + META_BACK_CONN_INITED_SET( msc ); + } + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + + if ( rs->sr_err != LDAP_SUCCESS ) { + /* Get the error message and print it in TRACE mode */ + if ( LogTest( LDAP_DEBUG_TRACE ) ) { + Log4( LDAP_DEBUG_TRACE, ldap_syslog_level, "%s: meta_back_init_one_conn[%d] failed err=%d text=%s\n", + op->o_log_prefix, candidate, rs->sr_err, rs->sr_text ); + } + + rs->sr_err = slap_map_api2result( rs ); + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + } + + return rs->sr_err; +} + +/* + * meta_back_retry + * + * Retries one connection + */ +int +meta_back_retry( + Operation *op, + SlapReply *rs, + metaconn_t **mcp, + int candidate, + ldap_back_send_t sendok ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + metaconn_t *mc = *mcp; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + int rc = LDAP_UNAVAILABLE, + binding, + quarantine = 1; + + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + + assert( !META_BACK_CONN_CREATING( msc ) ); + binding = LDAP_BACK_CONN_BINDING( msc ); + LDAP_BACK_CONN_BINDING_CLEAR( msc ); + + assert( mc->mc_refcnt > 0 ); + if ( mc->mc_refcnt == 1 ) { + struct berval save_cred; + + if ( LogTest( LDAP_DEBUG_ANY ) ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + /* this lock is required; however, + * it's invoked only when logging is on */ + ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex ); + snprintf( buf, sizeof( buf ), + "retrying URI=\"%s\" DN=\"%s\"", + mt->mt_uri, + BER_BVISNULL( &msc->msc_bound_ndn ) ? + "" : msc->msc_bound_ndn.bv_val ); + ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex ); + + Debug( LDAP_DEBUG_ANY, + "%s meta_back_retry[%d]: %s.\n", + op->o_log_prefix, candidate, buf ); + } + + /* save credentials, if any, for later use; + * meta_clear_one_candidate() would free them */ + save_cred = msc->msc_cred; + BER_BVZERO( &msc->msc_cred ); + + meta_clear_one_candidate( op, mc, candidate ); + LDAP_BACK_CONN_ISBOUND_CLEAR( msc ); + + ( void )rewrite_session_delete( mt->mt_rwmap.rwm_rw, op->o_conn ); + + /* mc here must be the regular mc, reset and ready for init */ + rc = meta_back_init_one_conn( op, rs, mc, candidate, + LDAP_BACK_CONN_ISPRIV( mc ), sendok, 0 ); + + /* restore credentials, if any and if needed; + * meta_back_init_one_conn() restores msc_bound_ndn, if any; + * if no msc_bound_ndn is restored, destroy credentials */ + if ( !BER_BVISNULL( &msc->msc_bound_ndn ) + && BER_BVISNULL( &msc->msc_cred ) ) + { + msc->msc_cred = save_cred; + + } else if ( !BER_BVISNULL( &save_cred ) ) { + memset( save_cred.bv_val, 0, save_cred.bv_len ); + ber_memfree_x( save_cred.bv_val, NULL ); + } + + /* restore the "binding" flag, in case */ + if ( binding ) { + LDAP_BACK_CONN_BINDING_SET( msc ); + } + + if ( rc == LDAP_SUCCESS ) { + quarantine = 0; + LDAP_BACK_CONN_BINDING_SET( msc ); binding = 1; + rc = meta_back_single_dobind( op, rs, mcp, candidate, + sendok, mt->mt_nretries, 0 ); + + Debug( LDAP_DEBUG_ANY, + "%s meta_back_retry[%d]: " + "meta_back_single_dobind=%d\n", + op->o_log_prefix, candidate, rc ); + if ( rc == LDAP_SUCCESS ) { + if ( !BER_BVISNULL( &msc->msc_bound_ndn ) && + !BER_BVISEMPTY( &msc->msc_bound_ndn ) ) + { + LDAP_BACK_CONN_ISBOUND_SET( msc ); + + } else { + LDAP_BACK_CONN_ISANON_SET( msc ); + } + + /* when bound, dispose of the "binding" flag */ + if ( binding ) { + LDAP_BACK_CONN_BINDING_CLEAR( msc ); + } + } + } + +#if 0 /* ITS#7591, following stmt drops needed result msgs */ + /* don't send twice */ + sendok &= ~LDAP_BACK_SENDERR; +#endif + } + + if ( rc != LDAP_SUCCESS ) { + SlapReply *candidates = meta_back_candidates_get( op ); + + candidates[ candidate ].sr_err = rc; + + if ( *mcp != NULL ) { + if ( mc->mc_refcnt == 1 ) { + if ( binding ) { + LDAP_BACK_CONN_BINDING_CLEAR( msc ); + } + (void)meta_clear_one_candidate( op, mc, candidate ); + } + + LDAP_BACK_CONN_TAINTED_SET( mc ); + /* only release if mandatory; otherwise + * let the caller do what's best before + * releasing */ + if ( META_BACK_ONERR_STOP( mi ) ) { + meta_back_release_conn_lock( mi, mc, 0 ); + *mcp = NULL; + + } else { +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, ">>> meta_back_retry" ); +#endif /* META_BACK_PRINT_CONNTREE */ + + /* FIXME: could be done better, reworking meta_back_release_conn_lock() */ + if ( LDAP_BACK_PCONN_ISPRIV( mc ) ) { + if ( mc->mc_q.tqe_prev != NULL ) { + assert( LDAP_BACK_CONN_CACHED( mc ) ); + assert( mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num > 0 ); + LDAP_TAILQ_REMOVE( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv, + mc, mc_q ); + mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num--; + LDAP_TAILQ_ENTRY_INIT( mc, mc_q ); + + } else { + assert( !LDAP_BACK_CONN_CACHED( mc ) ); + } + + } else { + /* FIXME: check if in tree, for consistency? */ + (void)avl_delete( &mi->mi_conninfo.lai_tree, + ( caddr_t )mc, meta_back_conndnmc_cmp ); + } + LDAP_BACK_CONN_CACHED_CLEAR( mc ); + +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, "<<< meta_back_retry" ); +#endif /* META_BACK_PRINT_CONNTREE */ + } + } + + if ( sendok & LDAP_BACK_SENDERR ) { + rs->sr_err = rc; + rs->sr_text = "Unable to retry"; + send_ldap_result( op, rs ); + } + } + + if ( quarantine && META_BACK_TGT_QUARANTINE( mt ) ) { + meta_back_quarantine( op, rs, candidate ); + } + + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + + return rc == LDAP_SUCCESS ? 1 : 0; +} + +/* + * callback for unique candidate selection + */ +static int +meta_back_conn_cb( Operation *op, SlapReply *rs ) +{ + assert( op->o_tag == LDAP_REQ_SEARCH ); + + switch ( rs->sr_type ) { + case REP_SEARCH: + ((long *)op->o_callback->sc_private)[0] = (long)op->o_private; + break; + + case REP_SEARCHREF: + case REP_RESULT: + break; + + default: + return rs->sr_err; + } + + return 0; +} + + +static int +meta_back_get_candidate( + Operation *op, + SlapReply *rs, + struct berval *ndn ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + long candidate; + + /* + * tries to get a unique candidate + * (takes care of default target) + */ + candidate = meta_back_select_unique_candidate( mi, ndn ); + + /* + * if any is found, inits the connection + */ + if ( candidate == META_TARGET_NONE ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = "No suitable candidate target found"; + + } else if ( candidate == META_TARGET_MULTIPLE ) { + Operation op2 = *op; + SlapReply rs2 = { REP_RESULT }; + slap_callback cb2 = { 0 }; + int rc; + + /* try to get a unique match for the request ndn + * among the multiple candidates available */ + op2.o_tag = LDAP_REQ_SEARCH; + op2.o_req_dn = *ndn; + op2.o_req_ndn = *ndn; + op2.ors_scope = LDAP_SCOPE_BASE; + op2.ors_deref = LDAP_DEREF_NEVER; + op2.ors_attrs = slap_anlist_no_attrs; + op2.ors_attrsonly = 0; + op2.ors_limit = NULL; + op2.ors_slimit = 1; + op2.ors_tlimit = SLAP_NO_LIMIT; + + op2.ors_filter = (Filter *)slap_filter_objectClass_pres; + op2.ors_filterstr = *slap_filterstr_objectClass_pres; + + op2.o_callback = &cb2; + cb2.sc_response = meta_back_conn_cb; + cb2.sc_private = (void *)&candidate; + + rc = op->o_bd->be_search( &op2, &rs2 ); + + switch ( rs2.sr_err ) { + case LDAP_SUCCESS: + default: + rs->sr_err = rs2.sr_err; + break; + + case LDAP_SIZELIMIT_EXCEEDED: + /* if multiple candidates can serve the operation, + * and a default target is defined, and it is + * a candidate, try using it (FIXME: YMMV) */ + if ( mi->mi_defaulttarget != META_DEFAULT_TARGET_NONE + && meta_back_is_candidate( mi->mi_targets[ mi->mi_defaulttarget ], + ndn, op->o_tag == LDAP_REQ_SEARCH ? op->ors_scope : LDAP_SCOPE_BASE ) ) + { + candidate = mi->mi_defaulttarget; + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + + } else { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "Unable to select unique candidate target"; + } + break; + } + + } else { + rs->sr_err = LDAP_SUCCESS; + } + + return candidate; +} + +static void *meta_back_candidates_dummy; + +static void +meta_back_candidates_keyfree( + void *key, + void *data ) +{ + metacandidates_t *mc = (metacandidates_t *)data; + + ber_memfree_x( mc->mc_candidates, NULL ); + ber_memfree_x( data, NULL ); +} + +SlapReply * +meta_back_candidates_get( Operation *op ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metacandidates_t *mc; + + if ( op->o_threadctx ) { + void *data = NULL; + + ldap_pvt_thread_pool_getkey( op->o_threadctx, + &meta_back_candidates_dummy, &data, NULL ); + mc = (metacandidates_t *)data; + + } else { + mc = mi->mi_candidates; + } + + if ( mc == NULL ) { + mc = ch_calloc( sizeof( metacandidates_t ), 1 ); + mc->mc_ntargets = mi->mi_ntargets; + mc->mc_candidates = ch_calloc( sizeof( SlapReply ), mc->mc_ntargets ); + if ( op->o_threadctx ) { + void *data = NULL; + + data = (void *)mc; + ldap_pvt_thread_pool_setkey( op->o_threadctx, + &meta_back_candidates_dummy, data, + meta_back_candidates_keyfree, + NULL, NULL ); + + } else { + mi->mi_candidates = mc; + } + + } else if ( mc->mc_ntargets < mi->mi_ntargets ) { + /* NOTE: in the future, may want to allow back-config + * to add/remove targets from back-meta... */ + mc->mc_candidates = ch_realloc( mc->mc_candidates, + sizeof( SlapReply ) * mi->mi_ntargets ); + memset( &mc->mc_candidates[ mc->mc_ntargets ], 0, + sizeof( SlapReply ) * ( mi->mi_ntargets - mc->mc_ntargets ) ); + mc->mc_ntargets = mi->mi_ntargets; + } + + return mc->mc_candidates; +} + +/* + * meta_back_getconn + * + * Prepares the connection structure + * + * RATIONALE: + * + * - determine what DN is being requested: + * + * op requires candidate checks + * + * add unique parent of o_req_ndn + * bind unique^*[/all] o_req_ndn [no check] + * compare unique^+ o_req_ndn + * delete unique o_req_ndn + * modify unique o_req_ndn + * search any o_req_ndn + * modrdn unique[, unique] o_req_ndn[, orr_nnewSup] + * + * - for ops that require the candidate to be unique, in case of multiple + * occurrences an internal search with sizeLimit=1 is performed + * if a unique candidate can actually be determined. If none is found, + * the operation aborts; if multiple are found, the default target + * is used if defined and candidate; otherwise the operation aborts. + * + * *^note: actually, the bind operation is handled much like a search; + * i.e. the bind is broadcast to all candidate targets. + * + * +^note: actually, the compare operation is handled much like a search; + * i.e. the compare is broadcast to all candidate targets, while checking + * that exactly none (noSuchObject) or one (TRUE/FALSE/UNDEFINED) is + * returned. + */ +metaconn_t * +meta_back_getconn( + Operation *op, + SlapReply *rs, + int *candidate, + ldap_back_send_t sendok ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metaconn_t *mc = NULL, + mc_curr = {{ 0 }}; + int cached = META_TARGET_NONE, + i = META_TARGET_NONE, + err = LDAP_SUCCESS, + new_conn = 0, + ncandidates = 0; + + + meta_op_type op_type = META_OP_REQUIRE_SINGLE; + enum { + META_DNTYPE_ENTRY, + META_DNTYPE_PARENT, + META_DNTYPE_NEWPARENT + } dn_type = META_DNTYPE_ENTRY; + struct berval ndn = op->o_req_ndn, + pndn; + + SlapReply *candidates = meta_back_candidates_get( op ); + + /* Internal searches are privileged and shared. So is root. */ + if ( ( !BER_BVISEMPTY( &op->o_ndn ) && META_BACK_PROXYAUTHZ_ALWAYS( mi ) ) + || ( BER_BVISEMPTY( &op->o_ndn ) && META_BACK_PROXYAUTHZ_ANON( mi ) ) + || op->o_do_not_cache || be_isroot( op ) ) + { + LDAP_BACK_CONN_ISPRIV_SET( &mc_curr ); + mc_curr.mc_local_ndn = op->o_bd->be_rootndn; + LDAP_BACK_PCONN_ROOTDN_SET( &mc_curr, op ); + + } else if ( BER_BVISEMPTY( &op->o_ndn ) && META_BACK_PROXYAUTHZ_NOANON( mi ) ) + { + LDAP_BACK_CONN_ISANON_SET( &mc_curr ); + BER_BVSTR( &mc_curr.mc_local_ndn, "" ); + LDAP_BACK_PCONN_ANON_SET( &mc_curr, op ); + + } else { + mc_curr.mc_local_ndn = op->o_ndn; + + /* Explicit binds must not be shared */ + if ( !BER_BVISEMPTY( &op->o_ndn ) + || op->o_tag == LDAP_REQ_BIND + || SLAP_IS_AUTHZ_BACKEND( op ) ) + { + mc_curr.mc_conn = op->o_conn; + + } else { + LDAP_BACK_CONN_ISANON_SET( &mc_curr ); + LDAP_BACK_PCONN_ANON_SET( &mc_curr, op ); + } + } + + /* Explicit Bind requests always get their own conn */ + if ( sendok & LDAP_BACK_BINDING ) { + mc_curr.mc_conn = op->o_conn; + + } else { + /* Searches for a metaconn in the avl tree */ +retry_lock:; + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + if ( LDAP_BACK_PCONN_ISPRIV( &mc_curr ) ) { + /* lookup a conn that's not binding */ + LDAP_TAILQ_FOREACH( mc, + &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( &mc_curr ) ].mic_priv, + mc_q ) + { + if ( !LDAP_BACK_CONN_BINDING( mc ) && mc->mc_refcnt == 0 ) { + break; + } + } + + if ( mc != NULL ) { + /* move to tail of queue */ + if ( mc != LDAP_TAILQ_LAST( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv, + mc_conn_priv_q ) ) + { + LDAP_TAILQ_REMOVE( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv, + mc, mc_q ); + LDAP_TAILQ_ENTRY_INIT( mc, mc_q ); + LDAP_TAILQ_INSERT_TAIL( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv, + mc, mc_q ); + } + + } else if ( !LDAP_BACK_USE_TEMPORARIES( mi ) + && mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( &mc_curr ) ].mic_num == mi->mi_conn_priv_max ) + { + mc = LDAP_TAILQ_FIRST( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( &mc_curr ) ].mic_priv ); + } + + + } else { + mc = (metaconn_t *)avl_find( mi->mi_conninfo.lai_tree, + (caddr_t)&mc_curr, meta_back_conndn_cmp ); + } + + if ( mc ) { + /* catch taint errors */ + assert( !LDAP_BACK_CONN_TAINTED( mc ) ); + + /* Don't reuse connections while they're still binding + * NOTE: only makes sense for binds */ + if ( LDAP_BACK_CONN_BINDING( mc ) ) { + if ( !LDAP_BACK_USE_TEMPORARIES( mi ) ) { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + + ldap_pvt_thread_yield(); + goto retry_lock; + } + + /* release conn, and create a temporary */ + mc = NULL; + + } else { + if ( mc->mc_refcnt == 0 && (( mi->mi_conn_ttl != 0 && op->o_time > mc->mc_create_time + mi->mi_conn_ttl ) + || ( mi->mi_idle_timeout != 0 && op->o_time > mc->mc_time + mi->mi_idle_timeout )) ) + { +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, + ">>> meta_back_getconn(expired)" ); +#endif /* META_BACK_PRINT_CONNTREE */ + + /* don't let anyone else use this expired connection */ + if ( LDAP_BACK_PCONN_ISPRIV( mc ) ) { + if ( mc->mc_q.tqe_prev != NULL ) { + assert( LDAP_BACK_CONN_CACHED( mc ) ); + assert( mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num > 0 ); + LDAP_TAILQ_REMOVE( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv, + mc, mc_q ); + mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num--; + LDAP_TAILQ_ENTRY_INIT( mc, mc_q ); + + } else { + assert( !LDAP_BACK_CONN_CACHED( mc ) ); + } + + } else { + (void)avl_delete( &mi->mi_conninfo.lai_tree, + (caddr_t)mc, meta_back_conndnmc_cmp ); + } + +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, + "<<< meta_back_getconn(expired)" ); +#endif /* META_BACK_PRINT_CONNTREE */ + LDAP_BACK_CONN_TAINTED_SET( mc ); + LDAP_BACK_CONN_CACHED_CLEAR( mc ); + + if ( LogTest( LDAP_DEBUG_TRACE ) ) { + char buf[STRLENOF("4294967295U") + 1] = { 0 }; + mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) ); + + Debug( LDAP_DEBUG_TRACE, + "%s meta_back_getconn: mc=%p conn=%s expired (tainted).\n", + op->o_log_prefix, (void *)mc, buf ); + } + } + + mc->mc_refcnt++; + } + } + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + + switch ( op->o_tag ) { + case LDAP_REQ_ADD: + /* if we go to selection, the entry must not exist, + * and we must be able to resolve the parent */ + dn_type = META_DNTYPE_PARENT; + dnParent( &ndn, &pndn ); + break; + + case LDAP_REQ_MODRDN: + /* if nnewSuperior is not NULL, it must resolve + * to the same candidate as the req_ndn */ + if ( op->orr_nnewSup ) { + dn_type = META_DNTYPE_NEWPARENT; + } + break; + + case LDAP_REQ_BIND: + /* if bound as rootdn, the backend must bind to all targets + * with the administrative identity + * (unless pseoudoroot-bind-defer is TRUE) */ + if ( op->orb_method == LDAP_AUTH_SIMPLE && be_isroot_pw( op ) ) { + op_type = META_OP_REQUIRE_ALL; + } + break; + + case LDAP_REQ_COMPARE: + case LDAP_REQ_DELETE: + case LDAP_REQ_MODIFY: + /* just a unique candidate */ + break; + + case LDAP_REQ_SEARCH: + /* allow multiple candidates for the searchBase */ + op_type = META_OP_ALLOW_MULTIPLE; + break; + + default: + /* right now, just break (exop?) */ + break; + } + + /* + * require all connections ... + */ + if ( op_type == META_OP_REQUIRE_ALL ) { + + /* Looks like we didn't get a bind. Open a new session... */ + if ( mc == NULL ) { + assert( new_conn == 0 ); + mc = metaconn_alloc( op ); + mc->mc_conn = mc_curr.mc_conn; + ber_dupbv( &mc->mc_local_ndn, &mc_curr.mc_local_ndn ); + new_conn = 1; + if ( sendok & LDAP_BACK_BINDING ) { + LDAP_BACK_CONN_BINDING_SET( mc ); + } + if ( LDAP_BACK_CONN_ISPRIV( &mc_curr ) ) { + LDAP_BACK_CONN_ISPRIV_SET( mc ); + + } else if ( LDAP_BACK_CONN_ISANON( &mc_curr ) ) { + LDAP_BACK_CONN_ISANON_SET( mc ); + } + + } else if ( 0 ) { + /* TODO: if any of the connections is binding, + * release mc and create a new one */ + } + + for ( i = 0; i < mi->mi_ntargets; i++ ) { + /* + * The target is activated; if needed, it is + * also init'd + */ + candidates[ i ].sr_err = meta_back_init_one_conn( op, + rs, mc, i, LDAP_BACK_CONN_ISPRIV( &mc_curr ), + LDAP_BACK_DONTSEND, !new_conn ); + if ( candidates[ i ].sr_err == LDAP_SUCCESS ) { + if ( new_conn && ( sendok & LDAP_BACK_BINDING ) ) { + LDAP_BACK_CONN_BINDING_SET( &mc->mc_conns[ i ] ); + } + META_CANDIDATE_SET( &candidates[ i ] ); + ncandidates++; + + } else { + + /* + * FIXME: in case one target cannot + * be init'd, should the other ones + * be tried? + */ + META_CANDIDATE_RESET( &candidates[ i ] ); + err = candidates[ i ].sr_err; + continue; + } + } + + if ( ncandidates == 0 ) { + if ( new_conn ) { + mc->mc_refcnt = 0; + meta_back_conn_free( mc ); + + } else { + meta_back_release_conn( mi, mc ); + } + + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = "Unable to select valid candidates"; + + if ( sendok & LDAP_BACK_SENDERR ) { + if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) { + rs->sr_matched = op->o_bd->be_suffix[ 0 ].bv_val; + } + send_ldap_result( op, rs ); + rs->sr_matched = NULL; + } + + return NULL; + } + + goto done; + } + + /* + * looks in cache, if any + */ + if ( mi->mi_cache.ttl != META_DNCACHE_DISABLED ) { + cached = i = meta_dncache_get_target( &mi->mi_cache, &op->o_req_ndn ); + } + + if ( op_type == META_OP_REQUIRE_SINGLE ) { + metatarget_t *mt = NULL; + metasingleconn_t *msc = NULL; + + int j; + + for ( j = 0; j < mi->mi_ntargets; j++ ) { + META_CANDIDATE_RESET( &candidates[ j ] ); + } + + /* + * tries to get a unique candidate + * (takes care of default target) + */ + if ( i == META_TARGET_NONE ) { + i = meta_back_get_candidate( op, rs, &ndn ); + + if ( rs->sr_err == LDAP_NO_SUCH_OBJECT && dn_type == META_DNTYPE_PARENT ) { + i = meta_back_get_candidate( op, rs, &pndn ); + } + + if ( i < 0 || rs->sr_err != LDAP_SUCCESS ) { + if ( mc != NULL ) { + meta_back_release_conn( mi, mc ); + } + + if ( sendok & LDAP_BACK_SENDERR ) { + if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) { + rs->sr_matched = op->o_bd->be_suffix[ 0 ].bv_val; + } + send_ldap_result( op, rs ); + rs->sr_matched = NULL; + } + + return NULL; + } + } + + if ( dn_type == META_DNTYPE_NEWPARENT && meta_back_get_candidate( op, rs, op->orr_nnewSup ) != i ) + { + if ( mc != NULL ) { + meta_back_release_conn( mi, mc ); + } + + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "Cross-target rename not supported"; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + + return NULL; + } + + Debug( LDAP_DEBUG_TRACE, + "==>meta_back_getconn: got target=%d for ndn=\"%s\" from cache\n", + i, op->o_req_ndn.bv_val, 0 ); + + if ( mc == NULL ) { + /* Retries searching for a metaconn in the avl tree + * the reason is that the connection might have been + * created by meta_back_get_candidate() */ + if ( !( sendok & LDAP_BACK_BINDING ) ) { +retry_lock2:; + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + mc = (metaconn_t *)avl_find( mi->mi_conninfo.lai_tree, + (caddr_t)&mc_curr, meta_back_conndn_cmp ); + if ( mc != NULL ) { + /* catch taint errors */ + assert( !LDAP_BACK_CONN_TAINTED( mc ) ); + + /* Don't reuse connections while they're still binding */ + if ( META_BACK_CONN_CREATING( &mc->mc_conns[ i ] ) + || LDAP_BACK_CONN_BINDING( &mc->mc_conns[ i ] ) ) + { + if ( !LDAP_BACK_USE_TEMPORARIES( mi ) ) { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + ldap_pvt_thread_yield(); + goto retry_lock2; + } + + mc = NULL; + + } else { + mc->mc_refcnt++; + } + } + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + + /* Looks like we didn't get a bind. Open a new session... */ + if ( mc == NULL ) { + assert( new_conn == 0 ); + mc = metaconn_alloc( op ); + mc->mc_conn = mc_curr.mc_conn; + ber_dupbv( &mc->mc_local_ndn, &mc_curr.mc_local_ndn ); + new_conn = 1; + if ( sendok & LDAP_BACK_BINDING ) { + LDAP_BACK_CONN_BINDING_SET( mc ); + } + if ( LDAP_BACK_CONN_ISPRIV( &mc_curr ) ) { + LDAP_BACK_CONN_ISPRIV_SET( mc ); + + } else if ( LDAP_BACK_CONN_ISANON( &mc_curr ) ) { + LDAP_BACK_CONN_ISANON_SET( mc ); + } + } + } + + /* + * Clear all other candidates + */ + ( void )meta_clear_unused_candidates( op, i ); + + mt = mi->mi_targets[ i ]; + msc = &mc->mc_conns[ i ]; + + /* + * The target is activated; if needed, it is + * also init'd. In case of error, meta_back_init_one_conn + * sends the appropriate result. + */ + err = meta_back_init_one_conn( op, rs, mc, i, + LDAP_BACK_CONN_ISPRIV( &mc_curr ), sendok, !new_conn ); + if ( err != LDAP_SUCCESS ) { + /* + * FIXME: in case one target cannot + * be init'd, should the other ones + * be tried? + */ + META_CANDIDATE_RESET( &candidates[ i ] ); + if ( new_conn ) { + mc->mc_refcnt = 0; + meta_back_conn_free( mc ); + + } else { + meta_back_release_conn( mi, mc ); + } + return NULL; + } + + if ( new_conn && ( sendok & LDAP_BACK_BINDING ) ) { + LDAP_BACK_CONN_BINDING_SET( &mc->mc_conns[ i ] ); + } + + candidates[ i ].sr_err = LDAP_SUCCESS; + META_CANDIDATE_SET( &candidates[ i ] ); + ncandidates++; + + if ( candidate ) { + *candidate = i; + } + + /* + * if no unique candidate ... + */ + } else { + + /* Looks like we didn't get a bind. Open a new session... */ + if ( mc == NULL ) { + assert( new_conn == 0 ); + mc = metaconn_alloc( op ); + mc->mc_conn = mc_curr.mc_conn; + ber_dupbv( &mc->mc_local_ndn, &mc_curr.mc_local_ndn ); + new_conn = 1; + if ( LDAP_BACK_CONN_ISPRIV( &mc_curr ) ) { + LDAP_BACK_CONN_ISPRIV_SET( mc ); + + } else if ( LDAP_BACK_CONN_ISANON( &mc_curr ) ) { + LDAP_BACK_CONN_ISANON_SET( mc ); + } + } + + for ( i = 0; i < mi->mi_ntargets; i++ ) { + metatarget_t *mt = mi->mi_targets[ i ]; + + META_CANDIDATE_RESET( &candidates[ i ] ); + + if ( i == cached + || meta_back_is_candidate( mt, &op->o_req_ndn, + op->o_tag == LDAP_REQ_SEARCH ? op->ors_scope : LDAP_SCOPE_SUBTREE ) ) + { + + /* + * The target is activated; if needed, it is + * also init'd + */ + int lerr = meta_back_init_one_conn( op, rs, mc, i, + LDAP_BACK_CONN_ISPRIV( &mc_curr ), + LDAP_BACK_DONTSEND, !new_conn ); + candidates[ i ].sr_err = lerr; + if ( lerr == LDAP_SUCCESS ) { + META_CANDIDATE_SET( &candidates[ i ] ); + ncandidates++; + + Debug( LDAP_DEBUG_TRACE, "%s: meta_back_getconn[%d]\n", + op->o_log_prefix, i, 0 ); + + } else if ( lerr == LDAP_UNAVAILABLE && !META_BACK_ONERR_STOP( mi ) ) { + META_CANDIDATE_SET( &candidates[ i ] ); + + Debug( LDAP_DEBUG_TRACE, "%s: meta_back_getconn[%d] %s\n", + op->o_log_prefix, i, + mt->mt_isquarantined != LDAP_BACK_FQ_NO ? "quarantined" : "unavailable" ); + + } else { + + /* + * FIXME: in case one target cannot + * be init'd, should the other ones + * be tried? + */ + if ( new_conn ) { + ( void )meta_clear_one_candidate( op, mc, i ); + } + /* leave the target candidate, but record the error for later use */ + err = lerr; + + if ( lerr == LDAP_UNAVAILABLE && mt->mt_isquarantined != LDAP_BACK_FQ_NO ) { + Log4( LDAP_DEBUG_TRACE, ldap_syslog_level, "%s: meta_back_getconn[%d] quarantined err=%d text=%s\n", + op->o_log_prefix, i, lerr, rs->sr_text ); + + } else { + Log4( LDAP_DEBUG_ANY, ldap_syslog, "%s: meta_back_getconn[%d] failed err=%d text=%s\n", + op->o_log_prefix, i, lerr, rs->sr_text ); + } + + if ( META_BACK_ONERR_STOP( mi ) ) { + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + if ( new_conn ) { + mc->mc_refcnt = 0; + meta_back_conn_free( mc ); + + } else { + meta_back_release_conn( mi, mc ); + } + + return NULL; + } + + continue; + } + + } else { + if ( new_conn ) { + ( void )meta_clear_one_candidate( op, mc, i ); + } + } + } + + if ( ncandidates == 0 ) { + if ( new_conn ) { + mc->mc_refcnt = 0; + meta_back_conn_free( mc ); + + } else { + meta_back_release_conn( mi, mc ); + } + + if ( rs->sr_err == LDAP_SUCCESS ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = "Unable to select valid candidates"; + } + + if ( sendok & LDAP_BACK_SENDERR ) { + if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) { + rs->sr_matched = op->o_bd->be_suffix[ 0 ].bv_val; + } + send_ldap_result( op, rs ); + rs->sr_matched = NULL; + } + + return NULL; + } + } + +done:; + /* clear out meta_back_init_one_conn non-fatal errors */ + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + + /* touch the timestamp */ + if ( mi->mi_idle_timeout != 0 ) { + mc->mc_time = op->o_time; + } + + if ( new_conn ) { + if ( mi->mi_conn_ttl ) { + mc->mc_create_time = op->o_time; + } + + /* + * Inserts the newly created metaconn in the avl tree + */ + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, ">>> meta_back_getconn" ); +#endif /* META_BACK_PRINT_CONNTREE */ + + err = 0; + if ( LDAP_BACK_PCONN_ISPRIV( mc ) ) { + if ( mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num < mi->mi_conn_priv_max ) { + LDAP_TAILQ_INSERT_TAIL( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv, mc, mc_q ); + mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num++; + LDAP_BACK_CONN_CACHED_SET( mc ); + + } else { + LDAP_BACK_CONN_TAINTED_SET( mc ); + } + rs->sr_err = 0; + + } else if ( !( sendok & LDAP_BACK_BINDING ) ) { + err = avl_insert( &mi->mi_conninfo.lai_tree, ( caddr_t )mc, + meta_back_conndn_cmp, meta_back_conndn_dup ); + LDAP_BACK_CONN_CACHED_SET( mc ); + } + +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, "<<< meta_back_getconn" ); +#endif /* META_BACK_PRINT_CONNTREE */ + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + + if ( !LDAP_BACK_PCONN_ISPRIV( mc ) ) { + /* + * Err could be -1 in case a duplicate metaconn is inserted + */ + switch ( err ) { + case 0: + break; + + case -1: + LDAP_BACK_CONN_CACHED_CLEAR( mc ); + /* duplicate: free and try to get the newly created one */ + if ( !( sendok & LDAP_BACK_BINDING ) && !LDAP_BACK_USE_TEMPORARIES( mi ) ) { + mc->mc_refcnt = 0; + meta_back_conn_free( mc ); + + new_conn = 0; + goto retry_lock; + } + + LDAP_BACK_CONN_TAINTED_SET( mc ); + break; + + default: + LDAP_BACK_CONN_CACHED_CLEAR( mc ); + if ( LogTest( LDAP_DEBUG_ANY ) ) { + char buf[STRLENOF("4294967295U") + 1] = { 0 }; + mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) ); + + Debug( LDAP_DEBUG_ANY, + "%s meta_back_getconn: candidates=%d conn=%s insert failed\n", + op->o_log_prefix, ncandidates, buf ); + } + + mc->mc_refcnt = 0; + meta_back_conn_free( mc ); + + rs->sr_err = LDAP_OTHER; + rs->sr_text = "Proxy bind collision"; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + return NULL; + } + } + + if ( LogTest( LDAP_DEBUG_TRACE ) ) { + char buf[STRLENOF("4294967295U") + 1] = { 0 }; + mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) ); + + Debug( LDAP_DEBUG_TRACE, + "%s meta_back_getconn: candidates=%d conn=%s inserted\n", + op->o_log_prefix, ncandidates, buf ); + } + + } else { + if ( LogTest( LDAP_DEBUG_TRACE ) ) { + char buf[STRLENOF("4294967295U") + 1] = { 0 }; + mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) ); + + Debug( LDAP_DEBUG_TRACE, + "%s meta_back_getconn: candidates=%d conn=%s fetched\n", + op->o_log_prefix, ncandidates, buf ); + } + } + + return mc; +} + +void +meta_back_release_conn_lock( + metainfo_t *mi, + metaconn_t *mc, + int dolock ) +{ + assert( mc != NULL ); + + if ( dolock ) { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + } + assert( mc->mc_refcnt > 0 ); + mc->mc_refcnt--; + /* NOTE: the connection is removed if either it is tainted + * or if it is shared and no one else is using it. This needs + * to occur because for intrinsic reasons cached connections + * that are not privileged would live forever and pollute + * the connection space (and eat up resources). Maybe this + * should be configurable... */ + if ( LDAP_BACK_CONN_TAINTED( mc ) || !LDAP_BACK_CONN_CACHED( mc ) ) { +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, ">>> meta_back_release_conn" ); +#endif /* META_BACK_PRINT_CONNTREE */ + + if ( LDAP_BACK_PCONN_ISPRIV( mc ) ) { + if ( mc->mc_q.tqe_prev != NULL ) { + assert( LDAP_BACK_CONN_CACHED( mc ) ); + assert( mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num > 0 ); + LDAP_TAILQ_REMOVE( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv, mc, mc_q ); + mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num--; + LDAP_TAILQ_ENTRY_INIT( mc, mc_q ); + + } else { + assert( !LDAP_BACK_CONN_CACHED( mc ) ); + } + + } else if ( LDAP_BACK_CONN_CACHED( mc ) ) { + metaconn_t *tmpmc; + + tmpmc = avl_delete( &mi->mi_conninfo.lai_tree, + ( caddr_t )mc, meta_back_conndnmc_cmp ); + + /* Overparanoid, but useful... */ + assert( tmpmc == NULL || tmpmc == mc ); + } + + LDAP_BACK_CONN_CACHED_CLEAR( mc ); + +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, "<<< meta_back_release_conn" ); +#endif /* META_BACK_PRINT_CONNTREE */ + + if ( mc->mc_refcnt == 0 ) { + meta_back_conn_free( mc ); + mc = NULL; + } + } + + if ( mc != NULL && LDAP_BACK_CONN_BINDING( mc ) ) { + LDAP_BACK_CONN_BINDING_CLEAR( mc ); + } + + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } +} + +void +meta_back_quarantine( + Operation *op, + SlapReply *rs, + int candidate ) +{ + metainfo_t *mi = (metainfo_t *)op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + + slap_retry_info_t *ri = &mt->mt_quarantine; + + ldap_pvt_thread_mutex_lock( &mt->mt_quarantine_mutex ); + + if ( rs->sr_err == LDAP_UNAVAILABLE ) { + time_t new_last = slap_get_time(); + + switch ( mt->mt_isquarantined ) { + case LDAP_BACK_FQ_NO: + if ( ri->ri_last == new_last ) { + goto done; + } + + Debug( LDAP_DEBUG_ANY, + "%s meta_back_quarantine[%d]: enter.\n", + op->o_log_prefix, candidate, 0 ); + + ri->ri_idx = 0; + ri->ri_count = 0; + break; + + case LDAP_BACK_FQ_RETRYING: + if ( LogTest( LDAP_DEBUG_ANY ) ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "meta_back_quarantine[%d]: block #%d try #%d failed", + candidate, ri->ri_idx, ri->ri_count ); + Debug( LDAP_DEBUG_ANY, "%s %s.\n", + op->o_log_prefix, buf, 0 ); + } + + ++ri->ri_count; + if ( ri->ri_num[ ri->ri_idx ] != SLAP_RETRYNUM_FOREVER + && ri->ri_count == ri->ri_num[ ri->ri_idx ] ) + { + ri->ri_count = 0; + ++ri->ri_idx; + } + break; + + default: + break; + } + + mt->mt_isquarantined = LDAP_BACK_FQ_YES; + ri->ri_last = new_last; + + } else if ( mt->mt_isquarantined == LDAP_BACK_FQ_RETRYING ) { + Debug( LDAP_DEBUG_ANY, + "%s meta_back_quarantine[%d]: exit.\n", + op->o_log_prefix, candidate, 0 ); + + if ( mi->mi_quarantine_f ) { + (void)mi->mi_quarantine_f( mi, candidate, + mi->mi_quarantine_p ); + } + + ri->ri_count = 0; + ri->ri_idx = 0; + mt->mt_isquarantined = LDAP_BACK_FQ_NO; + } + +done:; + ldap_pvt_thread_mutex_unlock( &mt->mt_quarantine_mutex ); +} diff --git a/servers/slapd/back-meta/delete.c b/servers/slapd/back-meta/delete.c new file mode 100644 index 0000000..1823a09 --- /dev/null +++ b/servers/slapd/back-meta/delete.c @@ -0,0 +1,103 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +int +meta_back_delete( Operation *op, SlapReply *rs ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt; + metaconn_t *mc = NULL; + int candidate = -1; + struct berval mdn = BER_BVNULL; + dncookie dc; + int msgid; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + LDAPControl **ctrls = NULL; + + mc = meta_back_getconn( op, rs, &candidate, LDAP_BACK_SENDERR ); + if ( !mc || !meta_back_dobind( op, rs, mc, LDAP_BACK_SENDERR ) ) { + return rs->sr_err; + } + + assert( mc->mc_conns[ candidate ].msc_ld != NULL ); + + /* + * Rewrite the compare dn, if needed + */ + mt = mi->mi_targets[ candidate ]; + dc.target = mt; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "deleteDN"; + + if ( ldap_back_dn_massage( &dc, &op->o_req_dn, &mdn ) ) { + send_ldap_result( op, rs ); + goto cleanup; + } + +retry:; + ctrls = op->o_ctrls; + if ( meta_back_controls_add( op, rs, mc, candidate, &ctrls ) != LDAP_SUCCESS ) + { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = ldap_delete_ext( mc->mc_conns[ candidate ].msc_ld, + mdn.bv_val, ctrls, NULL, &msgid ); + rs->sr_err = meta_back_op_result( mc, op, rs, candidate, msgid, + mt->mt_timeout[ SLAP_OP_DELETE ], ( LDAP_BACK_SENDRESULT | retrying ) ); + if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( meta_back_retry( op, rs, &mc, candidate, LDAP_BACK_SENDERR ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + goto retry; + } + } + +cleanup:; + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + + if ( mdn.bv_val != op->o_req_dn.bv_val ) { + free( mdn.bv_val ); + BER_BVZERO( &mdn ); + } + + if ( mc ) { + meta_back_release_conn( mi, mc ); + } + + return rs->sr_err; +} + diff --git a/servers/slapd/back-meta/dncache.c b/servers/slapd/back-meta/dncache.c new file mode 100644 index 0000000..d042654 --- /dev/null +++ b/servers/slapd/back-meta/dncache.c @@ -0,0 +1,235 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +/* + * The dncache, at present, maps an entry to the target that holds it. + */ + +typedef struct metadncacheentry_t { + struct berval dn; + int target; + + time_t lastupdated; +} metadncacheentry_t; + +/* + * meta_dncache_cmp + * + * compares two struct metadncacheentry; used by avl stuff + * FIXME: modify avl stuff to delete an entry based on cmp + * (e.g. when ttl expired?) + */ +int +meta_dncache_cmp( + const void *c1, + const void *c2 ) +{ + metadncacheentry_t *cc1 = ( metadncacheentry_t * )c1; + metadncacheentry_t *cc2 = ( metadncacheentry_t * )c2; + + /* + * case sensitive, because the dn MUST be normalized + */ + return ber_bvcmp( &cc1->dn, &cc2->dn); +} + +/* + * meta_dncache_dup + * + * returns -1 in case a duplicate struct metadncacheentry has been inserted; + * used by avl stuff + */ +int +meta_dncache_dup( + void *c1, + void *c2 ) +{ + metadncacheentry_t *cc1 = ( metadncacheentry_t * )c1; + metadncacheentry_t *cc2 = ( metadncacheentry_t * )c2; + + /* + * case sensitive, because the dn MUST be normalized + */ + return ( ber_bvcmp( &cc1->dn, &cc2->dn ) == 0 ) ? -1 : 0; +} + +/* + * meta_dncache_get_target + * + * returns the target a dn belongs to, or -1 in case the dn is not + * in the cache + */ +int +meta_dncache_get_target( + metadncache_t *cache, + struct berval *ndn ) +{ + metadncacheentry_t tmp_entry, + *entry; + int target = META_TARGET_NONE; + + assert( cache != NULL ); + assert( ndn != NULL ); + + tmp_entry.dn = *ndn; + ldap_pvt_thread_mutex_lock( &cache->mutex ); + entry = ( metadncacheentry_t * )avl_find( cache->tree, + ( caddr_t )&tmp_entry, meta_dncache_cmp ); + + if ( entry != NULL ) { + + /* + * if cache->ttl < 0, cache never expires; + * if cache->ttl = 0 no cache is used; shouldn't get here + * else, cache is used with ttl + */ + if ( cache->ttl < 0 ) { + target = entry->target; + + } else { + if ( entry->lastupdated+cache->ttl > slap_get_time() ) { + target = entry->target; + } + } + } + ldap_pvt_thread_mutex_unlock( &cache->mutex ); + + return target; +} + +/* + * meta_dncache_update_entry + * + * updates target and lastupdated of a struct metadncacheentry if exists, + * otherwise it gets created; returns -1 in case of error + */ +int +meta_dncache_update_entry( + metadncache_t *cache, + struct berval *ndn, + int target ) +{ + metadncacheentry_t *entry, + tmp_entry; + time_t curr_time = 0L; + int err = 0; + + assert( cache != NULL ); + assert( ndn != NULL ); + + /* + * if cache->ttl < 0, cache never expires; + * if cache->ttl = 0 no cache is used; shouldn't get here + * else, cache is used with ttl + */ + if ( cache->ttl > 0 ) { + curr_time = slap_get_time(); + } + + tmp_entry.dn = *ndn; + + ldap_pvt_thread_mutex_lock( &cache->mutex ); + entry = ( metadncacheentry_t * )avl_find( cache->tree, + ( caddr_t )&tmp_entry, meta_dncache_cmp ); + + if ( entry != NULL ) { + entry->target = target; + entry->lastupdated = curr_time; + + } else { + entry = ch_malloc( sizeof( metadncacheentry_t ) + ndn->bv_len + 1 ); + if ( entry == NULL ) { + err = -1; + goto error_return; + } + + entry->dn.bv_len = ndn->bv_len; + entry->dn.bv_val = (char *)&entry[ 1 ]; + AC_MEMCPY( entry->dn.bv_val, ndn->bv_val, ndn->bv_len ); + entry->dn.bv_val[ ndn->bv_len ] = '\0'; + + entry->target = target; + entry->lastupdated = curr_time; + + err = avl_insert( &cache->tree, ( caddr_t )entry, + meta_dncache_cmp, meta_dncache_dup ); + } + +error_return:; + ldap_pvt_thread_mutex_unlock( &cache->mutex ); + + return err; +} + +/* + * meta_dncache_update_entry + * + * updates target and lastupdated of a struct metadncacheentry if exists, + * otherwise it gets created; returns -1 in case of error + */ +int +meta_dncache_delete_entry( + metadncache_t *cache, + struct berval *ndn ) +{ + metadncacheentry_t *entry, + tmp_entry; + + assert( cache != NULL ); + assert( ndn != NULL ); + + tmp_entry.dn = *ndn; + + ldap_pvt_thread_mutex_lock( &cache->mutex ); + entry = avl_delete( &cache->tree, ( caddr_t )&tmp_entry, + meta_dncache_cmp ); + ldap_pvt_thread_mutex_unlock( &cache->mutex ); + + if ( entry != NULL ) { + meta_dncache_free( ( void * )entry ); + } + + return 0; +} + +/* + * meta_dncache_free + * + * frees an entry + * + */ +void +meta_dncache_free( + void *e ) +{ + free( e ); +} + diff --git a/servers/slapd/back-meta/init.c b/servers/slapd/back-meta/init.c new file mode 100644 index 0000000..3c405b3 --- /dev/null +++ b/servers/slapd/back-meta/init.c @@ -0,0 +1,475 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "config.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +int +meta_back_open( + BackendInfo *bi ) +{ + /* FIXME: need to remove the pagedResults, and likely more... */ + bi->bi_controls = slap_known_controls; + + return 0; +} + +int +meta_back_initialize( + BackendInfo *bi ) +{ + bi->bi_flags = +#if 0 + /* this is not (yet) set essentially because back-meta does not + * directly support extended operations... */ +#ifdef LDAP_DYNAMIC_OBJECTS + /* this is set because all the support a proxy has to provide + * is the capability to forward the refresh exop, and to + * pass thru entries that contain the dynamicObject class + * and the entryTtl attribute */ + SLAP_BFLAG_DYNAMIC | +#endif /* LDAP_DYNAMIC_OBJECTS */ +#endif + + /* back-meta recognizes RFC4525 increment; + * let the remote server complain, if needed (ITS#5912) */ + SLAP_BFLAG_INCREMENT; + + bi->bi_open = meta_back_open; + bi->bi_config = 0; + bi->bi_close = 0; + bi->bi_destroy = 0; + + bi->bi_db_init = meta_back_db_init; + bi->bi_db_config = config_generic_wrapper; + bi->bi_db_open = meta_back_db_open; + bi->bi_db_close = 0; + bi->bi_db_destroy = meta_back_db_destroy; + + bi->bi_op_bind = meta_back_bind; + bi->bi_op_unbind = 0; + bi->bi_op_search = meta_back_search; + bi->bi_op_compare = meta_back_compare; + bi->bi_op_modify = meta_back_modify; + bi->bi_op_modrdn = meta_back_modrdn; + bi->bi_op_add = meta_back_add; + bi->bi_op_delete = meta_back_delete; + bi->bi_op_abandon = 0; + + bi->bi_extended = 0; + + bi->bi_chk_referrals = 0; + + bi->bi_connection_init = 0; + bi->bi_connection_destroy = meta_back_conn_destroy; + + return meta_back_init_cf( bi ); +} + +int +meta_back_db_init( + Backend *be, + ConfigReply *cr) +{ + metainfo_t *mi; + int i; + BackendInfo *bi; + + bi = backend_info( "ldap" ); + if ( !bi || !bi->bi_extra ) { + Debug( LDAP_DEBUG_ANY, + "meta_back_db_init: needs back-ldap\n", + 0, 0, 0 ); + return 1; + } + + mi = ch_calloc( 1, sizeof( metainfo_t ) ); + if ( mi == NULL ) { + return -1; + } + + /* set default flags */ + mi->mi_flags = + META_BACK_F_DEFER_ROOTDN_BIND + | META_BACK_F_PROXYAUTHZ_ALWAYS + | META_BACK_F_PROXYAUTHZ_ANON + | META_BACK_F_PROXYAUTHZ_NOANON; + + /* + * At present the default is no default target; + * this may change + */ + mi->mi_defaulttarget = META_DEFAULT_TARGET_NONE; + mi->mi_bind_timeout.tv_sec = 0; + mi->mi_bind_timeout.tv_usec = META_BIND_TIMEOUT; + + mi->mi_rebind_f = meta_back_default_rebind; + mi->mi_urllist_f = meta_back_default_urllist; + + ldap_pvt_thread_mutex_init( &mi->mi_conninfo.lai_mutex ); + ldap_pvt_thread_mutex_init( &mi->mi_cache.mutex ); + + /* safe default */ + mi->mi_nretries = META_RETRY_DEFAULT; + mi->mi_version = LDAP_VERSION3; + + for ( i = LDAP_BACK_PCONN_FIRST; i < LDAP_BACK_PCONN_LAST; i++ ) { + mi->mi_conn_priv[ i ].mic_num = 0; + LDAP_TAILQ_INIT( &mi->mi_conn_priv[ i ].mic_priv ); + } + mi->mi_conn_priv_max = LDAP_BACK_CONN_PRIV_DEFAULT; + + mi->mi_ldap_extra = (ldap_extra_t *)bi->bi_extra; + + be->be_private = mi; + be->be_cf_ocs = be->bd_info->bi_cf_ocs; + + return 0; +} + +int +meta_target_finish( + metainfo_t *mi, + metatarget_t *mt, + const char *log, + char *msg, + size_t msize +) +{ + slap_bindconf sb = { BER_BVNULL }; + struct berval mapped; + int rc; + + ber_str2bv( mt->mt_uri, 0, 0, &sb.sb_uri ); + sb.sb_version = mt->mt_version; + sb.sb_method = LDAP_AUTH_SIMPLE; + BER_BVSTR( &sb.sb_binddn, "" ); + + if ( META_BACK_TGT_T_F_DISCOVER( mt ) ) { + rc = slap_discover_feature( &sb, + slap_schema.si_ad_supportedFeatures->ad_cname.bv_val, + LDAP_FEATURE_ABSOLUTE_FILTERS ); + if ( rc == LDAP_COMPARE_TRUE ) { + mt->mt_flags |= LDAP_BACK_F_T_F; + } + } + + if ( META_BACK_TGT_CANCEL_DISCOVER( mt ) ) { + rc = slap_discover_feature( &sb, + slap_schema.si_ad_supportedExtension->ad_cname.bv_val, + LDAP_EXOP_CANCEL ); + if ( rc == LDAP_COMPARE_TRUE ) { + mt->mt_flags |= LDAP_BACK_F_CANCEL_EXOP; + } + } + + if ( !( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) + || mt->mt_idassert_authz != NULL ) + { + mi->mi_flags &= ~META_BACK_F_PROXYAUTHZ_ALWAYS; + } + + if ( ( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) + && !( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) ) + { + snprintf( msg, msize, + "%s: inconsistent idassert configuration " + "(likely authz=\"*\" used with \"non-prescriptive\" flag)", + log ); + Debug( LDAP_DEBUG_ANY, "%s (target %s)\n", + msg, mt->mt_uri, 0 ); + return 1; + } + + if ( !( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) ) + { + mi->mi_flags &= ~META_BACK_F_PROXYAUTHZ_ANON; + } + + if ( ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) ) + { + mi->mi_flags &= ~META_BACK_F_PROXYAUTHZ_NOANON; + } + + BER_BVZERO( &mapped ); + ldap_back_map( &mt->mt_rwmap.rwm_at, + &slap_schema.si_ad_entryDN->ad_cname, &mapped, + BACKLDAP_REMAP ); + if ( BER_BVISNULL( &mapped ) || mapped.bv_val[0] == '\0' ) { + mt->mt_rep_flags |= REP_NO_ENTRYDN; + } + + BER_BVZERO( &mapped ); + ldap_back_map( &mt->mt_rwmap.rwm_at, + &slap_schema.si_ad_subschemaSubentry->ad_cname, &mapped, + BACKLDAP_REMAP ); + if ( BER_BVISNULL( &mapped ) || mapped.bv_val[0] == '\0' ) { + mt->mt_rep_flags |= REP_NO_SUBSCHEMA; + } + + return 0; +} + +int +meta_back_db_open( + Backend *be, + ConfigReply *cr ) +{ + metainfo_t *mi = (metainfo_t *)be->be_private; + char msg[SLAP_TEXT_BUFLEN]; + + int i, rc; + + if ( mi->mi_ntargets == 0 ) { + /* Dynamically added, nothing to check here until + * some targets get added + */ + if ( slapMode & SLAP_SERVER_RUNNING ) + return 0; + + Debug( LDAP_DEBUG_ANY, + "meta_back_db_open: no targets defined\n", + 0, 0, 0 ); + return 1; + } + + for ( i = 0; i < mi->mi_ntargets; i++ ) { + metatarget_t *mt = mi->mi_targets[ i ]; + + if ( meta_target_finish( mi, mt, + "meta_back_db_open", msg, sizeof( msg ))) + return 1; + } + + return 0; +} + +/* + * meta_back_conn_free() + * + * actually frees a connection; the reference count must be 0, + * and it must not (or no longer) be in the cache. + */ +void +meta_back_conn_free( + void *v_mc ) +{ + metaconn_t *mc = v_mc; + int ntargets; + + assert( mc != NULL ); + assert( mc->mc_refcnt == 0 ); + + /* at least one must be present... */ + ntargets = mc->mc_info->mi_ntargets; + assert( ntargets > 0 ); + + for ( ; ntargets--; ) { + (void)meta_clear_one_candidate( NULL, mc, ntargets ); + } + + if ( !BER_BVISNULL( &mc->mc_local_ndn ) ) { + free( mc->mc_local_ndn.bv_val ); + } + + free( mc ); +} + +static void +mapping_free( + void *v_mapping ) +{ + struct ldapmapping *mapping = v_mapping; + ch_free( mapping->src.bv_val ); + ch_free( mapping->dst.bv_val ); + ch_free( mapping ); +} + +static void +mapping_dst_free( + void *v_mapping ) +{ + struct ldapmapping *mapping = v_mapping; + + if ( BER_BVISEMPTY( &mapping->dst ) ) { + mapping_free( &mapping[ -1 ] ); + } +} + +void +meta_back_map_free( struct ldapmap *lm ) +{ + avl_free( lm->remap, mapping_dst_free ); + avl_free( lm->map, mapping_free ); + lm->remap = NULL; + lm->map = NULL; +} + +static void +target_free( + metatarget_t *mt ) +{ + if ( mt->mt_uri ) { + free( mt->mt_uri ); + ldap_pvt_thread_mutex_destroy( &mt->mt_uri_mutex ); + } + if ( mt->mt_subtree ) { + meta_subtree_destroy( mt->mt_subtree ); + mt->mt_subtree = NULL; + } + if ( mt->mt_filter ) { + meta_filter_destroy( mt->mt_filter ); + mt->mt_filter = NULL; + } + if ( !BER_BVISNULL( &mt->mt_psuffix ) ) { + free( mt->mt_psuffix.bv_val ); + } + if ( !BER_BVISNULL( &mt->mt_nsuffix ) ) { + free( mt->mt_nsuffix.bv_val ); + } + if ( !BER_BVISNULL( &mt->mt_binddn ) ) { + free( mt->mt_binddn.bv_val ); + } + if ( !BER_BVISNULL( &mt->mt_bindpw ) ) { + free( mt->mt_bindpw.bv_val ); + } + if ( !BER_BVISNULL( &mt->mt_idassert_authcID ) ) { + ch_free( mt->mt_idassert_authcID.bv_val ); + } + if ( !BER_BVISNULL( &mt->mt_idassert_authcDN ) ) { + ch_free( mt->mt_idassert_authcDN.bv_val ); + } + if ( !BER_BVISNULL( &mt->mt_idassert_passwd ) ) { + ch_free( mt->mt_idassert_passwd.bv_val ); + } + if ( !BER_BVISNULL( &mt->mt_idassert_authzID ) ) { + ch_free( mt->mt_idassert_authzID.bv_val ); + } + if ( !BER_BVISNULL( &mt->mt_idassert_sasl_mech ) ) { + ch_free( mt->mt_idassert_sasl_mech.bv_val ); + } + if ( !BER_BVISNULL( &mt->mt_idassert_sasl_realm ) ) { + ch_free( mt->mt_idassert_sasl_realm.bv_val ); + } + if ( mt->mt_idassert_authz != NULL ) { + ber_bvarray_free( mt->mt_idassert_authz ); + } + if ( mt->mt_rwmap.rwm_rw ) { + rewrite_info_delete( &mt->mt_rwmap.rwm_rw ); + if ( mt->mt_rwmap.rwm_bva_rewrite ) + ber_bvarray_free( mt->mt_rwmap.rwm_bva_rewrite ); + } + meta_back_map_free( &mt->mt_rwmap.rwm_oc ); + meta_back_map_free( &mt->mt_rwmap.rwm_at ); + ber_bvarray_free( mt->mt_rwmap.rwm_bva_map ); + + free( mt ); +} + +int +meta_back_db_destroy( + Backend *be, + ConfigReply *cr ) +{ + metainfo_t *mi; + + if ( be->be_private ) { + int i; + + mi = ( metainfo_t * )be->be_private; + + /* + * Destroy the connection tree + */ + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + + if ( mi->mi_conninfo.lai_tree ) { + avl_free( mi->mi_conninfo.lai_tree, meta_back_conn_free ); + } + for ( i = LDAP_BACK_PCONN_FIRST; i < LDAP_BACK_PCONN_LAST; i++ ) { + while ( !LDAP_TAILQ_EMPTY( &mi->mi_conn_priv[ i ].mic_priv ) ) { + metaconn_t *mc = LDAP_TAILQ_FIRST( &mi->mi_conn_priv[ i ].mic_priv ); + + LDAP_TAILQ_REMOVE( &mi->mi_conn_priv[ i ].mic_priv, mc, mc_q ); + meta_back_conn_free( mc ); + } + } + + /* + * Destroy the per-target stuff (assuming there's at + * least one ...) + */ + if ( mi->mi_targets != NULL ) { + for ( i = 0; i < mi->mi_ntargets; i++ ) { + metatarget_t *mt = mi->mi_targets[ i ]; + + if ( META_BACK_TGT_QUARANTINE( mt ) ) { + if ( mt->mt_quarantine.ri_num != mi->mi_quarantine.ri_num ) + { + mi->mi_ldap_extra->retry_info_destroy( &mt->mt_quarantine ); + } + + ldap_pvt_thread_mutex_destroy( &mt->mt_quarantine_mutex ); + } + + target_free( mt ); + } + + free( mi->mi_targets ); + } + + ldap_pvt_thread_mutex_lock( &mi->mi_cache.mutex ); + if ( mi->mi_cache.tree ) { + avl_free( mi->mi_cache.tree, meta_dncache_free ); + } + + ldap_pvt_thread_mutex_unlock( &mi->mi_cache.mutex ); + ldap_pvt_thread_mutex_destroy( &mi->mi_cache.mutex ); + + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + ldap_pvt_thread_mutex_destroy( &mi->mi_conninfo.lai_mutex ); + + if ( mi->mi_candidates != NULL ) { + ber_memfree_x( mi->mi_candidates, NULL ); + } + + if ( META_BACK_QUARANTINE( mi ) ) { + mi->mi_ldap_extra->retry_info_destroy( &mi->mi_quarantine ); + } + } + + free( be->be_private ); + return 0; +} + +#if SLAPD_META == SLAPD_MOD_DYNAMIC + +/* conditionally define the init_module() function */ +SLAP_BACKEND_INIT_MODULE( meta ) + +#endif /* SLAPD_META == SLAPD_MOD_DYNAMIC */ + + diff --git a/servers/slapd/back-meta/map.c b/servers/slapd/back-meta/map.c new file mode 100644 index 0000000..f988776 --- /dev/null +++ b/servers/slapd/back-meta/map.c @@ -0,0 +1,877 @@ +/* map.c - ldap backend mapping routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ +/* This is an altered version */ +/* + * Copyright 1999, Howard Chu, All rights reserved. <hyc@highlandsun.com> + * + * Permission is granted to anyone to use this software for any purpose + * on any computer system, and to alter it and redistribute it, subject + * to the following restrictions: + * + * 1. The author is not responsible for the consequences of use of this + * software, no matter how awful, even if they arise from flaws in it. + * + * 2. The origin of this software must not be misrepresented, either by + * explicit claim or by omission. Since few users ever read sources, + * credits should appear in the documentation. + * + * 3. Altered versions must be plainly marked as such, and must not be + * misrepresented as being the original software. Since few users + * ever read sources, credits should appear in the documentation. + * + * 4. This notice may not be removed or altered. + * + * + * + * Copyright 2000, Pierangelo Masarati, All rights reserved. <ando@sys-net.it> + * + * This software is being modified by Pierangelo Masarati. + * The previously reported conditions apply to the modified code as well. + * Changes in the original code are highlighted where required. + * Credits for the original code go to the author, Howard Chu. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "lutil.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +int +mapping_cmp ( const void *c1, const void *c2 ) +{ + struct ldapmapping *map1 = (struct ldapmapping *)c1; + struct ldapmapping *map2 = (struct ldapmapping *)c2; + int rc = map1->src.bv_len - map2->src.bv_len; + if (rc) return rc; + return ( strcasecmp( map1->src.bv_val, map2->src.bv_val ) ); +} + +int +mapping_dup ( void *c1, void *c2 ) +{ + struct ldapmapping *map1 = (struct ldapmapping *)c1; + struct ldapmapping *map2 = (struct ldapmapping *)c2; + + return ( ( strcasecmp( map1->src.bv_val, map2->src.bv_val ) == 0 ) ? -1 : 0 ); +} + +void +ldap_back_map_init ( struct ldapmap *lm, struct ldapmapping **m ) +{ + struct ldapmapping *mapping; + + assert( m != NULL ); + + *m = NULL; + + mapping = (struct ldapmapping *)ch_calloc( 2, + sizeof( struct ldapmapping ) ); + if ( mapping == NULL ) { + return; + } + + ber_str2bv( "objectclass", STRLENOF("objectclass"), 1, &mapping[0].src); + ber_dupbv( &mapping[0].dst, &mapping[0].src ); + mapping[1].src = mapping[0].src; + mapping[1].dst = mapping[0].dst; + + avl_insert( &lm->map, (caddr_t)&mapping[0], + mapping_cmp, mapping_dup ); + avl_insert( &lm->remap, (caddr_t)&mapping[1], + mapping_cmp, mapping_dup ); + *m = mapping; +} + +int +ldap_back_mapping ( struct ldapmap *map, struct berval *s, struct ldapmapping **m, + int remap ) +{ + Avlnode *tree; + struct ldapmapping fmapping; + + assert( m != NULL ); + + /* let special attrnames slip through (ITS#5760) */ + if ( bvmatch( s, slap_bv_no_attrs ) + || bvmatch( s, slap_bv_all_user_attrs ) + || bvmatch( s, slap_bv_all_operational_attrs ) ) + { + *m = NULL; + return 0; + } + + if ( remap == BACKLDAP_REMAP ) { + tree = map->remap; + + } else { + tree = map->map; + } + + fmapping.src = *s; + *m = (struct ldapmapping *)avl_find( tree, (caddr_t)&fmapping, mapping_cmp ); + if ( *m == NULL ) { + return map->drop_missing; + } + + return 0; +} + +void +ldap_back_map ( struct ldapmap *map, struct berval *s, struct berval *bv, + int remap ) +{ + struct ldapmapping *mapping; + int drop_missing; + + /* map->map may be NULL when mapping is configured, + * but map->remap can't */ + if ( map->remap == NULL ) { + *bv = *s; + return; + } + + BER_BVZERO( bv ); + drop_missing = ldap_back_mapping( map, s, &mapping, remap ); + if ( mapping != NULL ) { + if ( !BER_BVISNULL( &mapping->dst ) ) { + *bv = mapping->dst; + } + return; + } + + if ( !drop_missing ) { + *bv = *s; + } +} + +int +ldap_back_map_attrs( + Operation *op, + struct ldapmap *at_map, + AttributeName *an, + int remap, + char ***mapped_attrs ) +{ + int i, x, j; + char **na; + struct berval mapped; + + if ( an == NULL && op->o_bd->be_extra_anlist == NULL ) { + *mapped_attrs = NULL; + return LDAP_SUCCESS; + } + + i = 0; + if ( an != NULL ) { + for ( ; !BER_BVISNULL( &an[i].an_name ); i++ ) + /* */ ; + } + + x = 0; + if ( op->o_bd->be_extra_anlist != NULL ) { + for ( ; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++ ) + /* */ ; + } + + assert( i > 0 || x > 0 ); + + na = (char **)ber_memcalloc_x( i + x + 1, sizeof(char *), op->o_tmpmemctx ); + if ( na == NULL ) { + *mapped_attrs = NULL; + return LDAP_NO_MEMORY; + } + + j = 0; + if ( i > 0 ) { + for ( i = 0; !BER_BVISNULL( &an[i].an_name ); i++ ) { + ldap_back_map( at_map, &an[i].an_name, &mapped, remap ); + if ( !BER_BVISNULL( &mapped ) && !BER_BVISEMPTY( &mapped ) ) { + na[j++] = mapped.bv_val; + } + } + } + + if ( x > 0 ) { + for ( x = 0; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++ ) { + if ( op->o_bd->be_extra_anlist[x].an_desc && + ad_inlist( op->o_bd->be_extra_anlist[x].an_desc, an ) ) + { + continue; + } + + ldap_back_map( at_map, &op->o_bd->be_extra_anlist[x].an_name, &mapped, remap ); + if ( !BER_BVISNULL( &mapped ) && !BER_BVISEMPTY( &mapped ) ) { + na[j++] = mapped.bv_val; + } + } + } + + if ( j == 0 && ( i > 0 || x > 0 ) ) { + na[j++] = LDAP_NO_ATTRS; + } + na[j] = NULL; + + *mapped_attrs = na; + + return LDAP_SUCCESS; +} + +static int +map_attr_value( + dncookie *dc, + AttributeDescription *ad, + struct berval *mapped_attr, + struct berval *value, + struct berval *mapped_value, + int remap, + void *memctx ) +{ + struct berval vtmp; + int freeval = 0; + + ldap_back_map( &dc->target->mt_rwmap.rwm_at, &ad->ad_cname, mapped_attr, remap ); + if ( BER_BVISNULL( mapped_attr ) || BER_BVISEMPTY( mapped_attr ) ) { +#if 0 + /* + * FIXME: are we sure we need to search oc_map if at_map fails? + */ + ldap_back_map( &dc->target->mt_rwmap.rwm_oc, &ad->ad_cname, mapped_attr, remap ); + if ( BER_BVISNULL( mapped_attr ) || BER_BVISEMPTY( mapped_attr ) ) { + *mapped_attr = ad->ad_cname; + } +#endif + if ( dc->target->mt_rwmap.rwm_at.drop_missing ) { + return -1; + } + + *mapped_attr = ad->ad_cname; + } + + if ( value == NULL ) { + return 0; + } + + if ( ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) + { + dncookie fdc = *dc; + +#ifdef ENABLE_REWRITE + fdc.ctx = "searchFilterAttrDN"; +#endif + + switch ( ldap_back_dn_massage( &fdc, value, &vtmp ) ) { + case LDAP_SUCCESS: + if ( vtmp.bv_val != value->bv_val ) { + freeval = 1; + } + break; + + case LDAP_UNWILLING_TO_PERFORM: + return -1; + + case LDAP_OTHER: + return -1; + } + + } else if ( ad->ad_type->sat_equality && + ad->ad_type->sat_equality->smr_usage & SLAP_MR_MUTATION_NORMALIZER ) + { + if ( ad->ad_type->sat_equality->smr_normalize( + (SLAP_MR_DENORMALIZE|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX), + NULL, NULL, value, &vtmp, memctx ) ) + { + return -1; + } + freeval = 2; + + } else if ( ad == slap_schema.si_ad_objectClass || ad == slap_schema.si_ad_structuralObjectClass ) { + ldap_back_map( &dc->target->mt_rwmap.rwm_oc, value, &vtmp, remap ); + if ( BER_BVISNULL( &vtmp ) || BER_BVISEMPTY( &vtmp ) ) { + vtmp = *value; + } + + } else { + vtmp = *value; + } + + filter_escape_value_x( &vtmp, mapped_value, memctx ); + + switch ( freeval ) { + case 1: + ber_memfree( vtmp.bv_val ); + break; + case 2: + ber_memfree_x( vtmp.bv_val, memctx ); + break; + } + + return 0; +} + +static int +ldap_back_int_filter_map_rewrite( + dncookie *dc, + Filter *f, + struct berval *fstr, + int remap, + void *memctx ) +{ + int i; + Filter *p; + struct berval atmp, + vtmp, + *tmp; + static struct berval + /* better than nothing... */ + ber_bvfalse = BER_BVC( "(!(objectClass=*))" ), + ber_bvtf_false = BER_BVC( "(|)" ), + /* better than nothing... */ + ber_bvtrue = BER_BVC( "(objectClass=*)" ), + ber_bvtf_true = BER_BVC( "(&)" ), +#if 0 + /* no longer needed; preserved for completeness */ + ber_bvundefined = BER_BVC( "(?=undefined)" ), +#endif + ber_bverror = BER_BVC( "(?=error)" ), + ber_bvunknown = BER_BVC( "(?=unknown)" ), + ber_bvnone = BER_BVC( "(?=none)" ); + ber_len_t len; + + assert( fstr != NULL ); + BER_BVZERO( fstr ); + + if ( f == NULL ) { + ber_dupbv_x( fstr, &ber_bvnone, memctx ); + return LDAP_OTHER; + } + + switch ( ( f->f_choice & SLAPD_FILTER_MASK ) ) { + case LDAP_FILTER_EQUALITY: + if ( map_attr_value( dc, f->f_av_desc, &atmp, + &f->f_av_value, &vtmp, remap, memctx ) ) + { + goto computed; + } + + fstr->bv_len = atmp.bv_len + vtmp.bv_len + + ( sizeof("(=)") - 1 ); + fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=%s)", + atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" ); + + ber_memfree_x( vtmp.bv_val, memctx ); + break; + + case LDAP_FILTER_GE: + if ( map_attr_value( dc, f->f_av_desc, &atmp, + &f->f_av_value, &vtmp, remap, memctx ) ) + { + goto computed; + } + + fstr->bv_len = atmp.bv_len + vtmp.bv_len + + ( sizeof("(>=)") - 1 ); + fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s>=%s)", + atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" ); + + ber_memfree_x( vtmp.bv_val, memctx ); + break; + + case LDAP_FILTER_LE: + if ( map_attr_value( dc, f->f_av_desc, &atmp, + &f->f_av_value, &vtmp, remap, memctx ) ) + { + goto computed; + } + + fstr->bv_len = atmp.bv_len + vtmp.bv_len + + ( sizeof("(<=)") - 1 ); + fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s<=%s)", + atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" ); + + ber_memfree_x( vtmp.bv_val, memctx ); + break; + + case LDAP_FILTER_APPROX: + if ( map_attr_value( dc, f->f_av_desc, &atmp, + &f->f_av_value, &vtmp, remap, memctx ) ) + { + goto computed; + } + + fstr->bv_len = atmp.bv_len + vtmp.bv_len + + ( sizeof("(~=)") - 1 ); + fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s~=%s)", + atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" ); + + ber_memfree_x( vtmp.bv_val, memctx ); + break; + + case LDAP_FILTER_SUBSTRINGS: + if ( map_attr_value( dc, f->f_sub_desc, &atmp, + NULL, NULL, remap, memctx ) ) + { + goto computed; + } + + /* cannot be a DN ... */ + + fstr->bv_len = atmp.bv_len + ( STRLENOF( "(=*)" ) ); + fstr->bv_val = ber_memalloc_x( fstr->bv_len + 128, memctx ); /* FIXME: why 128 ? */ + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)", + atmp.bv_val ); + + if ( !BER_BVISNULL( &f->f_sub_initial ) ) { + len = fstr->bv_len; + + filter_escape_value_x( &f->f_sub_initial, &vtmp, memctx ); + + fstr->bv_len += vtmp.bv_len; + fstr->bv_val = ber_memrealloc_x( fstr->bv_val, fstr->bv_len + 1, memctx ); + + snprintf( &fstr->bv_val[len - 2], vtmp.bv_len + 3, + /* "(attr=" */ "%s*)", + vtmp.bv_len ? vtmp.bv_val : "" ); + + ber_memfree_x( vtmp.bv_val, memctx ); + } + + if ( f->f_sub_any != NULL ) { + for ( i = 0; !BER_BVISNULL( &f->f_sub_any[i] ); i++ ) { + len = fstr->bv_len; + filter_escape_value_x( &f->f_sub_any[i], &vtmp, memctx ); + + fstr->bv_len += vtmp.bv_len + 1; + fstr->bv_val = ber_memrealloc_x( fstr->bv_val, fstr->bv_len + 1, memctx ); + + snprintf( &fstr->bv_val[len - 1], vtmp.bv_len + 3, + /* "(attr=[init]*[any*]" */ "%s*)", + vtmp.bv_len ? vtmp.bv_val : "" ); + ber_memfree_x( vtmp.bv_val, memctx ); + } + } + + if ( !BER_BVISNULL( &f->f_sub_final ) ) { + len = fstr->bv_len; + + filter_escape_value_x( &f->f_sub_final, &vtmp, memctx ); + + fstr->bv_len += vtmp.bv_len; + fstr->bv_val = ber_memrealloc_x( fstr->bv_val, fstr->bv_len + 1, memctx ); + + snprintf( &fstr->bv_val[len - 1], vtmp.bv_len + 3, + /* "(attr=[init*][any*]" */ "%s)", + vtmp.bv_len ? vtmp.bv_val : "" ); + + ber_memfree_x( vtmp.bv_val, memctx ); + } + + break; + + case LDAP_FILTER_PRESENT: + if ( map_attr_value( dc, f->f_desc, &atmp, + NULL, NULL, remap, memctx ) ) + { + goto computed; + } + + fstr->bv_len = atmp.bv_len + ( STRLENOF( "(=*)" ) ); + fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)", + atmp.bv_val ); + break; + + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: + fstr->bv_len = STRLENOF( "(%)" ); + fstr->bv_val = ber_memalloc_x( fstr->bv_len + 128, memctx ); /* FIXME: why 128? */ + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%c)", + f->f_choice == LDAP_FILTER_AND ? '&' : + f->f_choice == LDAP_FILTER_OR ? '|' : '!' ); + + for ( p = f->f_list; p != NULL; p = p->f_next ) { + int rc; + + len = fstr->bv_len; + + rc = ldap_back_int_filter_map_rewrite( dc, p, &vtmp, remap, memctx ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + fstr->bv_len += vtmp.bv_len; + fstr->bv_val = ber_memrealloc_x( fstr->bv_val, fstr->bv_len + 1, memctx ); + + snprintf( &fstr->bv_val[len-1], vtmp.bv_len + 2, + /*"("*/ "%s)", vtmp.bv_len ? vtmp.bv_val : "" ); + + ber_memfree_x( vtmp.bv_val, memctx ); + } + + break; + + case LDAP_FILTER_EXT: + if ( f->f_mr_desc ) { + if ( map_attr_value( dc, f->f_mr_desc, &atmp, + &f->f_mr_value, &vtmp, remap, memctx ) ) + { + goto computed; + } + + } else { + BER_BVSTR( &atmp, "" ); + filter_escape_value_x( &f->f_mr_value, &vtmp, memctx ); + } + + /* FIXME: cleanup (less ?: operators...) */ + fstr->bv_len = atmp.bv_len + + ( f->f_mr_dnattrs ? STRLENOF( ":dn" ) : 0 ) + + ( !BER_BVISEMPTY( &f->f_mr_rule_text ) ? f->f_mr_rule_text.bv_len + 1 : 0 ) + + vtmp.bv_len + ( STRLENOF( "(:=)" ) ); + fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s%s%s:=%s)", + atmp.bv_val, + f->f_mr_dnattrs ? ":dn" : "", + !BER_BVISEMPTY( &f->f_mr_rule_text ) ? ":" : "", + !BER_BVISEMPTY( &f->f_mr_rule_text ) ? f->f_mr_rule_text.bv_val : "", + vtmp.bv_len ? vtmp.bv_val : "" ); + ber_memfree_x( vtmp.bv_val, memctx ); + break; + + case SLAPD_FILTER_COMPUTED: + switch ( f->f_result ) { + /* FIXME: treat UNDEFINED as FALSE */ + case SLAPD_COMPARE_UNDEFINED: +computed:; + if ( META_BACK_TGT_NOUNDEFFILTER( dc->target ) ) { + return LDAP_COMPARE_FALSE; + } + /* fallthru */ + + case LDAP_COMPARE_FALSE: + if ( META_BACK_TGT_T_F( dc->target ) ) { + tmp = &ber_bvtf_false; + break; + } + tmp = &ber_bvfalse; + break; + + case LDAP_COMPARE_TRUE: + if ( META_BACK_TGT_T_F( dc->target ) ) { + tmp = &ber_bvtf_true; + break; + } + + tmp = &ber_bvtrue; + break; + + default: + tmp = &ber_bverror; + break; + } + + ber_dupbv_x( fstr, tmp, memctx ); + break; + + default: + ber_dupbv_x( fstr, &ber_bvunknown, memctx ); + break; + } + + return 0; +} + +int +ldap_back_filter_map_rewrite( + dncookie *dc, + Filter *f, + struct berval *fstr, + int remap, + void *memctx ) +{ + int rc; + dncookie fdc; + struct berval ftmp; + static char *dmy = ""; + + rc = ldap_back_int_filter_map_rewrite( dc, f, fstr, remap, memctx ); + +#ifdef ENABLE_REWRITE + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + fdc = *dc; + ftmp = *fstr; + + fdc.ctx = "searchFilter"; + + switch ( rewrite_session( fdc.target->mt_rwmap.rwm_rw, fdc.ctx, + ( !BER_BVISEMPTY( &ftmp ) ? ftmp.bv_val : dmy ), + fdc.conn, &fstr->bv_val ) ) + { + case REWRITE_REGEXEC_OK: + if ( !BER_BVISNULL( fstr ) ) { + fstr->bv_len = strlen( fstr->bv_val ); + + } else { + *fstr = ftmp; + } + Debug( LDAP_DEBUG_ARGS, + "[rw] %s: \"%s\" -> \"%s\"\n", + fdc.ctx, BER_BVISNULL( &ftmp ) ? "" : ftmp.bv_val, + BER_BVISNULL( fstr ) ? "" : fstr->bv_val ); + rc = LDAP_SUCCESS; + break; + + case REWRITE_REGEXEC_UNWILLING: + if ( fdc.rs ) { + fdc.rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + fdc.rs->sr_text = "Operation not allowed"; + } + rc = LDAP_UNWILLING_TO_PERFORM; + break; + + case REWRITE_REGEXEC_ERR: + if ( fdc.rs ) { + fdc.rs->sr_err = LDAP_OTHER; + fdc.rs->sr_text = "Rewrite error"; + } + rc = LDAP_OTHER; + break; + } + + if ( fstr->bv_val == dmy ) { + BER_BVZERO( fstr ); + + } else if ( fstr->bv_val != ftmp.bv_val ) { + /* NOTE: need to realloc mapped filter on slab + * and free the original one, until librewrite + * becomes slab-aware + */ + ber_dupbv_x( &ftmp, fstr, memctx ); + ch_free( fstr->bv_val ); + *fstr = ftmp; + } +#endif /* ENABLE_REWRITE */ + + return rc; +} + +int +ldap_back_referral_result_rewrite( + dncookie *dc, + BerVarray a_vals, + void *memctx +) +{ + int i, last; + + assert( dc != NULL ); + assert( a_vals != NULL ); + + for ( last = 0; !BER_BVISNULL( &a_vals[ last ] ); last++ ) + ; + last--; + + for ( i = 0; !BER_BVISNULL( &a_vals[ i ] ); i++ ) { + struct berval dn, + olddn = BER_BVNULL; + int rc; + LDAPURLDesc *ludp; + + rc = ldap_url_parse( a_vals[ i ].bv_val, &ludp ); + if ( rc != LDAP_URL_SUCCESS ) { + /* leave attr untouched if massage failed */ + continue; + } + + /* FIXME: URLs like "ldap:///dc=suffix" if passed + * thru ldap_url_parse() and ldap_url_desc2str() + * get rewritten as "ldap:///dc=suffix??base"; + * we don't want this to occur... */ + if ( ludp->lud_scope == LDAP_SCOPE_BASE ) { + ludp->lud_scope = LDAP_SCOPE_DEFAULT; + } + + ber_str2bv( ludp->lud_dn, 0, 0, &olddn ); + + rc = ldap_back_dn_massage( dc, &olddn, &dn ); + switch ( rc ) { + case LDAP_UNWILLING_TO_PERFORM: + /* + * FIXME: need to check if it may be considered + * legal to trim values when adding/modifying; + * it should be when searching (e.g. ACLs). + */ + ber_memfree( a_vals[ i ].bv_val ); + if ( last > i ) { + a_vals[ i ] = a_vals[ last ]; + } + BER_BVZERO( &a_vals[ last ] ); + last--; + i--; + break; + + default: + /* leave attr untouched if massage failed */ + if ( !BER_BVISNULL( &dn ) && olddn.bv_val != dn.bv_val ) + { + char *newurl; + + ludp->lud_dn = dn.bv_val; + newurl = ldap_url_desc2str( ludp ); + free( dn.bv_val ); + if ( newurl == NULL ) { + /* FIXME: leave attr untouched + * even if ldap_url_desc2str failed... + */ + break; + } + + ber_memfree_x( a_vals[ i ].bv_val, memctx ); + ber_str2bv_x( newurl, 0, 1, &a_vals[ i ], memctx ); + ber_memfree( newurl ); + ludp->lud_dn = olddn.bv_val; + } + break; + } + + ldap_free_urldesc( ludp ); + } + + return 0; +} + +/* + * I don't like this much, but we need two different + * functions because different heap managers may be + * in use in back-ldap/meta to reduce the amount of + * calls to malloc routines, and some of the free() + * routines may be macros with args + */ +int +ldap_dnattr_rewrite( + dncookie *dc, + BerVarray a_vals +) +{ + struct berval bv; + int i, last; + + assert( a_vals != NULL ); + + for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ ) + ; + last--; + + for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) { + switch ( ldap_back_dn_massage( dc, &a_vals[i], &bv ) ) { + case LDAP_UNWILLING_TO_PERFORM: + /* + * FIXME: need to check if it may be considered + * legal to trim values when adding/modifying; + * it should be when searching (e.g. ACLs). + */ + ch_free( a_vals[i].bv_val ); + if ( last > i ) { + a_vals[i] = a_vals[last]; + } + BER_BVZERO( &a_vals[last] ); + last--; + break; + + default: + /* leave attr untouched if massage failed */ + if ( !BER_BVISNULL( &bv ) && bv.bv_val != a_vals[i].bv_val ) { + ch_free( a_vals[i].bv_val ); + a_vals[i] = bv; + } + break; + } + } + + return 0; +} + +int +ldap_dnattr_result_rewrite( + dncookie *dc, + BerVarray a_vals +) +{ + struct berval bv; + int i, last; + + assert( a_vals != NULL ); + + for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ ) + ; + last--; + + for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) { + switch ( ldap_back_dn_massage( dc, &a_vals[i], &bv ) ) { + case LDAP_UNWILLING_TO_PERFORM: + /* + * FIXME: need to check if it may be considered + * legal to trim values when adding/modifying; + * it should be when searching (e.g. ACLs). + */ + ber_memfree( a_vals[i].bv_val ); + if ( last > i ) { + a_vals[i] = a_vals[last]; + } + BER_BVZERO( &a_vals[last] ); + last--; + break; + + default: + /* leave attr untouched if massage failed */ + if ( !BER_BVISNULL( &bv ) && a_vals[i].bv_val != bv.bv_val ) { + ber_memfree( a_vals[i].bv_val ); + a_vals[i] = bv; + } + break; + } + } + + return 0; +} + diff --git a/servers/slapd/back-meta/modify.c b/servers/slapd/back-meta/modify.c new file mode 100644 index 0000000..f06c076 --- /dev/null +++ b/servers/slapd/back-meta/modify.c @@ -0,0 +1,221 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +int +meta_back_modify( Operation *op, SlapReply *rs ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt; + metaconn_t *mc; + int rc = 0; + LDAPMod **modv = NULL; + LDAPMod *mods = NULL; + Modifications *ml; + int candidate = -1, i; + int isupdate; + struct berval mdn = BER_BVNULL; + struct berval mapped; + dncookie dc; + int msgid; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + LDAPControl **ctrls = NULL; + + mc = meta_back_getconn( op, rs, &candidate, LDAP_BACK_SENDERR ); + if ( !mc || !meta_back_dobind( op, rs, mc, LDAP_BACK_SENDERR ) ) { + return rs->sr_err; + } + + assert( mc->mc_conns[ candidate ].msc_ld != NULL ); + + /* + * Rewrite the modify dn, if needed + */ + mt = mi->mi_targets[ candidate ]; + dc.target = mt; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "modifyDN"; + + if ( ldap_back_dn_massage( &dc, &op->o_req_dn, &mdn ) ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + for ( i = 0, ml = op->orm_modlist; ml; i++ ,ml = ml->sml_next ) + ; + + mods = ch_malloc( sizeof( LDAPMod )*i ); + if ( mods == NULL ) { + rs->sr_err = LDAP_OTHER; + send_ldap_result( op, rs ); + goto cleanup; + } + modv = ( LDAPMod ** )ch_malloc( ( i + 1 )*sizeof( LDAPMod * ) ); + if ( modv == NULL ) { + rs->sr_err = LDAP_OTHER; + send_ldap_result( op, rs ); + goto cleanup; + } + + dc.ctx = "modifyAttrDN"; + isupdate = be_shadow_update( op ); + for ( i = 0, ml = op->orm_modlist; ml; ml = ml->sml_next ) { + int j, is_oc = 0; + + if ( !isupdate && !get_relax( op ) && ml->sml_desc->ad_type->sat_no_user_mod ) + { + continue; + } + + if ( ml->sml_desc == slap_schema.si_ad_objectClass + || ml->sml_desc == slap_schema.si_ad_structuralObjectClass ) + { + is_oc = 1; + mapped = ml->sml_desc->ad_cname; + + } else { + ldap_back_map( &mt->mt_rwmap.rwm_at, + &ml->sml_desc->ad_cname, &mapped, + BACKLDAP_MAP ); + if ( BER_BVISNULL( &mapped ) || BER_BVISEMPTY( &mapped ) ) { + continue; + } + } + + modv[ i ] = &mods[ i ]; + mods[ i ].mod_op = ml->sml_op | LDAP_MOD_BVALUES; + mods[ i ].mod_type = mapped.bv_val; + + /* + * FIXME: dn-valued attrs should be rewritten + * to allow their use in ACLs at the back-ldap + * level. + */ + if ( ml->sml_values != NULL ) { + if ( is_oc ) { + for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ ) + ; + mods[ i ].mod_bvalues = + (struct berval **)ch_malloc( ( j + 1 ) * + sizeof( struct berval * ) ); + for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); ) { + struct ldapmapping *mapping; + + ldap_back_mapping( &mt->mt_rwmap.rwm_oc, + &ml->sml_values[ j ], &mapping, BACKLDAP_MAP ); + + if ( mapping == NULL ) { + if ( mt->mt_rwmap.rwm_oc.drop_missing ) { + continue; + } + mods[ i ].mod_bvalues[ j ] = &ml->sml_values[ j ]; + + } else { + mods[ i ].mod_bvalues[ j ] = &mapping->dst; + } + j++; + } + mods[ i ].mod_bvalues[ j ] = NULL; + + } else { + if ( ml->sml_desc->ad_type->sat_syntax == + slap_schema.si_syn_distinguishedName ) + { + ( void )ldap_dnattr_rewrite( &dc, ml->sml_values ); + if ( ml->sml_values == NULL ) { + continue; + } + } + + for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ ) + ; + mods[ i ].mod_bvalues = + (struct berval **)ch_malloc( ( j + 1 ) * + sizeof( struct berval * ) ); + for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ ) { + mods[ i ].mod_bvalues[ j ] = &ml->sml_values[ j ]; + } + mods[ i ].mod_bvalues[ j ] = NULL; + } + + } else { + mods[ i ].mod_bvalues = NULL; + } + + i++; + } + modv[ i ] = 0; + +retry:; + ctrls = op->o_ctrls; + rc = meta_back_controls_add( op, rs, mc, candidate, &ctrls ); + if ( rc != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = ldap_modify_ext( mc->mc_conns[ candidate ].msc_ld, mdn.bv_val, + modv, ctrls, NULL, &msgid ); + rs->sr_err = meta_back_op_result( mc, op, rs, candidate, msgid, + mt->mt_timeout[ SLAP_OP_MODIFY ], ( LDAP_BACK_SENDRESULT | retrying ) ); + if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( meta_back_retry( op, rs, &mc, candidate, LDAP_BACK_SENDERR ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + goto retry; + } + } + +cleanup:; + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + + if ( mdn.bv_val != op->o_req_dn.bv_val ) { + free( mdn.bv_val ); + BER_BVZERO( &mdn ); + } + if ( modv != NULL ) { + for ( i = 0; modv[ i ]; i++ ) { + free( modv[ i ]->mod_bvalues ); + } + } + free( mods ); + free( modv ); + + if ( mc ) { + meta_back_release_conn( mi, mc ); + } + + return rs->sr_err; +} + diff --git a/servers/slapd/back-meta/modrdn.c b/servers/slapd/back-meta/modrdn.c new file mode 100644 index 0000000..f14c23b --- /dev/null +++ b/servers/slapd/back-meta/modrdn.c @@ -0,0 +1,177 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +int +meta_back_modrdn( Operation *op, SlapReply *rs ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt; + metaconn_t *mc; + int candidate = -1; + struct berval mdn = BER_BVNULL, + mnewSuperior = BER_BVNULL; + dncookie dc; + int msgid; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + LDAPControl **ctrls = NULL; + struct berval newrdn = BER_BVNULL; + + mc = meta_back_getconn( op, rs, &candidate, LDAP_BACK_SENDERR ); + if ( !mc || !meta_back_dobind( op, rs, mc, LDAP_BACK_SENDERR ) ) { + return rs->sr_err; + } + + assert( mc->mc_conns[ candidate ].msc_ld != NULL ); + + mt = mi->mi_targets[ candidate ]; + dc.target = mt; + dc.conn = op->o_conn; + dc.rs = rs; + + if ( op->orr_newSup ) { + + /* + * NOTE: the newParent, if defined, must be on the + * same target as the entry to be renamed. This check + * has been anticipated in meta_back_getconn() + */ + /* + * FIXME: one possibility is to delete the entry + * from one target and add it to the other; + * unfortunately we'd need write access to both, + * which is nearly impossible; for administration + * needs, the rootdn of the metadirectory could + * be mapped to an administrative account on each + * target (the binddn?); we'll see. + */ + /* + * NOTE: we need to port the identity assertion + * feature from back-ldap + */ + + /* needs LDAPv3 */ + switch ( mt->mt_version ) { + case LDAP_VERSION3: + break; + + case 0: + if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) { + break; + } + /* fall thru */ + + default: + /* op->o_protocol cannot be anything but LDAPv3, + * otherwise wouldn't be here */ + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + send_ldap_result( op, rs ); + goto cleanup; + } + + /* + * Rewrite the new superior, if defined and required + */ + dc.ctx = "newSuperiorDN"; + if ( ldap_back_dn_massage( &dc, op->orr_newSup, &mnewSuperior ) ) { + rs->sr_err = LDAP_OTHER; + send_ldap_result( op, rs ); + goto cleanup; + } + } + + /* + * Rewrite the modrdn dn, if required + */ + dc.ctx = "modrDN"; + if ( ldap_back_dn_massage( &dc, &op->o_req_dn, &mdn ) ) { + rs->sr_err = LDAP_OTHER; + send_ldap_result( op, rs ); + goto cleanup; + } + + /* NOTE: we need to copy the newRDN in case it was formed + * from a DN by simply changing the length (ITS#5397) */ + newrdn = op->orr_newrdn; + if ( newrdn.bv_val[ newrdn.bv_len ] != '\0' ) { + ber_dupbv_x( &newrdn, &op->orr_newrdn, op->o_tmpmemctx ); + } + +retry:; + ctrls = op->o_ctrls; + if ( meta_back_controls_add( op, rs, mc, candidate, &ctrls ) != LDAP_SUCCESS ) + { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = ldap_rename( mc->mc_conns[ candidate ].msc_ld, + mdn.bv_val, newrdn.bv_val, + mnewSuperior.bv_val, op->orr_deleteoldrdn, + ctrls, NULL, &msgid ); + rs->sr_err = meta_back_op_result( mc, op, rs, candidate, msgid, + mt->mt_timeout[ SLAP_OP_MODRDN ], ( LDAP_BACK_SENDRESULT | retrying ) ); + if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( meta_back_retry( op, rs, &mc, candidate, LDAP_BACK_SENDERR ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + goto retry; + } + } + +cleanup:; + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + + if ( mdn.bv_val != op->o_req_dn.bv_val ) { + free( mdn.bv_val ); + BER_BVZERO( &mdn ); + } + + if ( !BER_BVISNULL( &mnewSuperior ) + && mnewSuperior.bv_val != op->orr_newSup->bv_val ) + { + free( mnewSuperior.bv_val ); + BER_BVZERO( &mnewSuperior ); + } + + if ( newrdn.bv_val != op->orr_newrdn.bv_val ) { + op->o_tmpfree( newrdn.bv_val, op->o_tmpmemctx ); + } + + if ( mc ) { + meta_back_release_conn( mi, mc ); + } + + return rs->sr_err; +} + diff --git a/servers/slapd/back-meta/proto-meta.h b/servers/slapd/back-meta/proto-meta.h new file mode 100644 index 0000000..358bbd4 --- /dev/null +++ b/servers/slapd/back-meta/proto-meta.h @@ -0,0 +1,54 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#ifndef PROTO_META_H +#define PROTO_META_H + +LDAP_BEGIN_DECL + +extern BI_init meta_back_initialize; + +extern BI_open meta_back_open; +extern BI_close meta_back_close; +extern BI_destroy meta_back_destroy; + +extern BI_db_init meta_back_db_init; +extern BI_db_open meta_back_db_open; +extern BI_db_destroy meta_back_db_destroy; +extern BI_db_config meta_back_db_config; + +extern BI_op_bind meta_back_bind; +extern BI_op_search meta_back_search; +extern BI_op_compare meta_back_compare; +extern BI_op_modify meta_back_modify; +extern BI_op_modrdn meta_back_modrdn; +extern BI_op_add meta_back_add; +extern BI_op_delete meta_back_delete; +extern BI_op_abandon meta_back_abandon; + +extern BI_connection_destroy meta_back_conn_destroy; + +int meta_back_init_cf( BackendInfo *bi ); + +LDAP_END_DECL + +#endif /* PROTO_META_H */ diff --git a/servers/slapd/back-meta/search.c b/servers/slapd/back-meta/search.c new file mode 100644 index 0000000..7130bbc --- /dev/null +++ b/servers/slapd/back-meta/search.c @@ -0,0 +1,2465 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "lutil.h" +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" +#include "../../../libraries/liblber/lber-int.h" + +/* IGNORE means that target does not (no longer) participate + * in the search; + * NOTREADY means the search on that target has not been initialized yet + */ +#define META_MSGID_IGNORE (-1) +#define META_MSGID_NEED_BIND (-2) +#define META_MSGID_CONNECTING (-3) + +static int +meta_send_entry( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + int i, + LDAPMessage *e ); + +typedef enum meta_search_candidate_t { + META_SEARCH_UNDEFINED = -2, + META_SEARCH_ERR = -1, + META_SEARCH_NOT_CANDIDATE, + META_SEARCH_CANDIDATE, + META_SEARCH_BINDING, + META_SEARCH_NEED_BIND, + META_SEARCH_CONNECTING +} meta_search_candidate_t; + +/* + * meta_search_dobind_init() + * + * initiates bind for a candidate target of a search. + */ +static meta_search_candidate_t +meta_search_dobind_init( + Operation *op, + SlapReply *rs, + metaconn_t **mcp, + int candidate, + SlapReply *candidates ) +{ + metaconn_t *mc = *mcp; + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + + struct berval binddn = msc->msc_bound_ndn, + cred = msc->msc_cred; + int method; + + int rc; + + meta_search_candidate_t retcode; + + Debug( LDAP_DEBUG_TRACE, "%s >>> meta_search_dobind_init[%d]\n", + op->o_log_prefix, candidate, 0 ); + + /* + * all the targets are already bound as pseudoroot + */ + if ( mc->mc_authz_target == META_BOUND_ALL ) { + return META_SEARCH_CANDIDATE; + } + + retcode = META_SEARCH_BINDING; + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + if ( LDAP_BACK_CONN_ISBOUND( msc ) || LDAP_BACK_CONN_ISANON( msc ) ) { + /* already bound (or anonymous) */ + +#ifdef DEBUG_205 + char buf[ SLAP_TEXT_BUFLEN ] = { '\0' }; + int bound = 0; + + if ( LDAP_BACK_CONN_ISBOUND( msc ) ) { + bound = 1; + } + + snprintf( buf, sizeof( buf ), " mc=%p ld=%p%s DN=\"%s\"", + (void *)mc, (void *)msc->msc_ld, + bound ? " bound" : " anonymous", + bound == 0 ? "" : msc->msc_bound_ndn.bv_val ); + Debug( LDAP_DEBUG_ANY, "### %s meta_search_dobind_init[%d]%s\n", + op->o_log_prefix, candidate, buf ); +#endif /* DEBUG_205 */ + + retcode = META_SEARCH_CANDIDATE; + + } else if ( META_BACK_CONN_CREATING( msc ) || LDAP_BACK_CONN_BINDING( msc ) ) { + /* another thread is binding the target for this conn; wait */ + +#ifdef DEBUG_205 + char buf[ SLAP_TEXT_BUFLEN ] = { '\0' }; + + snprintf( buf, sizeof( buf ), " mc=%p ld=%p needbind", + (void *)mc, (void *)msc->msc_ld ); + Debug( LDAP_DEBUG_ANY, "### %s meta_search_dobind_init[%d]%s\n", + op->o_log_prefix, candidate, buf ); +#endif /* DEBUG_205 */ + + candidates[ candidate ].sr_msgid = META_MSGID_NEED_BIND; + retcode = META_SEARCH_NEED_BIND; + + } else { + /* we'll need to bind the target for this conn */ + +#ifdef DEBUG_205 + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), " mc=%p ld=%p binding", + (void *)mc, (void *)msc->msc_ld ); + Debug( LDAP_DEBUG_ANY, "### %s meta_search_dobind_init[%d]%s\n", + op->o_log_prefix, candidate, buf ); +#endif /* DEBUG_205 */ + + if ( msc->msc_ld == NULL ) { + /* for some reason (e.g. because formerly in "binding" + * state, with eventual connection expiration or invalidation) + * it was not initialized as expected */ + + Debug( LDAP_DEBUG_ANY, "%s meta_search_dobind_init[%d] mc=%p ld=NULL\n", + op->o_log_prefix, candidate, (void *)mc ); + + rc = meta_back_init_one_conn( op, rs, *mcp, candidate, + LDAP_BACK_CONN_ISPRIV( *mcp ), LDAP_BACK_DONTSEND, 0 ); + switch ( rc ) { + case LDAP_SUCCESS: + assert( msc->msc_ld != NULL ); + break; + + case LDAP_SERVER_DOWN: + case LDAP_UNAVAILABLE: + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + goto down; + + default: + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + goto other; + } + } + + LDAP_BACK_CONN_BINDING_SET( msc ); + } + + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + + if ( retcode != META_SEARCH_BINDING ) { + return retcode; + } + + /* NOTE: this obsoletes pseudorootdn */ + if ( op->o_conn != NULL && + !op->o_do_not_cache && + ( BER_BVISNULL( &msc->msc_bound_ndn ) || + BER_BVISEMPTY( &msc->msc_bound_ndn ) || + ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) ) ) + { + rc = meta_back_proxy_authz_cred( mc, candidate, op, rs, LDAP_BACK_DONTSEND, &binddn, &cred, &method ); + switch ( rc ) { + case LDAP_SUCCESS: + break; + case LDAP_UNAVAILABLE: + goto down; + default: + goto other; + } + + /* NOTE: we copy things here, even if bind didn't succeed yet, + * because the connection is not shared until bind is over */ + if ( !BER_BVISNULL( &binddn ) ) { + ber_bvreplace( &msc->msc_bound_ndn, &binddn ); + if ( META_BACK_TGT_SAVECRED( mt ) && !BER_BVISNULL( &cred ) ) { + if ( !BER_BVISNULL( &msc->msc_cred ) ) { + memset( msc->msc_cred.bv_val, 0, + msc->msc_cred.bv_len ); + } + ber_bvreplace( &msc->msc_cred, &cred ); + } + } + + if ( LDAP_BACK_CONN_ISBOUND( msc ) ) { + /* apparently, idassert was configured with SASL bind, + * so bind occurred inside meta_back_proxy_authz_cred() */ + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + LDAP_BACK_CONN_BINDING_CLEAR( msc ); + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + return META_SEARCH_CANDIDATE; + } + + /* paranoid */ + switch ( method ) { + case LDAP_AUTH_NONE: + case LDAP_AUTH_SIMPLE: + /* do a simple bind with binddn, cred */ + break; + + default: + assert( 0 ); + break; + } + } + + assert( msc->msc_ld != NULL ); + + /* connect must be async only the first time... */ + ldap_set_option( msc->msc_ld, LDAP_OPT_CONNECT_ASYNC, LDAP_OPT_ON ); + +retry:; + if ( !BER_BVISEMPTY( &binddn ) && BER_BVISEMPTY( &cred ) ) { + /* bind anonymously? */ + Debug( LDAP_DEBUG_ANY, "%s meta_search_dobind_init[%d] mc=%p: " + "non-empty dn with empty cred; binding anonymously\n", + op->o_log_prefix, candidate, (void *)mc ); + cred = slap_empty_bv; + + } else if ( BER_BVISEMPTY( &binddn ) && !BER_BVISEMPTY( &cred ) ) { + /* error */ + Debug( LDAP_DEBUG_ANY, "%s meta_search_dobind_init[%d] mc=%p: " + "empty dn with non-empty cred: error\n", + op->o_log_prefix, candidate, (void *)mc ); + rc = LDAP_OTHER; + goto other; + } + + rc = ldap_sasl_bind( msc->msc_ld, binddn.bv_val, LDAP_SASL_SIMPLE, &cred, + NULL, NULL, &candidates[ candidate ].sr_msgid ); + +#ifdef DEBUG_205 + { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), "meta_search_dobind_init[%d] mc=%p ld=%p rc=%d", + candidate, (void *)mc, (void *)mc->mc_conns[ candidate ].msc_ld, rc ); + Debug( LDAP_DEBUG_ANY, "### %s %s\n", + op->o_log_prefix, buf, 0 ); + } +#endif /* DEBUG_205 */ + + switch ( rc ) { + case LDAP_SUCCESS: + assert( candidates[ candidate ].sr_msgid >= 0 ); + META_BINDING_SET( &candidates[ candidate ] ); + return META_SEARCH_BINDING; + + case LDAP_X_CONNECTING: + /* must retry, same conn */ + candidates[ candidate ].sr_msgid = META_MSGID_CONNECTING; + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + LDAP_BACK_CONN_BINDING_CLEAR( msc ); + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + return META_SEARCH_CONNECTING; + + case LDAP_SERVER_DOWN: +down:; + /* This is the worst thing that could happen: + * the search will wait until the retry is over. */ + if ( !META_IS_RETRYING( &candidates[ candidate ] ) ) { + META_RETRYING_SET( &candidates[ candidate ] ); + + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + + assert( mc->mc_refcnt > 0 ); + if ( LogTest( LDAP_DEBUG_ANY ) ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + /* this lock is required; however, + * it's invoked only when logging is on */ + ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex ); + snprintf( buf, sizeof( buf ), + "retrying URI=\"%s\" DN=\"%s\"", + mt->mt_uri, + BER_BVISNULL( &msc->msc_bound_ndn ) ? + "" : msc->msc_bound_ndn.bv_val ); + ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex ); + + Debug( LDAP_DEBUG_ANY, + "%s meta_search_dobind_init[%d]: %s.\n", + op->o_log_prefix, candidate, buf ); + } + + meta_clear_one_candidate( op, mc, candidate ); + LDAP_BACK_CONN_ISBOUND_CLEAR( msc ); + + ( void )rewrite_session_delete( mt->mt_rwmap.rwm_rw, op->o_conn ); + + /* mc here must be the regular mc, reset and ready for init */ + rc = meta_back_init_one_conn( op, rs, mc, candidate, + LDAP_BACK_CONN_ISPRIV( mc ), LDAP_BACK_DONTSEND, 0 ); + + if ( rc == LDAP_SUCCESS ) { + LDAP_BACK_CONN_BINDING_SET( msc ); + } + + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + + if ( rc == LDAP_SUCCESS ) { + candidates[ candidate ].sr_msgid = META_MSGID_IGNORE; + binddn = msc->msc_bound_ndn; + cred = msc->msc_cred; + goto retry; + } + } + + if ( *mcp == NULL ) { + retcode = META_SEARCH_ERR; + rc = LDAP_UNAVAILABLE; + candidates[ candidate ].sr_msgid = META_MSGID_IGNORE; + break; + } + /* fall thru */ + + default: +other:; + /* convert rc to the correct LDAP error and send it back to the client: + assing the error to rs, so we can use it as argument to slap_map_api2result + and then assign the output back to rs->sr_err */ + rs->sr_err = rc; + rs->sr_err = slap_map_api2result( rs ); + + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + meta_clear_one_candidate( op, mc, candidate ); + candidates[ candidate ].sr_err = rs->sr_err; + if ( META_BACK_ONERR_STOP( mi ) ) { + LDAP_BACK_CONN_TAINTED_SET( mc ); + meta_back_release_conn_lock( mi, mc, 0 ); + *mcp = NULL; + + retcode = META_SEARCH_ERR; + + } else { + retcode = META_SEARCH_NOT_CANDIDATE; + } + candidates[ candidate ].sr_msgid = META_MSGID_IGNORE; + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + break; + } + + return retcode; +} + +static meta_search_candidate_t +meta_search_dobind_result( + Operation *op, + SlapReply *rs, + metaconn_t **mcp, + int candidate, + SlapReply *candidates, + LDAPMessage *res ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + metaconn_t *mc = *mcp; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + + meta_search_candidate_t retcode = META_SEARCH_NOT_CANDIDATE; + int rc; + + assert( msc->msc_ld != NULL ); + + /* FIXME: matched? referrals? response controls? */ + rc = ldap_parse_result( msc->msc_ld, res, + &candidates[ candidate ].sr_err, + NULL, NULL, NULL, NULL, 0 ); + if ( rc != LDAP_SUCCESS ) { + candidates[ candidate ].sr_err = rc; + } + rc = slap_map_api2result( &candidates[ candidate ] ); + + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + LDAP_BACK_CONN_BINDING_CLEAR( msc ); + if ( rc != LDAP_SUCCESS ) { + meta_clear_one_candidate( op, mc, candidate ); + candidates[ candidate ].sr_err = rc; + if ( META_BACK_ONERR_STOP( mi ) ) { + LDAP_BACK_CONN_TAINTED_SET( mc ); + meta_back_release_conn_lock( mi, mc, 0 ); + *mcp = NULL; + retcode = META_SEARCH_ERR; + rs->sr_err = rc; + } + + } else { + /* FIXME: check if bound as idassert authcDN! */ + if ( BER_BVISNULL( &msc->msc_bound_ndn ) + || BER_BVISEMPTY( &msc->msc_bound_ndn ) ) + { + LDAP_BACK_CONN_ISANON_SET( msc ); + + } else { + if ( META_BACK_TGT_SAVECRED( mt ) && + !BER_BVISNULL( &msc->msc_cred ) && + !BER_BVISEMPTY( &msc->msc_cred ) ) + { + ldap_set_rebind_proc( msc->msc_ld, mt->mt_rebind_f, msc ); + } + LDAP_BACK_CONN_ISBOUND_SET( msc ); + } + retcode = META_SEARCH_CANDIDATE; + + /* connect must be async */ + ldap_set_option( msc->msc_ld, LDAP_OPT_CONNECT_ASYNC, LDAP_OPT_OFF ); + } + + candidates[ candidate ].sr_msgid = META_MSGID_IGNORE; + META_BINDING_CLEAR( &candidates[ candidate ] ); + + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + + return retcode; +} + +static meta_search_candidate_t +meta_back_search_start( + Operation *op, + SlapReply *rs, + dncookie *dc, + metaconn_t **mcp, + int candidate, + SlapReply *candidates, + struct berval *prcookie, + ber_int_t prsize ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + metasingleconn_t *msc = &(*mcp)->mc_conns[ candidate ]; + struct berval realbase = op->o_req_dn; + int realscope = op->ors_scope; + struct berval mbase = BER_BVNULL; + struct berval mfilter = BER_BVNULL; + char **mapped_attrs = NULL; + int rc; + meta_search_candidate_t retcode; + struct timeval tv, *tvp = NULL; + int nretries = 1; + LDAPControl **ctrls = NULL; +#ifdef SLAPD_META_CLIENT_PR + LDAPControl **save_ctrls = NULL; +#endif /* SLAPD_META_CLIENT_PR */ + + /* this should not happen; just in case... */ + if ( msc->msc_ld == NULL ) { + Debug( LDAP_DEBUG_ANY, + "%s: meta_back_search_start candidate=%d ld=NULL%s.\n", + op->o_log_prefix, candidate, + META_BACK_ONERR_STOP( mi ) ? "" : " (ignored)" ); + candidates[ candidate ].sr_err = LDAP_OTHER; + if ( META_BACK_ONERR_STOP( mi ) ) { + return META_SEARCH_ERR; + } + candidates[ candidate ].sr_msgid = META_MSGID_IGNORE; + return META_SEARCH_NOT_CANDIDATE; + } + + Debug( LDAP_DEBUG_TRACE, "%s >>> meta_back_search_start[%d]\n", op->o_log_prefix, candidate, 0 ); + + /* + * modifies the base according to the scope, if required + */ + if ( mt->mt_nsuffix.bv_len > op->o_req_ndn.bv_len ) { + switch ( op->ors_scope ) { + case LDAP_SCOPE_SUBTREE: + /* + * make the target suffix the new base + * FIXME: this is very forgiving, because + * "illegal" searchBases may be turned + * into the suffix of the target; however, + * the requested searchBase already passed + * thru the candidate analyzer... + */ + if ( dnIsSuffix( &mt->mt_nsuffix, &op->o_req_ndn ) ) { + realbase = mt->mt_nsuffix; + if ( mt->mt_scope == LDAP_SCOPE_SUBORDINATE ) { + realscope = LDAP_SCOPE_SUBORDINATE; + } + + } else { + /* + * this target is no longer candidate + */ + retcode = META_SEARCH_NOT_CANDIDATE; + goto doreturn; + } + break; + + case LDAP_SCOPE_SUBORDINATE: + case LDAP_SCOPE_ONELEVEL: + { + struct berval rdn = mt->mt_nsuffix; + rdn.bv_len -= op->o_req_ndn.bv_len + STRLENOF( "," ); + if ( dnIsOneLevelRDN( &rdn ) + && dnIsSuffix( &mt->mt_nsuffix, &op->o_req_ndn ) ) + { + /* + * if there is exactly one level, + * make the target suffix the new + * base, and make scope "base" + */ + realbase = mt->mt_nsuffix; + if ( op->ors_scope == LDAP_SCOPE_SUBORDINATE ) { + if ( mt->mt_scope == LDAP_SCOPE_SUBORDINATE ) { + realscope = LDAP_SCOPE_SUBORDINATE; + } else { + realscope = LDAP_SCOPE_SUBTREE; + } + } else { + realscope = LDAP_SCOPE_BASE; + } + break; + } /* else continue with the next case */ + } + + case LDAP_SCOPE_BASE: + /* + * this target is no longer candidate + */ + retcode = META_SEARCH_NOT_CANDIDATE; + goto doreturn; + } + } + + /* check filter expression */ + if ( mt->mt_filter ) { + metafilter_t *mf; + for ( mf = mt->mt_filter; mf; mf = mf->mf_next ) { + if ( regexec( &mf->mf_regex, op->ors_filterstr.bv_val, 0, NULL, 0 ) == 0 ) + break; + } + /* nothing matched, this target is no longer a candidate */ + if ( !mf ) { + retcode = META_SEARCH_NOT_CANDIDATE; + goto doreturn; + } + } + + /* initiate dobind */ + retcode = meta_search_dobind_init( op, rs, mcp, candidate, candidates ); + + Debug( LDAP_DEBUG_TRACE, "%s <<< meta_search_dobind_init[%d]=%d\n", op->o_log_prefix, candidate, retcode ); + + if ( retcode != META_SEARCH_CANDIDATE ) { + goto doreturn; + } + + /* + * Rewrite the search base, if required + */ + dc->target = mt; + dc->ctx = "searchBase"; + switch ( ldap_back_dn_massage( dc, &realbase, &mbase ) ) { + case LDAP_SUCCESS: + break; + + case LDAP_UNWILLING_TO_PERFORM: + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "Operation not allowed"; + send_ldap_result( op, rs ); + retcode = META_SEARCH_ERR; + goto doreturn; + + default: + + /* + * this target is no longer candidate + */ + retcode = META_SEARCH_NOT_CANDIDATE; + goto doreturn; + } + + /* + * Maps filter + */ + rc = ldap_back_filter_map_rewrite( dc, op->ors_filter, + &mfilter, BACKLDAP_MAP, op->o_tmpmemctx ); + switch ( rc ) { + case LDAP_SUCCESS: + break; + + case LDAP_COMPARE_FALSE: + default: + /* + * this target is no longer candidate + */ + retcode = META_SEARCH_NOT_CANDIDATE; + goto done; + } + + /* + * Maps required attributes + */ + rc = ldap_back_map_attrs( op, &mt->mt_rwmap.rwm_at, + op->ors_attrs, BACKLDAP_MAP, &mapped_attrs ); + if ( rc != LDAP_SUCCESS ) { + /* + * this target is no longer candidate + */ + retcode = META_SEARCH_NOT_CANDIDATE; + goto done; + } + + if ( op->ors_tlimit != SLAP_NO_LIMIT ) { + tv.tv_sec = op->ors_tlimit > 0 ? op->ors_tlimit : 1; + tv.tv_usec = 0; + tvp = &tv; + } + +#ifdef SLAPD_META_CLIENT_PR + save_ctrls = op->o_ctrls; + { + LDAPControl *pr_c = NULL; + int i = 0, nc = 0; + + if ( save_ctrls ) { + for ( ; save_ctrls[i] != NULL; i++ ); + nc = i; + pr_c = ldap_control_find( LDAP_CONTROL_PAGEDRESULTS, save_ctrls, NULL ); + } + + if ( pr_c != NULL ) nc--; + if ( mt->mt_ps > 0 || prcookie != NULL ) nc++; + + if ( mt->mt_ps > 0 || prcookie != NULL || pr_c != NULL ) { + int src = 0, dst = 0; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + struct berval val = BER_BVNULL; + ber_len_t len; + + len = sizeof( LDAPControl * )*( nc + 1 ) + sizeof( LDAPControl ); + + if ( mt->mt_ps > 0 || prcookie != NULL ) { + struct berval nullcookie = BER_BVNULL; + ber_tag_t tag; + + if ( prsize == 0 && mt->mt_ps > 0 ) prsize = mt->mt_ps; + if ( prcookie == NULL ) prcookie = &nullcookie; + + ber_init2( ber, NULL, LBER_USE_DER ); + tag = ber_printf( ber, "{iO}", prsize, prcookie ); + if ( tag == LBER_ERROR ) { + /* error */ + (void) ber_free_buf( ber ); + goto done_pr; + } + + tag = ber_flatten2( ber, &val, 0 ); + if ( tag == LBER_ERROR ) { + /* error */ + (void) ber_free_buf( ber ); + goto done_pr; + } + + len += val.bv_len + 1; + } + + op->o_ctrls = op->o_tmpalloc( len, op->o_tmpmemctx ); + if ( save_ctrls ) { + for ( ; save_ctrls[ src ] != NULL; src++ ) { + if ( save_ctrls[ src ] != pr_c ) { + op->o_ctrls[ dst ] = save_ctrls[ src ]; + dst++; + } + } + } + + if ( mt->mt_ps > 0 || prcookie != NULL ) { + op->o_ctrls[ dst ] = (LDAPControl *)&op->o_ctrls[ nc + 1 ]; + + op->o_ctrls[ dst ]->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS; + op->o_ctrls[ dst ]->ldctl_iscritical = 1; + + op->o_ctrls[ dst ]->ldctl_value.bv_val = (char *)&op->o_ctrls[ dst ][ 1 ]; + AC_MEMCPY( op->o_ctrls[ dst ]->ldctl_value.bv_val, val.bv_val, val.bv_len + 1 ); + op->o_ctrls[ dst ]->ldctl_value.bv_len = val.bv_len; + dst++; + + (void)ber_free_buf( ber ); + } + + op->o_ctrls[ dst ] = NULL; + } +done_pr:; + } +#endif /* SLAPD_META_CLIENT_PR */ + +retry:; + ctrls = op->o_ctrls; + if ( meta_back_controls_add( op, rs, *mcp, candidate, &ctrls ) + != LDAP_SUCCESS ) + { + candidates[ candidate ].sr_msgid = META_MSGID_IGNORE; + retcode = META_SEARCH_NOT_CANDIDATE; + goto done; + } + + /* + * Starts the search + */ + assert( msc->msc_ld != NULL ); + rc = ldap_pvt_search( msc->msc_ld, + mbase.bv_val, realscope, mfilter.bv_val, + mapped_attrs, op->ors_attrsonly, + ctrls, NULL, tvp, op->ors_slimit, op->ors_deref, + &candidates[ candidate ].sr_msgid ); + switch ( rc ) { + case LDAP_SUCCESS: + retcode = META_SEARCH_CANDIDATE; + break; + + case LDAP_SERVER_DOWN: + if ( nretries && meta_back_retry( op, rs, mcp, candidate, LDAP_BACK_DONTSEND ) ) { + nretries = 0; + /* if the identity changed, there might be need to re-authz */ + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + goto retry; + } + + if ( *mcp == NULL ) { + retcode = META_SEARCH_ERR; + candidates[ candidate ].sr_msgid = META_MSGID_IGNORE; + break; + } + /* fall thru */ + + default: + candidates[ candidate ].sr_msgid = META_MSGID_IGNORE; + retcode = META_SEARCH_NOT_CANDIDATE; + } + +done:; + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); +#ifdef SLAPD_META_CLIENT_PR + if ( save_ctrls != op->o_ctrls ) { + op->o_tmpfree( op->o_ctrls, op->o_tmpmemctx ); + op->o_ctrls = save_ctrls; + } +#endif /* SLAPD_META_CLIENT_PR */ + + if ( mapped_attrs ) { + ber_memfree_x( mapped_attrs, op->o_tmpmemctx ); + } + if ( mfilter.bv_val != op->ors_filterstr.bv_val ) { + ber_memfree_x( mfilter.bv_val, op->o_tmpmemctx ); + } + if ( mbase.bv_val != realbase.bv_val ) { + free( mbase.bv_val ); + } + +doreturn:; + Debug( LDAP_DEBUG_TRACE, "%s <<< meta_back_search_start[%d]=%d\n", op->o_log_prefix, candidate, retcode ); + + return retcode; +} + +int +meta_back_search( Operation *op, SlapReply *rs ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metaconn_t *mc; + struct timeval save_tv = { 0, 0 }, + tv; + time_t stoptime = (time_t)(-1), + lastres_time = slap_get_time(), + timeout = 0; + int rc = 0, sres = LDAP_SUCCESS; + char *matched = NULL; + int last = 0, ncandidates = 0, + initial_candidates = 0, candidate_match = 0, + needbind = 0; + ldap_back_send_t sendok = LDAP_BACK_SENDERR; + long i; + dncookie dc; + int is_ok = 0; + void *savepriv; + SlapReply *candidates = NULL; + int do_taint = 0; + + rs_assert_ready( rs ); + rs->sr_flags &= ~REP_ENTRY_MASK; /* paranoia, we can set rs = non-entry */ + + /* + * controls are set in ldap_back_dobind() + * + * FIXME: in case of values return filter, we might want + * to map attrs and maybe rewrite value + */ +getconn:; + mc = meta_back_getconn( op, rs, NULL, sendok ); + if ( !mc ) { + return rs->sr_err; + } + + dc.conn = op->o_conn; + dc.rs = rs; + + if ( candidates == NULL ) candidates = meta_back_candidates_get( op ); + /* + * Inits searches + */ + for ( i = 0; i < mi->mi_ntargets; i++ ) { + /* reset sr_msgid; it is used in most loops + * to check if that target is still to be considered */ + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + + /* a target is marked as candidate by meta_back_getconn(); + * if for any reason (an error, it's over or so) it is + * no longer active, sr_msgid is set to META_MSGID_IGNORE + * but it remains candidate, which means it has been active + * at some point during the operation. This allows to + * use its response code and more to compute the final + * response */ + if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) { + continue; + } + + candidates[ i ].sr_matched = NULL; + candidates[ i ].sr_text = NULL; + candidates[ i ].sr_ref = NULL; + candidates[ i ].sr_ctrls = NULL; + candidates[ i ].sr_nentries = 0; + + /* get largest timeout among candidates */ + if ( mi->mi_targets[ i ]->mt_timeout[ SLAP_OP_SEARCH ] + && mi->mi_targets[ i ]->mt_timeout[ SLAP_OP_SEARCH ] > timeout ) + { + timeout = mi->mi_targets[ i ]->mt_timeout[ SLAP_OP_SEARCH ]; + } + } + + for ( i = 0; i < mi->mi_ntargets; i++ ) { + if ( !META_IS_CANDIDATE( &candidates[ i ] ) + || candidates[ i ].sr_err != LDAP_SUCCESS ) + { + continue; + } + + switch ( meta_back_search_start( op, rs, &dc, &mc, i, candidates, NULL, 0 ) ) + { + case META_SEARCH_NOT_CANDIDATE: + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + break; + + case META_SEARCH_NEED_BIND: + ++needbind; + /* fallthru */ + + case META_SEARCH_CONNECTING: + case META_SEARCH_CANDIDATE: + case META_SEARCH_BINDING: + candidates[ i ].sr_type = REP_INTERMEDIATE; + ++ncandidates; + break; + + case META_SEARCH_ERR: + savepriv = op->o_private; + op->o_private = (void *)i; + send_ldap_result( op, rs ); + op->o_private = savepriv; + rc = -1; + goto finish; + + default: + assert( 0 ); + break; + } + } + + if ( ncandidates > 0 && needbind == ncandidates ) { + /* + * give up the second time... + * + * NOTE: this should not occur the second time, since a fresh + * connection has ben created; however, targets may also + * need bind because the bind timed out or so. + */ + if ( sendok & LDAP_BACK_BINDING ) { + Debug( LDAP_DEBUG_ANY, + "%s meta_back_search: unable to initialize conn\n", + op->o_log_prefix, 0, 0 ); + rs->sr_err = LDAP_UNAVAILABLE; + rs->sr_text = "unable to initialize connection to remote targets"; + send_ldap_result( op, rs ); + rc = -1; + goto finish; + } + + /* FIXME: better create a separate connection? */ + sendok |= LDAP_BACK_BINDING; + +#ifdef DEBUG_205 + Debug( LDAP_DEBUG_ANY, "*** %s drop mc=%p create new connection\n", + op->o_log_prefix, (void *)mc, 0 ); +#endif /* DEBUG_205 */ + + meta_back_release_conn( mi, mc ); + mc = NULL; + + needbind = 0; + ncandidates = 0; + + goto getconn; + } + + initial_candidates = ncandidates; + + if ( LogTest( LDAP_DEBUG_TRACE ) ) { + char cnd[ SLAP_TEXT_BUFLEN ]; + int c; + + for ( c = 0; c < mi->mi_ntargets; c++ ) { + if ( META_IS_CANDIDATE( &candidates[ c ] ) ) { + cnd[ c ] = '*'; + } else { + cnd[ c ] = ' '; + } + } + cnd[ c ] = '\0'; + + Debug( LDAP_DEBUG_TRACE, "%s meta_back_search: ncandidates=%d " + "cnd=\"%s\"\n", op->o_log_prefix, ncandidates, cnd ); + } + + if ( initial_candidates == 0 ) { + /* NOTE: here we are not sending any matchedDN; + * this is intended, because if the back-meta + * is serving this search request, but no valid + * candidate could be looked up, it means that + * there is a hole in the mapping of the targets + * and thus no knowledge of any remote superior + * is available */ + Debug( LDAP_DEBUG_ANY, "%s meta_back_search: " + "base=\"%s\" scope=%d: " + "no candidate could be selected\n", + op->o_log_prefix, op->o_req_dn.bv_val, + op->ors_scope ); + + /* FIXME: we're sending the first error we encounter; + * maybe we should pick the worst... */ + rc = LDAP_NO_SUCH_OBJECT; + for ( i = 0; i < mi->mi_ntargets; i++ ) { + if ( META_IS_CANDIDATE( &candidates[ i ] ) + && candidates[ i ].sr_err != LDAP_SUCCESS ) + { + rc = candidates[ i ].sr_err; + break; + } + } + + send_ldap_error( op, rs, rc, NULL ); + + goto finish; + } + + /* We pull apart the ber result, stuff it into a slapd entry, and + * let send_search_entry stuff it back into ber format. Slow & ugly, + * but this is necessary for version matching, and for ACL processing. + */ + + if ( op->ors_tlimit != SLAP_NO_LIMIT ) { + stoptime = op->o_time + op->ors_tlimit; + } + + /* + * In case there are no candidates, no cycle takes place... + * + * FIXME: we might use a queue, to better balance the load + * among the candidates + */ + for ( rc = 0; ncandidates > 0; ) { + int gotit = 0, + doabandon = 0, + alreadybound = ncandidates; + + /* check timeout */ + if ( timeout && lastres_time > 0 + && ( slap_get_time() - lastres_time ) > timeout ) + { + doabandon = 1; + rs->sr_text = "Operation timed out"; + rc = rs->sr_err = op->o_protocol >= LDAP_VERSION3 ? + LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER; + savepriv = op->o_private; + op->o_private = (void *)i; + send_ldap_result( op, rs ); + op->o_private = savepriv; + goto finish; + } + + /* check time limit */ + if ( op->ors_tlimit != SLAP_NO_LIMIT + && slap_get_time() > stoptime ) + { + doabandon = 1; + rc = rs->sr_err = LDAP_TIMELIMIT_EXCEEDED; + savepriv = op->o_private; + op->o_private = (void *)i; + send_ldap_result( op, rs ); + op->o_private = savepriv; + goto finish; + } + + for ( i = 0; i < mi->mi_ntargets; i++ ) { + meta_search_candidate_t retcode = META_SEARCH_UNDEFINED; + metasingleconn_t *msc = &mc->mc_conns[ i ]; + LDAPMessage *res = NULL, *msg; + + /* if msgid is invalid, don't ldap_result() */ + if ( candidates[ i ].sr_msgid == META_MSGID_IGNORE ) { + continue; + } + + /* if target still needs bind, retry */ + if ( candidates[ i ].sr_msgid == META_MSGID_NEED_BIND + || candidates[ i ].sr_msgid == META_MSGID_CONNECTING ) + { + /* initiate dobind */ + retcode = meta_search_dobind_init( op, rs, &mc, i, candidates ); + + Debug( LDAP_DEBUG_TRACE, "%s <<< meta_search_dobind_init[%ld]=%d\n", + op->o_log_prefix, i, retcode ); + + switch ( retcode ) { + case META_SEARCH_NEED_BIND: + alreadybound--; + /* fallthru */ + + case META_SEARCH_CONNECTING: + case META_SEARCH_BINDING: + break; + + case META_SEARCH_ERR: + candidates[ i ].sr_err = rs->sr_err; + if ( META_BACK_ONERR_STOP( mi ) ) { + savepriv = op->o_private; + op->o_private = (void *)i; + send_ldap_result( op, rs ); + op->o_private = savepriv; + goto finish; + } + /* fallthru */ + + case META_SEARCH_NOT_CANDIDATE: + /* + * When no candidates are left, + * the outer cycle finishes + */ + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + assert( ncandidates > 0 ); + --ncandidates; + break; + + case META_SEARCH_CANDIDATE: + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + switch ( meta_back_search_start( op, rs, &dc, &mc, i, candidates, NULL, 0 ) ) + { + case META_SEARCH_CANDIDATE: + assert( candidates[ i ].sr_msgid >= 0 ); + break; + + case META_SEARCH_ERR: + candidates[ i ].sr_err = rs->sr_err; + if ( META_BACK_ONERR_STOP( mi ) ) { + savepriv = op->o_private; + op->o_private = (void *)i; + send_ldap_result( op, rs ); + op->o_private = savepriv; + goto finish; + } + /* fallthru */ + + case META_SEARCH_NOT_CANDIDATE: + /* means that meta_back_search_start() + * failed but onerr == continue */ + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + assert( ncandidates > 0 ); + --ncandidates; + break; + + default: + /* impossible */ + assert( 0 ); + break; + } + break; + + default: + /* impossible */ + assert( 0 ); + break; + } + continue; + } + + /* check for abandon */ + if ( op->o_abandon || LDAP_BACK_CONN_ABANDON( mc ) ) { + break; + } + +#ifdef DEBUG_205 + if ( msc->msc_ld == NULL ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + snprintf( buf, sizeof( buf ), + "%s meta_back_search[%ld] mc=%p msgid=%d%s%s%s\n", + op->o_log_prefix, (long)i, (void *)mc, + candidates[ i ].sr_msgid, + META_IS_BINDING( &candidates[ i ] ) ? " binding" : "", + LDAP_BACK_CONN_BINDING( &mc->mc_conns[ i ] ) ? " connbinding" : "", + META_BACK_CONN_CREATING( &mc->mc_conns[ i ] ) ? " conncreating" : "" ); + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + + Debug( LDAP_DEBUG_ANY, "!!! %s\n", buf, 0, 0 ); + } +#endif /* DEBUG_205 */ + + /* + * FIXME: handle time limit as well? + * Note that target servers are likely + * to handle it, so at some time we'll + * get a LDAP_TIMELIMIT_EXCEEDED from + * one of them ... + */ + tv = save_tv; + rc = ldap_result( msc->msc_ld, candidates[ i ].sr_msgid, + LDAP_MSG_RECEIVED, &tv, &res ); + switch ( rc ) { + case 0: + /* FIXME: res should not need to be freed */ + assert( res == NULL ); + continue; + + case -1: +really_bad:; + /* something REALLY bad happened! */ + if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) { + candidates[ i ].sr_type = REP_RESULT; + + if ( meta_back_retry( op, rs, &mc, i, LDAP_BACK_DONTSEND ) ) { + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + switch ( meta_back_search_start( op, rs, &dc, &mc, i, candidates, NULL, 0 ) ) + { + /* means that failed but onerr == continue */ + case META_SEARCH_NOT_CANDIDATE: + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + + assert( ncandidates > 0 ); + --ncandidates; + + candidates[ i ].sr_err = rs->sr_err; + if ( META_BACK_ONERR_STOP( mi ) ) { + savepriv = op->o_private; + op->o_private = (void *)i; + send_ldap_result( op, rs ); + op->o_private = savepriv; + goto finish; + } + /* fall thru */ + + case META_SEARCH_CANDIDATE: + /* get back into business... */ + continue; + + case META_SEARCH_BINDING: + case META_SEARCH_CONNECTING: + case META_SEARCH_NEED_BIND: + case META_SEARCH_UNDEFINED: + assert( 0 ); + + default: + /* unrecoverable error */ + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + rc = rs->sr_err = LDAP_OTHER; + goto finish; + } + } + + candidates[ i ].sr_err = rs->sr_err; + if ( META_BACK_ONERR_STOP( mi ) ) { + savepriv = op->o_private; + op->o_private = (void *)i; + send_ldap_result( op, rs ); + op->o_private = savepriv; + goto finish; + } + } + + /* + * When no candidates are left, + * the outer cycle finishes + */ + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + assert( ncandidates > 0 ); + --ncandidates; + rs->sr_err = candidates[ i ].sr_err; + continue; + + default: + lastres_time = slap_get_time(); + + /* only touch when activity actually took place... */ + if ( mi->mi_idle_timeout != 0 && msc->msc_time < lastres_time ) { + msc->msc_time = lastres_time; + } + break; + } + + for ( msg = ldap_first_message( msc->msc_ld, res ); + msg != NULL; + msg = ldap_next_message( msc->msc_ld, msg ) ) + { + rc = ldap_msgtype( msg ); + if ( rc == LDAP_RES_SEARCH_ENTRY ) { + LDAPMessage *e; + + if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) { + /* don't retry any more... */ + candidates[ i ].sr_type = REP_RESULT; + } + + /* count entries returned by target */ + candidates[ i ].sr_nentries++; + + is_ok++; + + e = ldap_first_entry( msc->msc_ld, msg ); + savepriv = op->o_private; + op->o_private = (void *)i; + rs->sr_err = meta_send_entry( op, rs, mc, i, e ); + + switch ( rs->sr_err ) { + case LDAP_SIZELIMIT_EXCEEDED: + savepriv = op->o_private; + op->o_private = (void *)i; + send_ldap_result( op, rs ); + op->o_private = savepriv; + rs->sr_err = LDAP_SUCCESS; + ldap_msgfree( res ); + res = NULL; + goto finish; + + case LDAP_UNAVAILABLE: + rs->sr_err = LDAP_OTHER; + ldap_msgfree( res ); + res = NULL; + goto finish; + } + op->o_private = savepriv; + + /* don't wait any longer... */ + gotit = 1; + save_tv.tv_sec = 0; + save_tv.tv_usec = 0; + + } else if ( rc == LDAP_RES_SEARCH_REFERENCE ) { + char **references = NULL; + int cnt; + + if ( META_BACK_TGT_NOREFS( mi->mi_targets[ i ] ) ) { + continue; + } + + if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) { + /* don't retry any more... */ + candidates[ i ].sr_type = REP_RESULT; + } + + is_ok++; + + rc = ldap_parse_reference( msc->msc_ld, msg, + &references, &rs->sr_ctrls, 0 ); + + if ( rc != LDAP_SUCCESS ) { + continue; + } + + if ( references == NULL ) { + continue; + } + +#ifdef ENABLE_REWRITE + dc.ctx = "referralDN"; +#else /* ! ENABLE_REWRITE */ + dc.tofrom = 0; + dc.normalized = 0; +#endif /* ! ENABLE_REWRITE */ + + /* FIXME: merge all and return at the end */ + + for ( cnt = 0; references[ cnt ]; cnt++ ) + ; + + rs->sr_ref = ber_memalloc_x( sizeof( struct berval ) * ( cnt + 1 ), + op->o_tmpmemctx ); + + for ( cnt = 0; references[ cnt ]; cnt++ ) { + ber_str2bv_x( references[ cnt ], 0, 1, &rs->sr_ref[ cnt ], + op->o_tmpmemctx ); + } + BER_BVZERO( &rs->sr_ref[ cnt ] ); + + ( void )ldap_back_referral_result_rewrite( &dc, rs->sr_ref, + op->o_tmpmemctx ); + + if ( rs->sr_ref != NULL && !BER_BVISNULL( &rs->sr_ref[ 0 ] ) ) { + /* ignore return value by now */ + savepriv = op->o_private; + op->o_private = (void *)i; + ( void )send_search_reference( op, rs ); + op->o_private = savepriv; + + ber_bvarray_free_x( rs->sr_ref, op->o_tmpmemctx ); + rs->sr_ref = NULL; + } + + /* cleanup */ + if ( references ) { + ber_memvfree( (void **)references ); + } + + if ( rs->sr_ctrls ) { + ldap_controls_free( rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + + } else if ( rc == LDAP_RES_INTERMEDIATE ) { + if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) { + /* don't retry any more... */ + candidates[ i ].sr_type = REP_RESULT; + } + + /* FIXME: response controls + * are passed without checks */ + rs->sr_err = ldap_parse_intermediate( msc->msc_ld, + msg, + (char **)&rs->sr_rspoid, + &rs->sr_rspdata, + &rs->sr_ctrls, + 0 ); + if ( rs->sr_err != LDAP_SUCCESS ) { + candidates[ i ].sr_type = REP_RESULT; + ldap_msgfree( res ); + res = NULL; + goto really_bad; + } + + slap_send_ldap_intermediate( op, rs ); + + if ( rs->sr_rspoid != NULL ) { + ber_memfree( (char *)rs->sr_rspoid ); + rs->sr_rspoid = NULL; + } + + if ( rs->sr_rspdata != NULL ) { + ber_bvfree( rs->sr_rspdata ); + rs->sr_rspdata = NULL; + } + + if ( rs->sr_ctrls != NULL ) { + ldap_controls_free( rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + + } else if ( rc == LDAP_RES_SEARCH_RESULT ) { + char buf[ SLAP_TEXT_BUFLEN ]; + char **references = NULL; + LDAPControl **ctrls = NULL; + + if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) { + /* don't retry any more... */ + candidates[ i ].sr_type = REP_RESULT; + } + + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + + /* NOTE: ignores response controls + * (and intermediate response controls + * as well, except for those with search + * references); this may not be correct, + * but if they're not ignored then + * back-meta would need to merge them + * consistently (think of pagedResults...) + */ + /* FIXME: response controls? */ + rs->sr_err = ldap_parse_result( msc->msc_ld, + msg, + &candidates[ i ].sr_err, + (char **)&candidates[ i ].sr_matched, + (char **)&candidates[ i ].sr_text, + &references, + &ctrls /* &candidates[ i ].sr_ctrls (unused) */ , + 0 ); + if ( rs->sr_err != LDAP_SUCCESS ) { + candidates[ i ].sr_err = rs->sr_err; + sres = slap_map_api2result( &candidates[ i ] ); + candidates[ i ].sr_type = REP_RESULT; + ldap_msgfree( res ); + res = NULL; + goto really_bad; + } + + rs->sr_err = candidates[ i ].sr_err; + + /* massage matchedDN if need be */ + if ( candidates[ i ].sr_matched != NULL ) { + struct berval match, mmatch; + + ber_str2bv( candidates[ i ].sr_matched, + 0, 0, &match ); + candidates[ i ].sr_matched = NULL; + + dc.ctx = "matchedDN"; + dc.target = mi->mi_targets[ i ]; + if ( !ldap_back_dn_massage( &dc, &match, &mmatch ) ) { + if ( mmatch.bv_val == match.bv_val ) { + candidates[ i ].sr_matched + = ch_strdup( mmatch.bv_val ); + + } else { + candidates[ i ].sr_matched = mmatch.bv_val; + } + + candidate_match++; + } + ldap_memfree( match.bv_val ); + } + + /* add references to array */ + /* RFC 4511: referrals can only appear + * if result code is LDAP_REFERRAL */ + if ( references != NULL + && references[ 0 ] != NULL + && references[ 0 ][ 0 ] != '\0' ) + { + if ( rs->sr_err != LDAP_REFERRAL ) { + Debug( LDAP_DEBUG_ANY, + "%s meta_back_search[%ld]: " + "got referrals with err=%d\n", + op->o_log_prefix, + i, rs->sr_err ); + + } else { + BerVarray sr_ref; + int cnt; + + for ( cnt = 0; references[ cnt ]; cnt++ ) + ; + + sr_ref = ber_memalloc_x( sizeof( struct berval ) * ( cnt + 1 ), + op->o_tmpmemctx ); + + for ( cnt = 0; references[ cnt ]; cnt++ ) { + ber_str2bv_x( references[ cnt ], 0, 1, &sr_ref[ cnt ], + op->o_tmpmemctx ); + } + BER_BVZERO( &sr_ref[ cnt ] ); + + ( void )ldap_back_referral_result_rewrite( &dc, sr_ref, + op->o_tmpmemctx ); + + if ( rs->sr_v2ref == NULL ) { + rs->sr_v2ref = sr_ref; + + } else { + for ( cnt = 0; !BER_BVISNULL( &sr_ref[ cnt ] ); cnt++ ) { + ber_bvarray_add_x( &rs->sr_v2ref, &sr_ref[ cnt ], + op->o_tmpmemctx ); + } + ber_memfree_x( sr_ref, op->o_tmpmemctx ); + } + } + + } else if ( rs->sr_err == LDAP_REFERRAL ) { + Debug( LDAP_DEBUG_ANY, + "%s meta_back_search[%ld]: " + "got err=%d with null " + "or empty referrals\n", + op->o_log_prefix, + i, rs->sr_err ); + + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } + + /* cleanup */ + ber_memvfree( (void **)references ); + + sres = slap_map_api2result( rs ); + + if ( LogTest( LDAP_DEBUG_TRACE | LDAP_DEBUG_ANY ) ) { + snprintf( buf, sizeof( buf ), + "%s meta_back_search[%ld] " + "match=\"%s\" err=%ld", + op->o_log_prefix, i, + candidates[ i ].sr_matched ? candidates[ i ].sr_matched : "", + (long) candidates[ i ].sr_err ); + if ( candidates[ i ].sr_err == LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "%s.\n", buf, 0, 0 ); + + } else { + Debug( LDAP_DEBUG_ANY, "%s (%s) text=\"%s\".\n", + buf, ldap_err2string( candidates[ i ].sr_err ), + candidates[ i ].sr_text ? candidates[i].sr_text : "" ); + } + } + + switch ( sres ) { + case LDAP_NO_SUCH_OBJECT: + /* is_ok is touched any time a valid + * (even intermediate) result is + * returned; as a consequence, if + * a candidate returns noSuchObject + * it is ignored and the candidate + * is simply demoted. */ + if ( is_ok ) { + sres = LDAP_SUCCESS; + } + break; + + case LDAP_SUCCESS: + if ( ctrls != NULL && ctrls[0] != NULL ) { +#ifdef SLAPD_META_CLIENT_PR + LDAPControl *pr_c; + + pr_c = ldap_control_find( LDAP_CONTROL_PAGEDRESULTS, ctrls, NULL ); + if ( pr_c != NULL ) { + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_tag_t tag; + ber_int_t prsize; + struct berval prcookie; + + /* unsolicited, do not accept */ + if ( mi->mi_targets[i]->mt_ps == 0 ) { + rs->sr_err = LDAP_OTHER; + goto err_pr; + } + + ber_init2( ber, &pr_c->ldctl_value, LBER_USE_DER ); + + tag = ber_scanf( ber, "{im}", &prsize, &prcookie ); + if ( tag == LBER_ERROR ) { + rs->sr_err = LDAP_OTHER; + goto err_pr; + } + + /* more pages? new search request */ + if ( !BER_BVISNULL( &prcookie ) && !BER_BVISEMPTY( &prcookie ) ) { + if ( mi->mi_targets[i]->mt_ps > 0 ) { + /* ignore size if specified */ + prsize = 0; + + } else if ( prsize == 0 ) { + /* guess the page size from the entries returned so far */ + prsize = candidates[ i ].sr_nentries; + } + + candidates[ i ].sr_nentries = 0; + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + candidates[ i ].sr_type = REP_INTERMEDIATE; + + assert( candidates[ i ].sr_matched == NULL ); + assert( candidates[ i ].sr_text == NULL ); + assert( candidates[ i ].sr_ref == NULL ); + + switch ( meta_back_search_start( op, rs, &dc, &mc, i, candidates, &prcookie, prsize ) ) + { + case META_SEARCH_CANDIDATE: + assert( candidates[ i ].sr_msgid >= 0 ); + ldap_controls_free( ctrls ); + goto free_message; + + case META_SEARCH_ERR: +err_pr:; + candidates[ i ].sr_err = rs->sr_err; + if ( META_BACK_ONERR_STOP( mi ) ) { + savepriv = op->o_private; + op->o_private = (void *)i; + send_ldap_result( op, rs ); + op->o_private = savepriv; + ldap_controls_free( ctrls ); + goto finish; + } + /* fallthru */ + + case META_SEARCH_NOT_CANDIDATE: + /* means that meta_back_search_start() + * failed but onerr == continue */ + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + assert( ncandidates > 0 ); + --ncandidates; + break; + + default: + /* impossible */ + assert( 0 ); + break; + } + break; + } + } +#endif /* SLAPD_META_CLIENT_PR */ + } + /* fallthru */ + + case LDAP_REFERRAL: + is_ok++; + break; + + case LDAP_SIZELIMIT_EXCEEDED: + /* if a target returned sizelimitExceeded + * and the entry count is equal to the + * proxy's limit, the target would have + * returned more, and the error must be + * propagated to the client; otherwise, + * the target enforced a limit lower + * than what requested by the proxy; + * ignore it */ + candidates[ i ].sr_err = rs->sr_err; + if ( rs->sr_nentries == op->ors_slimit + || META_BACK_ONERR_STOP( mi ) ) + { + const char *save_text; +got_err: + save_text = rs->sr_text; + savepriv = op->o_private; + op->o_private = (void *)i; + rs->sr_text = candidates[ i ].sr_text; + send_ldap_result( op, rs ); + rs->sr_text = save_text; + op->o_private = savepriv; + ldap_msgfree( res ); + res = NULL; + ldap_controls_free( ctrls ); + goto finish; + } + break; + + default: + candidates[ i ].sr_err = rs->sr_err; + if ( META_BACK_ONERR_STOP( mi ) ) + goto got_err; + break; + } + + ldap_controls_free( ctrls ); + last = i; + rc = 0; + + /* + * When no candidates are left, + * the outer cycle finishes + */ + assert( ncandidates > 0 ); + --ncandidates; + + } else if ( rc == LDAP_RES_BIND ) { + meta_search_candidate_t retcode; + + retcode = meta_search_dobind_result( op, rs, &mc, i, candidates, msg ); + if ( retcode == META_SEARCH_CANDIDATE ) { + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + retcode = meta_back_search_start( op, rs, &dc, &mc, i, candidates, NULL, 0 ); + } + + switch ( retcode ) { + case META_SEARCH_CANDIDATE: + break; + + /* means that failed but onerr == continue */ + case META_SEARCH_NOT_CANDIDATE: + case META_SEARCH_ERR: + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + assert( ncandidates > 0 ); + --ncandidates; + + candidates[ i ].sr_err = rs->sr_err; + if ( META_BACK_ONERR_STOP( mi ) ) { + savepriv = op->o_private; + op->o_private = (void *)i; + send_ldap_result( op, rs ); + op->o_private = savepriv; + ldap_msgfree( res ); + res = NULL; + goto finish; + } + goto free_message; + + default: + assert( 0 ); + break; + } + + } else { + Debug( LDAP_DEBUG_ANY, + "%s meta_back_search[%ld]: " + "unrecognized response message tag=%d\n", + op->o_log_prefix, + i, rc ); + + ldap_msgfree( res ); + res = NULL; + goto really_bad; + } + } + +free_message:; + ldap_msgfree( res ); + res = NULL; + } + + /* check for abandon */ + if ( op->o_abandon || LDAP_BACK_CONN_ABANDON( mc ) ) { + for ( i = 0; i < mi->mi_ntargets; i++ ) { + if ( candidates[ i ].sr_msgid >= 0 + || candidates[ i ].sr_msgid == META_MSGID_CONNECTING ) + { + if ( META_IS_BINDING( &candidates[ i ] ) + || candidates[ i ].sr_msgid == META_MSGID_CONNECTING ) + { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + if ( LDAP_BACK_CONN_BINDING( &mc->mc_conns[ i ] ) + || candidates[ i ].sr_msgid == META_MSGID_CONNECTING ) + { + /* if still binding, destroy */ + +#ifdef DEBUG_205 + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf), "%s meta_back_search(abandon) " + "ldap_unbind_ext[%ld] mc=%p ld=%p", + op->o_log_prefix, i, (void *)mc, + (void *)mc->mc_conns[i].msc_ld ); + + Debug( LDAP_DEBUG_ANY, "### %s\n", buf, 0, 0 ); +#endif /* DEBUG_205 */ + + meta_clear_one_candidate( op, mc, i ); + } + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + META_BINDING_CLEAR( &candidates[ i ] ); + + } else { + (void)meta_back_cancel( mc, op, rs, + candidates[ i ].sr_msgid, i, + LDAP_BACK_DONTSEND ); + } + + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + assert( ncandidates > 0 ); + --ncandidates; + } + } + + if ( op->o_abandon ) { + rc = SLAPD_ABANDON; + } + + /* let send_ldap_result play cleanup handlers (ITS#4645) */ + break; + } + + /* if no entry was found during this loop, + * set a minimal timeout */ + if ( ncandidates > 0 && gotit == 0 ) { + if ( save_tv.tv_sec == 0 && save_tv.tv_usec == 0 ) { + save_tv.tv_usec = LDAP_BACK_RESULT_UTIMEOUT/initial_candidates; + + /* arbitrarily limit to something between 1 and 2 minutes */ + } else if ( ( stoptime == -1 && save_tv.tv_sec < 60 ) + || save_tv.tv_sec < ( stoptime - slap_get_time() ) / ( 2 * ncandidates ) ) + { + /* double the timeout */ + lutil_timermul( &save_tv, 2, &save_tv ); + } + + if ( alreadybound == 0 ) { + tv = save_tv; + (void)select( 0, NULL, NULL, NULL, &tv ); + + } else { + ldap_pvt_thread_yield(); + } + } + } + + if ( rc == -1 ) { + /* + * FIXME: need a better strategy to handle errors + */ + if ( mc ) { + rc = meta_back_op_result( mc, op, rs, META_TARGET_NONE, + -1, stoptime != -1 ? (stoptime - slap_get_time()) : 0, + LDAP_BACK_SENDERR ); + } else { + rc = rs->sr_err; + } + goto finish; + } + + /* + * Rewrite the matched portion of the search base, if required + * + * FIXME: only the last one gets caught! + */ + savepriv = op->o_private; + op->o_private = (void *)(long)mi->mi_ntargets; + if ( candidate_match > 0 ) { + struct berval pmatched = BER_BVNULL; + + /* we use the first one */ + for ( i = 0; i < mi->mi_ntargets; i++ ) { + if ( META_IS_CANDIDATE( &candidates[ i ] ) + && candidates[ i ].sr_matched != NULL ) + { + struct berval bv, pbv; + int rc; + + /* if we got success, and this target + * returned noSuchObject, and its suffix + * is a superior of the searchBase, + * ignore the matchedDN */ + if ( sres == LDAP_SUCCESS + && candidates[ i ].sr_err == LDAP_NO_SUCH_OBJECT + && op->o_req_ndn.bv_len > mi->mi_targets[ i ]->mt_nsuffix.bv_len ) + { + free( (char *)candidates[ i ].sr_matched ); + candidates[ i ].sr_matched = NULL; + continue; + } + + ber_str2bv( candidates[ i ].sr_matched, 0, 0, &bv ); + rc = dnPretty( NULL, &bv, &pbv, op->o_tmpmemctx ); + + if ( rc == LDAP_SUCCESS ) { + + /* NOTE: if they all are superiors + * of the baseDN, the shorter is also + * superior of the longer... */ + if ( pbv.bv_len > pmatched.bv_len ) { + if ( !BER_BVISNULL( &pmatched ) ) { + op->o_tmpfree( pmatched.bv_val, op->o_tmpmemctx ); + } + pmatched = pbv; + op->o_private = (void *)i; + + } else { + op->o_tmpfree( pbv.bv_val, op->o_tmpmemctx ); + } + } + + if ( candidates[ i ].sr_matched != NULL ) { + free( (char *)candidates[ i ].sr_matched ); + candidates[ i ].sr_matched = NULL; + } + } + } + + if ( !BER_BVISNULL( &pmatched ) ) { + matched = pmatched.bv_val; + } + + } else if ( sres == LDAP_NO_SUCH_OBJECT ) { + matched = op->o_bd->be_suffix[ 0 ].bv_val; + } + + /* + * In case we returned at least one entry, we return LDAP_SUCCESS + * otherwise, the latter error code we got + */ + + if ( sres == LDAP_SUCCESS ) { + if ( rs->sr_v2ref ) { + sres = LDAP_REFERRAL; + } + + if ( META_BACK_ONERR_REPORT( mi ) ) { + /* + * Report errors, if any + * + * FIXME: we should handle error codes and return the more + * important/reasonable + */ + for ( i = 0; i < mi->mi_ntargets; i++ ) { + if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) { + continue; + } + + if ( candidates[ i ].sr_err != LDAP_SUCCESS + && candidates[ i ].sr_err != LDAP_NO_SUCH_OBJECT ) + { + sres = candidates[ i ].sr_err; + break; + } + } + } + } + + rs->sr_err = sres; + rs->sr_matched = ( sres == LDAP_SUCCESS ? NULL : matched ); + rs->sr_ref = ( sres == LDAP_REFERRAL ? rs->sr_v2ref : NULL ); + send_ldap_result( op, rs ); + op->o_private = savepriv; + rs->sr_matched = NULL; + rs->sr_ref = NULL; + +finish:; + if ( matched && matched != op->o_bd->be_suffix[ 0 ].bv_val ) { + op->o_tmpfree( matched, op->o_tmpmemctx ); + } + + if ( rs->sr_v2ref ) { + ber_bvarray_free_x( rs->sr_v2ref, op->o_tmpmemctx ); + } + + for ( i = 0; i < mi->mi_ntargets; i++ ) { + if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) { + continue; + } + + if ( mc ) { + if ( META_IS_BINDING( &candidates[ i ] ) + || candidates[ i ].sr_msgid == META_MSGID_CONNECTING ) + { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + if ( LDAP_BACK_CONN_BINDING( &mc->mc_conns[ i ] ) + || candidates[ i ].sr_msgid == META_MSGID_CONNECTING ) + { + assert( candidates[ i ].sr_msgid >= 0 + || candidates[ i ].sr_msgid == META_MSGID_CONNECTING ); + assert( mc->mc_conns[ i ].msc_ld != NULL ); + +#ifdef DEBUG_205 + Debug( LDAP_DEBUG_ANY, "### %s meta_back_search(cleanup) " + "ldap_unbind_ext[%ld] ld=%p\n", + op->o_log_prefix, i, (void *)mc->mc_conns[i].msc_ld ); +#endif /* DEBUG_205 */ + + /* if still binding, destroy */ + meta_clear_one_candidate( op, mc, i ); + } + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + META_BINDING_CLEAR( &candidates[ i ] ); + + } else if ( candidates[ i ].sr_msgid >= 0 ) { + (void)meta_back_cancel( mc, op, rs, + candidates[ i ].sr_msgid, i, + LDAP_BACK_DONTSEND ); + } + } + + if ( candidates[ i ].sr_matched ) { + free( (char *)candidates[ i ].sr_matched ); + candidates[ i ].sr_matched = NULL; + } + + if ( candidates[ i ].sr_text ) { + ldap_memfree( (char *)candidates[ i ].sr_text ); + candidates[ i ].sr_text = NULL; + } + + if ( candidates[ i ].sr_ref ) { + ber_bvarray_free( candidates[ i ].sr_ref ); + candidates[ i ].sr_ref = NULL; + } + + if ( candidates[ i ].sr_ctrls ) { + ldap_controls_free( candidates[ i ].sr_ctrls ); + candidates[ i ].sr_ctrls = NULL; + } + + if ( META_BACK_TGT_QUARANTINE( mi->mi_targets[ i ] ) ) { + meta_back_quarantine( op, &candidates[ i ], i ); + } + + /* only in case of timelimit exceeded, if the timelimit exceeded because + * one contacted target never responded, invalidate the connection + * NOTE: should we quarantine the target as well? right now, the connection + * is invalidated; the next time it will be recreated and the target + * will be quarantined if it cannot be contacted */ + if ( mi->mi_idle_timeout != 0 + && rs->sr_err == LDAP_TIMELIMIT_EXCEEDED + && op->o_time > mc->mc_conns[ i ].msc_time ) + { + /* don't let anyone else use this expired connection */ + do_taint++; + } + } + + if ( mc ) { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + if ( do_taint ) { + LDAP_BACK_CONN_TAINTED_SET( mc ); + } + meta_back_release_conn_lock( mi, mc, 0 ); + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + + return rs->sr_err; +} + +static int +meta_send_entry( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + int target, + LDAPMessage *e ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + struct berval a, mapped; + int check_duplicate_attrs = 0; + int check_sorted_attrs = 0; + Entry ent = { 0 }; + BerElement ber = *ldap_get_message_ber( e ); + Attribute *attr, **attrp; + struct berval bdn, + dn = BER_BVNULL; + const char *text; + dncookie dc; + ber_len_t len; + int rc; + + if ( ber_scanf( &ber, "l{", &len ) == LBER_ERROR ) { + return LDAP_DECODING_ERROR; + } + + if ( ber_set_option( &ber, LBER_OPT_REMAINING_BYTES, &len ) != LBER_OPT_SUCCESS ) { + return LDAP_OTHER; + } + + if ( ber_scanf( &ber, "m{", &bdn ) == LBER_ERROR ) { + return LDAP_DECODING_ERROR; + } + + /* + * Rewrite the dn of the result, if needed + */ + dc.target = mi->mi_targets[ target ]; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "searchResult"; + + rs->sr_err = ldap_back_dn_massage( &dc, &bdn, &dn ); + if ( rs->sr_err != LDAP_SUCCESS) { + return rs->sr_err; + } + + /* + * Note: this may fail if the target host(s) schema differs + * from the one known to the meta, and a DN with unknown + * attributes is returned. + * + * FIXME: should we log anything, or delegate to dnNormalize? + */ + rc = dnPrettyNormal( NULL, &dn, &ent.e_name, &ent.e_nname, + op->o_tmpmemctx ); + if ( dn.bv_val != bdn.bv_val ) { + free( dn.bv_val ); + } + BER_BVZERO( &dn ); + + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "%s meta_send_entry(\"%s\"): " + "invalid DN syntax\n", + op->o_log_prefix, ent.e_name.bv_val, 0 ); + rc = LDAP_INVALID_DN_SYNTAX; + goto done; + } + + /* + * cache dn + */ + if ( mi->mi_cache.ttl != META_DNCACHE_DISABLED ) { + ( void )meta_dncache_update_entry( &mi->mi_cache, + &ent.e_nname, target ); + } + + attrp = &ent.e_attrs; + + dc.ctx = "searchAttrDN"; + while ( ber_scanf( &ber, "{m", &a ) != LBER_ERROR ) { + int last = 0; + slap_syntax_validate_func *validate; + slap_syntax_transform_func *pretty; + + if ( ber_pvt_ber_remaining( &ber ) < 0 ) { + Debug( LDAP_DEBUG_ANY, + "%s meta_send_entry(\"%s\"): " + "unable to parse attr \"%s\".\n", + op->o_log_prefix, ent.e_name.bv_val, a.bv_val ); + + rc = LDAP_OTHER; + goto done; + } + + if ( ber_pvt_ber_remaining( &ber ) == 0 ) { + break; + } + + ldap_back_map( &mi->mi_targets[ target ]->mt_rwmap.rwm_at, + &a, &mapped, BACKLDAP_REMAP ); + if ( BER_BVISNULL( &mapped ) || mapped.bv_val[0] == '\0' ) { + ( void )ber_scanf( &ber, "x" /* [W] */ ); + continue; + } + if ( mapped.bv_val != a.bv_val ) { + /* will need to check for duplicate attrs */ + check_duplicate_attrs++; + } + attr = attr_alloc( NULL ); + if ( attr == NULL ) { + rc = LDAP_OTHER; + goto done; + } + if ( slap_bv2ad( &mapped, &attr->a_desc, &text ) + != LDAP_SUCCESS) { + if ( slap_bv2undef_ad( &mapped, &attr->a_desc, &text, + SLAP_AD_PROXIED ) != LDAP_SUCCESS ) + { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "%s meta_send_entry(\"%s\"): " + "slap_bv2undef_ad(%s): %s\n", + op->o_log_prefix, ent.e_name.bv_val, + mapped.bv_val, text ); + + Debug( LDAP_DEBUG_ANY, "%s", buf, 0, 0 ); + ( void )ber_scanf( &ber, "x" /* [W] */ ); + attr_free( attr ); + continue; + } + } + + if ( attr->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL ) + check_sorted_attrs = 1; + + /* no subschemaSubentry */ + if ( attr->a_desc == slap_schema.si_ad_subschemaSubentry + || attr->a_desc == slap_schema.si_ad_entryDN ) + { + + /* + * We eat target's subschemaSubentry because + * a search for this value is likely not + * to resolve to the appropriate backend; + * later, the local subschemaSubentry is + * added. + * + * We also eat entryDN because the frontend + * will reattach it without checking if already + * present... + */ + ( void )ber_scanf( &ber, "x" /* [W] */ ); + attr_free(attr); + continue; + } + + if ( ber_scanf( &ber, "[W]", &attr->a_vals ) == LBER_ERROR + || attr->a_vals == NULL ) + { + attr->a_vals = (struct berval *)&slap_dummy_bv; + + } else { + for ( last = 0; !BER_BVISNULL( &attr->a_vals[ last ] ); ++last ) + ; + } + attr->a_numvals = last; + + validate = attr->a_desc->ad_type->sat_syntax->ssyn_validate; + pretty = attr->a_desc->ad_type->sat_syntax->ssyn_pretty; + + if ( !validate && !pretty ) { + attr_free( attr ); + goto next_attr; + } + + if ( attr->a_desc == slap_schema.si_ad_objectClass + || attr->a_desc == slap_schema.si_ad_structuralObjectClass ) + { + struct berval *bv; + + for ( bv = attr->a_vals; !BER_BVISNULL( bv ); bv++ ) { + ObjectClass *oc; + + ldap_back_map( &mi->mi_targets[ target ]->mt_rwmap.rwm_oc, + bv, &mapped, BACKLDAP_REMAP ); + if ( BER_BVISNULL( &mapped ) || mapped.bv_val[0] == '\0') { +remove_oc:; + free( bv->bv_val ); + BER_BVZERO( bv ); + if ( --last < 0 ) { + break; + } + *bv = attr->a_vals[ last ]; + BER_BVZERO( &attr->a_vals[ last ] ); + bv--; + + } else if ( mapped.bv_val != bv->bv_val ) { + int i; + + for ( i = 0; !BER_BVISNULL( &attr->a_vals[ i ] ); i++ ) { + if ( &attr->a_vals[ i ] == bv ) { + continue; + } + + if ( ber_bvstrcasecmp( &mapped, &attr->a_vals[ i ] ) == 0 ) { + break; + } + } + + if ( !BER_BVISNULL( &attr->a_vals[ i ] ) ) { + goto remove_oc; + } + + ber_bvreplace( bv, &mapped ); + + } else if ( ( oc = oc_bvfind_undef( bv ) ) == NULL ) { + goto remove_oc; + + } else { + ber_bvreplace( bv, &oc->soc_cname ); + } + } + /* + * It is necessary to try to rewrite attributes with + * dn syntax because they might be used in ACLs as + * members of groups; since ACLs are applied to the + * rewritten stuff, no dn-based subecj clause could + * be used at the ldap backend side (see + * http://www.OpenLDAP.org/faq/data/cache/452.html) + * The problem can be overcome by moving the dn-based + * ACLs to the target directory server, and letting + * everything pass thru the ldap backend. + */ + } else { + int i; + + if ( attr->a_desc->ad_type->sat_syntax == + slap_schema.si_syn_distinguishedName ) + { + ldap_dnattr_result_rewrite( &dc, attr->a_vals ); + + } else if ( attr->a_desc == slap_schema.si_ad_ref ) { + ldap_back_referral_result_rewrite( &dc, attr->a_vals, NULL ); + + } + + for ( i = 0; i < last; i++ ) { + struct berval pval; + int rc; + + if ( pretty ) { + rc = ordered_value_pretty( attr->a_desc, + &attr->a_vals[i], &pval, NULL ); + + } else { + rc = ordered_value_validate( attr->a_desc, + &attr->a_vals[i], 0 ); + } + + if ( rc ) { + ber_memfree( attr->a_vals[i].bv_val ); + if ( --last == i ) { + BER_BVZERO( &attr->a_vals[ i ] ); + break; + } + attr->a_vals[i] = attr->a_vals[last]; + BER_BVZERO( &attr->a_vals[last] ); + i--; + continue; + } + + if ( pretty ) { + ber_memfree( attr->a_vals[i].bv_val ); + attr->a_vals[i] = pval; + } + } + + if ( last == 0 && attr->a_vals != &slap_dummy_bv ) { + attr_free( attr ); + goto next_attr; + } + } + + if ( last && attr->a_desc->ad_type->sat_equality && + attr->a_desc->ad_type->sat_equality->smr_normalize ) + { + int i; + + attr->a_nvals = ch_malloc( ( last + 1 ) * sizeof( struct berval ) ); + for ( i = 0; i<last; i++ ) { + /* if normalizer fails, drop this value */ + if ( ordered_value_normalize( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + attr->a_desc, + attr->a_desc->ad_type->sat_equality, + &attr->a_vals[i], &attr->a_nvals[i], + NULL )) { + ber_memfree( attr->a_vals[i].bv_val ); + if ( --last == i ) { + BER_BVZERO( &attr->a_vals[ i ] ); + break; + } + attr->a_vals[i] = attr->a_vals[last]; + BER_BVZERO( &attr->a_vals[last] ); + i--; + } + } + BER_BVZERO( &attr->a_nvals[i] ); + if ( last == 0 ) { + attr_free( attr ); + goto next_attr; + } + + } else { + attr->a_nvals = attr->a_vals; + } + + attr->a_numvals = last; + *attrp = attr; + attrp = &attr->a_next; +next_attr:; + } + + /* only check if some mapping occurred */ + if ( check_duplicate_attrs ) { + Attribute **ap; + + for ( ap = &ent.e_attrs; *ap != NULL; ap = &(*ap)->a_next ) { + Attribute **tap; + + for ( tap = &(*ap)->a_next; *tap != NULL; ) { + if ( (*tap)->a_desc == (*ap)->a_desc ) { + Entry e = { 0 }; + Modification mod = { 0 }; + const char *text = NULL; + char textbuf[ SLAP_TEXT_BUFLEN ]; + Attribute *next = (*tap)->a_next; + + BER_BVSTR( &e.e_name, "" ); + BER_BVSTR( &e.e_nname, "" ); + e.e_attrs = *ap; + mod.sm_op = LDAP_MOD_ADD; + mod.sm_desc = (*ap)->a_desc; + mod.sm_type = mod.sm_desc->ad_cname; + mod.sm_numvals = (*ap)->a_numvals; + mod.sm_values = (*tap)->a_vals; + if ( (*tap)->a_nvals != (*tap)->a_vals ) { + mod.sm_nvalues = (*tap)->a_nvals; + } + + (void)modify_add_values( &e, &mod, + /* permissive */ 1, + &text, textbuf, sizeof( textbuf ) ); + + /* should not insert new attrs! */ + assert( e.e_attrs == *ap ); + + attr_free( *tap ); + *tap = next; + + } else { + tap = &(*tap)->a_next; + } + } + } + } + + /* Check for sorted attributes */ + if ( check_sorted_attrs ) { + for ( attr = ent.e_attrs; attr; attr = attr->a_next ) { + if ( attr->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL ) { + while ( attr->a_numvals > 1 ) { + int i; + int rc = slap_sort_vals( (Modifications *)attr, &text, &i, op->o_tmpmemctx ); + if ( rc != LDAP_TYPE_OR_VALUE_EXISTS ) + break; + + /* Strip duplicate values */ + if ( attr->a_nvals != attr->a_vals ) + ber_memfree( attr->a_nvals[i].bv_val ); + ber_memfree( attr->a_vals[i].bv_val ); + attr->a_numvals--; + if ( (unsigned)i < attr->a_numvals ) { + attr->a_vals[i] = attr->a_vals[attr->a_numvals]; + if ( attr->a_nvals != attr->a_vals ) + attr->a_nvals[i] = attr->a_nvals[attr->a_numvals]; + } + BER_BVZERO(&attr->a_vals[attr->a_numvals]); + if ( attr->a_nvals != attr->a_vals ) + BER_BVZERO(&attr->a_nvals[attr->a_numvals]); + } + attr->a_flags |= SLAP_ATTR_SORTED_VALS; + } + } + } + + ldap_get_entry_controls( mc->mc_conns[target].msc_ld, + e, &rs->sr_ctrls ); + rs->sr_entry = &ent; + rs->sr_attrs = op->ors_attrs; + rs->sr_operational_attrs = NULL; + rs->sr_flags = mi->mi_targets[ target ]->mt_rep_flags; + rs->sr_err = LDAP_SUCCESS; + rc = send_search_entry( op, rs ); + switch ( rc ) { + case LDAP_UNAVAILABLE: + rc = LDAP_OTHER; + break; + } + +done:; + rs->sr_entry = NULL; + rs->sr_attrs = NULL; + if ( rs->sr_ctrls != NULL ) { + ldap_controls_free( rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + if ( !BER_BVISNULL( &ent.e_name ) ) { + free( ent.e_name.bv_val ); + BER_BVZERO( &ent.e_name ); + } + if ( !BER_BVISNULL( &ent.e_nname ) ) { + free( ent.e_nname.bv_val ); + BER_BVZERO( &ent.e_nname ); + } + entry_clean( &ent ); + + return rc; +} + diff --git a/servers/slapd/back-meta/suffixmassage.c b/servers/slapd/back-meta/suffixmassage.c new file mode 100644 index 0000000..b7bb4ab --- /dev/null +++ b/servers/slapd/back-meta/suffixmassage.c @@ -0,0 +1,193 @@ +/* suffixmassage.c - massages ldap backend dns */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ +/* This is an altered version */ + +/* + * Copyright 1999, Howard Chu, All rights reserved. <hyc@highlandsun.com> + * Copyright 2000, Pierangelo Masarati, All rights reserved. <ando@sys-net.it> + * + * Module back-ldap, originally developed by Howard Chu + * + * has been modified by Pierangelo Masarati. The original copyright + * notice has been maintained. + * + * Permission is granted to anyone to use this software for any purpose + * on any computer system, and to alter it and redistribute it, subject + * to the following restrictions: + * + * 1. The author is not responsible for the consequences of use of this + * software, no matter how awful, even if they arise from flaws in it. + * + * 2. The origin of this software must not be misrepresented, either by + * explicit claim or by omission. Since few users ever read sources, + * credits should appear in the documentation. + * + * 3. Altered versions must be plainly marked as such, and must not be + * misrepresented as being the original software. Since few users + * ever read sources, credits should appear in the documentation. + * + * 4. This notice may not be removed or altered. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +#ifdef ENABLE_REWRITE +int +ldap_back_dn_massage( + dncookie *dc, + struct berval *dn, + struct berval *res ) +{ + int rc = 0; + static char *dmy = ""; + + switch ( rewrite_session( dc->target->mt_rwmap.rwm_rw, dc->ctx, + ( dn->bv_val ? dn->bv_val : dmy ), + dc->conn, &res->bv_val ) ) + { + case REWRITE_REGEXEC_OK: + if ( res->bv_val != NULL ) { + res->bv_len = strlen( res->bv_val ); + } else { + *res = *dn; + } + Debug( LDAP_DEBUG_ARGS, + "[rw] %s: \"%s\" -> \"%s\"\n", + dc->ctx, + BER_BVISNULL( dn ) ? "" : dn->bv_val, + BER_BVISNULL( res ) ? "" : res->bv_val ); + rc = LDAP_SUCCESS; + break; + + case REWRITE_REGEXEC_UNWILLING: + if ( dc->rs ) { + dc->rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + dc->rs->sr_text = "Operation not allowed"; + } + rc = LDAP_UNWILLING_TO_PERFORM; + break; + + case REWRITE_REGEXEC_ERR: + if ( dc->rs ) { + dc->rs->sr_err = LDAP_OTHER; + dc->rs->sr_text = "Rewrite error"; + } + rc = LDAP_OTHER; + break; + } + + if ( res->bv_val == dmy ) { + BER_BVZERO( res ); + } + + return rc; +} + +#else +/* + * ldap_back_dn_massage + * + * Aliases the suffix; based on suffix_alias (servers/slapd/suffixalias.c). + */ +int +ldap_back_dn_massage( + dncookie *dc, + struct berval *odn, + struct berval *res +) +{ + int i, src, dst; + struct berval pretty = {0,NULL}, *dn = odn; + + assert( res != NULL ); + + if ( dn == NULL ) { + res->bv_val = NULL; + res->bv_len = 0; + return 0; + } + if ( dc->target->mt_rwmap.rwm_suffix_massage == NULL ) { + *res = *dn; + return 0; + } + + if ( dc->tofrom ) { + src = 0 + dc->normalized; + dst = 2 + dc->normalized; + } else { + src = 2 + dc->normalized; + dst = 0 + dc->normalized; + /* DN from remote server may be in arbitrary form. + * Pretty it so we can parse reliably. + */ + dnPretty( NULL, dn, &pretty, NULL ); + if (pretty.bv_val) dn = &pretty; + } + + for ( i = 0; + dc->target->mt_rwmap.rwm_suffix_massage[i].bv_val != NULL; + i += 4 ) { + int aliasLength = dc->target->mt_rwmap.rwm_suffix_massage[i+src].bv_len; + int diff = dn->bv_len - aliasLength; + + if ( diff < 0 ) { + /* alias is longer than dn */ + continue; + } else if ( diff > 0 && ( !DN_SEPARATOR(dn->bv_val[diff-1]))) { + /* boundary is not at a DN separator */ + continue; + /* At a DN Separator */ + } + + if ( !strcmp( dc->target->mt_rwmap.rwm_suffix_massage[i+src].bv_val, &dn->bv_val[diff] ) ) { + res->bv_len = diff + dc->target->mt_rwmap.rwm_suffix_massage[i+dst].bv_len; + res->bv_val = ch_malloc( res->bv_len + 1 ); + strncpy( res->bv_val, dn->bv_val, diff ); + strcpy( &res->bv_val[diff], dc->target->mt_rwmap.rwm_suffix_massage[i+dst].bv_val ); + Debug( LDAP_DEBUG_ARGS, + "ldap_back_dn_massage:" + " converted \"%s\" to \"%s\"\n", + BER_BVISNULL( dn ) ? "" : dn->bv_val, + BER_BVISNULL( res ) ? "" : res->bv_val, 0 ); + break; + } + } + if (pretty.bv_val) { + ch_free(pretty.bv_val); + dn = odn; + } + /* Nothing matched, just return the original DN */ + if (res->bv_val == NULL) { + *res = *dn; + } + + return 0; +} +#endif /* !ENABLE_REWRITE */ diff --git a/servers/slapd/back-meta/unbind.c b/servers/slapd/back-meta/unbind.c new file mode 100644 index 0000000..4e363de --- /dev/null +++ b/servers/slapd/back-meta/unbind.c @@ -0,0 +1,89 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +int +meta_back_conn_destroy( + Backend *be, + Connection *conn ) +{ + metainfo_t *mi = ( metainfo_t * )be->be_private; + metaconn_t *mc, + mc_curr = {{ 0 }}; + int i; + + + Debug( LDAP_DEBUG_TRACE, + "=>meta_back_conn_destroy: fetching conn=%ld DN=\"%s\"\n", + conn->c_connid, + BER_BVISNULL( &conn->c_ndn ) ? "" : conn->c_ndn.bv_val, 0 ); + + mc_curr.mc_conn = conn; + + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, ">>> meta_back_conn_destroy" ); +#endif /* META_BACK_PRINT_CONNTREE */ + while ( ( mc = avl_delete( &mi->mi_conninfo.lai_tree, ( caddr_t )&mc_curr, meta_back_conn_cmp ) ) != NULL ) + { + assert( !LDAP_BACK_PCONN_ISPRIV( mc ) ); + Debug( LDAP_DEBUG_TRACE, + "=>meta_back_conn_destroy: destroying conn %lu " + "refcnt=%d flags=0x%08x\n", + mc->mc_conn->c_connid, mc->mc_refcnt, mc->msc_mscflags ); + + if ( mc->mc_refcnt > 0 ) { + /* someone else might be accessing the connection; + * mark for deletion */ + LDAP_BACK_CONN_CACHED_CLEAR( mc ); + LDAP_BACK_CONN_TAINTED_SET( mc ); + + } else { + meta_back_conn_free( mc ); + } + } +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, "<<< meta_back_conn_destroy" ); +#endif /* META_BACK_PRINT_CONNTREE */ + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + + /* + * Cleanup rewrite session + */ + for ( i = 0; i < mi->mi_ntargets; ++i ) { + rewrite_session_delete( mi->mi_targets[ i ]->mt_rwmap.rwm_rw, conn ); + } + + return 0; +} + diff --git a/servers/slapd/back-monitor/Makefile.in b/servers/slapd/back-monitor/Makefile.in new file mode 100644 index 0000000..0de9b5b --- /dev/null +++ b/servers/slapd/back-monitor/Makefile.in @@ -0,0 +1,49 @@ +# Makefile.in for back-monitor +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +SRCS = init.c search.c compare.c modify.c bind.c \ + operational.c \ + cache.c entry.c \ + backend.c database.c thread.c conn.c rww.c log.c \ + operation.c sent.c listener.c time.c overlay.c +OBJS = init.lo search.lo compare.lo modify.lo bind.lo \ + operational.lo \ + cache.lo entry.lo \ + backend.lo database.lo thread.lo conn.lo rww.lo log.lo \ + operation.lo sent.lo listener.lo time.lo overlay.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-monitor" +BUILD_MOD = @BUILD_MONITOR@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_MONITOR@_DEFS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_R_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBBASE = back_monitor + +XINCPATH = -I.. -I$(srcdir)/.. -I$(srcdir)/../slapi +XDEFS = $(MODULES_CPPFLAGS) + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ + diff --git a/servers/slapd/back-monitor/README b/servers/slapd/back-monitor/README new file mode 100644 index 0000000..21d7b86 --- /dev/null +++ b/servers/slapd/back-monitor/README @@ -0,0 +1,243 @@ +MONITOR BACKEND + + NAME: back-monitor + + Backend for monitoring the server's activity. + + + +COMPILE AND CONFIGURATION OPTIONS + +It must be explicitly enabled by configuring with + + --enable-monitor + +set; then it must be activated by placing in slapd.conf the database +configure directive + + database monitor + +The suffix "cn=Monitor" is implicitly activated (it cannot be given +as a suffix of the database as usually done for conventional backends). +Note that the "cn=Monitor" naming context appears in the rootDSE +in the attribute monitorContext + +A bind operation is provided; at present it allows to bind as the +backend rootdn. As a result, the backend supports the rootdn/rootpw +directives (only simple bind at present). + + + +NAMING CONTEXT AND TREE STRUCTURE + +The backend naming context is "cn=Monitor"; the first level entries +represent the monitored subsystems. It is implemented in a modular way, +to ease the addition of new subsystems. + + + +SCHEMA + +All the subsystems get a default "cn" attribute, represented by the +subsystem's name, and they all have "top", "monitor" and "extensibleObject" +objectclasses. +"extensibleObject" is used, and the "description" attribute +is used to hold the monitor information of each entry. + + + +FUNCTIONALITY + +Most of the sybsystems contain an additional depth level, represented +by detailed item monitoring. +All the entries undergo an update operation, if a related method is +defined, prior to being returned. Moreover, there's a mechanism to +allow volatile entries to be defined, and generated on the fly when +requested. As an instance, the connection statistics are updated +at each request, while each active connection data is created on the +fly. + +One nice feature of this solution is that granular ACLs can be applied +to each entry. + + + +OPERATIONS + +The backend currently supports: + + bind + compare + modify + search + + + +SUBSYSTEMS + +Currently some subsystems are partially supported. "Partially" +means their entries are correctly generated, but sometimes only +partially useful information is provided. + +The subsystems are: + + Backends + Connections + Databases + Listener + Log + Operations + Overlays + SASL + Statistics + Threads + Time + TLS + Read/Write Waiters + + + +BACKENDS SUBSYSTEMS + +The main entry contains the type of backends enabled at compile time; +the subentries, for each backend, contain the type of the backend. +It should also contain the modules that have been loaded if dynamic +backends are enabled. + + + +CONNECTIONS + +The main entry is empty; it should contain some statistics on the number +of connections. +Dynamic subentries are created for each open connection, with stats on +the activity on that connection (the format will be detailed later). +There are two special subentries that show the number of total and +current connections respectively. + + + +DATABASES SUBSYSTEM + +The main entry contains the naming context of each configured database; +the subentries contain, for each database, the type and the naming +context. + + + +LISTENER SUBSYSTEM + +It contains the description of the devices the server is currently +listening on + + + +LOG SUBSYSTEM + +It contains the currently active log items. The "Log" subsystem allows +user modify operations on the "description" attribute, whose values MUST +be in the list of admittable log switches: + + Trace + Packets + Args + Conns + BER + Filter + Config (useless) + ACL + Stats + Stats2 + Shell + Parse + Cache (deprecated) + Index + +These values can be added, replaced or deleted; they affect what +messages are sent to the syslog device. + + + +OPERATIONS SUBSYSTEM + +It shows some statistics on the operations performed by the server: + + Initiated + Completed + +and for each operation type, i.e.: + + Bind + Unbind + Add + Delete + Modrdn + Modify + Compare + Search + Abandon + Extended + + + +OVERLAYS SUBSYSTEM + +The main entry contains the type of overlays available at run-time; +the subentries, for each overlay, contain the type of the overlay. +It should also contain the modules that have been loaded if dynamic +overlays are enabled. + + + +SASL + +Currently empty. + + + +STATISTICS SUBSYSTEM + +It shows some statistics on the data sent by the server: + + Bytes + PDU + Entries + Referrals + + + +THREADS SUBSYSTEM + +It contains the maximum number of threads enabled at startup and the +current backload. + + + +TIME SUBSYSTEM + +It contains two subentries with the start time and the current time +of the server. + + + +TLS + +Currently empty. + + + +READ/WRITE WAITERS SUBSYSTEM + +It contains the number of current read waiters. + + + +NOTES + +This document is in a very early stage of maturity and will +probably be rewritten many times before the monitor backend is released. + + + +AUTHOR: Pierangelo Masarati <ando@OpenLDAP.org> + diff --git a/servers/slapd/back-monitor/back-monitor.h b/servers/slapd/back-monitor/back-monitor.h new file mode 100644 index 0000000..3e705f3 --- /dev/null +++ b/servers/slapd/back-monitor/back-monitor.h @@ -0,0 +1,325 @@ +/* back-monitor.h - ldap monitor back-end header file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#ifndef _BACK_MONITOR_H_ +#define _BACK_MONITOR_H_ + +#include <ldap_pvt.h> +#include <ldap_pvt_thread.h> +#include <avl.h> +#include <slap.h> + +LDAP_BEGIN_DECL + +/* define if si_ad_labeledURI is removed from slap_schema */ +#undef MONITOR_DEFINE_LABELEDURI + +typedef struct monitor_callback_t { + int (*mc_update)( Operation *op, SlapReply *rs, Entry *e, void *priv ); + /* update callback + for user-defined entries */ + int (*mc_modify)( Operation *op, SlapReply *rs, Entry *e, void *priv ); + /* modify callback + for user-defined entries */ + int (*mc_free)( Entry *e, void **priv ); + /* delete callback + for user-defined entries */ + void (*mc_dispose)( void **priv ); + /* dispose callback + to dispose of the callback + private data itself */ + void *mc_private; /* opaque pointer to + private data */ + struct monitor_callback_t *mc_next; +} monitor_callback_t; + + +typedef struct monitor_entry_t { + ldap_pvt_thread_mutex_t mp_mutex; /* entry mutex */ + Entry *mp_next; /* pointer to next sibling */ + Entry *mp_children; /* pointer to first child */ + struct monitor_subsys_t *mp_info; /* subsystem info */ +#define mp_type mp_info->mss_type + unsigned long mp_flags; /* flags */ + +#define MONITOR_F_NONE 0x0000U +#define MONITOR_F_SUB 0x0001U /* subentry of subsystem */ +#define MONITOR_F_PERSISTENT 0x0010U /* persistent entry */ +#define MONITOR_F_PERSISTENT_CH 0x0020U /* subsystem generates + persistent entries */ +#define MONITOR_F_VOLATILE 0x0040U /* volatile entry */ +#define MONITOR_F_VOLATILE_CH 0x0080U /* subsystem generates + volatile entries */ +#define MONITOR_F_EXTERNAL 0x0100U /* externally added - don't free */ +/* NOTE: flags with 0xF0000000U mask are reserved for subsystem internals */ + + struct monitor_callback_t *mp_cb; /* callback sequence */ + void *mp_private; +} monitor_entry_t; + +struct entry_limbo_t; /* in init.c */ + +typedef struct monitor_info_t { + + /* + * Internal data + */ + Avlnode *mi_cache; + ldap_pvt_thread_mutex_t mi_cache_mutex; + + /* + * Config parameters + */ + struct berval mi_startTime; /* don't free it! */ + struct berval mi_creatorsName; /* don't free it! */ + struct berval mi_ncreatorsName; /* don't free it! */ + + /* + * Specific schema entities + */ + ObjectClass *mi_oc_monitor; + ObjectClass *mi_oc_monitorServer; + ObjectClass *mi_oc_monitorContainer; + ObjectClass *mi_oc_monitorCounterObject; + ObjectClass *mi_oc_monitorOperation; + ObjectClass *mi_oc_monitorConnection; + ObjectClass *mi_oc_managedObject; + ObjectClass *mi_oc_monitoredObject; + + AttributeDescription *mi_ad_monitoredInfo; + AttributeDescription *mi_ad_managedInfo; + AttributeDescription *mi_ad_monitorCounter; + AttributeDescription *mi_ad_monitorOpCompleted; + AttributeDescription *mi_ad_monitorOpInitiated; + AttributeDescription *mi_ad_monitorConnectionNumber; + AttributeDescription *mi_ad_monitorConnectionAuthzDN; + AttributeDescription *mi_ad_monitorConnectionLocalAddress; + AttributeDescription *mi_ad_monitorConnectionPeerAddress; + AttributeDescription *mi_ad_monitorTimestamp; + AttributeDescription *mi_ad_monitorOverlay; + AttributeDescription *mi_ad_monitorConnectionProtocol; + AttributeDescription *mi_ad_monitorConnectionOpsReceived; + AttributeDescription *mi_ad_monitorConnectionOpsExecuting; + AttributeDescription *mi_ad_monitorConnectionOpsPending; + AttributeDescription *mi_ad_monitorConnectionOpsCompleted; + AttributeDescription *mi_ad_monitorConnectionGet; + AttributeDescription *mi_ad_monitorConnectionRead; + AttributeDescription *mi_ad_monitorConnectionWrite; + AttributeDescription *mi_ad_monitorConnectionMask; + AttributeDescription *mi_ad_monitorConnectionListener; + AttributeDescription *mi_ad_monitorConnectionPeerDomain; + AttributeDescription *mi_ad_monitorConnectionStartTime; + AttributeDescription *mi_ad_monitorConnectionActivityTime; + AttributeDescription *mi_ad_monitorIsShadow; + AttributeDescription *mi_ad_monitorUpdateRef; + AttributeDescription *mi_ad_monitorRuntimeConfig; + AttributeDescription *mi_ad_monitorSuperiorDN; + + /* + * Generic description attribute + */ + AttributeDescription *mi_ad_readOnly; + AttributeDescription *mi_ad_restrictedOperation; + + struct entry_limbo_t *mi_entry_limbo; +} monitor_info_t; + +/* + * DNs + */ + +enum { + SLAPD_MONITOR_BACKEND = 0, + SLAPD_MONITOR_CONN, + SLAPD_MONITOR_DATABASE, + SLAPD_MONITOR_LISTENER, + SLAPD_MONITOR_LOG, + SLAPD_MONITOR_OPS, + SLAPD_MONITOR_OVERLAY, + SLAPD_MONITOR_SASL, + SLAPD_MONITOR_SENT, + SLAPD_MONITOR_THREAD, + SLAPD_MONITOR_TIME, + SLAPD_MONITOR_TLS, + SLAPD_MONITOR_RWW, + + SLAPD_MONITOR_LAST +}; + +#define SLAPD_MONITOR_AT "cn" + +#define SLAPD_MONITOR_BACKEND_NAME "Backends" +#define SLAPD_MONITOR_BACKEND_RDN \ + SLAPD_MONITOR_AT "=" SLAPD_MONITOR_BACKEND_NAME +#define SLAPD_MONITOR_BACKEND_DN \ + SLAPD_MONITOR_BACKEND_RDN "," SLAPD_MONITOR_DN + +#define SLAPD_MONITOR_CONN_NAME "Connections" +#define SLAPD_MONITOR_CONN_RDN \ + SLAPD_MONITOR_AT "=" SLAPD_MONITOR_CONN_NAME +#define SLAPD_MONITOR_CONN_DN \ + SLAPD_MONITOR_CONN_RDN "," SLAPD_MONITOR_DN + +#define SLAPD_MONITOR_DATABASE_NAME "Databases" +#define SLAPD_MONITOR_DATABASE_RDN \ + SLAPD_MONITOR_AT "=" SLAPD_MONITOR_DATABASE_NAME +#define SLAPD_MONITOR_DATABASE_DN \ + SLAPD_MONITOR_DATABASE_RDN "," SLAPD_MONITOR_DN + +#define SLAPD_MONITOR_LISTENER_NAME "Listeners" +#define SLAPD_MONITOR_LISTENER_RDN \ + SLAPD_MONITOR_AT "=" SLAPD_MONITOR_LISTENER_NAME +#define SLAPD_MONITOR_LISTENER_DN \ + SLAPD_MONITOR_LISTENER_RDN "," SLAPD_MONITOR_DN + +#define SLAPD_MONITOR_LOG_NAME "Log" +#define SLAPD_MONITOR_LOG_RDN \ + SLAPD_MONITOR_AT "=" SLAPD_MONITOR_LOG_NAME +#define SLAPD_MONITOR_LOG_DN \ + SLAPD_MONITOR_LOG_RDN "," SLAPD_MONITOR_DN + +#define SLAPD_MONITOR_OPS_NAME "Operations" +#define SLAPD_MONITOR_OPS_RDN \ + SLAPD_MONITOR_AT "=" SLAPD_MONITOR_OPS_NAME +#define SLAPD_MONITOR_OPS_DN \ + SLAPD_MONITOR_OPS_RDN "," SLAPD_MONITOR_DN + +#define SLAPD_MONITOR_OVERLAY_NAME "Overlays" +#define SLAPD_MONITOR_OVERLAY_RDN \ + SLAPD_MONITOR_AT "=" SLAPD_MONITOR_OVERLAY_NAME +#define SLAPD_MONITOR_OVERLAY_DN \ + SLAPD_MONITOR_OVERLAY_RDN "," SLAPD_MONITOR_DN + +#define SLAPD_MONITOR_SASL_NAME "SASL" +#define SLAPD_MONITOR_SASL_RDN \ + SLAPD_MONITOR_AT "=" SLAPD_MONITOR_SASL_NAME +#define SLAPD_MONITOR_SASL_DN \ + SLAPD_MONITOR_SASL_RDN "," SLAPD_MONITOR_DN + +#define SLAPD_MONITOR_SENT_NAME "Statistics" +#define SLAPD_MONITOR_SENT_RDN \ + SLAPD_MONITOR_AT "=" SLAPD_MONITOR_SENT_NAME +#define SLAPD_MONITOR_SENT_DN \ + SLAPD_MONITOR_SENT_RDN "," SLAPD_MONITOR_DN + +#define SLAPD_MONITOR_THREAD_NAME "Threads" +#define SLAPD_MONITOR_THREAD_RDN \ + SLAPD_MONITOR_AT "=" SLAPD_MONITOR_THREAD_NAME +#define SLAPD_MONITOR_THREAD_DN \ + SLAPD_MONITOR_THREAD_RDN "," SLAPD_MONITOR_DN + +#define SLAPD_MONITOR_TIME_NAME "Time" +#define SLAPD_MONITOR_TIME_RDN \ + SLAPD_MONITOR_AT "=" SLAPD_MONITOR_TIME_NAME +#define SLAPD_MONITOR_TIME_DN \ + SLAPD_MONITOR_TIME_RDN "," SLAPD_MONITOR_DN + +#define SLAPD_MONITOR_TLS_NAME "TLS" +#define SLAPD_MONITOR_TLS_RDN \ + SLAPD_MONITOR_AT "=" SLAPD_MONITOR_TLS_NAME +#define SLAPD_MONITOR_TLS_DN \ + SLAPD_MONITOR_TLS_RDN "," SLAPD_MONITOR_DN + +#define SLAPD_MONITOR_RWW_NAME "Waiters" +#define SLAPD_MONITOR_RWW_RDN \ + SLAPD_MONITOR_AT "=" SLAPD_MONITOR_RWW_NAME +#define SLAPD_MONITOR_RWW_DN \ + SLAPD_MONITOR_RWW_RDN "," SLAPD_MONITOR_DN + +typedef struct monitor_subsys_t { + char *mss_name; + struct berval mss_rdn; + struct berval mss_dn; + struct berval mss_ndn; + struct berval mss_desc[ 3 ]; + int mss_flags; +#define MONITOR_F_OPENED 0x10000000U + +#define MONITOR_HAS_VOLATILE_CH( mp ) \ + ( ( mp )->mp_flags & MONITOR_F_VOLATILE_CH ) +#define MONITOR_HAS_CHILDREN( mp ) \ + ( ( mp )->mp_children || MONITOR_HAS_VOLATILE_CH( mp ) ) + + /* initialize entry and subentries */ + int ( *mss_open )( BackendDB *, struct monitor_subsys_t *ms ); + /* destroy structure */ + int ( *mss_destroy )( BackendDB *, struct monitor_subsys_t *ms ); + /* update existing dynamic entry and subentries */ + int ( *mss_update )( Operation *, SlapReply *, Entry * ); + /* create new dynamic subentries */ + int ( *mss_create )( Operation *, SlapReply *, + struct berval *ndn, Entry *, Entry ** ); + /* modify entry and subentries */ + int ( *mss_modify )( Operation *, SlapReply *, Entry * ); + + void *mss_private; +} monitor_subsys_t; + +extern BackendDB *be_monitor; + +/* increase this bufsize if entries in string form get too big */ +#define BACKMONITOR_BUFSIZE 8192 + +typedef int (monitor_cbfunc)( struct berval *ndn, monitor_callback_t *cb, + struct berval *base, int scope, struct berval *filter ); + +typedef int (monitor_cbafunc)( struct berval *ndn, Attribute *a, + monitor_callback_t *cb, + struct berval *base, int scope, struct berval *filter ); + +typedef struct monitor_extra_t { + int (*is_configured)(void); + monitor_subsys_t * (*get_subsys)( const char *name ); + monitor_subsys_t * (*get_subsys_by_dn)( struct berval *ndn, int sub ); + + int (*register_subsys)( monitor_subsys_t *ms ); + int (*register_backend)( BackendInfo *bi ); + int (*register_database)( BackendDB *be, struct berval *ndn_out ); + int (*register_overlay_info)( slap_overinst *on ); + int (*register_overlay)( BackendDB *be, slap_overinst *on, struct berval *ndn_out ); + int (*register_entry)( Entry *e, monitor_callback_t *cb, + monitor_subsys_t *ms, unsigned long flags ); + int (*register_entry_parent)( Entry *e, monitor_callback_t *cb, + monitor_subsys_t *ms, unsigned long flags, + struct berval *base, int scope, struct berval *filter ); + monitor_cbafunc *register_entry_attrs; + monitor_cbfunc *register_entry_callback; + + int (*unregister_entry)( struct berval *ndn ); + monitor_cbfunc *unregister_entry_parent; + monitor_cbafunc *unregister_entry_attrs; + monitor_cbfunc *unregister_entry_callback; + Entry * (*entry_stub)( struct berval *pdn, + struct berval *pndn, + struct berval *rdn, + ObjectClass *oc, + struct berval *create, + struct berval *modify ); + monitor_entry_t * (*entrypriv_create)( void ); + int (*register_subsys_late)( monitor_subsys_t *ms ); +} monitor_extra_t; + +LDAP_END_DECL + +#include "proto-back-monitor.h" + +#endif /* _back_monitor_h_ */ + diff --git a/servers/slapd/back-monitor/backend.c b/servers/slapd/back-monitor/backend.c new file mode 100644 index 0000000..19c5451 --- /dev/null +++ b/servers/slapd/back-monitor/backend.c @@ -0,0 +1,160 @@ +/* backend.c - deals with backend subsystem */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-monitor.h" + +/* + * initializes backend subentries + */ +int +monitor_subsys_backend_init( + BackendDB *be, + monitor_subsys_t *ms +) +{ + monitor_info_t *mi; + Entry *e_backend, **ep; + int i; + monitor_entry_t *mp; + monitor_subsys_t *ms_database; + BackendInfo *bi; + + mi = ( monitor_info_t * )be->be_private; + + ms_database = monitor_back_get_subsys( SLAPD_MONITOR_DATABASE_NAME ); + if ( ms_database == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_backend_init: " + "unable to get " + "\"" SLAPD_MONITOR_DATABASE_NAME "\" " + "subsystem\n", + 0, 0, 0 ); + return -1; + } + + if ( monitor_cache_get( mi, &ms->mss_ndn, &e_backend ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_backend_init: " + "unable to get entry \"%s\"\n", + ms->mss_ndn.bv_val, 0, 0 ); + return( -1 ); + } + + mp = ( monitor_entry_t * )e_backend->e_private; + mp->mp_children = NULL; + ep = &mp->mp_children; + + i = -1; + LDAP_STAILQ_FOREACH( bi, &backendInfo, bi_next ) { + char buf[ BACKMONITOR_BUFSIZE ]; + BackendDB *be; + struct berval bv; + int j; + Entry *e; + + i++; + + bv.bv_len = snprintf( buf, sizeof( buf ), "cn=Backend %d", i ); + bv.bv_val = buf; + + e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv, + mi->mi_oc_monitoredObject, NULL, NULL ); + + if ( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_backend_init: " + "unable to create entry \"cn=Backend %d,%s\"\n", + i, ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + + ber_str2bv( bi->bi_type, 0, 0, &bv ); + attr_merge_normalize_one( e, mi->mi_ad_monitoredInfo, + &bv, NULL ); + attr_merge_normalize_one( e_backend, mi->mi_ad_monitoredInfo, + &bv, NULL ); + + attr_merge_normalize_one( e, mi->mi_ad_monitorRuntimeConfig, + bi->bi_cf_ocs == NULL ? (struct berval *)&slap_false_bv : + (struct berval *)&slap_true_bv, NULL ); + + if ( bi->bi_controls ) { + int j; + + for ( j = 0; bi->bi_controls[ j ]; j++ ) { + ber_str2bv( bi->bi_controls[ j ], 0, 0, &bv ); + attr_merge_one( e, slap_schema.si_ad_supportedControl, + &bv, &bv ); + } + } + + j = -1; + LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) { + char buf[ SLAP_LDAPDN_MAXLEN ]; + struct berval dn; + + j++; + + if ( be->bd_info != bi ) { + continue; + } + + snprintf( buf, sizeof( buf ), "cn=Database %d,%s", + j, ms_database->mss_dn.bv_val ); + + ber_str2bv( buf, 0, 0, &dn ); + attr_merge_normalize_one( e, slap_schema.si_ad_seeAlso, + &dn, NULL ); + } + + mp = monitor_entrypriv_create(); + if ( mp == NULL ) { + return -1; + } + e->e_private = ( void * )mp; + mp->mp_info = ms; + mp->mp_flags = ms->mss_flags | MONITOR_F_SUB; + + if ( monitor_cache_add( mi, e ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_backend_init: " + "unable to add entry \"cn=Backend %d,%s\"\n", + i, + ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + + *ep = e; + ep = &mp->mp_next; + } + + monitor_cache_release( mi, e_backend ); + + return( 0 ); +} + diff --git a/servers/slapd/back-monitor/bind.c b/servers/slapd/back-monitor/bind.c new file mode 100644 index 0000000..7994955 --- /dev/null +++ b/servers/slapd/back-monitor/bind.c @@ -0,0 +1,48 @@ +/* bind.c - monitor backend bind routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <slap.h> +#include "back-monitor.h" + +/* + * At present, only rootdn can bind with simple bind + */ + +int +monitor_back_bind( Operation *op, SlapReply *rs ) +{ + Debug(LDAP_DEBUG_ARGS, "==> monitor_back_bind: dn: %s\n", + op->o_req_dn.bv_val, 0, 0 ); + + if ( be_isroot_pw( op ) ) { + return LDAP_SUCCESS; + } + + rs->sr_err = LDAP_INVALID_CREDENTIALS; + send_ldap_result( op, rs ); + + return rs->sr_err; +} + diff --git a/servers/slapd/back-monitor/cache.c b/servers/slapd/back-monitor/cache.c new file mode 100644 index 0000000..71e06a1 --- /dev/null +++ b/servers/slapd/back-monitor/cache.c @@ -0,0 +1,449 @@ +/* cache.c - routines to maintain an in-core cache of entries */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> +#include "ac/string.h" + +#include "slap.h" + +#include "back-monitor.h" + +/* + * The cache maps DNs to Entries. + * Each entry, on turn, holds the list of its children in the e_private field. + * This is used by search operation to perform onelevel and subtree candidate + * selection. + */ +typedef struct monitor_cache_t { + struct berval mc_ndn; + Entry *mc_e; +} monitor_cache_t; + +/* + * compares entries based on the dn + */ +int +monitor_cache_cmp( + const void *c1, + const void *c2 ) +{ + monitor_cache_t *cc1 = ( monitor_cache_t * )c1; + monitor_cache_t *cc2 = ( monitor_cache_t * )c2; + + /* + * case sensitive, because the dn MUST be normalized + */ + return ber_bvcmp( &cc1->mc_ndn, &cc2->mc_ndn ); +} + +/* + * checks for duplicate entries + */ +int +monitor_cache_dup( + void *c1, + void *c2 ) +{ + monitor_cache_t *cc1 = ( monitor_cache_t * )c1; + monitor_cache_t *cc2 = ( monitor_cache_t * )c2; + + /* + * case sensitive, because the dn MUST be normalized + */ + return ber_bvcmp( &cc1->mc_ndn, &cc2->mc_ndn ) == 0 ? -1 : 0; +} + +/* + * adds an entry to the cache and inits the mutex + */ +int +monitor_cache_add( + monitor_info_t *mi, + Entry *e ) +{ + monitor_cache_t *mc; + monitor_entry_t *mp; + int rc; + + assert( mi != NULL ); + assert( e != NULL ); + + mp = ( monitor_entry_t *)e->e_private; + + mc = ( monitor_cache_t * )ch_malloc( sizeof( monitor_cache_t ) ); + mc->mc_ndn = e->e_nname; + mc->mc_e = e; + ldap_pvt_thread_mutex_lock( &mi->mi_cache_mutex ); + rc = avl_insert( &mi->mi_cache, ( caddr_t )mc, + monitor_cache_cmp, monitor_cache_dup ); + ldap_pvt_thread_mutex_unlock( &mi->mi_cache_mutex ); + + return rc; +} + +/* + * locks the entry (no r/w) + */ +int +monitor_cache_lock( + Entry *e ) +{ + monitor_entry_t *mp; + + assert( e != NULL ); + assert( e->e_private != NULL ); + + mp = ( monitor_entry_t * )e->e_private; + ldap_pvt_thread_mutex_lock( &mp->mp_mutex ); + + return( 0 ); +} + +/* + * tries to lock the entry (no r/w) + */ +int +monitor_cache_trylock( + Entry *e ) +{ + monitor_entry_t *mp; + + assert( e != NULL ); + assert( e->e_private != NULL ); + + mp = ( monitor_entry_t * )e->e_private; + return ldap_pvt_thread_mutex_trylock( &mp->mp_mutex ); +} + +/* + * gets an entry from the cache based on the normalized dn + * with mutex locked + */ +int +monitor_cache_get( + monitor_info_t *mi, + struct berval *ndn, + Entry **ep ) +{ + monitor_cache_t tmp_mc, *mc; + + assert( mi != NULL ); + assert( ndn != NULL ); + assert( ep != NULL ); + + *ep = NULL; + + tmp_mc.mc_ndn = *ndn; +retry:; + ldap_pvt_thread_mutex_lock( &mi->mi_cache_mutex ); + mc = ( monitor_cache_t * )avl_find( mi->mi_cache, + ( caddr_t )&tmp_mc, monitor_cache_cmp ); + + if ( mc != NULL ) { + /* entry is returned with mutex locked */ + if ( monitor_cache_trylock( mc->mc_e ) ) { + ldap_pvt_thread_mutex_unlock( &mi->mi_cache_mutex ); + ldap_pvt_thread_yield(); + goto retry; + } + *ep = mc->mc_e; + } + + ldap_pvt_thread_mutex_unlock( &mi->mi_cache_mutex ); + + return ( *ep == NULL ? -1 : 0 ); +} + +/* + * gets an entry from the cache based on the normalized dn + * with mutex locked + */ +int +monitor_cache_remove( + monitor_info_t *mi, + struct berval *ndn, + Entry **ep ) +{ + monitor_cache_t tmp_mc, *mc; + struct berval pndn; + + assert( mi != NULL ); + assert( ndn != NULL ); + assert( ep != NULL ); + + *ep = NULL; + + dnParent( ndn, &pndn ); + +retry:; + ldap_pvt_thread_mutex_lock( &mi->mi_cache_mutex ); + + tmp_mc.mc_ndn = *ndn; + mc = ( monitor_cache_t * )avl_find( mi->mi_cache, + ( caddr_t )&tmp_mc, monitor_cache_cmp ); + + if ( mc != NULL ) { + monitor_cache_t *pmc; + + if ( monitor_cache_trylock( mc->mc_e ) ) { + ldap_pvt_thread_mutex_unlock( &mi->mi_cache_mutex ); + goto retry; + } + + tmp_mc.mc_ndn = pndn; + pmc = ( monitor_cache_t * )avl_find( mi->mi_cache, + ( caddr_t )&tmp_mc, monitor_cache_cmp ); + if ( pmc != NULL ) { + monitor_entry_t *mp = (monitor_entry_t *)mc->mc_e->e_private, + *pmp = (monitor_entry_t *)pmc->mc_e->e_private; + Entry **entryp; + + if ( monitor_cache_trylock( pmc->mc_e ) ) { + monitor_cache_release( mi, mc->mc_e ); + ldap_pvt_thread_mutex_unlock( &mi->mi_cache_mutex ); + goto retry; + } + + for ( entryp = &pmp->mp_children; *entryp != NULL; ) { + monitor_entry_t *next = (monitor_entry_t *)(*entryp)->e_private; + if ( next == mp ) { + *entryp = next->mp_next; + entryp = NULL; + break; + } + + entryp = &next->mp_next; + } + + if ( entryp != NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_cache_remove(\"%s\"): " + "not in parent's list\n", + ndn->bv_val, 0, 0 ); + } + + /* either succeeded, and the entry is no longer + * in its parent's list, or failed, and the + * entry is neither mucked with nor returned */ + monitor_cache_release( mi, pmc->mc_e ); + + if ( entryp == NULL ) { + monitor_cache_t *tmpmc; + + tmp_mc.mc_ndn = *ndn; + tmpmc = avl_delete( &mi->mi_cache, + ( caddr_t )&tmp_mc, monitor_cache_cmp ); + assert( tmpmc == mc ); + + *ep = mc->mc_e; + ch_free( mc ); + mc = NULL; + + /* NOTE: we destroy the mutex, but otherwise + * leave the private data around; specifically, + * callbacks need be freed by someone else */ + + ldap_pvt_thread_mutex_destroy( &mp->mp_mutex ); + mp->mp_next = NULL; + mp->mp_children = NULL; + } + + } + + if ( mc ) { + monitor_cache_release( mi, mc->mc_e ); + } + } + + ldap_pvt_thread_mutex_unlock( &mi->mi_cache_mutex ); + + return ( *ep == NULL ? -1 : 0 ); +} + +/* + * If the entry exists in cache, it is returned in locked status; + * otherwise, if the parent exists, if it may generate volatile + * descendants an attempt to generate the required entry is + * performed and, if successful, the entry is returned + */ +int +monitor_cache_dn2entry( + Operation *op, + SlapReply *rs, + struct berval *ndn, + Entry **ep, + Entry **matched ) +{ + monitor_info_t *mi = (monitor_info_t *)op->o_bd->be_private; + int rc; + struct berval p_ndn = BER_BVNULL; + Entry *e_parent; + monitor_entry_t *mp; + + assert( mi != NULL ); + assert( ndn != NULL ); + assert( ep != NULL ); + assert( matched != NULL ); + + *matched = NULL; + + if ( !dnIsSuffix( ndn, &op->o_bd->be_nsuffix[ 0 ] ) ) { + return( -1 ); + } + + rc = monitor_cache_get( mi, ndn, ep ); + if ( !rc && *ep != NULL ) { + return( 0 ); + } + + /* try with parent/ancestors */ + if ( BER_BVISNULL( ndn ) ) { + BER_BVSTR( &p_ndn, "" ); + + } else { + dnParent( ndn, &p_ndn ); + } + + rc = monitor_cache_dn2entry( op, rs, &p_ndn, &e_parent, matched ); + if ( rc || e_parent == NULL ) { + return( -1 ); + } + + mp = ( monitor_entry_t * )e_parent->e_private; + rc = -1; + if ( mp->mp_flags & MONITOR_F_VOLATILE_CH ) { + /* parent entry generates volatile children */ + rc = monitor_entry_create( op, rs, ndn, e_parent, ep ); + } + + if ( !rc ) { + monitor_cache_lock( *ep ); + monitor_cache_release( mi, e_parent ); + + } else { + *matched = e_parent; + } + + return( rc ); +} + +/* + * releases the lock of the entry; if it is marked as volatile, it is + * destroyed. + */ +int +monitor_cache_release( + monitor_info_t *mi, + Entry *e ) +{ + monitor_entry_t *mp; + + assert( mi != NULL ); + assert( e != NULL ); + assert( e->e_private != NULL ); + + mp = ( monitor_entry_t * )e->e_private; + + if ( mp->mp_flags & MONITOR_F_VOLATILE ) { + monitor_cache_t *mc, tmp_mc; + + /* volatile entries do not return to cache */ + ldap_pvt_thread_mutex_lock( &mi->mi_cache_mutex ); + tmp_mc.mc_ndn = e->e_nname; + mc = avl_delete( &mi->mi_cache, + ( caddr_t )&tmp_mc, monitor_cache_cmp ); + ldap_pvt_thread_mutex_unlock( &mi->mi_cache_mutex ); + if ( mc != NULL ) { + ch_free( mc ); + } + + ldap_pvt_thread_mutex_unlock( &mp->mp_mutex ); + ldap_pvt_thread_mutex_destroy( &mp->mp_mutex ); + ch_free( mp ); + e->e_private = NULL; + entry_free( e ); + + return( 0 ); + } + + ldap_pvt_thread_mutex_unlock( &mp->mp_mutex ); + + return( 0 ); +} + +static void +monitor_entry_destroy( void *v_mc ) +{ + monitor_cache_t *mc = (monitor_cache_t *)v_mc; + + if ( mc->mc_e != NULL ) { + monitor_entry_t *mp; + + assert( mc->mc_e->e_private != NULL ); + + mp = ( monitor_entry_t * )mc->mc_e->e_private; + + if ( mp->mp_cb ) { + monitor_callback_t *cb; + + for ( cb = mp->mp_cb; cb != NULL; ) { + monitor_callback_t *next = cb->mc_next; + + if ( cb->mc_free ) { + (void)cb->mc_free( mc->mc_e, &cb->mc_private ); + } + ch_free( mp->mp_cb ); + + cb = next; + } + } + + ldap_pvt_thread_mutex_destroy( &mp->mp_mutex ); + + ch_free( mp ); + mc->mc_e->e_private = NULL; + entry_free( mc->mc_e ); + } + + ch_free( mc ); +} + +int +monitor_cache_destroy( + monitor_info_t *mi ) +{ + if ( mi->mi_cache ) { + avl_free( mi->mi_cache, monitor_entry_destroy ); + } + + return 0; +} + +int monitor_back_release( + Operation *op, + Entry *e, + int rw ) +{ + monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private; + return monitor_cache_release( mi, e ); +} diff --git a/servers/slapd/back-monitor/compare.c b/servers/slapd/back-monitor/compare.c new file mode 100644 index 0000000..aeb6115 --- /dev/null +++ b/servers/slapd/back-monitor/compare.c @@ -0,0 +1,76 @@ +/* compare.c - monitor backend compare routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <slap.h> +#include "back-monitor.h" + +int +monitor_back_compare( Operation *op, SlapReply *rs ) +{ + monitor_info_t *mi = ( monitor_info_t * ) op->o_bd->be_private; + Entry *e, *matched = NULL; + int rc; + + /* get entry with reader lock */ + monitor_cache_dn2entry( op, rs, &op->o_req_ndn, &e, &matched ); + if ( e == NULL ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + if ( matched ) { + if ( !access_allowed_mask( op, matched, + slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL, NULL ) ) + { + /* do nothing */ ; + } else { + rs->sr_matched = matched->e_dn; + } + } + send_ldap_result( op, rs ); + if ( matched ) { + monitor_cache_release( mi, matched ); + rs->sr_matched = NULL; + } + + return rs->sr_err; + } + + monitor_entry_update( op, rs, e ); + rs->sr_err = slap_compare_entry( op, e, op->orc_ava ); + rc = rs->sr_err; + switch ( rc ) { + case LDAP_COMPARE_FALSE: + case LDAP_COMPARE_TRUE: + rc = LDAP_SUCCESS; + break; + } + + send_ldap_result( op, rs ); + rs->sr_err = rc; + + monitor_cache_release( mi, e ); + + return rs->sr_err; +} + diff --git a/servers/slapd/back-monitor/conn.c b/servers/slapd/back-monitor/conn.c new file mode 100644 index 0000000..64167c9 --- /dev/null +++ b/servers/slapd/back-monitor/conn.c @@ -0,0 +1,528 @@ +/* conn.c - deal with connection subsystem */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "slap.h" +#include "lutil.h" +#include "back-monitor.h" + +static int +monitor_subsys_conn_update( + Operation *op, + SlapReply *rs, + Entry *e ); + +static int +monitor_subsys_conn_create( + Operation *op, + SlapReply *rs, + struct berval *ndn, + Entry *e_parent, + Entry **ep ); + +int +monitor_subsys_conn_init( + BackendDB *be, + monitor_subsys_t *ms ) +{ + monitor_info_t *mi; + Entry *e, **ep, *e_conn; + monitor_entry_t *mp; + char buf[ BACKMONITOR_BUFSIZE ]; + struct berval bv; + + assert( be != NULL ); + + ms->mss_update = monitor_subsys_conn_update; + ms->mss_create = monitor_subsys_conn_create; + + mi = ( monitor_info_t * )be->be_private; + + if ( monitor_cache_get( mi, &ms->mss_ndn, &e_conn ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_conn_init: " + "unable to get entry \"%s\"\n", + ms->mss_ndn.bv_val, 0, 0 ); + return( -1 ); + } + + mp = ( monitor_entry_t * )e_conn->e_private; + mp->mp_children = NULL; + ep = &mp->mp_children; + + /* + * Max file descriptors + */ + BER_BVSTR( &bv, "cn=Max File Descriptors" ); + e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv, + mi->mi_oc_monitorCounterObject, NULL, NULL ); + + if ( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_conn_init: " + "unable to create entry \"%s,%s\"\n", + bv.bv_val, ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + + if ( dtblsize ) { + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%d", dtblsize ); + + } else { + BER_BVSTR( &bv, "0" ); + } + attr_merge_one( e, mi->mi_ad_monitorCounter, &bv, NULL ); + + mp = monitor_entrypriv_create(); + if ( mp == NULL ) { + return -1; + } + e->e_private = ( void * )mp; + mp->mp_info = ms; + mp->mp_flags = ms->mss_flags \ + | MONITOR_F_SUB | MONITOR_F_PERSISTENT; + mp->mp_flags &= ~MONITOR_F_VOLATILE_CH; + + if ( monitor_cache_add( mi, e ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_conn_init: " + "unable to add entry \"cn=Total,%s\"\n", + ms->mss_ndn.bv_val, 0, 0 ); + return( -1 ); + } + + *ep = e; + ep = &mp->mp_next; + + /* + * Total conns + */ + BER_BVSTR( &bv, "cn=Total" ); + e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv, + mi->mi_oc_monitorCounterObject, NULL, NULL ); + + if ( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_conn_init: " + "unable to create entry \"cn=Total,%s\"\n", + ms->mss_ndn.bv_val, 0, 0 ); + return( -1 ); + } + + BER_BVSTR( &bv, "-1" ); + attr_merge_one( e, mi->mi_ad_monitorCounter, &bv, NULL ); + + mp = monitor_entrypriv_create(); + if ( mp == NULL ) { + return -1; + } + e->e_private = ( void * )mp; + mp->mp_info = ms; + mp->mp_flags = ms->mss_flags \ + | MONITOR_F_SUB | MONITOR_F_PERSISTENT; + mp->mp_flags &= ~MONITOR_F_VOLATILE_CH; + + if ( monitor_cache_add( mi, e ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_conn_init: " + "unable to add entry \"cn=Total,%s\"\n", + ms->mss_ndn.bv_val, 0, 0 ); + return( -1 ); + } + + *ep = e; + ep = &mp->mp_next; + + /* + * Current conns + */ + BER_BVSTR( &bv, "cn=Current" ); + e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv, + mi->mi_oc_monitorCounterObject, NULL, NULL ); + + if ( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_conn_init: " + "unable to create entry \"cn=Current,%s\"\n", + ms->mss_ndn.bv_val, 0, 0 ); + return( -1 ); + } + + BER_BVSTR( &bv, "0" ); + attr_merge_one( e, mi->mi_ad_monitorCounter, &bv, NULL ); + + mp = monitor_entrypriv_create(); + if ( mp == NULL ) { + return -1; + } + e->e_private = ( void * )mp; + mp->mp_info = ms; + mp->mp_flags = ms->mss_flags \ + | MONITOR_F_SUB | MONITOR_F_PERSISTENT; + mp->mp_flags &= ~MONITOR_F_VOLATILE_CH; + + if ( monitor_cache_add( mi, e ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_conn_init: " + "unable to add entry \"cn=Current,%s\"\n", + ms->mss_ndn.bv_val, 0, 0 ); + return( -1 ); + } + + *ep = e; + ep = &mp->mp_next; + + monitor_cache_release( mi, e_conn ); + + return( 0 ); +} + +static int +monitor_subsys_conn_update( + Operation *op, + SlapReply *rs, + Entry *e ) +{ + monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private; + + long n = -1; + static struct berval total_bv = BER_BVC( "cn=total" ), + current_bv = BER_BVC( "cn=current" ); + struct berval rdn; + + assert( mi != NULL ); + assert( e != NULL ); + + dnRdn( &e->e_nname, &rdn ); + + if ( dn_match( &rdn, &total_bv ) ) { + n = connections_nextid(); + + } else if ( dn_match( &rdn, ¤t_bv ) ) { + Connection *c; + ber_socket_t connindex; + + for ( n = 0, c = connection_first( &connindex ); + c != NULL; + n++, c = connection_next( c, &connindex ) ) + { + /* No Op */ ; + } + connection_done( c ); + } + + if ( n != -1 ) { + Attribute *a; + char buf[LDAP_PVT_INTTYPE_CHARS(long)]; + ber_len_t len; + + a = attr_find( e->e_attrs, mi->mi_ad_monitorCounter ); + if ( a == NULL ) { + return( -1 ); + } + + snprintf( buf, sizeof( buf ), "%ld", n ); + len = strlen( buf ); + if ( len > a->a_vals[ 0 ].bv_len ) { + a->a_vals[ 0 ].bv_val = ber_memrealloc( a->a_vals[ 0 ].bv_val, len + 1 ); + } + a->a_vals[ 0 ].bv_len = len; + AC_MEMCPY( a->a_vals[ 0 ].bv_val, buf, len + 1 ); + + /* FIXME: touch modifyTimestamp? */ + } + + return SLAP_CB_CONTINUE; +} + +static int +conn_create( + monitor_info_t *mi, + Connection *c, + Entry **ep, + monitor_subsys_t *ms ) +{ + monitor_entry_t *mp; + struct tm tm; + char buf[ BACKMONITOR_BUFSIZE ]; + char buf2[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + char buf3[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + + struct berval bv, ctmbv, mtmbv; + struct berval bv_unknown= BER_BVC("unknown"); + + Entry *e; + + assert( c != NULL ); + assert( ep != NULL ); + + ldap_pvt_gmtime( &c->c_starttime, &tm ); + + ctmbv.bv_len = lutil_gentime( buf2, sizeof( buf2 ), &tm ); + ctmbv.bv_val = buf2; + + ldap_pvt_gmtime( &c->c_activitytime, &tm ); + mtmbv.bv_len = lutil_gentime( buf3, sizeof( buf3 ), &tm ); + mtmbv.bv_val = buf3; + + bv.bv_len = snprintf( buf, sizeof( buf ), + "cn=Connection %ld", c->c_connid ); + bv.bv_val = buf; + e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv, + mi->mi_oc_monitorConnection, &ctmbv, &mtmbv ); + + if ( e == NULL) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_conn_create: " + "unable to create entry " + "\"cn=Connection %ld,%s\"\n", + c->c_connid, + ms->mss_dn.bv_val, 0 ); + return( -1 ); + } + +#ifdef MONITOR_LEGACY_CONN + /* NOTE: this will disappear, as the exploded data + * has been moved to dedicated attributes */ + bv.bv_len = snprintf( buf, sizeof( buf ), + "%ld " + ": %ld " + ": %ld/%ld/%ld/%ld " + ": %ld/%ld/%ld " + ": %s%s%s%s%s%s " + ": %s " + ": %s " + ": %s " + ": %s " + ": %s " + ": %s " + ": %s", + c->c_connid, + (long) c->c_protocol, + c->c_n_ops_received, c->c_n_ops_executing, + c->c_n_ops_pending, c->c_n_ops_completed, + + /* add low-level counters here */ + c->c_n_get, c->c_n_read, c->c_n_write, + + c->c_currentber ? "r" : "", + c->c_writewaiter ? "w" : "", + LDAP_STAILQ_EMPTY( &c->c_ops ) ? "" : "x", + LDAP_STAILQ_EMPTY( &c->c_pending_ops ) ? "" : "p", + connection_state2str( c->c_conn_state ), + c->c_sasl_bind_in_progress ? "S" : "", + + c->c_dn.bv_len ? c->c_dn.bv_val : SLAPD_ANONYMOUS, + + c->c_listener_url.bv_val, + BER_BVISNULL( &c->c_peer_domain ) + ? "" : c->c_peer_domain.bv_val, + BER_BVISNULL( &c->c_peer_name ) + ? "" : c->c_peer_name.bv_val, + c->c_sock_name.bv_val, + + buf2, + buf3 ); + attr_merge_normalize_one( e, mi->mi_ad_monitoredInfo, &bv, NULL ); +#endif /* MONITOR_LEGACY_CONN */ + + bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", c->c_connid ); + attr_merge_one( e, mi->mi_ad_monitorConnectionNumber, &bv, NULL ); + + bv.bv_len = snprintf( buf, sizeof( buf ), "%ld", (long) c->c_protocol ); + attr_merge_normalize_one( e, mi->mi_ad_monitorConnectionProtocol, &bv, NULL ); + + bv.bv_len = snprintf( buf, sizeof( buf ), "%ld", c->c_n_ops_received ); + attr_merge_one( e, mi->mi_ad_monitorConnectionOpsReceived, &bv, NULL ); + + bv.bv_len = snprintf( buf, sizeof( buf ), "%ld", c->c_n_ops_executing ); + attr_merge_one( e, mi->mi_ad_monitorConnectionOpsExecuting, &bv, NULL ); + + bv.bv_len = snprintf( buf, sizeof( buf ), "%ld", c->c_n_ops_pending ); + attr_merge_one( e, mi->mi_ad_monitorConnectionOpsPending, &bv, NULL ); + + bv.bv_len = snprintf( buf, sizeof( buf ), "%ld", c->c_n_ops_completed ); + attr_merge_one( e, mi->mi_ad_monitorConnectionOpsCompleted, &bv, NULL ); + + bv.bv_len = snprintf( buf, sizeof( buf ), "%ld", c->c_n_get ); + attr_merge_one( e, mi->mi_ad_monitorConnectionGet, &bv, NULL ); + + bv.bv_len = snprintf( buf, sizeof( buf ), "%ld", c->c_n_read ); + attr_merge_one( e, mi->mi_ad_monitorConnectionRead, &bv, NULL ); + + bv.bv_len = snprintf( buf, sizeof( buf ), "%ld", c->c_n_write ); + attr_merge_one( e, mi->mi_ad_monitorConnectionWrite, &bv, NULL ); + + bv.bv_len = snprintf( buf, sizeof( buf ), "%s%s%s%s%s%s", + c->c_currentber ? "r" : "", + c->c_writewaiter ? "w" : "", + LDAP_STAILQ_EMPTY( &c->c_ops ) ? "" : "x", + LDAP_STAILQ_EMPTY( &c->c_pending_ops ) ? "" : "p", + connection_state2str( c->c_conn_state ), + c->c_sasl_bind_in_progress ? "S" : "" ); + attr_merge_normalize_one( e, mi->mi_ad_monitorConnectionMask, &bv, NULL ); + + attr_merge_one( e, mi->mi_ad_monitorConnectionAuthzDN, + &c->c_dn, &c->c_ndn ); + + /* NOTE: client connections leave the c_peer_* fields NULL */ + assert( !BER_BVISNULL( &c->c_listener_url ) ); + attr_merge_normalize_one( e, mi->mi_ad_monitorConnectionListener, + &c->c_listener_url, NULL ); + + attr_merge_normalize_one( e, mi->mi_ad_monitorConnectionPeerDomain, + BER_BVISNULL( &c->c_peer_domain ) ? &bv_unknown : &c->c_peer_domain, + NULL ); + + attr_merge_normalize_one( e, mi->mi_ad_monitorConnectionPeerAddress, + BER_BVISNULL( &c->c_peer_name ) ? &bv_unknown : &c->c_peer_name, + NULL ); + + assert( !BER_BVISNULL( &c->c_sock_name ) ); + attr_merge_normalize_one( e, mi->mi_ad_monitorConnectionLocalAddress, + &c->c_sock_name, NULL ); + + attr_merge_normalize_one( e, mi->mi_ad_monitorConnectionStartTime, &ctmbv, NULL ); + + attr_merge_normalize_one( e, mi->mi_ad_monitorConnectionActivityTime, &mtmbv, NULL ); + + mp = monitor_entrypriv_create(); + if ( mp == NULL ) { + return LDAP_OTHER; + } + e->e_private = ( void * )mp; + mp->mp_info = ms; + mp->mp_flags = MONITOR_F_SUB | MONITOR_F_VOLATILE; + + *ep = e; + + return SLAP_CB_CONTINUE; +} + +static int +monitor_subsys_conn_create( + Operation *op, + SlapReply *rs, + struct berval *ndn, + Entry *e_parent, + Entry **ep ) +{ + monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private; + + int rc = SLAP_CB_CONTINUE; + monitor_subsys_t *ms; + + assert( mi != NULL ); + assert( e_parent != NULL ); + assert( ep != NULL ); + + ms = (( monitor_entry_t *)e_parent->e_private)->mp_info; + + *ep = NULL; + + if ( ndn == NULL ) { + Connection *c; + ber_socket_t connindex; + Entry *e = NULL, + *e_tmp = NULL; + + /* create all the children of e_parent */ + for ( c = connection_first( &connindex ); + c != NULL; + c = connection_next( c, &connindex ) ) + { + monitor_entry_t *mp; + + if ( conn_create( mi, c, &e, ms ) != SLAP_CB_CONTINUE + || e == NULL ) + { + for ( ; e_tmp != NULL; ) { + mp = ( monitor_entry_t * )e_tmp->e_private; + e = mp->mp_next; + + ch_free( mp ); + e_tmp->e_private = NULL; + entry_free( e_tmp ); + + e_tmp = e; + } + rc = rs->sr_err = LDAP_OTHER; + break; + } + mp = ( monitor_entry_t * )e->e_private; + mp->mp_next = e_tmp; + e_tmp = e; + } + connection_done( c ); + *ep = e; + + } else { + Connection *c; + ber_socket_t connindex; + unsigned long connid; + char *next = NULL; + static struct berval nconn_bv = BER_BVC( "cn=connection " ); + + rc = LDAP_NO_SUCH_OBJECT; + + /* create exactly the required entry; + * the normalized DN must start with "cn=connection ", + * followed by the connection id, followed by + * the RDN separator "," */ + if ( ndn->bv_len <= nconn_bv.bv_len + || strncmp( ndn->bv_val, nconn_bv.bv_val, nconn_bv.bv_len ) != 0 ) + { + return -1; + } + + connid = strtol( &ndn->bv_val[ nconn_bv.bv_len ], &next, 10 ); + if ( next[ 0 ] != ',' ) { + return ( rs->sr_err = LDAP_OTHER ); + } + + for ( c = connection_first( &connindex ); + c != NULL; + c = connection_next( c, &connindex ) ) + { + if ( c->c_connid == connid ) { + rc = conn_create( mi, c, ep, ms ); + if ( rc != SLAP_CB_CONTINUE ) { + rs->sr_err = rc; + + } else if ( *ep == NULL ) { + rc = rs->sr_err = LDAP_OTHER; + } + + break; + } + } + + connection_done( c ); + } + + return rc; +} + diff --git a/servers/slapd/back-monitor/database.c b/servers/slapd/back-monitor/database.c new file mode 100644 index 0000000..0c6a7b9 --- /dev/null +++ b/servers/slapd/back-monitor/database.c @@ -0,0 +1,1031 @@ +/* database.c - deals with database subsystem */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/unistd.h> + +#include "slap.h" +#include "back-monitor.h" + +#if defined(LDAP_SLAPI) +#include "slapi.h" +static int monitor_back_add_plugin( monitor_info_t *mi, Backend *be, Entry *e ); +#endif /* defined(LDAP_SLAPI) */ + +static int +monitor_subsys_database_modify( + Operation *op, + SlapReply *rs, + Entry *e ); + +static struct restricted_ops_t { + struct berval op; + unsigned int tag; +} restricted_ops[] = { + { BER_BVC( "add" ), SLAP_RESTRICT_OP_ADD }, + { BER_BVC( "bind" ), SLAP_RESTRICT_OP_BIND }, + { BER_BVC( "compare" ), SLAP_RESTRICT_OP_COMPARE }, + { BER_BVC( "delete" ), SLAP_RESTRICT_OP_DELETE }, + { BER_BVC( "extended" ), SLAP_RESTRICT_OP_EXTENDED }, + { BER_BVC( "modify" ), SLAP_RESTRICT_OP_MODIFY }, + { BER_BVC( "rename" ), SLAP_RESTRICT_OP_RENAME }, + { BER_BVC( "search" ), SLAP_RESTRICT_OP_SEARCH }, + { BER_BVNULL, 0 } +}, restricted_exops[] = { + { BER_BVC( LDAP_EXOP_START_TLS ), SLAP_RESTRICT_EXOP_START_TLS }, + { BER_BVC( LDAP_EXOP_MODIFY_PASSWD ), SLAP_RESTRICT_EXOP_MODIFY_PASSWD }, + { BER_BVC( LDAP_EXOP_WHO_AM_I ), SLAP_RESTRICT_EXOP_WHOAMI }, + { BER_BVC( LDAP_EXOP_CANCEL ), SLAP_RESTRICT_EXOP_CANCEL }, + { BER_BVNULL, 0 } +}; + +static int +init_readOnly( monitor_info_t *mi, Entry *e, slap_mask_t restrictops ) +{ + struct berval *tf = ( ( restrictops & SLAP_RESTRICT_OP_MASK ) == SLAP_RESTRICT_OP_WRITES ) ? + (struct berval *)&slap_true_bv : (struct berval *)&slap_false_bv; + + return attr_merge_one( e, mi->mi_ad_readOnly, tf, NULL ); +} + +static int +init_restrictedOperation( monitor_info_t *mi, Entry *e, slap_mask_t restrictops ) +{ + int i, rc; + + for ( i = 0; restricted_ops[ i ].op.bv_val; i++ ) { + if ( restrictops & restricted_ops[ i ].tag ) { + rc = attr_merge_one( e, mi->mi_ad_restrictedOperation, + &restricted_ops[ i ].op, + &restricted_ops[ i ].op ); + if ( rc ) { + return rc; + } + } + } + + for ( i = 0; restricted_exops[ i ].op.bv_val; i++ ) { + if ( restrictops & restricted_exops[ i ].tag ) { + rc = attr_merge_one( e, mi->mi_ad_restrictedOperation, + &restricted_exops[ i ].op, + &restricted_exops[ i ].op ); + if ( rc ) { + return rc; + } + } + } + + return LDAP_SUCCESS; +} + +static int +monitor_subsys_overlay_init_one( + monitor_info_t *mi, + BackendDB *be, + monitor_subsys_t *ms, + monitor_subsys_t *ms_overlay, + slap_overinst *on, + Entry *e_database, + Entry **ep_overlay ) +{ + char buf[ BACKMONITOR_BUFSIZE ]; + int j, o; + Entry *e_overlay; + slap_overinst *on2; + slap_overinfo *oi = NULL; + BackendInfo *bi; + monitor_entry_t *mp_overlay; + struct berval bv; + + assert( overlay_is_over( be ) ); + + oi = (slap_overinfo *)be->bd_info->bi_private; + bi = oi->oi_orig; + + /* find the overlay number, o */ + for ( o = 0, on2 = oi->oi_list; on2 && on2 != on; on2 = on2->on_next, o++ ) + ; + + if ( on2 == NULL ) { + return -1; + } + + /* find the overlay type number, j */ + for ( on2 = overlay_next( NULL ), j = 0; on2; on2 = overlay_next( on2 ), j++ ) { + if ( on2->on_bi.bi_type == on->on_bi.bi_type ) { + break; + } + } + assert( on2 != NULL ); + + bv.bv_len = snprintf( buf, sizeof( buf ), "cn=Overlay %d", o ); + bv.bv_val = buf; + + e_overlay = monitor_entry_stub( &e_database->e_name, &e_database->e_nname, &bv, + mi->mi_oc_monitoredObject, NULL, NULL ); + + if ( e_overlay == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_overlay_init_one: " + "unable to create entry " + "\"cn=Overlay %d,%s\"\n", + o, e_database->e_name.bv_val, 0 ); + return( -1 ); + } + ber_str2bv( on->on_bi.bi_type, 0, 0, &bv ); + attr_merge_normalize_one( e_overlay, mi->mi_ad_monitoredInfo, &bv, NULL ); + + bv.bv_len = snprintf( buf, sizeof( buf ), "cn=Overlay %d,%s", + j, ms_overlay->mss_dn.bv_val ); + bv.bv_val = buf; + attr_merge_normalize_one( e_overlay, slap_schema.si_ad_seeAlso, + &bv, NULL ); + + if ( SLAP_MONITOR( be ) ) { + attr_merge( e_overlay, slap_schema.si_ad_monitorContext, + be->be_suffix, be->be_nsuffix ); + + } else { + attr_merge( e_overlay, slap_schema.si_ad_namingContexts, + be->be_suffix, NULL ); + } + + mp_overlay = monitor_entrypriv_create(); + if ( mp_overlay == NULL ) { + return -1; + } + e_overlay->e_private = ( void * )mp_overlay; + mp_overlay->mp_info = ms; + mp_overlay->mp_flags = ms->mss_flags | MONITOR_F_SUB; + + if ( monitor_cache_add( mi, e_overlay ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_overlay_init_one: " + "unable to add entry " + "\"cn=Overlay %d,%s\"\n", + o, e_database->e_name.bv_val, 0 ); + return -1; + } + + *ep_overlay = e_overlay; + ep_overlay = &mp_overlay->mp_next; + + return 0; +} + +static int +monitor_subsys_database_init_one( + monitor_info_t *mi, + BackendDB *be, + monitor_subsys_t *ms, + monitor_subsys_t *ms_backend, + monitor_subsys_t *ms_overlay, + struct berval *rdn, + Entry *e_database, + Entry ***epp ) +{ + char buf[ BACKMONITOR_BUFSIZE ]; + int j; + slap_overinfo *oi = NULL; + BackendInfo *bi, *bi2; + Entry *e; + monitor_entry_t *mp; + char *rdnval = strchr( rdn->bv_val, '=' ) + 1; + struct berval bv; + + bi = be->bd_info; + + if ( overlay_is_over( be ) ) { + oi = (slap_overinfo *)be->bd_info->bi_private; + bi = oi->oi_orig; + } + + e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, rdn, + mi->mi_oc_monitoredObject, NULL, NULL ); + + if ( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_database_init_one: " + "unable to create entry \"%s,%s\"\n", + rdn->bv_val, ms->mss_dn.bv_val, 0 ); + return( -1 ); + } + + ber_str2bv( bi->bi_type, 0, 0, &bv ); + attr_merge_normalize_one( e, mi->mi_ad_monitoredInfo, &bv, NULL ); + attr_merge_one( e, mi->mi_ad_monitorIsShadow, + SLAP_SHADOW( be ) ? (struct berval *)&slap_true_bv : + (struct berval *)&slap_false_bv, NULL ); + + if ( SLAP_MONITOR( be ) ) { + attr_merge( e, slap_schema.si_ad_monitorContext, + be->be_suffix, be->be_nsuffix ); + attr_merge( e_database, slap_schema.si_ad_monitorContext, + be->be_suffix, be->be_nsuffix ); + + } else { + if ( be->be_suffix == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_database_init_one: " + "missing suffix for %s\n", + rdnval, 0, 0 ); + } else { + attr_merge( e, slap_schema.si_ad_namingContexts, + be->be_suffix, NULL ); + attr_merge( e_database, slap_schema.si_ad_namingContexts, + be->be_suffix, NULL ); + } + + if ( SLAP_GLUE_SUBORDINATE( be ) ) { + BackendDB *sup_be = select_backend( &be->be_nsuffix[ 0 ], 1 ); + if ( sup_be == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_database_init: " + "unable to get superior for %s\n", + be->be_suffix[ 0 ].bv_val, 0, 0 ); + + } else { + attr_merge( e, mi->mi_ad_monitorSuperiorDN, + sup_be->be_suffix, sup_be->be_nsuffix ); + } + } + } + + (void)init_readOnly( mi, e, be->be_restrictops ); + (void)init_restrictedOperation( mi, e, be->be_restrictops ); + + if ( SLAP_SHADOW( be ) && be->be_update_refs ) { + attr_merge_normalize( e, mi->mi_ad_monitorUpdateRef, + be->be_update_refs, NULL ); + } + + if ( oi != NULL ) { + slap_overinst *on = oi->oi_list, + *on1 = on; + + for ( ; on; on = on->on_next ) { + slap_overinst *on2; + + for ( on2 = on1; on2 != on; on2 = on2->on_next ) { + if ( on2->on_bi.bi_type == on->on_bi.bi_type ) { + break; + } + } + + if ( on2 != on ) { + break; + } + + ber_str2bv( on->on_bi.bi_type, 0, 0, &bv ); + attr_merge_normalize_one( e, mi->mi_ad_monitorOverlay, + &bv, NULL ); + + /* find the overlay number, j */ + for ( on2 = overlay_next( NULL ), j = 0; on2; on2 = overlay_next( on2 ), j++ ) { + if ( on2->on_bi.bi_type == on->on_bi.bi_type ) { + break; + } + } + assert( on2 != NULL ); + + snprintf( buf, sizeof( buf ), + "cn=Overlay %d,%s", + j, ms_overlay->mss_dn.bv_val ); + ber_str2bv( buf, 0, 0, &bv ); + attr_merge_normalize_one( e, + slap_schema.si_ad_seeAlso, + &bv, NULL ); + } + } + + j = -1; + LDAP_STAILQ_FOREACH( bi2, &backendInfo, bi_next ) { + j++; + if ( bi2->bi_type == bi->bi_type ) { + snprintf( buf, sizeof( buf ), + "cn=Backend %d,%s", + j, ms_backend->mss_dn.bv_val ); + bv.bv_val = buf; + bv.bv_len = strlen( buf ); + attr_merge_normalize_one( e, + slap_schema.si_ad_seeAlso, + &bv, NULL ); + break; + } + } + /* we must find it! */ + assert( j >= 0 ); + + mp = monitor_entrypriv_create(); + if ( mp == NULL ) { + return -1; + } + e->e_private = ( void * )mp; + mp->mp_info = ms; + mp->mp_flags = ms->mss_flags + | MONITOR_F_SUB; + mp->mp_private = be; + + if ( monitor_cache_add( mi, e ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_database_init_one: " + "unable to add entry \"%s,%s\"\n", + rdn->bv_val, ms->mss_dn.bv_val, 0 ); + return( -1 ); + } + +#if defined(LDAP_SLAPI) + monitor_back_add_plugin( mi, be, e ); +#endif /* defined(LDAP_SLAPI) */ + + if ( oi != NULL ) { + Entry **ep_overlay = &mp->mp_children; + slap_overinst *on = oi->oi_list; + + for ( ; on; on = on->on_next ) { + monitor_subsys_overlay_init_one( mi, be, + ms, ms_overlay, on, e, ep_overlay ); + } + } + + **epp = e; + *epp = &mp->mp_next; + + return 0; +} + +static int +monitor_back_register_database_and_overlay( + BackendDB *be, + struct slap_overinst *on, + struct berval *ndn_out ) +{ + monitor_info_t *mi; + Entry *e_database, **ep; + int i, rc; + monitor_entry_t *mp; + monitor_subsys_t *ms_backend, + *ms_database, + *ms_overlay; + struct berval bv; + char buf[ BACKMONITOR_BUFSIZE ]; + + assert( be_monitor != NULL ); + + if ( !monitor_subsys_is_opened() ) { + if ( on ) { + return monitor_back_register_overlay_limbo( be, on, ndn_out ); + + } else { + return monitor_back_register_database_limbo( be, ndn_out ); + } + } + + mi = ( monitor_info_t * )be_monitor->be_private; + + ms_backend = monitor_back_get_subsys( SLAPD_MONITOR_BACKEND_NAME ); + if ( ms_backend == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_database: " + "unable to get " + "\"" SLAPD_MONITOR_BACKEND_NAME "\" " + "subsystem\n", + 0, 0, 0 ); + return -1; + } + + ms_database = monitor_back_get_subsys( SLAPD_MONITOR_DATABASE_NAME ); + if ( ms_database == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_database: " + "unable to get " + "\"" SLAPD_MONITOR_DATABASE_NAME "\" " + "subsystem\n", + 0, 0, 0 ); + return -1; + } + + ms_overlay = monitor_back_get_subsys( SLAPD_MONITOR_OVERLAY_NAME ); + if ( ms_overlay == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_database: " + "unable to get " + "\"" SLAPD_MONITOR_OVERLAY_NAME "\" " + "subsystem\n", + 0, 0, 0 ); + return -1; + } + + if ( monitor_cache_get( mi, &ms_database->mss_ndn, &e_database ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_database_init: " + "unable to get entry \"%s\"\n", + ms_database->mss_ndn.bv_val, 0, 0 ); + return( -1 ); + } + + mp = ( monitor_entry_t * )e_database->e_private; + for ( i = -1, ep = &mp->mp_children; *ep; i++ ) { + mp = ( monitor_entry_t * )(*ep)->e_private; + + assert( mp != NULL ); + if ( mp->mp_private == be->bd_self ) { + rc = 0; + goto done; + } + ep = &mp->mp_next; + } + + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "cn=Database %d", i ); + if ( bv.bv_len >= sizeof( buf ) ) { + rc = -1; + goto done; + } + + rc = monitor_subsys_database_init_one( mi, be, + ms_database, ms_backend, ms_overlay, &bv, e_database, &ep ); + if ( rc != 0 ) { + goto done; + } + /* database_init_one advanced ep past where we want. + * But it stored the entry we want in mp->mp_next. + */ + ep = &mp->mp_next; + +done:; + monitor_cache_release( mi, e_database ); + if ( rc == 0 && ndn_out && ep && *ep ) { + if ( on ) { + Entry *e_ov; + struct berval ov_type; + + ber_str2bv( on->on_bi.bi_type, 0, 0, &ov_type ); + + mp = ( monitor_entry_t * ) (*ep)->e_private; + for ( e_ov = mp->mp_children; e_ov; ) { + Attribute *a = attr_find( e_ov->e_attrs, mi->mi_ad_monitoredInfo ); + + if ( a != NULL && bvmatch( &a->a_nvals[ 0 ], &ov_type ) ) { + *ndn_out = e_ov->e_nname; + break; + } + + mp = ( monitor_entry_t * ) e_ov->e_private; + e_ov = mp->mp_next; + } + + } else { + *ndn_out = (*ep)->e_nname; + } + } + + return rc; +} + +int +monitor_back_register_database( + BackendDB *be, + struct berval *ndn_out ) +{ + return monitor_back_register_database_and_overlay( be, NULL, ndn_out ); +} + +int +monitor_back_register_overlay( + BackendDB *be, + struct slap_overinst *on, + struct berval *ndn_out ) +{ + return monitor_back_register_database_and_overlay( be, on, ndn_out ); +} + +int +monitor_subsys_database_init( + BackendDB *be, + monitor_subsys_t *ms ) +{ + monitor_info_t *mi; + Entry *e_database, **ep; + int i, rc; + monitor_entry_t *mp; + monitor_subsys_t *ms_backend, + *ms_overlay; + struct berval bv; + + assert( be != NULL ); + + ms->mss_modify = monitor_subsys_database_modify; + + mi = ( monitor_info_t * )be->be_private; + + ms_backend = monitor_back_get_subsys( SLAPD_MONITOR_BACKEND_NAME ); + if ( ms_backend == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_database_init: " + "unable to get " + "\"" SLAPD_MONITOR_BACKEND_NAME "\" " + "subsystem\n", + 0, 0, 0 ); + return -1; + } + + ms_overlay = monitor_back_get_subsys( SLAPD_MONITOR_OVERLAY_NAME ); + if ( ms_overlay == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_database_init: " + "unable to get " + "\"" SLAPD_MONITOR_OVERLAY_NAME "\" " + "subsystem\n", + 0, 0, 0 ); + return -1; + } + + if ( monitor_cache_get( mi, &ms->mss_ndn, &e_database ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_database_init: " + "unable to get entry \"%s\"\n", + ms->mss_ndn.bv_val, 0, 0 ); + return( -1 ); + } + + (void)init_readOnly( mi, e_database, frontendDB->be_restrictops ); + (void)init_restrictedOperation( mi, e_database, frontendDB->be_restrictops ); + + mp = ( monitor_entry_t * )e_database->e_private; + mp->mp_children = NULL; + ep = &mp->mp_children; + + BER_BVSTR( &bv, "cn=Frontend" ); + rc = monitor_subsys_database_init_one( mi, frontendDB, + ms, ms_backend, ms_overlay, &bv, e_database, &ep ); + if ( rc != 0 ) { + return rc; + } + + i = -1; + LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) { + char buf[ BACKMONITOR_BUFSIZE ]; + + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "cn=Database %d", ++i ); + if ( bv.bv_len >= sizeof( buf ) ) { + return -1; + } + + rc = monitor_subsys_database_init_one( mi, be, + ms, ms_backend, ms_overlay, &bv, e_database, &ep ); + if ( rc != 0 ) { + return rc; + } + } + + monitor_cache_release( mi, e_database ); + + return( 0 ); +} + +/* + * v: array of values + * cur: must not contain the tags corresponding to the values in v + * delta: will contain the tags corresponding to the values in v + */ +static int +value_mask( BerVarray v, slap_mask_t cur, slap_mask_t *delta ) +{ + for ( ; !BER_BVISNULL( v ); v++ ) { + struct restricted_ops_t *rops; + int i; + + if ( OID_LEADCHAR( v->bv_val[ 0 ] ) ) { + rops = restricted_exops; + + } else { + rops = restricted_ops; + } + + for ( i = 0; !BER_BVISNULL( &rops[ i ].op ); i++ ) { + if ( ber_bvstrcasecmp( v, &rops[ i ].op ) != 0 ) { + continue; + } + + if ( rops[ i ].tag & *delta ) { + return LDAP_OTHER; + } + + if ( rops[ i ].tag & cur ) { + return LDAP_OTHER; + } + + cur |= rops[ i ].tag; + *delta |= rops[ i ].tag; + + break; + } + + if ( BER_BVISNULL( &rops[ i ].op ) ) { + return LDAP_INVALID_SYNTAX; + } + } + + return LDAP_SUCCESS; +} + +static int +monitor_subsys_database_modify( + Operation *op, + SlapReply *rs, + Entry *e ) +{ + monitor_info_t *mi = (monitor_info_t *)op->o_bd->be_private; + int rc = LDAP_OTHER; + Attribute *save_attrs, *a; + Modifications *ml; + Backend *be; + int ro_gotval = 1, i, n; + slap_mask_t rp_add = 0, rp_delete = 0, rp_cur; + struct berval *tf; + + i = sscanf( e->e_nname.bv_val, "cn=database %d,", &n ); + if ( i != 1 ) { + return SLAP_CB_CONTINUE; + } + + if ( n < 0 || n >= nBackendDB ) { + rs->sr_text = "invalid database index"; + return ( rs->sr_err = LDAP_NO_SUCH_OBJECT ); + } + + LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) { + if ( n == 0 ) { + break; + } + n--; + } + /* do not allow some changes on back-monitor (needs work)... */ + if ( SLAP_MONITOR( be ) ) { + rs->sr_text = "no modifications allowed to monitor database entry"; + return ( rs->sr_err = LDAP_UNWILLING_TO_PERFORM ); + } + + rp_cur = be->be_restrictops; + + save_attrs = e->e_attrs; + e->e_attrs = attrs_dup( e->e_attrs ); + + for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) { + Modification *mod = &ml->sml_mod; + + if ( mod->sm_desc == mi->mi_ad_readOnly ) { + int val = -1; + + if ( mod->sm_values ) { + if ( !BER_BVISNULL( &mod->sm_values[ 1 ] ) ) { + rs->sr_text = "attempting to modify multiple values of single-valued attribute"; + rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + + if ( bvmatch( &slap_true_bv, mod->sm_values )) { + val = 1; + + } else if ( bvmatch( &slap_false_bv, mod->sm_values )) { + val = 0; + + } else { + assert( 0 ); + rc = rs->sr_err = LDAP_INVALID_SYNTAX; + goto done; + } + } + + switch ( mod->sm_op ) { + case LDAP_MOD_DELETE: + if ( ro_gotval < 1 ) { + rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + ro_gotval--; + + if ( val == 0 && ( rp_cur & SLAP_RESTRICT_OP_WRITES ) == SLAP_RESTRICT_OP_WRITES ) { + rc = rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE; + goto done; + } + + if ( val == 1 && ( rp_cur & SLAP_RESTRICT_OP_WRITES ) != SLAP_RESTRICT_OP_WRITES ) { + rc = rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE; + goto done; + } + + break; + + case LDAP_MOD_REPLACE: + ro_gotval = 0; + /* fall thru */ + + case LDAP_MOD_ADD: + if ( ro_gotval > 0 ) { + rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + ro_gotval++; + + if ( val == 1 ) { + rp_add |= (~rp_cur) & SLAP_RESTRICT_OP_WRITES; + rp_cur |= SLAP_RESTRICT_OP_WRITES; + rp_delete &= ~SLAP_RESTRICT_OP_WRITES; + + } else if ( val == 0 ) { + rp_delete |= rp_cur & SLAP_RESTRICT_OP_WRITES; + rp_cur &= ~SLAP_RESTRICT_OP_WRITES; + rp_add &= ~SLAP_RESTRICT_OP_WRITES; + } + break; + + default: + rc = rs->sr_err = LDAP_OTHER; + goto done; + } + + } else if ( mod->sm_desc == mi->mi_ad_restrictedOperation ) { + slap_mask_t mask = 0; + + switch ( mod->sm_op ) { + case LDAP_MOD_DELETE: + if ( mod->sm_values == NULL ) { + rp_delete = rp_cur; + rp_cur = 0; + rp_add = 0; + break; + } + rc = value_mask( mod->sm_values, ~rp_cur, &mask ); + if ( rc == LDAP_SUCCESS ) { + rp_delete |= mask; + rp_add &= ~mask; + rp_cur &= ~mask; + + } else if ( rc == LDAP_OTHER ) { + rc = LDAP_NO_SUCH_ATTRIBUTE; + } + break; + + case LDAP_MOD_REPLACE: + rp_delete = rp_cur; + rp_cur = 0; + rp_add = 0; + /* fall thru */ + + case LDAP_MOD_ADD: + rc = value_mask( mod->sm_values, rp_cur, &mask ); + if ( rc == LDAP_SUCCESS ) { + rp_add |= mask; + rp_cur |= mask; + rp_delete &= ~mask; + + } else if ( rc == LDAP_OTHER ) { + rc = rs->sr_err = LDAP_TYPE_OR_VALUE_EXISTS; + } + break; + + default: + rc = rs->sr_err = LDAP_OTHER; + break; + } + + if ( rc != LDAP_SUCCESS ) { + goto done; + } + + } else if ( is_at_operational( mod->sm_desc->ad_type )) { + /* accept all operational attributes */ + attr_delete( &e->e_attrs, mod->sm_desc ); + rc = attr_merge( e, mod->sm_desc, mod->sm_values, + mod->sm_nvalues ); + if ( rc ) { + rc = rs->sr_err = LDAP_OTHER; + break; + } + + } else { + rc = rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + break; + } + } + + /* sanity checks: */ + if ( ro_gotval < 1 ) { + rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + + if ( ( rp_cur & SLAP_RESTRICT_OP_EXTENDED ) && ( rp_cur & SLAP_RESTRICT_EXOP_MASK ) ) { + rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + + if ( rp_delete & rp_add ) { + rc = rs->sr_err = LDAP_OTHER; + goto done; + } + + /* check current value of readOnly */ + if ( ( rp_cur & SLAP_RESTRICT_OP_WRITES ) == SLAP_RESTRICT_OP_WRITES ) { + tf = (struct berval *)&slap_true_bv; + + } else { + tf = (struct berval *)&slap_false_bv; + } + + a = attr_find( e->e_attrs, mi->mi_ad_readOnly ); + if ( a == NULL ) { + rc = LDAP_OTHER; + goto done; + } + + if ( !bvmatch( &a->a_vals[ 0 ], tf ) ) { + attr_delete( &e->e_attrs, mi->mi_ad_readOnly ); + rc = attr_merge_one( e, mi->mi_ad_readOnly, tf, tf ); + } + + if ( rc == LDAP_SUCCESS ) { + if ( rp_delete ) { + if ( rp_delete == be->be_restrictops ) { + attr_delete( &e->e_attrs, mi->mi_ad_restrictedOperation ); + + } else { + a = attr_find( e->e_attrs, mi->mi_ad_restrictedOperation ); + if ( a == NULL ) { + rc = rs->sr_err = LDAP_OTHER; + goto done; + } + + for ( i = 0; !BER_BVISNULL( &restricted_ops[ i ].op ); i++ ) { + if ( rp_delete & restricted_ops[ i ].tag ) { + int j; + + for ( j = 0; !BER_BVISNULL( &a->a_nvals[ j ] ); j++ ) { + int k; + + if ( !bvmatch( &a->a_nvals[ j ], &restricted_ops[ i ].op ) ) { + continue; + } + + ch_free( a->a_vals[ j ].bv_val ); + ch_free( a->a_nvals[ j ].bv_val ); + + for ( k = j + 1; !BER_BVISNULL( &a->a_nvals[ k ] ); k++ ) { + a->a_vals[ k - 1 ] = a->a_vals[ k ]; + a->a_nvals[ k - 1 ] = a->a_nvals[ k ]; + } + + BER_BVZERO( &a->a_vals[ k - 1 ] ); + BER_BVZERO( &a->a_nvals[ k - 1 ] ); + a->a_numvals--; + } + } + } + + for ( i = 0; !BER_BVISNULL( &restricted_exops[ i ].op ); i++ ) { + if ( rp_delete & restricted_exops[ i ].tag ) { + int j; + + for ( j = 0; !BER_BVISNULL( &a->a_nvals[ j ] ); j++ ) { + int k; + + if ( !bvmatch( &a->a_nvals[ j ], &restricted_exops[ i ].op ) ) { + continue; + } + + ch_free( a->a_vals[ j ].bv_val ); + ch_free( a->a_nvals[ j ].bv_val ); + + for ( k = j + 1; !BER_BVISNULL( &a->a_nvals[ k ] ); k++ ) { + a->a_vals[ k - 1 ] = a->a_vals[ k ]; + a->a_nvals[ k - 1 ] = a->a_nvals[ k ]; + } + + BER_BVZERO( &a->a_vals[ k - 1 ] ); + BER_BVZERO( &a->a_nvals[ k - 1 ] ); + a->a_numvals--; + } + } + } + + if ( a->a_vals == NULL ) { + assert( a->a_numvals == 0 ); + + attr_delete( &e->e_attrs, mi->mi_ad_restrictedOperation ); + } + } + } + + if ( rp_add ) { + for ( i = 0; !BER_BVISNULL( &restricted_ops[ i ].op ); i++ ) { + if ( rp_add & restricted_ops[ i ].tag ) { + attr_merge_one( e, mi->mi_ad_restrictedOperation, + &restricted_ops[ i ].op, + &restricted_ops[ i ].op ); + } + } + + for ( i = 0; !BER_BVISNULL( &restricted_exops[ i ].op ); i++ ) { + if ( rp_add & restricted_exops[ i ].tag ) { + attr_merge_one( e, mi->mi_ad_restrictedOperation, + &restricted_exops[ i ].op, + &restricted_exops[ i ].op ); + } + } + } + } + + be->be_restrictops = rp_cur; + +done:; + if ( rc == LDAP_SUCCESS ) { + attrs_free( save_attrs ); + rc = SLAP_CB_CONTINUE; + + } else { + Attribute *tmp = e->e_attrs; + e->e_attrs = save_attrs; + attrs_free( tmp ); + } + return rc; +} + +#if defined(LDAP_SLAPI) +static int +monitor_back_add_plugin( monitor_info_t *mi, Backend *be, Entry *e_database ) +{ + Slapi_PBlock *pCurrentPB; + int i, rc = LDAP_SUCCESS; + + if ( slapi_int_pblock_get_first( be, &pCurrentPB ) != LDAP_SUCCESS ) { + /* + * LDAP_OTHER is returned if no plugins are installed + */ + rc = LDAP_OTHER; + goto done; + } + + i = 0; + do { + Slapi_PluginDesc *srchdesc; + char buf[ BACKMONITOR_BUFSIZE ]; + struct berval bv; + + rc = slapi_pblock_get( pCurrentPB, SLAPI_PLUGIN_DESCRIPTION, + &srchdesc ); + if ( rc != LDAP_SUCCESS ) { + goto done; + } + if ( srchdesc ) { + snprintf( buf, sizeof(buf), + "plugin %d name: %s; " + "vendor: %s; " + "version: %s; " + "description: %s", + i, + srchdesc->spd_id, + srchdesc->spd_vendor, + srchdesc->spd_version, + srchdesc->spd_description ); + } else { + snprintf( buf, sizeof(buf), + "plugin %d name: <no description available>", i ); + } + + ber_str2bv( buf, 0, 0, &bv ); + attr_merge_normalize_one( e_database, + mi->mi_ad_monitoredInfo, &bv, NULL ); + + i++; + + } while ( ( slapi_int_pblock_get_next( &pCurrentPB ) == LDAP_SUCCESS ) + && ( pCurrentPB != NULL ) ); + +done: + return rc; +} +#endif /* defined(LDAP_SLAPI) */ diff --git a/servers/slapd/back-monitor/entry.c b/servers/slapd/back-monitor/entry.c new file mode 100644 index 0000000..2b7ba03 --- /dev/null +++ b/servers/slapd/back-monitor/entry.c @@ -0,0 +1,223 @@ +/* entry.c - monitor backend entry handling routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <slap.h> +#include "back-monitor.h" + +int +monitor_entry_update( + Operation *op, + SlapReply *rs, + Entry *e +) +{ + monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private; + monitor_entry_t *mp; + + int rc = SLAP_CB_CONTINUE; + + assert( mi != NULL ); + assert( e != NULL ); + assert( e->e_private != NULL ); + + mp = ( monitor_entry_t * )e->e_private; + + if ( mp->mp_cb ) { + struct monitor_callback_t *mc; + + for ( mc = mp->mp_cb; mc; mc = mc->mc_next ) { + if ( mc->mc_update ) { + rc = mc->mc_update( op, rs, e, mc->mc_private ); + if ( rc != SLAP_CB_CONTINUE ) { + break; + } + } + } + } + + if ( rc == SLAP_CB_CONTINUE && mp->mp_info && mp->mp_info->mss_update ) { + rc = mp->mp_info->mss_update( op, rs, e ); + } + + if ( rc == SLAP_CB_CONTINUE ) { + rc = LDAP_SUCCESS; + } + + return rc; +} + +int +monitor_entry_create( + Operation *op, + SlapReply *rs, + struct berval *ndn, + Entry *e_parent, + Entry **ep ) +{ + monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private; + monitor_entry_t *mp; + + int rc = SLAP_CB_CONTINUE; + + assert( mi != NULL ); + assert( e_parent != NULL ); + assert( e_parent->e_private != NULL ); + assert( ep != NULL ); + + mp = ( monitor_entry_t * )e_parent->e_private; + + if ( mp->mp_info && mp->mp_info->mss_create ) { + rc = mp->mp_info->mss_create( op, rs, ndn, e_parent, ep ); + } + + if ( rc == SLAP_CB_CONTINUE ) { + rc = LDAP_SUCCESS; + } + + return rc; +} + +int +monitor_entry_modify( + Operation *op, + SlapReply *rs, + Entry *e +) +{ + monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private; + monitor_entry_t *mp; + + int rc = SLAP_CB_CONTINUE; + + assert( mi != NULL ); + assert( e != NULL ); + assert( e->e_private != NULL ); + + mp = ( monitor_entry_t * )e->e_private; + + if ( mp->mp_cb ) { + struct monitor_callback_t *mc; + + for ( mc = mp->mp_cb; mc; mc = mc->mc_next ) { + if ( mc->mc_modify ) { + rc = mc->mc_modify( op, rs, e, mc->mc_private ); + if ( rc != SLAP_CB_CONTINUE ) { + break; + } + } + } + } + + if ( rc == SLAP_CB_CONTINUE && mp->mp_info && mp->mp_info->mss_modify ) { + rc = mp->mp_info->mss_modify( op, rs, e ); + } + + if ( rc == SLAP_CB_CONTINUE ) { + rc = LDAP_SUCCESS; + } + + return rc; +} + +int +monitor_entry_test_flags( + monitor_entry_t *mp, + int cond +) +{ + assert( mp != NULL ); + + return( ( mp->mp_flags & cond ) || ( mp->mp_info->mss_flags & cond ) ); +} + +monitor_entry_t * +monitor_back_entrypriv_create( void ) +{ + monitor_entry_t *mp; + + mp = ( monitor_entry_t * )ch_calloc( sizeof( monitor_entry_t ), 1 ); + + mp->mp_next = NULL; + mp->mp_children = NULL; + mp->mp_info = NULL; + mp->mp_flags = MONITOR_F_NONE; + mp->mp_cb = NULL; + + ldap_pvt_thread_mutex_init( &mp->mp_mutex ); + + return mp; +} + +Entry * +monitor_entry_stub( + struct berval *pdn, + struct berval *pndn, + struct berval *rdn, + ObjectClass *oc, + struct berval *create, + struct berval *modify +) +{ + monitor_info_t *mi; + AttributeDescription *nad = NULL; + Entry *e; + struct berval nat; + char *ptr; + const char *text; + int rc; + + mi = ( monitor_info_t * )be_monitor->be_private; + + nat = *rdn; + ptr = strchr( nat.bv_val, '=' ); + nat.bv_len = ptr - nat.bv_val; + rc = slap_bv2ad( &nat, &nad, &text ); + if ( rc ) + return NULL; + + e = entry_alloc(); + if ( e ) { + struct berval nrdn; + + rdnNormalize( 0, NULL, NULL, rdn, &nrdn, NULL ); + build_new_dn( &e->e_name, pdn, rdn, NULL ); + build_new_dn( &e->e_nname, pndn, &nrdn, NULL ); + ber_memfree( nrdn.bv_val ); + nat.bv_val = ptr + 1; + nat.bv_len = rdn->bv_len - ( nat.bv_val - rdn->bv_val ); + attr_merge_normalize_one( e, slap_schema.si_ad_objectClass, + &oc->soc_cname, NULL ); + attr_merge_normalize_one( e, slap_schema.si_ad_structuralObjectClass, + &oc->soc_cname, NULL ); + attr_merge_normalize_one( e, nad, &nat, NULL ); + attr_merge_one( e, slap_schema.si_ad_creatorsName, &mi->mi_creatorsName, + &mi->mi_ncreatorsName ); + attr_merge_one( e, slap_schema.si_ad_modifiersName, &mi->mi_creatorsName, + &mi->mi_ncreatorsName ); + attr_merge_normalize_one( e, slap_schema.si_ad_createTimestamp, + create ? create : &mi->mi_startTime, NULL ); + attr_merge_normalize_one( e, slap_schema.si_ad_modifyTimestamp, + modify ? modify : &mi->mi_startTime, NULL ); + } + return e; +} diff --git a/servers/slapd/back-monitor/init.c b/servers/slapd/back-monitor/init.c new file mode 100644 index 0000000..1629849 --- /dev/null +++ b/servers/slapd/back-monitor/init.c @@ -0,0 +1,2598 @@ +/* init.c - initialize monitor backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include <lutil.h> +#include "slap.h" +#include "config.h" +#include "lber_pvt.h" +#include "back-monitor.h" + +#include "config.h" + +#undef INTEGRATE_CORE_SCHEMA + +/* + * used by many functions to add description to entries + * + * WARNING: be_monitor may change as new databases are added, + * so it should not be used outside monitor_back_db_init() + * until monitor_back_db_open is called. + */ +BackendDB *be_monitor; + +static struct monitor_subsys_t **monitor_subsys; +static int monitor_subsys_opened; +static monitor_info_t monitor_info; +static const monitor_extra_t monitor_extra = { + monitor_back_is_configured, + monitor_back_get_subsys, + monitor_back_get_subsys_by_dn, + + monitor_back_register_subsys, + monitor_back_register_backend, + monitor_back_register_database, + monitor_back_register_overlay_info, + monitor_back_register_overlay, + monitor_back_register_entry, + monitor_back_register_entry_parent, + monitor_back_register_entry_attrs, + monitor_back_register_entry_callback, + + monitor_back_unregister_entry, + monitor_back_unregister_entry_parent, + monitor_back_unregister_entry_attrs, + monitor_back_unregister_entry_callback, + + monitor_back_entry_stub, + monitor_back_entrypriv_create, + monitor_back_register_subsys_late +}; + + +/* + * subsystem data + * + * the known subsystems are added to the subsystems + * array at backend initialization; other subsystems + * may be added by calling monitor_back_register_subsys() + * before the database is opened (e.g. by other backends + * or by overlays or modules). + */ +static struct monitor_subsys_t known_monitor_subsys[] = { + { + SLAPD_MONITOR_BACKEND_NAME, + BER_BVNULL, BER_BVNULL, BER_BVNULL, + { BER_BVC( "This subsystem contains information about available backends." ), + BER_BVNULL }, + MONITOR_F_PERSISTENT_CH, + monitor_subsys_backend_init, + NULL, /* destroy */ + NULL, /* update */ + NULL, /* create */ + NULL /* modify */ + }, { + SLAPD_MONITOR_CONN_NAME, + BER_BVNULL, BER_BVNULL, BER_BVNULL, + { BER_BVC( "This subsystem contains information about connections." ), + BER_BVNULL }, + MONITOR_F_VOLATILE_CH, + monitor_subsys_conn_init, + NULL, /* destroy */ + NULL, /* update */ + NULL, /* create */ + NULL /* modify */ + }, { + SLAPD_MONITOR_DATABASE_NAME, + BER_BVNULL, BER_BVNULL, BER_BVNULL, + { BER_BVC( "This subsystem contains information about configured databases." ), + BER_BVNULL }, + MONITOR_F_PERSISTENT_CH, + monitor_subsys_database_init, + NULL, /* destroy */ + NULL, /* update */ + NULL, /* create */ + NULL /* modify */ + }, { + SLAPD_MONITOR_LISTENER_NAME, + BER_BVNULL, BER_BVNULL, BER_BVNULL, + { BER_BVC( "This subsystem contains information about active listeners." ), + BER_BVNULL }, + MONITOR_F_PERSISTENT_CH, + monitor_subsys_listener_init, + NULL, /* destroy */ + NULL, /* update */ + NULL, /* create */ + NULL /* modify */ + }, { + SLAPD_MONITOR_LOG_NAME, + BER_BVNULL, BER_BVNULL, BER_BVNULL, + { BER_BVC( "This subsystem contains information about logging." ), + BER_BVC( "Set the attribute \"managedInfo\" to the desired log levels." ), + BER_BVNULL }, + MONITOR_F_NONE, + monitor_subsys_log_init, + NULL, /* destroy */ + NULL, /* update */ + NULL, /* create */ + NULL, /* modify */ + }, { + SLAPD_MONITOR_OPS_NAME, + BER_BVNULL, BER_BVNULL, BER_BVNULL, + { BER_BVC( "This subsystem contains information about performed operations." ), + BER_BVNULL }, + MONITOR_F_PERSISTENT_CH, + monitor_subsys_ops_init, + NULL, /* destroy */ + NULL, /* update */ + NULL, /* create */ + NULL, /* modify */ + }, { + SLAPD_MONITOR_OVERLAY_NAME, + BER_BVNULL, BER_BVNULL, BER_BVNULL, + { BER_BVC( "This subsystem contains information about available overlays." ), + BER_BVNULL }, + MONITOR_F_PERSISTENT_CH, + monitor_subsys_overlay_init, + NULL, /* destroy */ + NULL, /* update */ + NULL, /* create */ + NULL, /* modify */ + }, { + SLAPD_MONITOR_SASL_NAME, + BER_BVNULL, BER_BVNULL, BER_BVNULL, + { BER_BVC( "This subsystem contains information about SASL." ), + BER_BVNULL }, + MONITOR_F_NONE, + NULL, /* init */ + NULL, /* destroy */ + NULL, /* update */ + NULL, /* create */ + NULL /* modify */ + }, { + SLAPD_MONITOR_SENT_NAME, + BER_BVNULL, BER_BVNULL, BER_BVNULL, + { BER_BVC( "This subsystem contains statistics." ), + BER_BVNULL }, + MONITOR_F_PERSISTENT_CH, + monitor_subsys_sent_init, + NULL, /* destroy */ + NULL, /* update */ + NULL, /* create */ + NULL, /* modify */ + }, { + SLAPD_MONITOR_THREAD_NAME, + BER_BVNULL, BER_BVNULL, BER_BVNULL, + { BER_BVC( "This subsystem contains information about threads." ), + BER_BVNULL }, + MONITOR_F_PERSISTENT_CH, + monitor_subsys_thread_init, + NULL, /* destroy */ + NULL, /* update */ + NULL, /* create */ + NULL /* modify */ + }, { + SLAPD_MONITOR_TIME_NAME, + BER_BVNULL, BER_BVNULL, BER_BVNULL, + { BER_BVC( "This subsystem contains information about time." ), + BER_BVNULL }, + MONITOR_F_PERSISTENT_CH, + monitor_subsys_time_init, + NULL, /* destroy */ + NULL, /* update */ + NULL, /* create */ + NULL, /* modify */ + }, { + SLAPD_MONITOR_TLS_NAME, + BER_BVNULL, BER_BVNULL, BER_BVNULL, + { BER_BVC( "This subsystem contains information about TLS." ), + BER_BVNULL }, + MONITOR_F_NONE, + NULL, /* init */ + NULL, /* destroy */ + NULL, /* update */ + NULL, /* create */ + NULL /* modify */ + }, { + SLAPD_MONITOR_RWW_NAME, + BER_BVNULL, BER_BVNULL, BER_BVNULL, + { BER_BVC( "This subsystem contains information about read/write waiters." ), + BER_BVNULL }, + MONITOR_F_PERSISTENT_CH, + monitor_subsys_rww_init, + NULL, /* destroy */ + NULL, /* update */ + NULL, /* create */ + NULL /* modify */ + }, { NULL } +}; + +int +monitor_subsys_is_opened( void ) +{ + return monitor_subsys_opened; +} + +int +monitor_back_register_subsys( + monitor_subsys_t *ms ) +{ + int i = 0; + + if ( monitor_subsys ) { + for ( ; monitor_subsys[ i ] != NULL; i++ ) + /* just count'em */ ; + } + + monitor_subsys = ch_realloc( monitor_subsys, + ( 2 + i ) * sizeof( monitor_subsys_t * ) ); + + if ( monitor_subsys == NULL ) { + return -1; + } + + monitor_subsys[ i ] = ms; + monitor_subsys[ i + 1 ] = NULL; + + /* if a subsystem is registered __AFTER__ subsystem + * initialization (depending on the sequence the databases + * are listed in slapd.conf), init it */ + if ( monitor_subsys_is_opened() ) { + + /* FIXME: this should only be possible + * if be_monitor is already initialized */ + assert( be_monitor != NULL ); + + if ( ms->mss_open && ( *ms->mss_open )( be_monitor, ms ) ) { + return -1; + } + + ms->mss_flags |= MONITOR_F_OPENED; + } + + return 0; +} + +enum { + LIMBO_ENTRY, + LIMBO_ENTRY_PARENT, + LIMBO_ATTRS, + LIMBO_CB, + LIMBO_BACKEND, + LIMBO_DATABASE, + LIMBO_OVERLAY_INFO, + LIMBO_OVERLAY, + LIMBO_SUBSYS, + + LIMBO_LAST +}; + +typedef struct entry_limbo_t { + int el_type; + BackendInfo *el_bi; + BackendDB *el_be; + slap_overinst *el_on; + Entry *el_e; + Attribute *el_a; + struct berval *el_ndn; + struct berval el_nbase; + int el_scope; + struct berval el_filter; + monitor_callback_t *el_cb; + monitor_subsys_t *el_mss; + unsigned long el_flags; + struct entry_limbo_t *el_next; +} entry_limbo_t; + +int +monitor_back_is_configured( void ) +{ + return be_monitor != NULL; +} + +int +monitor_back_register_subsys_late( + monitor_subsys_t *ms ) +{ + entry_limbo_t **elpp, el = { 0 }; + monitor_info_t *mi; + + if ( be_monitor == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_subsys_late: " + "monitor database not configured.\n", + 0, 0, 0 ); + return -1; + } + + /* everyting is ready, can register already */ + if ( monitor_subsys_is_opened() ) { + return monitor_back_register_subsys( ms ); + } + + mi = ( monitor_info_t * )be_monitor->be_private; + + + el.el_type = LIMBO_SUBSYS; + + el.el_mss = ms; + + for ( elpp = &mi->mi_entry_limbo; + *elpp; + elpp = &(*elpp)->el_next ) + /* go to last */; + + *elpp = (entry_limbo_t *)ch_malloc( sizeof( entry_limbo_t ) ); + + el.el_next = NULL; + **elpp = el; + + return 0; +} + +int +monitor_back_register_backend( + BackendInfo *bi ) +{ + return -1; +} + +int +monitor_back_register_overlay_info( + slap_overinst *on ) +{ + return -1; +} + +int +monitor_back_register_backend_limbo( + BackendInfo *bi ) +{ + return -1; +} + +int +monitor_back_register_database_limbo( + BackendDB *be, + struct berval *ndn_out ) +{ + entry_limbo_t **elpp, el = { 0 }; + monitor_info_t *mi; + + if ( be_monitor == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_database_limbo: " + "monitor database not configured.\n", + 0, 0, 0 ); + return -1; + } + + mi = ( monitor_info_t * )be_monitor->be_private; + + + el.el_type = LIMBO_DATABASE; + + el.el_be = be->bd_self; + el.el_ndn = ndn_out; + + for ( elpp = &mi->mi_entry_limbo; + *elpp; + elpp = &(*elpp)->el_next ) + /* go to last */; + + *elpp = (entry_limbo_t *)ch_malloc( sizeof( entry_limbo_t ) ); + + el.el_next = NULL; + **elpp = el; + + return 0; +} + +int +monitor_back_register_overlay_info_limbo( + slap_overinst *on ) +{ + return -1; +} + +int +monitor_back_register_overlay_limbo( + BackendDB *be, + struct slap_overinst *on, + struct berval *ndn_out ) +{ + entry_limbo_t **elpp, el = { 0 }; + monitor_info_t *mi; + + if ( be_monitor == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_overlay_limbo: " + "monitor database not configured.\n", + 0, 0, 0 ); + return -1; + } + + mi = ( monitor_info_t * )be_monitor->be_private; + + + el.el_type = LIMBO_OVERLAY; + + el.el_be = be->bd_self; + el.el_on = on; + el.el_ndn = ndn_out; + + for ( elpp = &mi->mi_entry_limbo; + *elpp; + elpp = &(*elpp)->el_next ) + /* go to last */; + + *elpp = (entry_limbo_t *)ch_malloc( sizeof( entry_limbo_t ) ); + + el.el_next = NULL; + **elpp = el; + + return 0; +} + +int +monitor_back_register_entry( + Entry *e, + monitor_callback_t *cb, + monitor_subsys_t *mss, + unsigned long flags ) +{ + monitor_info_t *mi; + + if ( be_monitor == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry(\"%s\"): " + "monitor database not configured.\n", + e->e_name.bv_val, 0, 0 ); + return -1; + } + + mi = ( monitor_info_t * )be_monitor->be_private; + + assert( mi != NULL ); + assert( e != NULL ); + assert( e->e_private == NULL ); + + if ( monitor_subsys_is_opened() ) { + Entry *e_parent = NULL, + *e_new = NULL, + **ep = NULL; + struct berval pdn = BER_BVNULL; + monitor_entry_t *mp = NULL, + *mp_parent = NULL; + int rc = 0; + + if ( monitor_cache_get( mi, &e->e_nname, &e_parent ) == 0 ) { + /* entry exists */ + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry(\"%s\"): " + "entry exists\n", + e->e_name.bv_val, 0, 0 ); + monitor_cache_release( mi, e_parent ); + return -1; + } + + dnParent( &e->e_nname, &pdn ); + if ( monitor_cache_get( mi, &pdn, &e_parent ) != 0 ) { + /* parent does not exist */ + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry(\"%s\"): " + "parent \"%s\" not found\n", + e->e_name.bv_val, pdn.bv_val, 0 ); + return -1; + } + + assert( e_parent->e_private != NULL ); + mp_parent = ( monitor_entry_t * )e_parent->e_private; + + if ( mp_parent->mp_flags & MONITOR_F_VOLATILE ) { + /* entry is volatile; cannot append children */ + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry(\"%s\"): " + "parent \"%s\" is volatile\n", + e->e_name.bv_val, e_parent->e_name.bv_val, 0 ); + rc = -1; + goto done; + } + + mp = monitor_entrypriv_create(); + if ( mp == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry(\"%s\"): " + "monitor_entrypriv_create() failed\n", + e->e_name.bv_val, 0, 0 ); + rc = -1; + goto done; + } + + e_new = entry_dup( e ); + if ( e_new == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry(\"%s\"): " + "entry_dup() failed\n", + e->e_name.bv_val, 0, 0 ); + rc = -1; + goto done; + } + + e_new->e_private = ( void * )mp; + if ( mss != NULL ) { + mp->mp_info = mss; + mp->mp_flags = flags; + + } else { + mp->mp_info = mp_parent->mp_info; + mp->mp_flags = mp_parent->mp_flags | MONITOR_F_SUB; + } + mp->mp_cb = cb; + + ep = &mp_parent->mp_children; + for ( ; *ep; ) { + mp_parent = ( monitor_entry_t * )(*ep)->e_private; + ep = &mp_parent->mp_next; + } + *ep = e_new; + + if ( monitor_cache_add( mi, e_new ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry(\"%s\"): " + "unable to add entry\n", + e->e_name.bv_val, 0, 0 ); + rc = -1; + goto done; + } + +done:; + if ( rc ) { + if ( mp ) { + ch_free( mp ); + } + if ( e_new ) { + e_new->e_private = NULL; + entry_free( e_new ); + } + } + + if ( e_parent ) { + monitor_cache_release( mi, e_parent ); + } + + } else { + entry_limbo_t **elpp, el = { 0 }; + + el.el_type = LIMBO_ENTRY; + + el.el_e = entry_dup( e ); + if ( el.el_e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry(\"%s\"): " + "entry_dup() failed\n", + e->e_name.bv_val, 0, 0 ); + return -1; + } + + el.el_cb = cb; + el.el_mss = mss; + el.el_flags = flags; + + for ( elpp = &mi->mi_entry_limbo; + *elpp; + elpp = &(*elpp)->el_next ) + /* go to last */; + + *elpp = (entry_limbo_t *)ch_malloc( sizeof( entry_limbo_t ) ); + if ( *elpp == NULL ) { + el.el_e->e_private = NULL; + entry_free( el.el_e ); + return -1; + } + + el.el_next = NULL; + **elpp = el; + } + + return 0; +} + +int +monitor_back_register_entry_parent( + Entry *e, + monitor_callback_t *cb, + monitor_subsys_t *mss, + unsigned long flags, + struct berval *nbase, + int scope, + struct berval *filter ) +{ + monitor_info_t *mi; + struct berval ndn = BER_BVNULL; + + if ( be_monitor == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry_parent(base=\"%s\" scope=%s filter=\"%s\"): " + "monitor database not configured.\n", + BER_BVISNULL( nbase ) ? "" : nbase->bv_val, + ldap_pvt_scope2str( scope ), + BER_BVISNULL( filter ) ? "" : filter->bv_val ); + return -1; + } + + mi = ( monitor_info_t * )be_monitor->be_private; + + assert( mi != NULL ); + assert( e != NULL ); + assert( e->e_private == NULL ); + + if ( BER_BVISNULL( filter ) ) { + /* need a filter */ + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry_parent(\"\"): " + "need a valid filter\n", + 0, 0, 0 ); + return -1; + } + + if ( monitor_subsys_is_opened() ) { + Entry *e_parent = NULL, + *e_new = NULL, + **ep = NULL; + struct berval e_name = BER_BVNULL, + e_nname = BER_BVNULL; + monitor_entry_t *mp = NULL, + *mp_parent = NULL; + int rc = 0; + + if ( monitor_search2ndn( nbase, scope, filter, &ndn ) ) { + /* entry does not exist */ + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry_parent(\"\"): " + "base=\"%s\" scope=%s filter=\"%s\": " + "unable to find entry\n", + nbase->bv_val ? nbase->bv_val : "\"\"", + ldap_pvt_scope2str( scope ), + filter->bv_val ); + return -1; + } + + if ( monitor_cache_get( mi, &ndn, &e_parent ) != 0 ) { + /* entry does not exist */ + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry_parent(\"%s\"): " + "parent entry does not exist\n", + ndn.bv_val, 0, 0 ); + rc = -1; + goto done; + } + + assert( e_parent->e_private != NULL ); + mp_parent = ( monitor_entry_t * )e_parent->e_private; + + if ( mp_parent->mp_flags & MONITOR_F_VOLATILE ) { + /* entry is volatile; cannot append callback */ + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry_parent(\"%s\"): " + "entry is volatile\n", + e_parent->e_name.bv_val, 0, 0 ); + rc = -1; + goto done; + } + + build_new_dn( &e_name, &e_parent->e_name, &e->e_name, NULL ); + build_new_dn( &e_nname, &e_parent->e_nname, &e->e_nname, NULL ); + + if ( monitor_cache_get( mi, &e_nname, &e_new ) == 0 ) { + /* entry already exists */ + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry_parent(\"%s\"): " + "entry already exists\n", + e_name.bv_val, 0, 0 ); + monitor_cache_release( mi, e_new ); + e_new = NULL; + rc = -1; + goto done; + } + + mp = monitor_entrypriv_create(); + if ( mp == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry_parent(\"%s\"): " + "monitor_entrypriv_create() failed\n", + e->e_name.bv_val, 0, 0 ); + rc = -1; + goto done; + } + + e_new = entry_dup( e ); + if ( e_new == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry(\"%s\"): " + "entry_dup() failed\n", + e->e_name.bv_val, 0, 0 ); + rc = -1; + goto done; + } + ch_free( e_new->e_name.bv_val ); + ch_free( e_new->e_nname.bv_val ); + e_new->e_name = e_name; + e_new->e_nname = e_nname; + + e_new->e_private = ( void * )mp; + if ( mss != NULL ) { + mp->mp_info = mss; + mp->mp_flags = flags; + + } else { + mp->mp_info = mp_parent->mp_info; + mp->mp_flags = mp_parent->mp_flags | MONITOR_F_SUB; + } + mp->mp_cb = cb; + + ep = &mp_parent->mp_children; + for ( ; *ep; ) { + mp_parent = ( monitor_entry_t * )(*ep)->e_private; + ep = &mp_parent->mp_next; + } + *ep = e_new; + + if ( monitor_cache_add( mi, e_new ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry(\"%s\"): " + "unable to add entry\n", + e->e_name.bv_val, 0, 0 ); + rc = -1; + goto done; + } + +done:; + if ( !BER_BVISNULL( &ndn ) ) { + ch_free( ndn.bv_val ); + } + + if ( rc ) { + if ( mp ) { + ch_free( mp ); + } + if ( e_new ) { + e_new->e_private = NULL; + entry_free( e_new ); + } + } + + if ( e_parent ) { + monitor_cache_release( mi, e_parent ); + } + + } else { + entry_limbo_t **elpp = NULL, el = { 0 }; + + el.el_type = LIMBO_ENTRY_PARENT; + + el.el_e = entry_dup( e ); + if ( el.el_e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry(\"%s\"): " + "entry_dup() failed\n", + e->e_name.bv_val, 0, 0 ); + goto done_limbo; + } + + if ( !BER_BVISNULL( nbase ) ) { + ber_dupbv( &el.el_nbase, nbase ); + } + + el.el_scope = scope; + if ( !BER_BVISNULL( filter ) ) { + ber_dupbv( &el.el_filter, filter ); + } + + el.el_cb = cb; + el.el_mss = mss; + el.el_flags = flags; + + for ( elpp = &mi->mi_entry_limbo; + *elpp; + elpp = &(*elpp)->el_next ) + /* go to last */; + + *elpp = (entry_limbo_t *)ch_malloc( sizeof( entry_limbo_t ) ); + if ( *elpp == NULL ) { + goto done_limbo; + } + +done_limbo:; + if ( *elpp != NULL ) { + el.el_next = NULL; + **elpp = el; + + } else { + if ( !BER_BVISNULL( &el.el_filter ) ) { + ch_free( el.el_filter.bv_val ); + } + if ( !BER_BVISNULL( &el.el_nbase ) ) { + ch_free( el.el_nbase.bv_val ); + } + entry_free( el.el_e ); + return -1; + } + } + + return 0; +} + +static int +monitor_search2ndn_cb( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH ) { + struct berval *ndn = op->o_callback->sc_private; + + if ( !BER_BVISNULL( ndn ) ) { + rs->sr_err = LDAP_SIZELIMIT_EXCEEDED; + ch_free( ndn->bv_val ); + BER_BVZERO( ndn ); + return rs->sr_err; + } + + ber_dupbv( ndn, &rs->sr_entry->e_nname ); + } + + return 0; +} + +int +monitor_search2ndn( + struct berval *nbase, + int scope, + struct berval *filter, + struct berval *ndn ) +{ + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation *op; + void *thrctx; + SlapReply rs = { REP_RESULT }; + slap_callback cb = { NULL, monitor_search2ndn_cb, NULL, NULL }; + int rc; + + BER_BVZERO( ndn ); + + if ( be_monitor == NULL ) { + return -1; + } + + thrctx = ldap_pvt_thread_pool_context(); + connection_fake_init2( &conn, &opbuf, thrctx, 0 ); + op = &opbuf.ob_op; + + op->o_tag = LDAP_REQ_SEARCH; + + /* use global malloc for now */ + if ( op->o_tmpmemctx ) { + op->o_tmpmemctx = NULL; + } + op->o_tmpmfuncs = &ch_mfuncs; + + op->o_bd = be_monitor; + if ( nbase == NULL || BER_BVISNULL( nbase ) ) { + ber_dupbv_x( &op->o_req_dn, &op->o_bd->be_suffix[ 0 ], + op->o_tmpmemctx ); + ber_dupbv_x( &op->o_req_ndn, &op->o_bd->be_nsuffix[ 0 ], + op->o_tmpmemctx ); + + } else { + if ( dnPrettyNormal( NULL, nbase, &op->o_req_dn, &op->o_req_ndn, + op->o_tmpmemctx ) ) { + return -1; + } + } + + op->o_callback = &cb; + cb.sc_private = (void *)ndn; + + op->ors_scope = scope; + op->ors_filter = str2filter_x( op, filter->bv_val ); + if ( op->ors_filter == NULL ) { + rc = LDAP_OTHER; + goto cleanup; + } + ber_dupbv_x( &op->ors_filterstr, filter, op->o_tmpmemctx ); + op->ors_attrs = slap_anlist_no_attrs; + op->ors_attrsonly = 0; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_slimit = 1; + op->ors_limit = NULL; + op->ors_deref = LDAP_DEREF_NEVER; + + op->o_nocaching = 1; + op->o_managedsait = SLAP_CONTROL_NONCRITICAL; + + op->o_dn = be_monitor->be_rootdn; + op->o_ndn = be_monitor->be_rootndn; + + rc = op->o_bd->be_search( op, &rs ); + +cleanup:; + if ( op->ors_filter != NULL ) { + filter_free_x( op, op->ors_filter, 1 ); + } + if ( !BER_BVISNULL( &op->ors_filterstr ) ) { + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + } + if ( !BER_BVISNULL( &op->o_req_dn ) ) { + op->o_tmpfree( op->o_req_dn.bv_val, op->o_tmpmemctx ); + } + if ( !BER_BVISNULL( &op->o_req_ndn ) ) { + op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx ); + } + + if ( rc != 0 ) { + return rc; + } + + switch ( rs.sr_err ) { + case LDAP_SUCCESS: + if ( BER_BVISNULL( ndn ) ) { + rc = -1; + } + break; + + case LDAP_SIZELIMIT_EXCEEDED: + default: + if ( !BER_BVISNULL( ndn ) ) { + ber_memfree( ndn->bv_val ); + BER_BVZERO( ndn ); + } + rc = -1; + break; + } + + return rc; +} + +int +monitor_back_register_entry_attrs( + struct berval *ndn_in, + Attribute *a, + monitor_callback_t *cb, + struct berval *nbase, + int scope, + struct berval *filter ) +{ + monitor_info_t *mi; + struct berval ndn = BER_BVNULL; + char *fname = ( a == NULL ? "callback" : "attrs" ); + struct berval empty_bv = BER_BVC(""); + + if ( nbase == NULL ) nbase = &empty_bv; + if ( filter == NULL ) filter = &empty_bv; + + if ( be_monitor == NULL ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "monitor_back_register_entry_%s(base=\"%s\" scope=%s filter=\"%s\"): " + "monitor database not configured.\n", + fname, + BER_BVISNULL( nbase ) ? "" : nbase->bv_val, + ldap_pvt_scope2str( scope ), + BER_BVISNULL( filter ) ? "" : filter->bv_val ); + Debug( LDAP_DEBUG_ANY, "%s\n", buf, 0, 0 ); + + return -1; + } + + mi = ( monitor_info_t * )be_monitor->be_private; + + assert( mi != NULL ); + + if ( ndn_in != NULL ) { + ndn = *ndn_in; + } + + if ( a == NULL && cb == NULL ) { + /* nothing to do */ + return -1; + } + + if ( ( ndn_in == NULL || BER_BVISNULL( &ndn ) ) + && BER_BVISNULL( filter ) ) + { + /* need a filter */ + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry_%s(\"\"): " + "need a valid filter\n", + fname, 0, 0 ); + return -1; + } + + if ( monitor_subsys_is_opened() ) { + Entry *e = NULL; + Attribute **atp = NULL; + monitor_entry_t *mp = NULL; + monitor_callback_t **mcp = NULL; + int rc = 0; + int freeit = 0; + + if ( BER_BVISNULL( &ndn ) ) { + if ( monitor_search2ndn( nbase, scope, filter, &ndn ) ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "monitor_back_register_entry_%s(\"\"): " + "base=\"%s\" scope=%s filter=\"%s\": " + "unable to find entry\n", + fname, + nbase->bv_val ? nbase->bv_val : "\"\"", + ldap_pvt_scope2str( scope ), + filter->bv_val ); + + /* entry does not exist */ + Debug( LDAP_DEBUG_ANY, "%s\n", buf, 0, 0 ); + return -1; + } + + freeit = 1; + } + + if ( monitor_cache_get( mi, &ndn, &e ) != 0 ) { + /* entry does not exist */ + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry_%s(\"%s\"): " + "entry does not exist\n", + fname, ndn.bv_val, 0 ); + rc = -1; + goto done; + } + + assert( e->e_private != NULL ); + mp = ( monitor_entry_t * )e->e_private; + + if ( mp->mp_flags & MONITOR_F_VOLATILE ) { + /* entry is volatile; cannot append callback */ + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry_%s(\"%s\"): " + "entry is volatile\n", + fname, e->e_name.bv_val, 0 ); + rc = -1; + goto done; + } + + if ( a ) { + for ( atp = &e->e_attrs; *atp; atp = &(*atp)->a_next ) + /* just get to last */ ; + + for ( ; a != NULL; a = a->a_next ) { + assert( a->a_desc != NULL ); + assert( a->a_vals != NULL ); + + if ( attr_find( e->e_attrs, a->a_desc ) ) { + attr_merge( e, a->a_desc, a->a_vals, + a->a_nvals == a->a_vals ? NULL : a->a_nvals ); + + } else { + *atp = attr_dup( a ); + if ( *atp == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_register_entry_%s(\"%s\"): " + "attr_dup() failed\n", + fname, e->e_name.bv_val, 0 ); + rc = -1; + goto done; + } + atp = &(*atp)->a_next; + } + } + } + + if ( cb ) { + for ( mcp = &mp->mp_cb; *mcp; mcp = &(*mcp)->mc_next ) + /* go to tail */ ; + + /* NOTE: we do not clear cb->mc_next, so this function + * can be used to append a list of callbacks */ + (*mcp) = cb; + } + +done:; + if ( rc ) { + if ( atp && *atp ) { + attrs_free( *atp ); + *atp = NULL; + } + } + + if ( freeit ) { + ber_memfree( ndn.bv_val ); + } + + if ( e ) { + monitor_cache_release( mi, e ); + } + + } else { + entry_limbo_t **elpp, el = { 0 }; + + el.el_type = LIMBO_ATTRS; + el.el_ndn = ndn_in; + if ( !BER_BVISNULL( nbase ) ) { + ber_dupbv( &el.el_nbase, nbase); + } + el.el_scope = scope; + if ( !BER_BVISNULL( filter ) ) { + ber_dupbv( &el.el_filter, filter ); + } + + el.el_a = attrs_dup( a ); + el.el_cb = cb; + + for ( elpp = &mi->mi_entry_limbo; + *elpp; + elpp = &(*elpp)->el_next ) + /* go to last */; + + *elpp = (entry_limbo_t *)ch_malloc( sizeof( entry_limbo_t ) ); + if ( *elpp == NULL ) { + if ( !BER_BVISNULL( &el.el_filter ) ) { + ch_free( el.el_filter.bv_val ); + } + if ( el.el_a != NULL ) { + attrs_free( el.el_a ); + } + if ( !BER_BVISNULL( &el.el_nbase ) ) { + ch_free( &el.el_nbase.bv_val ); + } + return -1; + } + + el.el_next = NULL; + **elpp = el; + } + + return 0; +} + +int +monitor_back_register_entry_callback( + struct berval *ndn, + monitor_callback_t *cb, + struct berval *nbase, + int scope, + struct berval *filter ) +{ + return monitor_back_register_entry_attrs( ndn, NULL, cb, + nbase, scope, filter ); +} + +/* + * TODO: add corresponding calls to remove installed callbacks, entries + * and so, in case the entity that installed them is removed (e.g. a + * database, via back-config) + */ +int +monitor_back_unregister_entry( + struct berval *ndn ) +{ + monitor_info_t *mi; + + if ( be_monitor == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_unregister_entry(\"%s\"): " + "monitor database not configured.\n", + ndn->bv_val, 0, 0 ); + + return -1; + } + + /* entry will be regularly freed, and resources released + * according to callbacks */ + if ( slapd_shutdown ) { + return 0; + } + + mi = ( monitor_info_t * )be_monitor->be_private; + + assert( mi != NULL ); + + if ( monitor_subsys_is_opened() ) { + Entry *e = NULL; + monitor_entry_t *mp = NULL; + monitor_callback_t *cb = NULL; + + if ( monitor_cache_remove( mi, ndn, &e ) != 0 ) { + /* entry does not exist */ + Debug( LDAP_DEBUG_ANY, + "monitor_back_unregister_entry(\"%s\"): " + "entry removal failed.\n", + ndn->bv_val, 0, 0 ); + return -1; + } + + mp = (monitor_entry_t *)e->e_private; + assert( mp != NULL ); + + for ( cb = mp->mp_cb; cb != NULL; ) { + monitor_callback_t *next = cb->mc_next; + + if ( cb->mc_free ) { + (void)cb->mc_free( e, &cb->mc_private ); + } + ch_free( cb ); + + cb = next; + } + + ch_free( mp ); + e->e_private = NULL; + entry_free( e ); + + } else { + entry_limbo_t **elpp; + + for ( elpp = &mi->mi_entry_limbo; + *elpp; + elpp = &(*elpp)->el_next ) + { + entry_limbo_t *elp = *elpp; + + if ( elp->el_type == LIMBO_ENTRY + && dn_match( ndn, &elp->el_e->e_nname ) ) + { + monitor_callback_t *cb, *next; + + for ( cb = elp->el_cb; cb; cb = next ) { + /* FIXME: call callbacks? */ + next = cb->mc_next; + if ( cb->mc_dispose ) { + cb->mc_dispose( &cb->mc_private ); + } + ch_free( cb ); + } + assert( elp->el_e != NULL ); + elp->el_e->e_private = NULL; + entry_free( elp->el_e ); + *elpp = elp->el_next; + ch_free( elp ); + elpp = NULL; + break; + } + } + + if ( elpp != NULL ) { + /* not found! where did it go? */ + return 1; + } + } + + return 0; +} + +int +monitor_back_unregister_entry_parent( + struct berval *nrdn, + monitor_callback_t *target_cb, + struct berval *nbase, + int scope, + struct berval *filter ) +{ + monitor_info_t *mi; + struct berval ndn = BER_BVNULL; + + if ( be_monitor == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_unregister_entry_parent(base=\"%s\" scope=%s filter=\"%s\"): " + "monitor database not configured.\n", + BER_BVISNULL( nbase ) ? "" : nbase->bv_val, + ldap_pvt_scope2str( scope ), + BER_BVISNULL( filter ) ? "" : filter->bv_val ); + + return -1; + } + + /* entry will be regularly freed, and resources released + * according to callbacks */ + if ( slapd_shutdown ) { + return 0; + } + + mi = ( monitor_info_t * )be_monitor->be_private; + + assert( mi != NULL ); + + if ( ( nrdn == NULL || BER_BVISNULL( nrdn ) ) + && BER_BVISNULL( filter ) ) + { + /* need a filter */ + Debug( LDAP_DEBUG_ANY, + "monitor_back_unregister_entry_parent(\"\"): " + "need a valid filter\n", + 0, 0, 0 ); + return -1; + } + + if ( monitor_subsys_is_opened() ) { + Entry *e = NULL; + monitor_entry_t *mp = NULL; + + if ( monitor_search2ndn( nbase, scope, filter, &ndn ) ) { + /* entry does not exist */ + Debug( LDAP_DEBUG_ANY, + "monitor_back_unregister_entry_parent(\"\"): " + "base=\"%s\" scope=%s filter=\"%s\": " + "unable to find entry\n", + nbase->bv_val ? nbase->bv_val : "\"\"", + ldap_pvt_scope2str( scope ), + filter->bv_val ); + return -1; + } + + if ( monitor_cache_remove( mi, &ndn, &e ) != 0 ) { + /* entry does not exist */ + Debug( LDAP_DEBUG_ANY, + "monitor_back_unregister_entry(\"%s\"): " + "entry removal failed.\n", + ndn.bv_val, 0, 0 ); + ber_memfree( ndn.bv_val ); + return -1; + } + ber_memfree( ndn.bv_val ); + + mp = (monitor_entry_t *)e->e_private; + assert( mp != NULL ); + + if ( target_cb != NULL ) { + monitor_callback_t **cbp; + + for ( cbp = &mp->mp_cb; *cbp != NULL; cbp = &(*cbp)->mc_next ) { + if ( *cbp == target_cb ) { + if ( (*cbp)->mc_free ) { + (void)(*cbp)->mc_free( e, &(*cbp)->mc_private ); + } + *cbp = (*cbp)->mc_next; + ch_free( target_cb ); + break; + } + } + } + + + ch_free( mp ); + e->e_private = NULL; + entry_free( e ); + + } else { + entry_limbo_t **elpp; + + for ( elpp = &mi->mi_entry_limbo; + *elpp; + elpp = &(*elpp)->el_next ) + { + entry_limbo_t *elp = *elpp; + + if ( elp->el_type == LIMBO_ENTRY_PARENT + && dn_match( nrdn, &elp->el_e->e_nname ) + && dn_match( nbase, &elp->el_nbase ) + && scope == elp->el_scope + && bvmatch( filter, &elp->el_filter ) ) + { + monitor_callback_t *cb, *next; + + for ( cb = elp->el_cb; cb; cb = next ) { + /* FIXME: call callbacks? */ + next = cb->mc_next; + if ( cb->mc_dispose ) { + cb->mc_dispose( &cb->mc_private ); + } + ch_free( cb ); + } + assert( elp->el_e != NULL ); + elp->el_e->e_private = NULL; + entry_free( elp->el_e ); + if ( !BER_BVISNULL( &elp->el_nbase ) ) { + ch_free( elp->el_nbase.bv_val ); + } + if ( !BER_BVISNULL( &elp->el_filter ) ) { + ch_free( elp->el_filter.bv_val ); + } + *elpp = elp->el_next; + ch_free( elp ); + elpp = NULL; + break; + } + } + + if ( elpp != NULL ) { + /* not found! where did it go? */ + return 1; + } + } + + return 0; +} + +int +monitor_back_unregister_entry_attrs( + struct berval *ndn_in, + Attribute *target_a, + monitor_callback_t *target_cb, + struct berval *nbase, + int scope, + struct berval *filter ) +{ + monitor_info_t *mi; + struct berval ndn = BER_BVNULL; + char *fname = ( target_a == NULL ? "callback" : "attrs" ); + + if ( be_monitor == NULL ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "monitor_back_unregister_entry_%s(base=\"%s\" scope=%s filter=\"%s\"): " + "monitor database not configured.\n", + fname, + BER_BVISNULL( nbase ) ? "" : nbase->bv_val, + ldap_pvt_scope2str( scope ), + BER_BVISNULL( filter ) ? "" : filter->bv_val ); + Debug( LDAP_DEBUG_ANY, "%s\n", buf, 0, 0 ); + + return -1; + } + + /* entry will be regularly freed, and resources released + * according to callbacks */ + if ( slapd_shutdown ) { + return 0; + } + + mi = ( monitor_info_t * )be_monitor->be_private; + + assert( mi != NULL ); + + if ( ndn_in != NULL ) { + ndn = *ndn_in; + } + + if ( target_a == NULL && target_cb == NULL ) { + /* nothing to do */ + return -1; + } + + if ( ( ndn_in == NULL || BER_BVISNULL( &ndn ) ) + && BER_BVISNULL( filter ) ) + { + /* need a filter */ + Debug( LDAP_DEBUG_ANY, + "monitor_back_unregister_entry_%s(\"\"): " + "need a valid filter\n", + fname, 0, 0 ); + return -1; + } + + if ( monitor_subsys_is_opened() ) { + Entry *e = NULL; + monitor_entry_t *mp = NULL; + int freeit = 0; + + if ( BER_BVISNULL( &ndn ) ) { + if ( monitor_search2ndn( nbase, scope, filter, &ndn ) ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "monitor_back_unregister_entry_%s(\"\"): " + "base=\"%s\" scope=%d filter=\"%s\": " + "unable to find entry\n", + fname, + nbase->bv_val ? nbase->bv_val : "\"\"", + scope, filter->bv_val ); + + /* entry does not exist */ + Debug( LDAP_DEBUG_ANY, "%s\n", buf, 0, 0 ); + return -1; + } + + freeit = 1; + } + + if ( monitor_cache_get( mi, &ndn, &e ) != 0 ) { + /* entry does not exist */ + Debug( LDAP_DEBUG_ANY, + "monitor_back_unregister_entry(\"%s\"): " + "entry removal failed.\n", + ndn.bv_val, 0, 0 ); + return -1; + } + + mp = (monitor_entry_t *)e->e_private; + assert( mp != NULL ); + + if ( target_cb != NULL ) { + monitor_callback_t **cbp; + + for ( cbp = &mp->mp_cb; *cbp != NULL; cbp = &(*cbp)->mc_next ) { + if ( *cbp == target_cb ) { + if ( (*cbp)->mc_free ) { + (void)(*cbp)->mc_free( e, &(*cbp)->mc_private ); + } + *cbp = (*cbp)->mc_next; + ch_free( target_cb ); + break; + } + } + } + + if ( target_a != NULL ) { + Attribute *a; + + for ( a = target_a; a != NULL; a = a->a_next ) { + Modification mod = { 0 }; + const char *text; + char textbuf[ SLAP_TEXT_BUFLEN ]; + + mod.sm_op = LDAP_MOD_DELETE; + mod.sm_desc = a->a_desc; + mod.sm_values = a->a_vals; + mod.sm_nvalues = a->a_nvals; + + (void)modify_delete_values( e, &mod, 1, + &text, textbuf, sizeof( textbuf ) ); + } + } + + if ( freeit ) { + ber_memfree( ndn.bv_val ); + } + + monitor_cache_release( mi, e ); + + } else { + entry_limbo_t **elpp; + + for ( elpp = &mi->mi_entry_limbo; + *elpp; + elpp = &(*elpp)->el_next ) + { + entry_limbo_t *elp = *elpp; + + if ( elp->el_type == LIMBO_ATTRS + && dn_match( nbase, &elp->el_nbase ) + && scope == elp->el_scope + && bvmatch( filter, &elp->el_filter ) ) + { + monitor_callback_t *cb, *next; + + for ( cb = elp->el_cb; cb; cb = next ) { + /* FIXME: call callbacks? */ + next = cb->mc_next; + if ( cb->mc_dispose ) { + cb->mc_dispose( &cb->mc_private ); + } + ch_free( cb ); + } + assert( elp->el_e == NULL ); + if ( elp->el_a != NULL ) { + attrs_free( elp->el_a ); + } + if ( !BER_BVISNULL( &elp->el_nbase ) ) { + ch_free( elp->el_nbase.bv_val ); + } + if ( !BER_BVISNULL( &elp->el_filter ) ) { + ch_free( elp->el_filter.bv_val ); + } + *elpp = elp->el_next; + ch_free( elp ); + elpp = NULL; + break; + } + } + + if ( elpp != NULL ) { + /* not found! where did it go? */ + return 1; + } + } + + return 0; +} + +int +monitor_back_unregister_entry_callback( + struct berval *ndn, + monitor_callback_t *cb, + struct berval *nbase, + int scope, + struct berval *filter ) +{ + /* TODO: lookup entry (by ndn, if not NULL, and/or by callback); + * unregister the callback; if a is not null, unregister the + * given attrs. In any case, call cb->cb_free */ + return monitor_back_unregister_entry_attrs( ndn, + NULL, cb, nbase, scope, filter ); +} + +monitor_subsys_t * +monitor_back_get_subsys( const char *name ) +{ + if ( monitor_subsys != NULL ) { + int i; + + for ( i = 0; monitor_subsys[ i ] != NULL; i++ ) { + if ( strcasecmp( monitor_subsys[ i ]->mss_name, name ) == 0 ) { + return monitor_subsys[ i ]; + } + } + } + + return NULL; +} + +monitor_subsys_t * +monitor_back_get_subsys_by_dn( + struct berval *ndn, + int sub ) +{ + if ( monitor_subsys != NULL ) { + int i; + + if ( sub ) { + for ( i = 0; monitor_subsys[ i ] != NULL; i++ ) { + if ( dnIsSuffix( ndn, &monitor_subsys[ i ]->mss_ndn ) ) { + return monitor_subsys[ i ]; + } + } + + } else { + for ( i = 0; monitor_subsys[ i ] != NULL; i++ ) { + if ( dn_match( ndn, &monitor_subsys[ i ]->mss_ndn ) ) { + return monitor_subsys[ i ]; + } + } + } + } + + return NULL; +} + +int +monitor_back_initialize( + BackendInfo *bi ) +{ + static char *controls[] = { + LDAP_CONTROL_MANAGEDSAIT, + NULL + }; + + static ConfigTable monitorcfg[] = { + { NULL, NULL, 0, 0, 0, ARG_IGNORED, + NULL, NULL, NULL, NULL } + }; + + static ConfigOCs monitorocs[] = { + { "( OLcfgDbOc:4.1 " + "NAME 'olcMonitorConfig' " + "DESC 'Monitor backend configuration' " + "SUP olcDatabaseConfig " + ")", + Cft_Database, monitorcfg }, + { NULL, 0, NULL } + }; + + struct m_s { + char *schema; + slap_mask_t flags; + int offset; + } moc[] = { + { "( 1.3.6.1.4.1.4203.666.3.16.1 " + "NAME 'monitor' " + "DESC 'OpenLDAP system monitoring' " + "SUP top STRUCTURAL " + "MUST cn " + "MAY ( " + "description " + "$ seeAlso " + "$ labeledURI " + "$ monitoredInfo " + "$ managedInfo " + "$ monitorOverlay " + ") )", SLAP_OC_OPERATIONAL|SLAP_OC_HIDE, + offsetof(monitor_info_t, mi_oc_monitor) }, + { "( 1.3.6.1.4.1.4203.666.3.16.2 " + "NAME 'monitorServer' " + "DESC 'Server monitoring root entry' " + "SUP monitor STRUCTURAL )", SLAP_OC_OPERATIONAL|SLAP_OC_HIDE, + offsetof(monitor_info_t, mi_oc_monitorServer) }, + { "( 1.3.6.1.4.1.4203.666.3.16.3 " + "NAME 'monitorContainer' " + "DESC 'monitor container class' " + "SUP monitor STRUCTURAL )", SLAP_OC_OPERATIONAL|SLAP_OC_HIDE, + offsetof(monitor_info_t, mi_oc_monitorContainer) }, + { "( 1.3.6.1.4.1.4203.666.3.16.4 " + "NAME 'monitorCounterObject' " + "DESC 'monitor counter class' " + "SUP monitor STRUCTURAL )", SLAP_OC_OPERATIONAL|SLAP_OC_HIDE, + offsetof(monitor_info_t, mi_oc_monitorCounterObject) }, + { "( 1.3.6.1.4.1.4203.666.3.16.5 " + "NAME 'monitorOperation' " + "DESC 'monitor operation class' " + "SUP monitor STRUCTURAL )", SLAP_OC_OPERATIONAL|SLAP_OC_HIDE, + offsetof(monitor_info_t, mi_oc_monitorOperation) }, + { "( 1.3.6.1.4.1.4203.666.3.16.6 " + "NAME 'monitorConnection' " + "DESC 'monitor connection class' " + "SUP monitor STRUCTURAL )", SLAP_OC_OPERATIONAL|SLAP_OC_HIDE, + offsetof(monitor_info_t, mi_oc_monitorConnection) }, + { "( 1.3.6.1.4.1.4203.666.3.16.7 " + "NAME 'managedObject' " + "DESC 'monitor managed entity class' " + "SUP monitor STRUCTURAL )", SLAP_OC_OPERATIONAL|SLAP_OC_HIDE, + offsetof(monitor_info_t, mi_oc_managedObject) }, + { "( 1.3.6.1.4.1.4203.666.3.16.8 " + "NAME 'monitoredObject' " + "DESC 'monitor monitored entity class' " + "SUP monitor STRUCTURAL )", SLAP_OC_OPERATIONAL|SLAP_OC_HIDE, + offsetof(monitor_info_t, mi_oc_monitoredObject) }, + { NULL, 0, -1 } + }, mat[] = { + { "( 1.3.6.1.4.1.4203.666.1.55.1 " + "NAME 'monitoredInfo' " + "DESC 'monitored info' " + /* "SUP name " */ + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768} " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitoredInfo) }, + { "( 1.3.6.1.4.1.4203.666.1.55.2 " + "NAME 'managedInfo' " + "DESC 'monitor managed info' " + "SUP name )", SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_managedInfo) }, + { "( 1.3.6.1.4.1.4203.666.1.55.3 " + "NAME 'monitorCounter' " + "DESC 'monitor counter' " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorCounter) }, + { "( 1.3.6.1.4.1.4203.666.1.55.4 " + "NAME 'monitorOpCompleted' " + "DESC 'monitor completed operations' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorOpCompleted) }, + { "( 1.3.6.1.4.1.4203.666.1.55.5 " + "NAME 'monitorOpInitiated' " + "DESC 'monitor initiated operations' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorOpInitiated) }, + { "( 1.3.6.1.4.1.4203.666.1.55.6 " + "NAME 'monitorConnectionNumber' " + "DESC 'monitor connection number' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorConnectionNumber) }, + { "( 1.3.6.1.4.1.4203.666.1.55.7 " + "NAME 'monitorConnectionAuthzDN' " + "DESC 'monitor connection authorization DN' " + /* "SUP distinguishedName " */ + "EQUALITY distinguishedNameMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorConnectionAuthzDN) }, + { "( 1.3.6.1.4.1.4203.666.1.55.8 " + "NAME 'monitorConnectionLocalAddress' " + "DESC 'monitor connection local address' " + "SUP monitoredInfo " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorConnectionLocalAddress) }, + { "( 1.3.6.1.4.1.4203.666.1.55.9 " + "NAME 'monitorConnectionPeerAddress' " + "DESC 'monitor connection peer address' " + "SUP monitoredInfo " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorConnectionPeerAddress) }, + { "( 1.3.6.1.4.1.4203.666.1.55.10 " + "NAME 'monitorTimestamp' " + "DESC 'monitor timestamp' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "SINGLE-VALUE " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorTimestamp) }, + { "( 1.3.6.1.4.1.4203.666.1.55.11 " + "NAME 'monitorOverlay' " + "DESC 'name of overlays defined for a given database' " + "SUP monitoredInfo " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorOverlay) }, + { "( 1.3.6.1.4.1.4203.666.1.55.12 " + "NAME 'readOnly' " + "DESC 'read/write status of a given database' " + "EQUALITY booleanMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 " + "SINGLE-VALUE " + "USAGE dSAOperation )", SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_readOnly) }, + { "( 1.3.6.1.4.1.4203.666.1.55.13 " + "NAME 'restrictedOperation' " + "DESC 'name of restricted operation for a given database' " + "SUP managedInfo )", SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_restrictedOperation ) }, + { "( 1.3.6.1.4.1.4203.666.1.55.14 " + "NAME 'monitorConnectionProtocol' " + "DESC 'monitor connection protocol' " + "SUP monitoredInfo " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorConnectionProtocol) }, + { "( 1.3.6.1.4.1.4203.666.1.55.15 " + "NAME 'monitorConnectionOpsReceived' " + "DESC 'monitor number of operations received by the connection' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorConnectionOpsReceived) }, + { "( 1.3.6.1.4.1.4203.666.1.55.16 " + "NAME 'monitorConnectionOpsExecuting' " + "DESC 'monitor number of operations in execution within the connection' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorConnectionOpsExecuting) }, + { "( 1.3.6.1.4.1.4203.666.1.55.17 " + "NAME 'monitorConnectionOpsPending' " + "DESC 'monitor number of pending operations within the connection' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorConnectionOpsPending) }, + { "( 1.3.6.1.4.1.4203.666.1.55.18 " + "NAME 'monitorConnectionOpsCompleted' " + "DESC 'monitor number of operations completed within the connection' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorConnectionOpsCompleted) }, + { "( 1.3.6.1.4.1.4203.666.1.55.19 " + "NAME 'monitorConnectionGet' " + "DESC 'number of times connection_get() was called so far' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorConnectionGet) }, + { "( 1.3.6.1.4.1.4203.666.1.55.20 " + "NAME 'monitorConnectionRead' " + "DESC 'number of times connection_read() was called so far' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorConnectionRead) }, + { "( 1.3.6.1.4.1.4203.666.1.55.21 " + "NAME 'monitorConnectionWrite' " + "DESC 'number of times connection_write() was called so far' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorConnectionWrite) }, + { "( 1.3.6.1.4.1.4203.666.1.55.22 " + "NAME 'monitorConnectionMask' " + "DESC 'monitor connection mask' " + "SUP monitoredInfo " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorConnectionMask) }, + { "( 1.3.6.1.4.1.4203.666.1.55.23 " + "NAME 'monitorConnectionListener' " + "DESC 'monitor connection listener' " + "SUP monitoredInfo " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorConnectionListener) }, + { "( 1.3.6.1.4.1.4203.666.1.55.24 " + "NAME 'monitorConnectionPeerDomain' " + "DESC 'monitor connection peer domain' " + "SUP monitoredInfo " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorConnectionPeerDomain) }, + { "( 1.3.6.1.4.1.4203.666.1.55.25 " + "NAME 'monitorConnectionStartTime' " + "DESC 'monitor connection start time' " + "SUP monitorTimestamp " + "SINGLE-VALUE " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorConnectionStartTime) }, + { "( 1.3.6.1.4.1.4203.666.1.55.26 " + "NAME 'monitorConnectionActivityTime' " + "DESC 'monitor connection activity time' " + "SUP monitorTimestamp " + "SINGLE-VALUE " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorConnectionActivityTime) }, + { "( 1.3.6.1.4.1.4203.666.1.55.27 " + "NAME 'monitorIsShadow' " + "DESC 'TRUE if the database is shadow' " + "EQUALITY booleanMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 " + "SINGLE-VALUE " + "USAGE dSAOperation )", SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorIsShadow) }, + { "( 1.3.6.1.4.1.4203.666.1.55.28 " + "NAME 'monitorUpdateRef' " + "DESC 'update referral for shadow databases' " + "SUP monitoredInfo " + "SINGLE-VALUE " + "USAGE dSAOperation )", SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorUpdateRef) }, + { "( 1.3.6.1.4.1.4203.666.1.55.29 " + "NAME 'monitorRuntimeConfig' " + "DESC 'TRUE if component allows runtime configuration' " + "EQUALITY booleanMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 " + "SINGLE-VALUE " + "USAGE dSAOperation )", SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorRuntimeConfig) }, + { "( 1.3.6.1.4.1.4203.666.1.55.30 " + "NAME 'monitorSuperiorDN' " + "DESC 'monitor superior DN' " + /* "SUP distinguishedName " */ + "EQUALITY distinguishedNameMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE, + offsetof(monitor_info_t, mi_ad_monitorSuperiorDN) }, + { NULL, 0, -1 } + }; + + static struct { + char *name; + char *oid; + } s_oid[] = { + { "olmAttributes", "1.3.6.1.4.1.4203.666.1.55" }, + { "olmSubSystemAttributes", "olmAttributes:0" }, + { "olmGenericAttributes", "olmSubSystemAttributes:0" }, + { "olmDatabaseAttributes", "olmSubSystemAttributes:1" }, + + /* for example, back-bdb specific attrs + * are in "olmDatabaseAttributes:1" + * + * NOTE: developers, please record here OID assignments + * for other modules */ + + { "olmObjectClasses", "1.3.6.1.4.1.4203.666.3.16" }, + { "olmSubSystemObjectClasses", "olmObjectClasses:0" }, + { "olmGenericObjectClasses", "olmSubSystemObjectClasses:0" }, + { "olmDatabaseObjectClasses", "olmSubSystemObjectClasses:1" }, + + /* for example, back-bdb specific objectClasses + * are in "olmDatabaseObjectClasses:1" + * + * NOTE: developers, please record here OID assignments + * for other modules */ + + { NULL } + }; + + int i, rc; + monitor_info_t *mi = &monitor_info; + ConfigArgs c; + char *argv[ 3 ]; + + argv[ 0 ] = "monitor"; + c.argv = argv; + c.argc = 3; + c.fname = argv[0]; + + for ( i = 0; s_oid[ i ].name; i++ ) { + argv[ 1 ] = s_oid[ i ].name; + argv[ 2 ] = s_oid[ i ].oid; + + if ( parse_oidm( &c, 0, NULL ) != 0 ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_initialize: unable to add " + "objectIdentifier \"%s=%s\"\n", + s_oid[ i ].name, s_oid[ i ].oid, 0 ); + return 1; + } + } + + /* schema integration */ + for ( i = 0; mat[ i ].schema; i++ ) { + int code; + AttributeDescription **ad = + ((AttributeDescription **)&(((char *)mi)[ mat[ i ].offset ])); + + *ad = NULL; + code = register_at( mat[ i ].schema, ad, 0 ); + + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_db_init: register_at failed\n", 0, 0, 0 ); + return -1; + } + (*ad)->ad_type->sat_flags |= mat[ i ].flags; + } + + for ( i = 0; moc[ i ].schema; i++ ) { + int code; + ObjectClass **Oc = + ((ObjectClass **)&(((char *)mi)[ moc[ i ].offset ])); + + code = register_oc( moc[ i ].schema, Oc, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "monitor_back_db_init: register_oc failed\n", 0, 0, 0 ); + return -1; + } + (*Oc)->soc_flags |= moc[ i ].flags; + } + + bi->bi_controls = controls; + + bi->bi_init = 0; + bi->bi_open = 0; + bi->bi_config = monitor_back_config; + bi->bi_close = 0; + bi->bi_destroy = 0; + + bi->bi_db_init = monitor_back_db_init; +#if 0 + bi->bi_db_config = monitor_back_db_config; +#endif + bi->bi_db_open = monitor_back_db_open; + bi->bi_db_close = 0; + bi->bi_db_destroy = monitor_back_db_destroy; + + bi->bi_op_bind = monitor_back_bind; + bi->bi_op_unbind = 0; + bi->bi_op_search = monitor_back_search; + bi->bi_op_compare = monitor_back_compare; + bi->bi_op_modify = monitor_back_modify; + bi->bi_op_modrdn = 0; + bi->bi_op_add = 0; + bi->bi_op_delete = 0; + bi->bi_op_abandon = 0; + + bi->bi_extended = 0; + + bi->bi_entry_release_rw = monitor_back_release; + bi->bi_chk_referrals = 0; + bi->bi_operational = monitor_back_operational; + + /* + * hooks for slap tools + */ + bi->bi_tool_entry_open = 0; + bi->bi_tool_entry_close = 0; + bi->bi_tool_entry_first = 0; + bi->bi_tool_entry_first_x = 0; + bi->bi_tool_entry_next = 0; + bi->bi_tool_entry_get = 0; + bi->bi_tool_entry_put = 0; + bi->bi_tool_entry_reindex = 0; + bi->bi_tool_sync = 0; + bi->bi_tool_dn2id_get = 0; + bi->bi_tool_entry_modify = 0; + + bi->bi_connection_init = 0; + bi->bi_connection_destroy = 0; + + bi->bi_extra = (void *)&monitor_extra; + + /* + * configuration objectClasses (fake) + */ + bi->bi_cf_ocs = monitorocs; + + rc = config_register_schema( monitorcfg, monitorocs ); + if ( rc ) { + return rc; + } + + return 0; +} + +int +monitor_back_db_init( + BackendDB *be, + ConfigReply *c) +{ + int rc; + struct berval dn = BER_BVC( SLAPD_MONITOR_DN ), + pdn, + ndn; + BackendDB *be2; + + monitor_subsys_t *ms; + + /* + * database monitor can be defined once only + */ + if ( be_monitor != NULL ) { + if (c) { + snprintf(c->msg, sizeof(c->msg),"only one monitor database allowed"); + } + return( -1 ); + } + be_monitor = be; + + /* + * register subsys + */ + for ( ms = known_monitor_subsys; ms->mss_name != NULL; ms++ ) { + if ( monitor_back_register_subsys( ms ) ) { + return -1; + } + } + + /* indicate system schema supported */ + SLAP_BFLAGS(be) |= SLAP_BFLAG_MONITOR; + + rc = dnPrettyNormal( NULL, &dn, &pdn, &ndn, NULL ); + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "unable to normalize/pretty monitor DN \"%s\" (%d)\n", + dn.bv_val, rc, 0 ); + return -1; + } + + ber_bvarray_add( &be->be_suffix, &pdn ); + ber_bvarray_add( &be->be_nsuffix, &ndn ); + + /* NOTE: only one monitor database is allowed, + * so we use static storage */ + ldap_pvt_thread_mutex_init( &monitor_info.mi_cache_mutex ); + + be->be_private = &monitor_info; + + be2 = select_backend( &ndn, 0 ); + if ( be2 != be ) { + char *type = be2->bd_info->bi_type; + + if ( overlay_is_over( be2 ) ) { + slap_overinfo *oi = (slap_overinfo *)be2->bd_info->bi_private; + type = oi->oi_orig->bi_type; + } + + if (c) { + snprintf(c->msg, sizeof(c->msg), + "\"monitor\" database serving namingContext \"%s\" " + "is hidden by \"%s\" database serving namingContext \"%s\".\n", + pdn.bv_val, type, be2->be_nsuffix[ 0 ].bv_val ); + } + return -1; + } + + return 0; +} + +static void +monitor_back_destroy_limbo_entry( + entry_limbo_t *el, + int dispose ) +{ + if ( el->el_e ) { + entry_free( el->el_e ); + } + if ( el->el_a ) { + attrs_free( el->el_a ); + } + if ( !BER_BVISNULL( &el->el_nbase ) ) { + ber_memfree( el->el_nbase.bv_val ); + } + if ( !BER_BVISNULL( &el->el_filter ) ) { + ber_memfree( el->el_filter.bv_val ); + } + + /* NOTE: callbacks are not copied; so only free them + * if disposing of */ + if ( el->el_cb && dispose != 0 ) { + monitor_callback_t *next; + + for ( ; el->el_cb; el->el_cb = next ) { + next = el->el_cb->mc_next; + if ( el->el_cb->mc_dispose ) { + el->el_cb->mc_dispose( &el->el_cb->mc_private ); + } + ch_free( el->el_cb ); + } + } + + ch_free( el ); +} + +int +monitor_back_db_open( + BackendDB *be, + ConfigReply *cr) +{ + monitor_info_t *mi = (monitor_info_t *)be->be_private; + struct monitor_subsys_t **ms; + Entry *e, **ep, *root; + monitor_entry_t *mp; + int i; + struct berval bv, rdn = BER_BVC(SLAPD_MONITOR_DN); + struct tm tms; + static char tmbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + struct berval desc[] = { + BER_BVC("This subtree contains monitoring/managing objects."), + BER_BVC("This object contains information about this server."), + BER_BVC("Most of the information is held in operational" + " attributes, which must be explicitly requested."), + BER_BVNULL }; + + int retcode = 0; + + assert( be_monitor != NULL ); + if ( be != be_monitor ) { + be_monitor = be; + } + + /* + * Start + */ + ldap_pvt_gmtime( &starttime, &tms ); + lutil_gentime( tmbuf, sizeof(tmbuf), &tms ); + + mi->mi_startTime.bv_val = tmbuf; + mi->mi_startTime.bv_len = strlen( tmbuf ); + + if ( BER_BVISEMPTY( &be->be_rootdn ) ) { + BER_BVSTR( &mi->mi_creatorsName, SLAPD_ANONYMOUS ); + BER_BVSTR( &mi->mi_ncreatorsName, SLAPD_ANONYMOUS ); + } else { + mi->mi_creatorsName = be->be_rootdn; + mi->mi_ncreatorsName = be->be_rootndn; + } + + /* + * creates the "cn=Monitor" entry + */ + e = monitor_entry_stub( NULL, NULL, &rdn, mi->mi_oc_monitorServer, + NULL, NULL ); + + if ( e == NULL) { + Debug( LDAP_DEBUG_ANY, + "unable to create \"%s\" entry\n", + SLAPD_MONITOR_DN, 0, 0 ); + return( -1 ); + } + + attr_merge_normalize( e, slap_schema.si_ad_description, desc, NULL ); + + bv.bv_val = strchr( (char *) Versionstr, '$' ); + if ( bv.bv_val != NULL ) { + char *end; + + bv.bv_val++; + for ( ; bv.bv_val[ 0 ] == ' '; bv.bv_val++ ) + ; + + end = strchr( bv.bv_val, '$' ); + if ( end != NULL ) { + end--; + + for ( ; end > bv.bv_val && end[ 0 ] == ' '; end-- ) + ; + + end++; + + bv.bv_len = end - bv.bv_val; + + } else { + bv.bv_len = strlen( bv.bv_val ); + } + + if ( attr_merge_normalize_one( e, mi->mi_ad_monitoredInfo, + &bv, NULL ) ) { + Debug( LDAP_DEBUG_ANY, + "unable to add monitoredInfo to \"%s\" entry\n", + SLAPD_MONITOR_DN, 0, 0 ); + return( -1 ); + } + } + + mp = monitor_entrypriv_create(); + if ( mp == NULL ) { + return -1; + } + e->e_private = ( void * )mp; + ep = &mp->mp_children; + + if ( monitor_cache_add( mi, e ) ) { + Debug( LDAP_DEBUG_ANY, + "unable to add entry \"%s\" to cache\n", + SLAPD_MONITOR_DN, 0, 0 ); + return -1; + } + root = e; + + /* + * Create all the subsystem specific entries + */ + for ( i = 0; monitor_subsys[ i ] != NULL; i++ ) { + int len = strlen( monitor_subsys[ i ]->mss_name ); + struct berval dn; + int rc; + + dn.bv_len = len + sizeof( "cn=" ) - 1; + dn.bv_val = ch_calloc( sizeof( char ), dn.bv_len + 1 ); + strcpy( dn.bv_val, "cn=" ); + strcat( dn.bv_val, monitor_subsys[ i ]->mss_name ); + rc = dnPretty( NULL, &dn, &monitor_subsys[ i ]->mss_rdn, NULL ); + free( dn.bv_val ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "monitor RDN \"%s\" is invalid\n", + dn.bv_val, 0, 0 ); + return( -1 ); + } + + e = monitor_entry_stub( &root->e_name, &root->e_nname, + &monitor_subsys[ i ]->mss_rdn, mi->mi_oc_monitorContainer, + NULL, NULL ); + + if ( e == NULL) { + Debug( LDAP_DEBUG_ANY, + "unable to create \"%s\" entry\n", + monitor_subsys[ i ]->mss_dn.bv_val, 0, 0 ); + return( -1 ); + } + monitor_subsys[i]->mss_dn = e->e_name; + monitor_subsys[i]->mss_ndn = e->e_nname; + + if ( !BER_BVISNULL( &monitor_subsys[ i ]->mss_desc[ 0 ] ) ) { + attr_merge_normalize( e, slap_schema.si_ad_description, + monitor_subsys[ i ]->mss_desc, NULL ); + } + + mp = monitor_entrypriv_create(); + if ( mp == NULL ) { + return -1; + } + e->e_private = ( void * )mp; + mp->mp_info = monitor_subsys[ i ]; + mp->mp_flags = monitor_subsys[ i ]->mss_flags; + + if ( monitor_cache_add( mi, e ) ) { + Debug( LDAP_DEBUG_ANY, + "unable to add entry \"%s\" to cache\n", + monitor_subsys[ i ]->mss_dn.bv_val, 0, 0 ); + return -1; + } + + *ep = e; + ep = &mp->mp_next; + } + + assert( be != NULL ); + + be->be_private = mi; + + /* + * opens the monitor backend subsystems + */ + for ( ms = monitor_subsys; ms[ 0 ] != NULL; ms++ ) { + if ( ms[ 0 ]->mss_open && ms[ 0 ]->mss_open( be, ms[ 0 ] ) ) { + return( -1 ); + } + ms[ 0 ]->mss_flags |= MONITOR_F_OPENED; + } + + monitor_subsys_opened = 1; + + if ( mi->mi_entry_limbo ) { + entry_limbo_t *el = mi->mi_entry_limbo; + + for ( ; el; ) { + entry_limbo_t *tmp; + int rc; + + switch ( el->el_type ) { + case LIMBO_ENTRY: + rc = monitor_back_register_entry( + el->el_e, + el->el_cb, + el->el_mss, + el->el_flags ); + break; + + case LIMBO_ENTRY_PARENT: + rc = monitor_back_register_entry_parent( + el->el_e, + el->el_cb, + el->el_mss, + el->el_flags, + &el->el_nbase, + el->el_scope, + &el->el_filter ); + break; + + + case LIMBO_ATTRS: + rc = monitor_back_register_entry_attrs( + el->el_ndn, + el->el_a, + el->el_cb, + &el->el_nbase, + el->el_scope, + &el->el_filter ); + break; + + case LIMBO_CB: + rc = monitor_back_register_entry_callback( + el->el_ndn, + el->el_cb, + &el->el_nbase, + el->el_scope, + &el->el_filter ); + break; + + case LIMBO_BACKEND: + rc = monitor_back_register_backend( el->el_bi ); + break; + + case LIMBO_DATABASE: + rc = monitor_back_register_database( el->el_be, el->el_ndn ); + break; + + case LIMBO_OVERLAY_INFO: + rc = monitor_back_register_overlay_info( el->el_on ); + break; + + case LIMBO_OVERLAY: + rc = monitor_back_register_overlay( el->el_be, el->el_on, el->el_ndn ); + break; + + case LIMBO_SUBSYS: + rc = monitor_back_register_subsys( el->el_mss ); + break; + + default: + assert( 0 ); + } + + tmp = el; + el = el->el_next; + monitor_back_destroy_limbo_entry( tmp, rc ); + + if ( rc != 0 ) { + /* try all, but report error at end */ + retcode = 1; + } + } + + mi->mi_entry_limbo = NULL; + } + + return retcode; +} + +int +monitor_back_config( + BackendInfo *bi, + const char *fname, + int lineno, + int argc, + char **argv ) +{ + /* + * eventually, will hold backend specific configuration parameters + */ + return SLAP_CONF_UNKNOWN; +} + +#if 0 +int +monitor_back_db_config( + Backend *be, + const char *fname, + int lineno, + int argc, + char **argv ) +{ + monitor_info_t *mi = ( monitor_info_t * )be->be_private; + + /* + * eventually, will hold database specific configuration parameters + */ + return SLAP_CONF_UNKNOWN; +} +#endif + +int +monitor_back_db_destroy( + BackendDB *be, + ConfigReply *cr) +{ + monitor_info_t *mi = ( monitor_info_t * )be->be_private; + + if ( mi == NULL ) { + return -1; + } + + /* + * FIXME: destroys all the data + */ + /* NOTE: mi points to static storage; don't free it */ + + (void)monitor_cache_destroy( mi ); + + if ( monitor_subsys ) { + int i; + + for ( i = 0; monitor_subsys[ i ] != NULL; i++ ) { + if ( monitor_subsys[ i ]->mss_destroy ) { + monitor_subsys[ i ]->mss_destroy( be, monitor_subsys[ i ] ); + } + + if ( !BER_BVISNULL( &monitor_subsys[ i ]->mss_rdn ) ) { + ch_free( monitor_subsys[ i ]->mss_rdn.bv_val ); + } + } + + ch_free( monitor_subsys ); + } + + if ( mi->mi_entry_limbo ) { + entry_limbo_t *el = mi->mi_entry_limbo; + + for ( ; el; ) { + entry_limbo_t *tmp = el; + el = el->el_next; + monitor_back_destroy_limbo_entry( tmp, 1 ); + } + } + + ldap_pvt_thread_mutex_destroy( &monitor_info.mi_cache_mutex ); + + be->be_private = NULL; + + return 0; +} + +#if SLAPD_MONITOR == SLAPD_MOD_DYNAMIC + +/* conditionally define the init_module() function */ +SLAP_BACKEND_INIT_MODULE( monitor ) + +#endif /* SLAPD_MONITOR == SLAPD_MOD_DYNAMIC */ + diff --git a/servers/slapd/back-monitor/listener.c b/servers/slapd/back-monitor/listener.c new file mode 100644 index 0000000..fdc6943 --- /dev/null +++ b/servers/slapd/back-monitor/listener.c @@ -0,0 +1,138 @@ +/* listener.c - deals with listener subsystem */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-monitor.h" + +int +monitor_subsys_listener_init( + BackendDB *be, + monitor_subsys_t *ms +) +{ + monitor_info_t *mi; + Entry *e_listener, **ep; + int i; + monitor_entry_t *mp; + Listener **l; + + assert( be != NULL ); + + if ( ( l = slapd_get_listeners() ) == NULL ) { + if ( slapMode & SLAP_TOOL_MODE ) { + return 0; + } + + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_listener_init: " + "unable to get listeners\n", 0, 0, 0 ); + return( -1 ); + } + + mi = ( monitor_info_t * )be->be_private; + + if ( monitor_cache_get( mi, &ms->mss_ndn, &e_listener ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_listener_init: " + "unable to get entry \"%s\"\n", + ms->mss_ndn.bv_val, 0, 0 ); + return( -1 ); + } + + mp = ( monitor_entry_t * )e_listener->e_private; + mp->mp_children = NULL; + ep = &mp->mp_children; + + for ( i = 0; l[ i ]; i++ ) { + char buf[ BACKMONITOR_BUFSIZE ]; + Entry *e; + struct berval bv; + + bv.bv_len = snprintf( buf, sizeof( buf ), + "cn=Listener %d", i ); + bv.bv_val = buf; + e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv, + mi->mi_oc_monitoredObject, NULL, NULL ); + + if ( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_listener_init: " + "unable to create entry \"cn=Listener %d,%s\"\n", + i, ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + + attr_merge_normalize_one( e, mi->mi_ad_monitorConnectionLocalAddress, + &l[ i ]->sl_name, NULL ); + + attr_merge_normalize_one( e, slap_schema.si_ad_labeledURI, + &l[ i ]->sl_url, NULL ); + +#ifdef HAVE_TLS + if ( l[ i ]->sl_is_tls ) { + struct berval bv; + + BER_BVSTR( &bv, "TLS" ); + attr_merge_normalize_one( e, mi->mi_ad_monitoredInfo, + &bv, NULL ); + } +#endif /* HAVE_TLS */ +#ifdef LDAP_CONNECTIONLESS + if ( l[ i ]->sl_is_udp ) { + struct berval bv; + + BER_BVSTR( &bv, "UDP" ); + attr_merge_normalize_one( e, mi->mi_ad_monitoredInfo, + &bv, NULL ); + } +#endif /* HAVE_TLS */ + + mp = monitor_entrypriv_create(); + if ( mp == NULL ) { + return -1; + } + e->e_private = ( void * )mp; + mp->mp_info = ms; + mp->mp_flags = ms->mss_flags + | MONITOR_F_SUB; + + if ( monitor_cache_add( mi, e ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_listener_init: " + "unable to add entry \"cn=Listener %d,%s\"\n", + i, ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + + *ep = e; + ep = &mp->mp_next; + } + + monitor_cache_release( mi, e_listener ); + + return( 0 ); +} + diff --git a/servers/slapd/back-monitor/log.c b/servers/slapd/back-monitor/log.c new file mode 100644 index 0000000..7f88df0 --- /dev/null +++ b/servers/slapd/back-monitor/log.c @@ -0,0 +1,455 @@ +/* log.c - deal with log subsystem */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> + +#include "slap.h" +#include <lber_pvt.h> +#include "lutil.h" +#include "ldif.h" +#include "back-monitor.h" + +static int +monitor_subsys_log_open( + BackendDB *be, + monitor_subsys_t *ms ); + +static int +monitor_subsys_log_modify( + Operation *op, + SlapReply *rs, + Entry *e ); + +/* + * log mutex + */ +ldap_pvt_thread_mutex_t monitor_log_mutex; + +static int add_values( Operation *op, Entry *e, Modification *mod, int *newlevel ); +static int delete_values( Operation *op, Entry *e, Modification *mod, int *newlevel ); +static int replace_values( Operation *op, Entry *e, Modification *mod, int *newlevel ); + +/* + * initializes log subentry + */ +int +monitor_subsys_log_init( + BackendDB *be, + monitor_subsys_t *ms ) +{ + ms->mss_open = monitor_subsys_log_open; + ms->mss_modify = monitor_subsys_log_modify; + + ldap_pvt_thread_mutex_init( &monitor_log_mutex ); + + return( 0 ); +} + +/* + * opens log subentry + */ +int +monitor_subsys_log_open( + BackendDB *be, + monitor_subsys_t *ms ) +{ + BerVarray bva = NULL; + + if ( loglevel2bvarray( ldap_syslog, &bva ) == 0 && bva != NULL ) { + monitor_info_t *mi; + Entry *e; + + mi = ( monitor_info_t * )be->be_private; + + if ( monitor_cache_get( mi, &ms->mss_ndn, &e ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_log_init: " + "unable to get entry \"%s\"\n", + ms->mss_ndn.bv_val, 0, 0 ); + ber_bvarray_free( bva ); + return( -1 ); + } + + attr_merge_normalize( e, mi->mi_ad_managedInfo, bva, NULL ); + ber_bvarray_free( bva ); + + monitor_cache_release( mi, e ); + } + + return( 0 ); +} + +static int +monitor_subsys_log_modify( + Operation *op, + SlapReply *rs, + Entry *e ) +{ + monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private; + int rc = LDAP_OTHER; + int newlevel = ldap_syslog; + Attribute *save_attrs; + Modifications *modlist = op->orm_modlist; + Modifications *ml; + + ldap_pvt_thread_mutex_lock( &monitor_log_mutex ); + + save_attrs = e->e_attrs; + e->e_attrs = attrs_dup( e->e_attrs ); + + for ( ml = modlist; ml != NULL; ml = ml->sml_next ) { + Modification *mod = &ml->sml_mod; + + /* + * accept all operational attributes; + * this includes modifersName and modifyTimestamp + * if lastmod is "on" + */ + if ( is_at_operational( mod->sm_desc->ad_type ) ) { + ( void ) attr_delete( &e->e_attrs, mod->sm_desc ); + rc = rs->sr_err = attr_merge( e, mod->sm_desc, + mod->sm_values, mod->sm_nvalues ); + if ( rc != LDAP_SUCCESS ) { + break; + } + continue; + + /* + * only the "managedInfo" attribute can be modified + */ + } else if ( mod->sm_desc != mi->mi_ad_managedInfo ) { + rc = rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + break; + } + + switch ( mod->sm_op ) { + case LDAP_MOD_ADD: + rc = add_values( op, e, mod, &newlevel ); + break; + + case LDAP_MOD_DELETE: + rc = delete_values( op, e, mod, &newlevel ); + break; + + case LDAP_MOD_REPLACE: + rc = replace_values( op, e, mod, &newlevel ); + break; + + default: + rc = LDAP_OTHER; + break; + } + + if ( rc != LDAP_SUCCESS ) { + rs->sr_err = rc; + break; + } + } + + /* set the new debug level */ + if ( rc == LDAP_SUCCESS ) { + const char *text; + static char textbuf[ BACKMONITOR_BUFSIZE ]; + + /* check for abandon */ + if ( op->o_abandon ) { + rc = rs->sr_err = SLAPD_ABANDON; + + goto cleanup; + } + + /* check that the entry still obeys the schema */ + rc = entry_schema_check( op, e, save_attrs, 0, 0, NULL, + &text, textbuf, sizeof( textbuf ) ); + if ( rc != LDAP_SUCCESS ) { + rs->sr_err = rc; + goto cleanup; + } + + /* + * Do we need to protect this with a mutex? + */ + ldap_syslog = newlevel; + +#if 0 /* debug rather than log */ + slap_debug = newlevel; + lutil_set_debug_level( "slapd", slap_debug ); + ber_set_option(NULL, LBER_OPT_DEBUG_LEVEL, &slap_debug); + ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &slap_debug); + ldif_debug = slap_debug; +#endif + } + +cleanup:; + if ( rc == LDAP_SUCCESS ) { + attrs_free( save_attrs ); + + } else { + attrs_free( e->e_attrs ); + e->e_attrs = save_attrs; + } + + ldap_pvt_thread_mutex_unlock( &monitor_log_mutex ); + + if ( rc == LDAP_SUCCESS ) { + rc = SLAP_CB_CONTINUE; + } + + return rc; +} + +static int +check_constraints( Modification *mod, int *newlevel ) +{ + int i; + + if ( mod->sm_nvalues != NULL ) { + ber_bvarray_free( mod->sm_nvalues ); + mod->sm_nvalues = NULL; + } + + for ( i = 0; !BER_BVISNULL( &mod->sm_values[ i ] ); i++ ) { + int l; + struct berval bv; + + if ( str2loglevel( mod->sm_values[ i ].bv_val, &l ) ) { + return LDAP_CONSTRAINT_VIOLATION; + } + + if ( loglevel2bv( l, &bv ) ) { + return LDAP_CONSTRAINT_VIOLATION; + } + + assert( bv.bv_len == mod->sm_values[ i ].bv_len ); + + AC_MEMCPY( mod->sm_values[ i ].bv_val, + bv.bv_val, bv.bv_len ); + + *newlevel |= l; + } + + return LDAP_SUCCESS; +} + +static int +add_values( Operation *op, Entry *e, Modification *mod, int *newlevel ) +{ + Attribute *a; + int i, rc; + MatchingRule *mr = mod->sm_desc->ad_type->sat_equality; + + assert( mod->sm_values != NULL ); + + rc = check_constraints( mod, newlevel ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + a = attr_find( e->e_attrs, mod->sm_desc ); + + if ( a != NULL ) { + /* "managedInfo" SHOULD have appropriate rules ... */ + if ( mr == NULL || !mr->smr_match ) { + return LDAP_INAPPROPRIATE_MATCHING; + } + + for ( i = 0; !BER_BVISNULL( &mod->sm_values[ i ] ); i++ ) { + int rc; + int j; + const char *text = NULL; + struct berval asserted; + + rc = asserted_value_validate_normalize( + mod->sm_desc, mr, SLAP_MR_EQUALITY, + &mod->sm_values[ i ], &asserted, &text, + op->o_tmpmemctx ); + + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + for ( j = 0; !BER_BVISNULL( &a->a_vals[ j ] ); j++ ) { + int match; + int rc = value_match( &match, mod->sm_desc, mr, + 0, &a->a_nvals[ j ], &asserted, &text ); + + if ( rc == LDAP_SUCCESS && match == 0 ) { + free( asserted.bv_val ); + return LDAP_TYPE_OR_VALUE_EXISTS; + } + } + + free( asserted.bv_val ); + } + } + + /* no - add them */ + rc = attr_merge_normalize( e, mod->sm_desc, mod->sm_values, + op->o_tmpmemctx ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + return LDAP_SUCCESS; +} + +static int +delete_values( Operation *op, Entry *e, Modification *mod, int *newlevel ) +{ + int i, j, k, found, rc, nl = 0; + Attribute *a; + MatchingRule *mr = mod->sm_desc->ad_type->sat_equality; + + /* delete the entire attribute */ + if ( mod->sm_values == NULL ) { + int rc = attr_delete( &e->e_attrs, mod->sm_desc ); + + if ( rc ) { + rc = LDAP_NO_SUCH_ATTRIBUTE; + + } else { + *newlevel = 0; + rc = LDAP_SUCCESS; + } + return rc; + } + + rc = check_constraints( mod, &nl ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + *newlevel &= ~nl; + + if ( mr == NULL || !mr->smr_match ) { + /* disallow specific attributes from being deleted if + * no equality rule */ + return LDAP_INAPPROPRIATE_MATCHING; + } + + /* delete specific values - find the attribute first */ + if ( (a = attr_find( e->e_attrs, mod->sm_desc )) == NULL ) { + return( LDAP_NO_SUCH_ATTRIBUTE ); + } + + /* find each value to delete */ + for ( i = 0; !BER_BVISNULL( &mod->sm_values[ i ] ); i++ ) { + int rc; + const char *text = NULL; + + struct berval asserted; + + rc = asserted_value_validate_normalize( + mod->sm_desc, mr, SLAP_MR_EQUALITY, + &mod->sm_values[ i ], &asserted, &text, + op->o_tmpmemctx ); + + if( rc != LDAP_SUCCESS ) return rc; + + found = 0; + for ( j = 0; !BER_BVISNULL( &a->a_vals[ j ] ); j++ ) { + int match; + int rc = value_match( &match, mod->sm_desc, mr, + 0, &a->a_nvals[ j ], &asserted, &text ); + + if( rc == LDAP_SUCCESS && match != 0 ) { + continue; + } + + /* found a matching value */ + found = 1; + + /* delete it */ + if ( a->a_nvals != a->a_vals ) { + free( a->a_nvals[ j ].bv_val ); + for ( k = j + 1; !BER_BVISNULL( &a->a_nvals[ k ] ); k++ ) { + a->a_nvals[ k - 1 ] = a->a_nvals[ k ]; + } + BER_BVZERO( &a->a_nvals[ k - 1 ] ); + } + + free( a->a_vals[ j ].bv_val ); + for ( k = j + 1; !BER_BVISNULL( &a->a_vals[ k ] ); k++ ) { + a->a_vals[ k - 1 ] = a->a_vals[ k ]; + } + BER_BVZERO( &a->a_vals[ k - 1 ] ); + a->a_numvals--; + + break; + } + + free( asserted.bv_val ); + + /* looked through them all w/o finding it */ + if ( ! found ) { + return LDAP_NO_SUCH_ATTRIBUTE; + } + } + + /* if no values remain, delete the entire attribute */ + if ( BER_BVISNULL( &a->a_vals[ 0 ] ) ) { + assert( a->a_numvals == 0 ); + + /* should already be zero */ + *newlevel = 0; + + if ( attr_delete( &e->e_attrs, mod->sm_desc ) ) { + return LDAP_NO_SUCH_ATTRIBUTE; + } + } + + return LDAP_SUCCESS; +} + +static int +replace_values( Operation *op, Entry *e, Modification *mod, int *newlevel ) +{ + int rc; + + if ( mod->sm_values != NULL ) { + *newlevel = 0; + rc = check_constraints( mod, newlevel ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + } + + rc = attr_delete( &e->e_attrs, mod->sm_desc ); + + if ( rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_ATTRIBUTE ) { + return rc; + } + + if ( mod->sm_values != NULL ) { + rc = attr_merge_normalize( e, mod->sm_desc, mod->sm_values, + op->o_tmpmemctx ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + } + + return LDAP_SUCCESS; +} + diff --git a/servers/slapd/back-monitor/modify.c b/servers/slapd/back-monitor/modify.c new file mode 100644 index 0000000..6c2c9b9 --- /dev/null +++ b/servers/slapd/back-monitor/modify.c @@ -0,0 +1,90 @@ +/* modify.c - monitor backend modify routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-monitor.h" +#include "proto-back-monitor.h" + +int +monitor_back_modify( Operation *op, SlapReply *rs ) +{ + int rc = 0; + monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private; + Entry *matched; + Entry *e; + + Debug(LDAP_DEBUG_ARGS, "monitor_back_modify:\n", 0, 0, 0); + + /* acquire and lock entry */ + monitor_cache_dn2entry( op, rs, &op->o_req_ndn, &e, &matched ); + if ( e == NULL ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + if ( matched ) { + if ( !access_allowed_mask( op, matched, + slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL, NULL ) ) + { + /* do nothing */ ; + } else { + rs->sr_matched = matched->e_dn; + } + } + send_ldap_result( op, rs ); + if ( matched != NULL ) { + rs->sr_matched = NULL; + monitor_cache_release( mi, matched ); + } + return rs->sr_err; + } + + if ( !acl_check_modlist( op, e, op->orm_modlist )) { + rc = LDAP_INSUFFICIENT_ACCESS; + + } else { + assert( !SLAP_SHADOW( op->o_bd ) ); + slap_mods_opattrs( op, &op->orm_modlist, 0 ); + + rc = monitor_entry_modify( op, rs, e ); + } + + if ( rc != LDAP_SUCCESS ) { + if ( !access_allowed_mask( op, e, slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL, NULL ) ) + { + rc = LDAP_NO_SUCH_OBJECT; + } + } + + rs->sr_err = rc; + send_ldap_result( op, rs ); + + monitor_cache_release( mi, e ); + + return rs->sr_err; +} + diff --git a/servers/slapd/back-monitor/operation.c b/servers/slapd/back-monitor/operation.c new file mode 100644 index 0000000..c9ebb85 --- /dev/null +++ b/servers/slapd/back-monitor/operation.c @@ -0,0 +1,245 @@ +/* operation.c - deal with operation subsystem */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-monitor.h" +#include "lber_pvt.h" + +struct monitor_ops_t { + struct berval rdn; + struct berval nrdn; +} monitor_op[] = { + { BER_BVC( "cn=Bind" ), BER_BVNULL }, + { BER_BVC( "cn=Unbind" ), BER_BVNULL }, + { BER_BVC( "cn=Search" ), BER_BVNULL }, + { BER_BVC( "cn=Compare" ), BER_BVNULL }, + { BER_BVC( "cn=Modify" ), BER_BVNULL }, + { BER_BVC( "cn=Modrdn" ), BER_BVNULL }, + { BER_BVC( "cn=Add" ), BER_BVNULL }, + { BER_BVC( "cn=Delete" ), BER_BVNULL }, + { BER_BVC( "cn=Abandon" ), BER_BVNULL }, + { BER_BVC( "cn=Extended" ), BER_BVNULL }, + { BER_BVNULL, BER_BVNULL } +}; + +static int +monitor_subsys_ops_destroy( + BackendDB *be, + monitor_subsys_t *ms ); + +static int +monitor_subsys_ops_update( + Operation *op, + SlapReply *rs, + Entry *e ); + +int +monitor_subsys_ops_init( + BackendDB *be, + monitor_subsys_t *ms ) +{ + monitor_info_t *mi; + + Entry *e_op, **ep; + monitor_entry_t *mp; + int i; + struct berval bv_zero = BER_BVC( "0" ); + + assert( be != NULL ); + + ms->mss_destroy = monitor_subsys_ops_destroy; + ms->mss_update = monitor_subsys_ops_update; + + mi = ( monitor_info_t * )be->be_private; + + if ( monitor_cache_get( mi, + &ms->mss_ndn, &e_op ) ) + { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_ops_init: " + "unable to get entry \"%s\"\n", + ms->mss_ndn.bv_val, + 0, 0 ); + return( -1 ); + } + + attr_merge_one( e_op, mi->mi_ad_monitorOpInitiated, &bv_zero, NULL ); + attr_merge_one( e_op, mi->mi_ad_monitorOpCompleted, &bv_zero, NULL ); + + mp = ( monitor_entry_t * )e_op->e_private; + mp->mp_children = NULL; + ep = &mp->mp_children; + + for ( i = 0; i < SLAP_OP_LAST; i++ ) { + struct berval rdn; + Entry *e; + struct berval bv; + + /* + * Initiated ops + */ + e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &monitor_op[i].rdn, + mi->mi_oc_monitorOperation, NULL, NULL ); + + if ( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_ops_init: " + "unable to create entry \"%s,%s\"\n", + monitor_op[ i ].rdn.bv_val, + ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + + BER_BVSTR( &bv, "0" ); + attr_merge_one( e, mi->mi_ad_monitorOpInitiated, &bv, NULL ); + attr_merge_one( e, mi->mi_ad_monitorOpCompleted, &bv, NULL ); + + /* steal normalized RDN */ + dnRdn( &e->e_nname, &rdn ); + ber_dupbv( &monitor_op[ i ].nrdn, &rdn ); + + mp = monitor_entrypriv_create(); + if ( mp == NULL ) { + return -1; + } + e->e_private = ( void * )mp; + mp->mp_info = ms; + mp->mp_flags = ms->mss_flags \ + | MONITOR_F_SUB | MONITOR_F_PERSISTENT; + + if ( monitor_cache_add( mi, e ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_ops_init: " + "unable to add entry \"%s,%s\"\n", + monitor_op[ i ].rdn.bv_val, + ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + + *ep = e; + ep = &mp->mp_next; + } + + monitor_cache_release( mi, e_op ); + + return( 0 ); +} + +static int +monitor_subsys_ops_destroy( + BackendDB *be, + monitor_subsys_t *ms ) +{ + int i; + + for ( i = 0; i < SLAP_OP_LAST; i++ ) { + if ( !BER_BVISNULL( &monitor_op[ i ].nrdn ) ) { + ch_free( monitor_op[ i ].nrdn.bv_val ); + } + } + + return 0; +} + +static int +monitor_subsys_ops_update( + Operation *op, + SlapReply *rs, + Entry *e ) +{ + monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private; + + ldap_pvt_mp_t nInitiated = LDAP_PVT_MP_INIT, + nCompleted = LDAP_PVT_MP_INIT; + struct berval rdn; + int i; + Attribute *a; + slap_counters_t *sc; + static struct berval bv_ops = BER_BVC( "cn=operations" ); + + assert( mi != NULL ); + assert( e != NULL ); + + dnRdn( &e->e_nname, &rdn ); + + if ( dn_match( &rdn, &bv_ops ) ) { + ldap_pvt_mp_init( nInitiated ); + ldap_pvt_mp_init( nCompleted ); + + ldap_pvt_thread_mutex_lock( &slap_counters.sc_mutex ); + ldap_pvt_mp_add( nInitiated, slap_counters.sc_ops_initiated ); + ldap_pvt_mp_add( nCompleted, slap_counters.sc_ops_completed ); + for ( sc = slap_counters.sc_next; sc; sc = sc->sc_next ) { + ldap_pvt_thread_mutex_lock( &sc->sc_mutex ); + ldap_pvt_mp_add( nInitiated, sc->sc_ops_initiated ); + ldap_pvt_mp_add( nCompleted, sc->sc_ops_completed ); + ldap_pvt_thread_mutex_unlock( &sc->sc_mutex ); + } + ldap_pvt_thread_mutex_unlock( &slap_counters.sc_mutex ); + + } else { + for ( i = 0; i < SLAP_OP_LAST; i++ ) { + if ( dn_match( &rdn, &monitor_op[ i ].nrdn ) ) + { + ldap_pvt_thread_mutex_lock( &slap_counters.sc_mutex ); + ldap_pvt_mp_init_set( nInitiated, slap_counters.sc_ops_initiated_[ i ] ); + ldap_pvt_mp_init_set( nCompleted, slap_counters.sc_ops_completed_[ i ] ); + for ( sc = slap_counters.sc_next; sc; sc = sc->sc_next ) { + ldap_pvt_thread_mutex_lock( &sc->sc_mutex ); + ldap_pvt_mp_add( nInitiated, sc->sc_ops_initiated_[ i ] ); + ldap_pvt_mp_add( nCompleted, sc->sc_ops_completed_[ i ] ); + ldap_pvt_thread_mutex_unlock( &sc->sc_mutex ); + } + ldap_pvt_thread_mutex_unlock( &slap_counters.sc_mutex ); + break; + } + } + + if ( i == SLAP_OP_LAST ) { + /* not found ... */ + return( 0 ); + } + } + + a = attr_find( e->e_attrs, mi->mi_ad_monitorOpInitiated ); + assert ( a != NULL ); + + /* NOTE: no minus sign is allowed in the counters... */ + UI2BV( &a->a_vals[ 0 ], nInitiated ); + ldap_pvt_mp_clear( nInitiated ); + + a = attr_find( e->e_attrs, mi->mi_ad_monitorOpCompleted ); + assert ( a != NULL ); + + /* NOTE: no minus sign is allowed in the counters... */ + UI2BV( &a->a_vals[ 0 ], nCompleted ); + ldap_pvt_mp_clear( nCompleted ); + + /* FIXME: touch modifyTimestamp? */ + + return SLAP_CB_CONTINUE; +} + diff --git a/servers/slapd/back-monitor/operational.c b/servers/slapd/back-monitor/operational.c new file mode 100644 index 0000000..2794fcf --- /dev/null +++ b/servers/slapd/back-monitor/operational.c @@ -0,0 +1,72 @@ +/* operational.c - monitor backend operational attributes function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-monitor.h" +#include "proto-back-monitor.h" + +/* + * sets the supported operational attributes (if required) + */ + +int +monitor_back_operational( + Operation *op, + SlapReply *rs ) +{ + Attribute **ap; + + assert( rs->sr_entry != NULL ); + + for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next ) { + if ( (*ap)->a_desc == slap_schema.si_ad_hasSubordinates ) { + break; + } + } + + if ( *ap == NULL && + attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_hasSubordinates ) == NULL && + ( SLAP_OPATTRS( rs->sr_attr_flags ) || + ad_inlist( slap_schema.si_ad_hasSubordinates, rs->sr_attrs ) ) ) + { + int hs; + monitor_entry_t *mp; + + mp = ( monitor_entry_t * )rs->sr_entry->e_private; + + assert( mp != NULL ); + + hs = MONITOR_HAS_CHILDREN( mp ); + *ap = slap_operational_hasSubordinate( hs ); + assert( *ap != NULL ); + ap = &(*ap)->a_next; + } + + return LDAP_SUCCESS; +} + diff --git a/servers/slapd/back-monitor/overlay.c b/servers/slapd/back-monitor/overlay.c new file mode 100644 index 0000000..e2e8fab --- /dev/null +++ b/servers/slapd/back-monitor/overlay.c @@ -0,0 +1,141 @@ +/* overlay.c - deals with overlay subsystem */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-monitor.h" + +/* + * initializes overlay subentries + */ +int +monitor_subsys_overlay_init( + BackendDB *be, + monitor_subsys_t *ms +) +{ + monitor_info_t *mi; + Entry *e_overlay, **ep; + int i; + monitor_entry_t *mp; + slap_overinst *on; + monitor_subsys_t *ms_database; + + mi = ( monitor_info_t * )be->be_private; + + ms_database = monitor_back_get_subsys( SLAPD_MONITOR_DATABASE_NAME ); + if ( ms_database == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_backend_init: " + "unable to get " + "\"" SLAPD_MONITOR_DATABASE_NAME "\" " + "subsystem\n", + 0, 0, 0 ); + return -1; + } + + if ( monitor_cache_get( mi, &ms->mss_ndn, &e_overlay ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_overlay_init: " + "unable to get entry \"%s\"\n", + ms->mss_ndn.bv_val, 0, 0 ); + return( -1 ); + } + + mp = ( monitor_entry_t * )e_overlay->e_private; + mp->mp_children = NULL; + ep = &mp->mp_children; + + for ( on = overlay_next( NULL ), i = 0; on; on = overlay_next( on ), i++ ) { + char buf[ BACKMONITOR_BUFSIZE ]; + struct berval bv; + int j; + Entry *e; + BackendDB *be; + + bv.bv_len = snprintf( buf, sizeof( buf ), "cn=Overlay %d", i ); + bv.bv_val = buf; + e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv, + mi->mi_oc_monitoredObject, NULL, NULL ); + if ( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_overlay_init: " + "unable to create entry \"cn=Overlay %d,%s\"\n", + i, ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + ber_str2bv( on->on_bi.bi_type, 0, 0, &bv ); + attr_merge_normalize_one( e, mi->mi_ad_monitoredInfo, &bv, NULL ); + attr_merge_normalize_one( e, mi->mi_ad_monitorRuntimeConfig, + on->on_bi.bi_cf_ocs ? (struct berval *)&slap_true_bv : + (struct berval *)&slap_false_bv, NULL ); + + attr_merge_normalize_one( e_overlay, mi->mi_ad_monitoredInfo, + &bv, NULL ); + + j = -1; + LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) { + char buf[ SLAP_LDAPDN_MAXLEN ]; + struct berval dn; + + j++; + if ( !overlay_is_inst( be, on->on_bi.bi_type ) ) { + continue; + } + + snprintf( buf, sizeof( buf ), "cn=Database %d,%s", + j, ms_database->mss_dn.bv_val ); + + ber_str2bv( buf, 0, 0, &dn ); + attr_merge_normalize_one( e, slap_schema.si_ad_seeAlso, + &dn, NULL ); + } + + mp = monitor_entrypriv_create(); + if ( mp == NULL ) { + return -1; + } + e->e_private = ( void * )mp; + mp->mp_info = ms; + mp->mp_flags = ms->mss_flags + | MONITOR_F_SUB; + + if ( monitor_cache_add( mi, e ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_overlay_init: " + "unable to add entry \"cn=Overlay %d,%s\"\n", + i, ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + + *ep = e; + ep = &mp->mp_next; + } + + monitor_cache_release( mi, e_overlay ); + + return( 0 ); +} + diff --git a/servers/slapd/back-monitor/proto-back-monitor.h b/servers/slapd/back-monitor/proto-back-monitor.h new file mode 100644 index 0000000..6b6809e --- /dev/null +++ b/servers/slapd/back-monitor/proto-back-monitor.h @@ -0,0 +1,338 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#ifndef _PROTO_BACK_MONITOR +#define _PROTO_BACK_MONITOR + +#include <ldap_cdefs.h> + +LDAP_BEGIN_DECL + +/* + * backends + */ +int +monitor_subsys_backend_init LDAP_P(( + BackendDB *be, + monitor_subsys_t *ms )); + +/* + * cache + */ +extern int +monitor_cache_cmp LDAP_P(( + const void *c1, + const void *c2 )); +extern int +monitor_cache_dup LDAP_P(( + void *c1, + void *c2 )); +extern int +monitor_cache_add LDAP_P(( + monitor_info_t *mi, + Entry *e )); +extern int +monitor_cache_get LDAP_P(( + monitor_info_t *mi, + struct berval *ndn, + Entry **ep )); +extern int +monitor_cache_remove LDAP_P(( + monitor_info_t *mi, + struct berval *ndn, + Entry **ep )); +extern int +monitor_cache_dn2entry LDAP_P(( + Operation *op, + SlapReply *rs, + struct berval *ndn, + Entry **ep, + Entry **matched )); +extern int +monitor_cache_lock LDAP_P(( + Entry *e )); +extern int +monitor_cache_release LDAP_P(( + monitor_info_t *mi, + Entry *e )); + +extern int +monitor_cache_destroy LDAP_P(( + monitor_info_t *mi )); + +extern int +monitor_back_release( + Operation *op, + Entry *e, + int rw ); + +/* + * connections + */ +extern int +monitor_subsys_conn_init LDAP_P(( + BackendDB *be, + monitor_subsys_t *ms )); + +/* + * databases + */ +extern int +monitor_subsys_database_init LDAP_P(( + BackendDB *be, + monitor_subsys_t *ms )); + +/* + * entry + */ +extern int +monitor_entry_update LDAP_P(( + Operation *op, + SlapReply *rs, + Entry *e )); +extern int +monitor_entry_create LDAP_P(( + Operation *op, + SlapReply *rs, + struct berval *ndn, + Entry *e_parent, + Entry **ep )); +extern int +monitor_entry_modify LDAP_P(( + Operation *op, + SlapReply *rs, + Entry *e )); +extern int +monitor_entry_test_flags LDAP_P(( + monitor_entry_t *mp, + int cond )); +extern monitor_entry_t * +monitor_back_entrypriv_create LDAP_P(( + void )); +extern Entry * +monitor_back_entry_stub LDAP_P(( + struct berval *pdn, + struct berval *pndn, + struct berval *rdn, + ObjectClass *oc, + struct berval *create, + struct berval *modify )); + +#define monitor_entrypriv_create monitor_back_entrypriv_create +#define monitor_entry_stub monitor_back_entry_stub + +/* + * init + */ +extern int +monitor_subsys_is_opened LDAP_P(( + void )); +extern int +monitor_back_register_subsys LDAP_P(( + monitor_subsys_t *ms )); +extern int +monitor_back_register_subsys_late LDAP_P(( + monitor_subsys_t *ms )); +extern int +monitor_back_register_backend LDAP_P(( + BackendInfo *bi )); +extern int +monitor_back_register_database LDAP_P(( + BackendDB *be, + struct berval *ndn )); +extern int +monitor_back_register_overlay_info LDAP_P(( + slap_overinst *on )); +extern int +monitor_back_register_overlay LDAP_P(( + BackendDB *be, + struct slap_overinst *on, + struct berval *ndn_out )); +extern int +monitor_back_register_backend_limbo LDAP_P(( + BackendInfo *bi )); +extern int +monitor_back_register_database_limbo LDAP_P(( + BackendDB *be, + struct berval *ndn_out )); +extern int +monitor_back_register_overlay_info_limbo LDAP_P(( + slap_overinst *on )); +extern int +monitor_back_register_overlay_limbo LDAP_P(( + BackendDB *be, + struct slap_overinst *on, + struct berval *ndn_out )); +extern monitor_subsys_t * +monitor_back_get_subsys LDAP_P(( + const char *name )); +extern monitor_subsys_t * +monitor_back_get_subsys_by_dn LDAP_P(( + struct berval *ndn, + int sub )); +extern int +monitor_back_is_configured LDAP_P(( void )); +extern int +monitor_back_register_entry LDAP_P(( + Entry *e, + monitor_callback_t *cb, + monitor_subsys_t *mss, + unsigned long flags )); +extern int +monitor_back_register_entry_parent LDAP_P(( + Entry *e, + monitor_callback_t *cb, + monitor_subsys_t *mss, + unsigned long flags, + struct berval *base, + int scope, + struct berval *filter )); +extern int +monitor_search2ndn LDAP_P(( + struct berval *base, + int scope, + struct berval *filter, + struct berval *ndn )); +extern int +monitor_back_register_entry_attrs LDAP_P(( + struct berval *ndn, + Attribute *a, + monitor_callback_t *cb, + struct berval *base, + int scope, + struct berval *filter )); +extern int +monitor_back_register_entry_callback LDAP_P(( + struct berval *ndn, + monitor_callback_t *cb, + struct berval *base, + int scope, + struct berval *filter )); +extern int +monitor_back_unregister_entry LDAP_P(( + struct berval *ndn )); +extern int +monitor_back_unregister_entry_parent LDAP_P(( + struct berval *nrdn, + monitor_callback_t *target_cb, + struct berval *base, + int scope, + struct berval *filter )); +extern int +monitor_back_unregister_entry_attrs LDAP_P(( + struct berval *ndn, + Attribute *a, + monitor_callback_t *cb, + struct berval *base, + int scope, + struct berval *filter )); +extern int +monitor_back_unregister_entry_callback LDAP_P(( + struct berval *ndn, + monitor_callback_t *cb, + struct berval *base, + int scope, + struct berval *filter )); + +/* + * listener + */ +extern int +monitor_subsys_listener_init LDAP_P(( + BackendDB *be, + monitor_subsys_t *ms )); + +/* + * log + */ +extern int +monitor_subsys_log_init LDAP_P(( + BackendDB *be, + monitor_subsys_t *ms )); + +/* + * operations + */ +extern int +monitor_subsys_ops_init LDAP_P(( + BackendDB *be, + monitor_subsys_t *ms )); + +/* + * overlay + */ +extern int +monitor_subsys_overlay_init LDAP_P(( + BackendDB *be, + monitor_subsys_t *ms )); + +/* + * sent + */ +extern int +monitor_subsys_sent_init LDAP_P(( + BackendDB *be, + monitor_subsys_t *ms )); + +/* + * threads + */ +extern int +monitor_subsys_thread_init LDAP_P(( + BackendDB *be, + monitor_subsys_t *ms )); + +/* + * time + */ +extern int monitor_subsys_time_init LDAP_P(( + BackendDB *be, + monitor_subsys_t *ms )); + +/* + * waiters + */ +extern int +monitor_subsys_rww_init LDAP_P(( + BackendDB *be, + monitor_subsys_t *ms )); + +/* + * former external.h + */ + +extern BI_init monitor_back_initialize; + +extern BI_db_init monitor_back_db_init; +extern BI_db_open monitor_back_db_open; +extern BI_config monitor_back_config; +extern BI_db_destroy monitor_back_db_destroy; +extern BI_db_config monitor_back_db_config; + +extern BI_op_search monitor_back_search; +extern BI_op_compare monitor_back_compare; +extern BI_op_modify monitor_back_modify; +extern BI_op_bind monitor_back_bind; +extern BI_operational monitor_back_operational; + +LDAP_END_DECL + +#endif /* _PROTO_BACK_MONITOR */ + diff --git a/servers/slapd/back-monitor/rww.c b/servers/slapd/back-monitor/rww.c new file mode 100644 index 0000000..cfe3a54 --- /dev/null +++ b/servers/slapd/back-monitor/rww.c @@ -0,0 +1,232 @@ +/* readw.c - deal with read waiters subsystem */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "slap.h" +#include "lutil.h" +#include "back-monitor.h" + +static int +monitor_subsys_rww_destroy( + BackendDB *be, + monitor_subsys_t *ms ); + +static int +monitor_subsys_rww_update( + Operation *op, + SlapReply *rs, + Entry *e ); + +enum { + MONITOR_RWW_READ = 0, + MONITOR_RWW_WRITE, + + MONITOR_RWW_LAST +}; + +static struct monitor_rww_t { + struct berval rdn; + struct berval nrdn; +} monitor_rww[] = { + { BER_BVC("cn=Read"), BER_BVNULL }, + { BER_BVC("cn=Write"), BER_BVNULL }, + { BER_BVNULL, BER_BVNULL } +}; + +int +monitor_subsys_rww_init( + BackendDB *be, + monitor_subsys_t *ms ) +{ + monitor_info_t *mi; + + Entry **ep, *e_conn; + monitor_entry_t *mp; + int i; + + assert( be != NULL ); + + ms->mss_destroy = monitor_subsys_rww_destroy; + ms->mss_update = monitor_subsys_rww_update; + + mi = ( monitor_info_t * )be->be_private; + + if ( monitor_cache_get( mi, &ms->mss_ndn, &e_conn ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_rww_init: " + "unable to get entry \"%s\"\n", + ms->mss_ndn.bv_val, 0, 0 ); + return( -1 ); + } + + mp = ( monitor_entry_t * )e_conn->e_private; + mp->mp_children = NULL; + ep = &mp->mp_children; + + for ( i = 0; i < MONITOR_RWW_LAST; i++ ) { + struct berval nrdn, bv; + Entry *e; + + e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &monitor_rww[i].rdn, + mi->mi_oc_monitorCounterObject, NULL, NULL ); + if ( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_rww_init: " + "unable to create entry \"cn=Read,%s\"\n", + ms->mss_ndn.bv_val, 0, 0 ); + return( -1 ); + } + + /* steal normalized RDN */ + dnRdn( &e->e_nname, &nrdn ); + ber_dupbv( &monitor_rww[ i ].nrdn, &nrdn ); + + BER_BVSTR( &bv, "0" ); + attr_merge_one( e, mi->mi_ad_monitorCounter, &bv, NULL ); + + mp = monitor_entrypriv_create(); + if ( mp == NULL ) { + return -1; + } + e->e_private = ( void * )mp; + mp->mp_info = ms; + mp->mp_flags = ms->mss_flags \ + | MONITOR_F_SUB | MONITOR_F_PERSISTENT; + + if ( monitor_cache_add( mi, e ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_rww_init: " + "unable to add entry \"%s,%s\"\n", + monitor_rww[ i ].rdn.bv_val, + ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + + *ep = e; + ep = &mp->mp_next; + } + + monitor_cache_release( mi, e_conn ); + + return( 0 ); +} + +static int +monitor_subsys_rww_destroy( + BackendDB *be, + monitor_subsys_t *ms ) +{ + int i; + + for ( i = 0; i < MONITOR_RWW_LAST; i++ ) { + ber_memfree_x( monitor_rww[ i ].nrdn.bv_val, NULL ); + } + + return 0; +} + +static int +monitor_subsys_rww_update( + Operation *op, + SlapReply *rs, + Entry *e ) +{ + monitor_info_t *mi = (monitor_info_t *)op->o_bd->be_private; + Connection *c; + ber_socket_t connindex; + long nconns, nwritewaiters, nreadwaiters; + + int i; + struct berval nrdn; + + Attribute *a; + char buf[LDAP_PVT_INTTYPE_CHARS(long)]; + long num = 0; + ber_len_t len; + + assert( mi != NULL ); + assert( e != NULL ); + + dnRdn( &e->e_nname, &nrdn ); + + for ( i = 0; !BER_BVISNULL( &monitor_rww[ i ].nrdn ); i++ ) { + if ( dn_match( &nrdn, &monitor_rww[ i ].nrdn ) ) { + break; + } + } + + if ( i == MONITOR_RWW_LAST ) { + return SLAP_CB_CONTINUE; + } + + nconns = nwritewaiters = nreadwaiters = 0; + for ( c = connection_first( &connindex ); + c != NULL; + c = connection_next( c, &connindex ), nconns++ ) + { + if ( c->c_writewaiter ) { + nwritewaiters++; + } + + /* FIXME: ?!? */ + if ( c->c_currentber != NULL ) { + nreadwaiters++; + } + } + connection_done(c); + + switch ( i ) { + case MONITOR_RWW_READ: + num = nreadwaiters; + break; + + case MONITOR_RWW_WRITE: + num = nwritewaiters; + break; + + default: + assert( 0 ); + } + + snprintf( buf, sizeof( buf ), "%ld", num ); + + a = attr_find( e->e_attrs, mi->mi_ad_monitorCounter ); + assert( a != NULL ); + len = strlen( buf ); + if ( len > a->a_vals[ 0 ].bv_len ) { + a->a_vals[ 0 ].bv_val = ber_memrealloc( a->a_vals[ 0 ].bv_val, len + 1 ); + if ( BER_BVISNULL( &a->a_vals[ 0 ] ) ) { + BER_BVZERO( &a->a_vals[ 0 ] ); + return SLAP_CB_CONTINUE; + } + } + AC_MEMCPY( a->a_vals[ 0 ].bv_val, buf, len + 1 ); + a->a_vals[ 0 ].bv_len = len; + + /* FIXME: touch modifyTimestamp? */ + + return SLAP_CB_CONTINUE; +} + diff --git a/servers/slapd/back-monitor/search.c b/servers/slapd/back-monitor/search.c new file mode 100644 index 0000000..bf00965 --- /dev/null +++ b/servers/slapd/back-monitor/search.c @@ -0,0 +1,271 @@ +/* search.c - monitor backend search function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-monitor.h" +#include "proto-back-monitor.h" + +static void +monitor_find_children( + Operation *op, + SlapReply *rs, + Entry *e_parent, + Entry **nonv, + Entry **vol +) +{ + monitor_entry_t *mp; + + mp = ( monitor_entry_t * )e_parent->e_private; + *nonv = mp->mp_children; + + if ( MONITOR_HAS_VOLATILE_CH( mp ) ) { + monitor_entry_create( op, rs, NULL, e_parent, vol ); + } +} + +static int +monitor_send_children( + Operation *op, + SlapReply *rs, + Entry *e_nonvolatile, + Entry *e_ch, + int sub ) +{ + monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private; + Entry *e, + *e_tmp; + monitor_entry_t *mp; + int rc, + nonvolatile = 0; + + e = e_nonvolatile; + + /* no volatile entries? */ + if ( e_ch == NULL ) { + /* no persistent entries? return */ + if ( e == NULL ) { + return LDAP_SUCCESS; + } + + /* volatile entries */ + } else { + /* if no persistent, return only volatile */ + if ( e == NULL ) { + e = e_ch; + + /* else append persistent to volatile */ + } else { + e_tmp = e_ch; + do { + mp = ( monitor_entry_t * )e_tmp->e_private; + e_tmp = mp->mp_next; + + if ( e_tmp == NULL ) { + mp->mp_next = e; + break; + } + } while ( e_tmp ); + e = e_ch; + } + } + + /* return entries */ + for ( ; e != NULL; e = e_tmp ) { + Entry *sub_nv = NULL, *sub_ch = NULL; + + monitor_cache_lock( e ); + monitor_entry_update( op, rs, e ); + + if ( e == e_nonvolatile ) + nonvolatile = 1; + + mp = ( monitor_entry_t * )e->e_private; + e_tmp = mp->mp_next; + + if ( op->o_abandon ) { + monitor_cache_release( mi, e ); + rc = SLAPD_ABANDON; + goto freeout; + } + + if ( sub ) + monitor_find_children( op, rs, e, &sub_nv, &sub_ch ); + + rc = test_filter( op, e, op->oq_search.rs_filter ); + if ( rc == LDAP_COMPARE_TRUE ) { + rs->sr_entry = e; + rs->sr_flags = REP_ENTRY_MUSTRELEASE; + rc = send_search_entry( op, rs ); + if ( rc ) { + for ( e = sub_ch; e != NULL; e = sub_nv ) { + mp = ( monitor_entry_t * )e->e_private; + sub_nv = mp->mp_next; + monitor_cache_lock( e ); + monitor_cache_release( mi, e ); + } + goto freeout; + } + } else { + monitor_cache_release( mi, e ); + } + + if ( sub ) { + rc = monitor_send_children( op, rs, sub_nv, sub_ch, sub ); + if ( rc ) { +freeout: + if ( nonvolatile == 0 ) { + for ( ; e_tmp != NULL; ) { + mp = ( monitor_entry_t * )e_tmp->e_private; + e = e_tmp; + e_tmp = mp->mp_next; + monitor_cache_lock( e ); + monitor_cache_release( mi, e ); + + if ( e_tmp == e_nonvolatile ) { + break; + } + } + } + + return( rc ); + } + } + } + + return LDAP_SUCCESS; +} + +int +monitor_back_search( Operation *op, SlapReply *rs ) +{ + monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private; + int rc = LDAP_SUCCESS; + Entry *e = NULL, *matched = NULL; + Entry *e_nv = NULL, *e_ch = NULL; + slap_mask_t mask; + + Debug( LDAP_DEBUG_TRACE, "=> monitor_back_search\n", 0, 0, 0 ); + + + /* get entry with reader lock */ + monitor_cache_dn2entry( op, rs, &op->o_req_ndn, &e, &matched ); + if ( e == NULL ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + if ( matched ) { + if ( !access_allowed_mask( op, matched, + slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL, NULL ) ) + { + /* do nothing */ ; + } else { + rs->sr_matched = matched->e_dn; + } + } + + send_ldap_result( op, rs ); + if ( matched ) { + monitor_cache_release( mi, matched ); + rs->sr_matched = NULL; + } + + return rs->sr_err; + } + + /* NOTE: __NEW__ "search" access is required + * on searchBase object */ + if ( !access_allowed_mask( op, e, slap_schema.si_ad_entry, + NULL, ACL_SEARCH, NULL, &mask ) ) + { + monitor_cache_release( mi, e ); + + if ( !ACL_GRANT( mask, ACL_DISCLOSE ) ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } else { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + } + + send_ldap_result( op, rs ); + + return rs->sr_err; + } + + rs->sr_attrs = op->oq_search.rs_attrs; + switch ( op->oq_search.rs_scope ) { + case LDAP_SCOPE_BASE: + monitor_entry_update( op, rs, e ); + rc = test_filter( op, e, op->oq_search.rs_filter ); + if ( rc == LDAP_COMPARE_TRUE ) { + rs->sr_entry = e; + rs->sr_flags = REP_ENTRY_MUSTRELEASE; + send_search_entry( op, rs ); + rs->sr_entry = NULL; + } else { + monitor_cache_release( mi, e ); + } + rc = LDAP_SUCCESS; + break; + + case LDAP_SCOPE_ONELEVEL: + case LDAP_SCOPE_SUBORDINATE: + monitor_find_children( op, rs, e, &e_nv, &e_ch ); + monitor_cache_release( mi, e ); + rc = monitor_send_children( op, rs, e_nv, e_ch, + op->oq_search.rs_scope == LDAP_SCOPE_SUBORDINATE ); + break; + + case LDAP_SCOPE_SUBTREE: + monitor_entry_update( op, rs, e ); + monitor_find_children( op, rs, e, &e_nv, &e_ch ); + rc = test_filter( op, e, op->oq_search.rs_filter ); + if ( rc == LDAP_COMPARE_TRUE ) { + rs->sr_entry = e; + rs->sr_flags = REP_ENTRY_MUSTRELEASE; + send_search_entry( op, rs ); + rs->sr_entry = NULL; + } else { + monitor_cache_release( mi, e ); + } + + rc = monitor_send_children( op, rs, e_nv, e_ch, 1 ); + break; + + default: + rc = LDAP_UNWILLING_TO_PERFORM; + monitor_cache_release( mi, e ); + } + + rs->sr_attrs = NULL; + rs->sr_err = rc; + if ( rs->sr_err != SLAPD_ABANDON ) { + send_ldap_result( op, rs ); + } + + return rs->sr_err; +} + diff --git a/servers/slapd/back-monitor/sent.c b/servers/slapd/back-monitor/sent.c new file mode 100644 index 0000000..8d4b74c --- /dev/null +++ b/servers/slapd/back-monitor/sent.c @@ -0,0 +1,241 @@ +/* sent.c - deal with data sent subsystem */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-monitor.h" + +static int +monitor_subsys_sent_destroy( + BackendDB *be, + monitor_subsys_t *ms ); + +static int +monitor_subsys_sent_update( + Operation *op, + SlapReply *rs, + Entry *e ); + +enum { + MONITOR_SENT_BYTES = 0, + MONITOR_SENT_PDU, + MONITOR_SENT_ENTRIES, + MONITOR_SENT_REFERRALS, + + MONITOR_SENT_LAST +}; + +struct monitor_sent_t { + struct berval rdn; + struct berval nrdn; +} monitor_sent[] = { + { BER_BVC("cn=Bytes"), BER_BVNULL }, + { BER_BVC("cn=PDU"), BER_BVNULL }, + { BER_BVC("cn=Entries"), BER_BVNULL }, + { BER_BVC("cn=Referrals"), BER_BVNULL }, + { BER_BVNULL, BER_BVNULL } +}; + +int +monitor_subsys_sent_init( + BackendDB *be, + monitor_subsys_t *ms ) +{ + monitor_info_t *mi; + + Entry **ep, *e_sent; + monitor_entry_t *mp; + int i; + + assert( be != NULL ); + + ms->mss_destroy = monitor_subsys_sent_destroy; + ms->mss_update = monitor_subsys_sent_update; + + mi = ( monitor_info_t * )be->be_private; + + if ( monitor_cache_get( mi, &ms->mss_ndn, &e_sent ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_sent_init: " + "unable to get entry \"%s\"\n", + ms->mss_ndn.bv_val, 0, 0 ); + return( -1 ); + } + + mp = ( monitor_entry_t * )e_sent->e_private; + mp->mp_children = NULL; + ep = &mp->mp_children; + + for ( i = 0; i < MONITOR_SENT_LAST; i++ ) { + struct berval nrdn, bv; + Entry *e; + + e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, + &monitor_sent[i].rdn, mi->mi_oc_monitorCounterObject, + NULL, NULL ); + + if ( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_sent_init: " + "unable to create entry \"%s,%s\"\n", + monitor_sent[ i ].rdn.bv_val, + ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + + /* steal normalized RDN */ + dnRdn( &e->e_nname, &nrdn ); + ber_dupbv( &monitor_sent[ i ].nrdn, &nrdn ); + + BER_BVSTR( &bv, "0" ); + attr_merge_one( e, mi->mi_ad_monitorCounter, &bv, NULL ); + + mp = monitor_entrypriv_create(); + if ( mp == NULL ) { + return -1; + } + e->e_private = ( void * )mp; + mp->mp_info = ms; + mp->mp_flags = ms->mss_flags \ + | MONITOR_F_SUB | MONITOR_F_PERSISTENT; + + if ( monitor_cache_add( mi, e ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_sent_init: " + "unable to add entry \"%s,%s\"\n", + monitor_sent[ i ].rdn.bv_val, + ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + + *ep = e; + ep = &mp->mp_next; + } + + monitor_cache_release( mi, e_sent ); + + return( 0 ); +} + +static int +monitor_subsys_sent_destroy( + BackendDB *be, + monitor_subsys_t *ms ) +{ + int i; + + for ( i = 0; i < MONITOR_SENT_LAST; i++ ) { + if ( !BER_BVISNULL( &monitor_sent[ i ].nrdn ) ) { + ch_free( monitor_sent[ i ].nrdn.bv_val ); + } + } + + return 0; +} + +static int +monitor_subsys_sent_update( + Operation *op, + SlapReply *rs, + Entry *e ) +{ + monitor_info_t *mi = ( monitor_info_t *)op->o_bd->be_private; + + struct berval nrdn; + ldap_pvt_mp_t n; + Attribute *a; + slap_counters_t *sc; + int i; + + assert( mi != NULL ); + assert( e != NULL ); + + dnRdn( &e->e_nname, &nrdn ); + + for ( i = 0; i < MONITOR_SENT_LAST; i++ ) { + if ( dn_match( &nrdn, &monitor_sent[ i ].nrdn ) ) { + break; + } + } + + if ( i == MONITOR_SENT_LAST ) { + return SLAP_CB_CONTINUE; + } + + ldap_pvt_thread_mutex_lock(&slap_counters.sc_mutex); + switch ( i ) { + case MONITOR_SENT_ENTRIES: + ldap_pvt_mp_init_set( n, slap_counters.sc_entries ); + for ( sc = slap_counters.sc_next; sc; sc = sc->sc_next ) { + ldap_pvt_thread_mutex_lock( &sc->sc_mutex ); + ldap_pvt_mp_add( n, sc->sc_entries ); + ldap_pvt_thread_mutex_unlock( &sc->sc_mutex ); + } + break; + + case MONITOR_SENT_REFERRALS: + ldap_pvt_mp_init_set( n, slap_counters.sc_refs ); + for ( sc = slap_counters.sc_next; sc; sc = sc->sc_next ) { + ldap_pvt_thread_mutex_lock( &sc->sc_mutex ); + ldap_pvt_mp_add( n, sc->sc_refs ); + ldap_pvt_thread_mutex_unlock( &sc->sc_mutex ); + } + break; + + case MONITOR_SENT_PDU: + ldap_pvt_mp_init_set( n, slap_counters.sc_pdu ); + for ( sc = slap_counters.sc_next; sc; sc = sc->sc_next ) { + ldap_pvt_thread_mutex_lock( &sc->sc_mutex ); + ldap_pvt_mp_add( n, sc->sc_pdu ); + ldap_pvt_thread_mutex_unlock( &sc->sc_mutex ); + } + break; + + case MONITOR_SENT_BYTES: + ldap_pvt_mp_init_set( n, slap_counters.sc_bytes ); + for ( sc = slap_counters.sc_next; sc; sc = sc->sc_next ) { + ldap_pvt_thread_mutex_lock( &sc->sc_mutex ); + ldap_pvt_mp_add( n, sc->sc_bytes ); + ldap_pvt_thread_mutex_unlock( &sc->sc_mutex ); + } + break; + + default: + assert(0); + } + ldap_pvt_thread_mutex_unlock(&slap_counters.sc_mutex); + + a = attr_find( e->e_attrs, mi->mi_ad_monitorCounter ); + assert( a != NULL ); + + /* NOTE: no minus sign is allowed in the counters... */ + UI2BV( &a->a_vals[ 0 ], n ); + ldap_pvt_mp_clear( n ); + + /* FIXME: touch modifyTimestamp? */ + + return SLAP_CB_CONTINUE; +} + diff --git a/servers/slapd/back-monitor/thread.c b/servers/slapd/back-monitor/thread.c new file mode 100644 index 0000000..b3f05de --- /dev/null +++ b/servers/slapd/back-monitor/thread.c @@ -0,0 +1,358 @@ +/* thread.c - deal with thread subsystem */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-monitor.h" + +#include <ldap_rq.h> + +#ifndef NO_THREADS +typedef enum { + MT_UNKNOWN, + MT_RUNQUEUE, + MT_TASKLIST, + + MT_LAST +} monitor_thread_t; + +static struct { + struct berval rdn; + struct berval desc; + struct berval nrdn; + ldap_pvt_thread_pool_param_t param; + monitor_thread_t mt; +} mt[] = { + { BER_BVC( "cn=Max" ), + BER_BVC("Maximum number of threads as configured"), + BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_MAX, MT_UNKNOWN }, + { BER_BVC( "cn=Max Pending" ), + BER_BVC("Maximum number of pending threads"), + BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_MAX_PENDING, MT_UNKNOWN }, + { BER_BVC( "cn=Open" ), + BER_BVC("Number of open threads"), + BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_OPEN, MT_UNKNOWN }, + { BER_BVC( "cn=Starting" ), + BER_BVC("Number of threads being started"), + BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_STARTING, MT_UNKNOWN }, + { BER_BVC( "cn=Active" ), + BER_BVC("Number of active threads"), + BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_ACTIVE, MT_UNKNOWN }, + { BER_BVC( "cn=Pending" ), + BER_BVC("Number of pending threads"), + BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_PENDING, MT_UNKNOWN }, + { BER_BVC( "cn=Backload" ), + BER_BVC("Number of active plus pending threads"), + BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_BACKLOAD, MT_UNKNOWN }, +#if 0 /* not meaningful right now */ + { BER_BVC( "cn=Active Max" ), + BER_BVNULL, + BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_ACTIVE_MAX, MT_UNKNOWN }, + { BER_BVC( "cn=Pending Max" ), + BER_BVNULL, + BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_PENDING_MAX, MT_UNKNOWN }, + { BER_BVC( "cn=Backload Max" ), + BER_BVNULL, + BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_BACKLOAD_MAX,MT_UNKNOWN }, +#endif + { BER_BVC( "cn=State" ), + BER_BVC("Thread pool state"), + BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_STATE, MT_UNKNOWN }, + + { BER_BVC( "cn=Runqueue" ), + BER_BVC("Queue of running threads - besides those handling operations"), + BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_UNKNOWN, MT_RUNQUEUE }, + { BER_BVC( "cn=Tasklist" ), + BER_BVC("List of running plus standby threads - besides those handling operations"), + BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_UNKNOWN, MT_TASKLIST }, + + { BER_BVNULL } +}; + +static int +monitor_subsys_thread_update( + Operation *op, + SlapReply *rs, + Entry *e ); +#endif /* ! NO_THREADS */ + +/* + * initializes log subentry + */ +int +monitor_subsys_thread_init( + BackendDB *be, + monitor_subsys_t *ms ) +{ +#ifndef NO_THREADS + monitor_info_t *mi; + monitor_entry_t *mp; + Entry *e, **ep, *e_thread; + int i; + + ms->mss_update = monitor_subsys_thread_update; + + mi = ( monitor_info_t * )be->be_private; + + if ( monitor_cache_get( mi, &ms->mss_ndn, &e_thread ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_thread_init: unable to get entry \"%s\"\n", + ms->mss_dn.bv_val, + 0, 0 ); + return( -1 ); + } + + mp = ( monitor_entry_t * )e_thread->e_private; + mp->mp_children = NULL; + ep = &mp->mp_children; + + for ( i = 0; !BER_BVISNULL( &mt[ i ].rdn ); i++ ) { + static char buf[ BACKMONITOR_BUFSIZE ]; + int count = -1; + char *state = NULL; + struct berval bv = BER_BVNULL; + + /* + * Max + */ + e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, + &mt[ i ].rdn, + mi->mi_oc_monitoredObject, NULL, NULL ); + if ( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_thread_init: " + "unable to create entry \"%s,%s\"\n", + mt[ i ].rdn.bv_val, + ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + + /* NOTE: reference to the normalized DN of the entry, + * under the assumption it's not modified */ + dnRdn( &e->e_nname, &mt[ i ].nrdn ); + + switch ( mt[ i ].param ) { + case LDAP_PVT_THREAD_POOL_PARAM_UNKNOWN: + break; + + case LDAP_PVT_THREAD_POOL_PARAM_STATE: + if ( ldap_pvt_thread_pool_query( &connection_pool, + mt[ i ].param, (void *)&state ) == 0 ) + { + ber_str2bv( state, 0, 0, &bv ); + + } else { + BER_BVSTR( &bv, "unknown" ); + } + break; + + default: + /* NOTE: in case of error, it'll be set to -1 */ + (void)ldap_pvt_thread_pool_query( &connection_pool, + mt[ i ].param, (void *)&count ); + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%d", count ); + break; + } + + if ( !BER_BVISNULL( &bv ) ) { + attr_merge_normalize_one( e, mi->mi_ad_monitoredInfo, &bv, NULL ); + } + + if ( !BER_BVISNULL( &mt[ i ].desc ) ) { + attr_merge_normalize_one( e, + slap_schema.si_ad_description, + &mt[ i ].desc, NULL ); + } + + mp = monitor_entrypriv_create(); + if ( mp == NULL ) { + return -1; + } + e->e_private = ( void * )mp; + mp->mp_info = ms; + mp->mp_flags = ms->mss_flags \ + | MONITOR_F_SUB | MONITOR_F_PERSISTENT; + + if ( monitor_cache_add( mi, e ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_thread_init: " + "unable to add entry \"%s,%s\"\n", + mt[ i ].rdn.bv_val, + ms->mss_dn.bv_val, 0 ); + return( -1 ); + } + + *ep = e; + ep = &mp->mp_next; + } + + monitor_cache_release( mi, e_thread ); + +#endif /* ! NO_THREADS */ + return( 0 ); +} + +#ifndef NO_THREADS +static int +monitor_subsys_thread_update( + Operation *op, + SlapReply *rs, + Entry *e ) +{ + monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private; + Attribute *a; + BerVarray vals = NULL; + char buf[ BACKMONITOR_BUFSIZE ]; + struct berval rdn, bv; + int which, i; + struct re_s *re; + int count = -1; + char *state = NULL; + + assert( mi != NULL ); + + dnRdn( &e->e_nname, &rdn ); + + for ( i = 0; !BER_BVISNULL( &mt[ i ].nrdn ); i++ ) { + if ( dn_match( &mt[ i ].nrdn, &rdn ) ) { + break; + } + } + + which = i; + if ( BER_BVISNULL( &mt[ which ].nrdn ) ) { + return SLAP_CB_CONTINUE; + } + + a = attr_find( e->e_attrs, mi->mi_ad_monitoredInfo ); + + switch ( mt[ which ].param ) { + case LDAP_PVT_THREAD_POOL_PARAM_UNKNOWN: + switch ( mt[ which ].mt ) { + case MT_RUNQUEUE: + if ( a != NULL ) { + if ( a->a_nvals != a->a_vals ) { + ber_bvarray_free( a->a_nvals ); + } + ber_bvarray_free( a->a_vals ); + a->a_vals = NULL; + a->a_nvals = NULL; + a->a_numvals = 0; + } + + i = 0; + bv.bv_val = buf; + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + LDAP_STAILQ_FOREACH( re, &slapd_rq.run_list, rnext ) { + bv.bv_len = snprintf( buf, sizeof( buf ), "{%d}%s(%s)", + i, re->tname, re->tspec ); + if ( bv.bv_len < sizeof( buf ) ) { + value_add_one( &vals, &bv ); + } + i++; + } + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + if ( vals ) { + attr_merge_normalize( e, mi->mi_ad_monitoredInfo, vals, NULL ); + ber_bvarray_free( vals ); + + } else { + attr_delete( &e->e_attrs, mi->mi_ad_monitoredInfo ); + } + break; + + case MT_TASKLIST: + if ( a != NULL ) { + if ( a->a_nvals != a->a_vals ) { + ber_bvarray_free( a->a_nvals ); + } + ber_bvarray_free( a->a_vals ); + a->a_vals = NULL; + a->a_nvals = NULL; + a->a_numvals = 0; + } + + i = 0; + bv.bv_val = buf; + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + LDAP_STAILQ_FOREACH( re, &slapd_rq.task_list, tnext ) { + bv.bv_len = snprintf( buf, sizeof( buf ), "{%d}%s(%s)", + i, re->tname, re->tspec ); + if ( bv.bv_len < sizeof( buf ) ) { + value_add_one( &vals, &bv ); + } + i++; + } + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + if ( vals ) { + attr_merge_normalize( e, mi->mi_ad_monitoredInfo, vals, NULL ); + ber_bvarray_free( vals ); + + } else { + attr_delete( &e->e_attrs, mi->mi_ad_monitoredInfo ); + } + break; + + default: + assert( 0 ); + } + break; + + case LDAP_PVT_THREAD_POOL_PARAM_STATE: + if ( a == NULL ) { + return rs->sr_err = LDAP_OTHER; + } + if ( ldap_pvt_thread_pool_query( &connection_pool, + mt[ i ].param, (void *)&state ) == 0 ) + { + ber_str2bv( state, 0, 0, &bv ); + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + } + break; + + default: + if ( a == NULL ) { + return rs->sr_err = LDAP_OTHER; + } + if ( ldap_pvt_thread_pool_query( &connection_pool, + mt[ i ].param, (void *)&count ) == 0 ) + { + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%d", count ); + if ( bv.bv_len < sizeof( buf ) ) { + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + } + } + break; + } + + /* FIXME: touch modifyTimestamp? */ + + return SLAP_CB_CONTINUE; +} +#endif /* ! NO_THREADS */ diff --git a/servers/slapd/back-monitor/time.c b/servers/slapd/back-monitor/time.c new file mode 100644 index 0000000..5e1fc45 --- /dev/null +++ b/servers/slapd/back-monitor/time.c @@ -0,0 +1,247 @@ +/* time.c - deal with time subsystem */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/time.h> + + +#include "slap.h" +#include <lutil.h> +#include "proto-slap.h" +#include "back-monitor.h" + +static int +monitor_subsys_time_update( + Operation *op, + SlapReply *rs, + Entry *e ); + +int +monitor_subsys_time_init( + BackendDB *be, + monitor_subsys_t *ms ) +{ + monitor_info_t *mi; + + Entry *e, **ep, *e_time; + monitor_entry_t *mp; + struct berval bv, value; + + assert( be != NULL ); + + ms->mss_update = monitor_subsys_time_update; + + mi = ( monitor_info_t * )be->be_private; + + if ( monitor_cache_get( mi, + &ms->mss_ndn, &e_time ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_time_init: " + "unable to get entry \"%s\"\n", + ms->mss_ndn.bv_val, 0, 0 ); + return( -1 ); + } + + mp = ( monitor_entry_t * )e_time->e_private; + mp->mp_children = NULL; + ep = &mp->mp_children; + + BER_BVSTR( &bv, "cn=Start" ); + e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv, + mi->mi_oc_monitoredObject, NULL, NULL ); + if ( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_time_init: " + "unable to create entry \"%s,%s\"\n", + bv.bv_val, ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + attr_merge_normalize_one( e, mi->mi_ad_monitorTimestamp, + &mi->mi_startTime, NULL ); + + mp = monitor_entrypriv_create(); + if ( mp == NULL ) { + return -1; + } + e->e_private = ( void * )mp; + mp->mp_info = ms; + mp->mp_flags = ms->mss_flags \ + | MONITOR_F_SUB | MONITOR_F_PERSISTENT; + + if ( monitor_cache_add( mi, e ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_time_init: " + "unable to add entry \"%s,%s\"\n", + bv.bv_val, ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + + *ep = e; + ep = &mp->mp_next; + + /* + * Current + */ + BER_BVSTR( &bv, "cn=Current" ); + e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv, + mi->mi_oc_monitoredObject, NULL, NULL ); + if ( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_time_init: " + "unable to create entry \"%s,%s\"\n", + bv.bv_val, ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + attr_merge_normalize_one( e, mi->mi_ad_monitorTimestamp, + &mi->mi_startTime, NULL ); + + mp = monitor_entrypriv_create(); + if ( mp == NULL ) { + return -1; + } + e->e_private = ( void * )mp; + mp->mp_info = ms; + mp->mp_flags = ms->mss_flags \ + | MONITOR_F_SUB | MONITOR_F_PERSISTENT; + + if ( monitor_cache_add( mi, e ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_time_init: " + "unable to add entry \"%s,%s\"\n", + bv.bv_val, ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + + *ep = e; + ep = &mp->mp_next; + + /* + * Uptime + */ + BER_BVSTR( &bv, "cn=Uptime" ); + e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv, + mi->mi_oc_monitoredObject, NULL, NULL ); + if ( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_time_init: " + "unable to create entry \"%s,%s\"\n", + bv.bv_val, ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + BER_BVSTR( &value, "0" ); + attr_merge_normalize_one( e, mi->mi_ad_monitoredInfo, + &value, NULL ); + + mp = monitor_entrypriv_create(); + if ( mp == NULL ) { + return -1; + } + e->e_private = ( void * )mp; + mp->mp_info = ms; + mp->mp_flags = ms->mss_flags \ + | MONITOR_F_SUB | MONITOR_F_PERSISTENT; + + if ( monitor_cache_add( mi, e ) ) { + Debug( LDAP_DEBUG_ANY, + "monitor_subsys_time_init: " + "unable to add entry \"%s,%s\"\n", + bv.bv_val, ms->mss_ndn.bv_val, 0 ); + return( -1 ); + } + + *ep = e; + ep = &mp->mp_next; + + monitor_cache_release( mi, e_time ); + + return( 0 ); +} + +static int +monitor_subsys_time_update( + Operation *op, + SlapReply *rs, + Entry *e ) +{ + monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private; + static struct berval bv_current = BER_BVC( "cn=current" ), + bv_uptime = BER_BVC( "cn=uptime" ); + struct berval rdn; + + assert( mi != NULL ); + assert( e != NULL ); + + dnRdn( &e->e_nname, &rdn ); + + if ( dn_match( &rdn, &bv_current ) ) { + struct tm tm; + char tmbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + Attribute *a; + ber_len_t len; + time_t currtime; + + currtime = slap_get_time(); + + ldap_pvt_gmtime( &currtime, &tm ); + lutil_gentime( tmbuf, sizeof( tmbuf ), &tm ); + + len = strlen( tmbuf ); + + a = attr_find( e->e_attrs, mi->mi_ad_monitorTimestamp ); + if ( a == NULL ) { + return rs->sr_err = LDAP_OTHER; + } + + assert( len == a->a_vals[ 0 ].bv_len ); + AC_MEMCPY( a->a_vals[ 0 ].bv_val, tmbuf, len ); + + /* FIXME: touch modifyTimestamp? */ + + } else if ( dn_match( &rdn, &bv_uptime ) ) { + Attribute *a; + double diff; + char buf[ BACKMONITOR_BUFSIZE ]; + struct berval bv; + + a = attr_find( e->e_attrs, mi->mi_ad_monitoredInfo ); + if ( a == NULL ) { + return rs->sr_err = LDAP_OTHER; + } + + diff = difftime( slap_get_time(), starttime ); + bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", + (unsigned long) diff ); + bv.bv_val = buf; + + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + if ( a->a_nvals != a->a_vals ) { + ber_bvreplace( &a->a_nvals[ 0 ], &bv ); + } + + /* FIXME: touch modifyTimestamp? */ + } + + return SLAP_CB_CONTINUE; +} + diff --git a/servers/slapd/back-ndb/Makefile.in b/servers/slapd/back-ndb/Makefile.in new file mode 100644 index 0000000..c318d0d --- /dev/null +++ b/servers/slapd/back-ndb/Makefile.in @@ -0,0 +1,59 @@ +# Makefile.in for back-ndb +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 2008-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +## +## ACKNOWLEDGEMENTS: +## This work was initially developed by Howard Chu for inclusion +## in OpenLDAP Software. This work was sponsored by MySQL. + +SRCS = init.cpp tools.cpp config.cpp ndbio.cpp \ + add.cpp bind.cpp compare.cpp delete.cpp modify.cpp modrdn.cpp search.cpp + +OBJS = init.lo tools.lo config.lo ndbio.lo \ + add.lo bind.lo compare.lo delete.lo modify.lo modrdn.lo search.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-ndb" +BUILD_MOD = @BUILD_NDB@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_NDB@_DEFS) +MOD_LIBS = $(SLAPD_NDB_LIBS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_R_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBBASE = back_ndb + +XINCPATH = -I.. -I$(srcdir)/.. @SLAPD_NDB_INCS@ +XDEFS = $(MODULES_CPPFLAGS) + +AC_CXX = g++ +CXX = $(AC_CXX) +LTCXX_MOD = $(LIBTOOL) $(LTONLY_MOD) --mode=compile \ + $(CXX) $(LT_CFLAGS) $(LT_CPPFLAGS) $(MOD_DEFS) -c + +all-local-lib: ../.backend + +.SUFFIXES: .c .o .lo .cpp + +.cpp.lo: + $(LTCXX_MOD) $< + +../.backend: lib$(LIBBASE).a + @touch $@ + diff --git a/servers/slapd/back-ndb/TODO b/servers/slapd/back-ndb/TODO new file mode 100644 index 0000000..0393954 --- /dev/null +++ b/servers/slapd/back-ndb/TODO @@ -0,0 +1,6 @@ +LDAP features not currently supported: + +tagged attributes +aliases +substring indexing +subtree rename diff --git a/servers/slapd/back-ndb/add.cpp b/servers/slapd/back-ndb/add.cpp new file mode 100644 index 0000000..54cc8ad --- /dev/null +++ b/servers/slapd/back-ndb/add.cpp @@ -0,0 +1,347 @@ +/* add.cpp - ldap NDB back-end add routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-ndb.h" + +extern "C" int +ndb_back_add(Operation *op, SlapReply *rs ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + Entry p = {0}; + Attribute poc; + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof textbuf; + AttributeDescription *children = slap_schema.si_ad_children; + AttributeDescription *entry = slap_schema.si_ad_entry; + NdbArgs NA; + NdbRdns rdns; + struct berval matched; + struct berval pdn, pndn; + + int num_retries = 0; + int success; + + LDAPControl **postread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + + Debug(LDAP_DEBUG_ARGS, "==> " LDAP_XSTRING(ndb_back_add) ": %s\n", + op->oq_add.rs_e->e_name.bv_val, 0, 0); + + ctrls[num_ctrls] = 0; + NA.txn = NULL; + + /* check entry's schema */ + rs->sr_err = entry_schema_check( op, op->oq_add.rs_e, NULL, + get_relax(op), 1, NULL, &rs->sr_text, textbuf, textlen ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_add) ": entry failed schema check: " + "%s (%d)\n", rs->sr_text, rs->sr_err, 0 ); + goto return_results; + } + + /* add opattrs to shadow as well, only missing attrs will actually + * be added; helps compatibility with older OL versions */ + rs->sr_err = slap_add_opattrs( op, &rs->sr_text, textbuf, textlen, 1 ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_add) ": entry failed op attrs add: " + "%s (%d)\n", rs->sr_text, rs->sr_err, 0 ); + goto return_results; + } + + /* Get our NDB handle */ + rs->sr_err = ndb_thread_handle( op, &NA.ndb ); + + /* + * Get the parent dn and see if the corresponding entry exists. + */ + if ( be_issuffix( op->o_bd, &op->oq_add.rs_e->e_nname ) ) { + pdn = slap_empty_bv; + pndn = slap_empty_bv; + } else { + dnParent( &op->ora_e->e_name, &pdn ); + dnParent( &op->ora_e->e_nname, &pndn ); + } + p.e_name = op->ora_e->e_name; + p.e_nname = op->ora_e->e_nname; + + op->ora_e->e_id = NOID; + rdns.nr_num = 0; + NA.rdns = &rdns; + + if( 0 ) { +retry: /* transaction retry */ + NA.txn->close(); + NA.txn = NULL; + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto return_results; + } + ndb_trans_backoff( ++num_retries ); + } + + NA.txn = NA.ndb->startTransaction(); + rs->sr_text = NULL; + if( !NA.txn ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_add) ": startTransaction failed: %s (%d)\n", + NA.ndb->getNdbError().message, NA.ndb->getNdbError().code, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + /* get entry or parent */ + NA.e = &p; + NA.ocs = NULL; + rs->sr_err = ndb_entry_get_info( op, &NA, 0, &matched ); + switch( rs->sr_err ) { + case 0: + rs->sr_err = LDAP_ALREADY_EXISTS; + goto return_results; + case LDAP_NO_SUCH_OBJECT: + break; +#if 0 + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; +#endif + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + if ( NA.ocs ) { + int i; + for ( i=0; !BER_BVISNULL( &NA.ocs[i] ); i++ ); + poc.a_numvals = i; + poc.a_desc = slap_schema.si_ad_objectClass; + poc.a_vals = NA.ocs; + poc.a_nvals = poc.a_vals; + poc.a_next = NULL; + p.e_attrs = &poc; + } + + if ( ber_bvstrcasecmp( &pndn, &matched ) ) { + rs->sr_matched = matched.bv_val; + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_add) ": parent " + "does not exist\n", 0, 0, 0 ); + + rs->sr_text = "parent does not exist"; + rs->sr_err = LDAP_NO_SUCH_OBJECT; + if ( p.e_attrs && is_entry_referral( &p )) { +is_ref: p.e_attrs = NULL; + ndb_entry_get_data( op, &NA, 0 ); + rs->sr_ref = get_entry_referrals( op, &p ); + rs->sr_err = LDAP_REFERRAL; + rs->sr_flags = REP_REF_MUSTBEFREED; + attrs_free( p.e_attrs ); + p.e_attrs = NULL; + } + goto return_results; + } + + p.e_name = pdn; + p.e_nname = pndn; + rs->sr_err = access_allowed( op, &p, + children, NULL, ACL_WADD, NULL ); + + if ( ! rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_add) ": no write access to parent\n", + 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to parent"; + goto return_results; + } + + if ( NA.ocs ) { + if ( is_entry_subentry( &p )) { + /* parent is a subentry, don't allow add */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_add) ": parent is subentry\n", + 0, 0, 0 ); + rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; + rs->sr_text = "parent is a subentry"; + goto return_results; + } + + if ( is_entry_alias( &p ) ) { + /* parent is an alias, don't allow add */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_add) ": parent is alias\n", + 0, 0, 0 ); + rs->sr_err = LDAP_ALIAS_PROBLEM; + rs->sr_text = "parent is an alias"; + goto return_results; + } + + if ( is_entry_referral( &p ) ) { + /* parent is a referral, don't allow add */ + rs->sr_matched = p.e_name.bv_val; + goto is_ref; + } + } + + rs->sr_err = access_allowed( op, op->ora_e, + entry, NULL, ACL_WADD, NULL ); + + if ( ! rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_add) ": no write access to entry\n", + 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to entry"; + goto return_results;; + } + + /* + * Check ACL for attribute write access + */ + if (!acl_check_modlist(op, op->ora_e, op->ora_modlist)) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": no write access to attribute\n", + 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to attribute"; + goto return_results;; + } + + + /* acquire entry ID */ + if ( op->ora_e->e_id == NOID ) { + rs->sr_err = ndb_next_id( op->o_bd, NA.ndb, &op->ora_e->e_id ); + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_add) ": next_id failed (%d)\n", + rs->sr_err, 0, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + } + + if ( matched.bv_val ) + rdns.nr_num++; + NA.e = op->ora_e; + /* dn2id index */ + rs->sr_err = ndb_entry_put_info( op->o_bd, &NA, 0 ); + if ( rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_add) ": ndb_entry_put_info failed (%d)\n", + rs->sr_err, 0, 0 ); + rs->sr_text = "internal error"; + goto return_results; + } + + /* id2entry index */ + rs->sr_err = ndb_entry_put_data( op->o_bd, &NA ); + if ( rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_add) ": ndb_entry_put_data failed (%d) %s(%d)\n", + rs->sr_err, NA.txn->getNdbError().message, NA.txn->getNdbError().code ); + rs->sr_text = "internal error"; + goto return_results; + } + + /* post-read */ + if( op->o_postread ) { + if( postread_ctrl == NULL ) { + postread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if ( slap_read_controls( op, rs, op->oq_add.rs_e, + &slap_post_read_bv, postread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_add) ": post-read " + "failed!\n", 0, 0, 0 ); + if ( op->o_postread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + if ( op->o_noop ) { + if (( rs->sr_err=NA.txn->execute( NdbTransaction::Rollback, + NdbOperation::AbortOnError, 1 )) != 0 ) { + rs->sr_text = "txn (no-op) failed"; + } else { + rs->sr_err = LDAP_X_NO_OPERATION; + } + + } else { + if(( rs->sr_err=NA.txn->execute( NdbTransaction::Commit, + NdbOperation::AbortOnError, 1 )) != 0 ) { + rs->sr_text = "txn_commit failed"; + } else { + rs->sr_err = LDAP_SUCCESS; + } + } + + if ( rs->sr_err != LDAP_SUCCESS && rs->sr_err != LDAP_X_NO_OPERATION ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_add) ": %s : %s (%d)\n", + rs->sr_text, NA.txn->getNdbError().message, NA.txn->getNdbError().code ); + rs->sr_err = LDAP_OTHER; + goto return_results; + } + NA.txn->close(); + NA.txn = NULL; + + Debug(LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_add) ": added%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + op->oq_add.rs_e->e_id, op->oq_add.rs_e->e_dn ); + + rs->sr_text = NULL; + if( num_ctrls ) rs->sr_ctrls = ctrls; + +return_results: + success = rs->sr_err; + send_ldap_result( op, rs ); + slap_graduate_commit_csn( op ); + + if( NA.txn != NULL ) { + NA.txn->execute( Rollback ); + NA.txn->close(); + } + + if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) { + slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *postread_ctrl, op->o_tmpmemctx ); + } + + return rs->sr_err; +} diff --git a/servers/slapd/back-ndb/attrsets.conf b/servers/slapd/back-ndb/attrsets.conf new file mode 100644 index 0000000..f135e0a --- /dev/null +++ b/servers/slapd/back-ndb/attrsets.conf @@ -0,0 +1,36 @@ +# Definition of useful attribute sets +# from X.521 section 5 +# +# TelecommunicationAttributeSet ATTRIBUTE ::= { +# facsimileTelephoneNumber | +# internationalISDNNumber | +# telephoneNumber | +# teletexTerminalIdentifier | +# telexNumber | +# preferredDeliveryMethod | +# destinationIndicator | +# registeredAddress | +# x121Address } +# +# PostalAttributeSet ATTRIBUTE ::= { +# physicalDeliveryOfficeName | +# postalAddress | +# postalCode | +# postOfficeBox | +# streetAddress } +# +# LocaleAttributeSet ATTRIBUTE ::= { +# localityName | +# stateOrProvinceName | +# streetAddress } +# +# OrganizationalAttributeSet ATTRIBUTE ::= { +# description | +# LocaleAttributeSet | +# PostalAttributeSet | +# TelecommunicationAttributeSet | +# businessCategory | +# seeAlso | +# searchGuide | +# userPassword } + diff --git a/servers/slapd/back-ndb/back-ndb.h b/servers/slapd/back-ndb/back-ndb.h new file mode 100644 index 0000000..045572c --- /dev/null +++ b/servers/slapd/back-ndb/back-ndb.h @@ -0,0 +1,168 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#ifndef SLAPD_NDB_H +#define SLAPD_NDB_H + +#include "slap.h" + +#include <mysql.h> +#include <NdbApi.hpp> + +LDAP_BEGIN_DECL + +/* The general design is to use one relational table per objectclass. This is + * complicated by objectclass inheritance and auxiliary classes though. + * + * Attributes must only occur in a single table. For objectclasses that inherit + * from other classes, attributes defined in the superior class are only stored + * in the superior class' table. When multiple unrelated classes define the same + * attributes, an attributeSet should be defined instead, containing all of the + * common attributes. + * + * The no_set table lists which other attributeSets apply to the current + * objectClass. The no_attrs table lists all of the non-inherited attributes of + * the class, including those residing in an attributeSet. + * + * Usually the table is named identically to the objectClass, but it can also + * be explicitly named something else if needed. + */ +#define NDB_MAX_OCSETS 8 + +struct ndb_attrinfo; + +typedef struct ndb_ocinfo { + struct berval no_name; /* objectclass cname */ + struct berval no_table; + ObjectClass *no_oc; + struct ndb_ocinfo *no_sets[NDB_MAX_OCSETS]; + struct ndb_attrinfo **no_attrs; + int no_flag; + int no_nsets; + int no_nattrs; +} NdbOcInfo; + +#define NDB_INFO_ATLEN 0x01 +#define NDB_INFO_ATSET 0x02 +#define NDB_INFO_INDEX 0x04 +#define NDB_INFO_ATBLOB 0x08 + +typedef struct ndb_attrinfo { + struct berval na_name; /* attribute cname */ + AttributeDescription *na_desc; + AttributeType *na_attr; + NdbOcInfo *na_oi; + int na_flag; + int na_len; + int na_column; + int na_ixcol; +} NdbAttrInfo; + +typedef struct ListNode { + struct ListNode *ln_next; + void *ln_data; +} ListNode; + +#define NDB_IS_OPEN(ni) (ni->ni_cluster != NULL) + +struct ndb_info { + /* NDB connection */ + char *ni_connectstr; + char *ni_dbname; + Ndb_cluster_connection **ni_cluster; + + /* MySQL connection parameters */ + MYSQL ni_sql; + char *ni_hostname; + char *ni_username; + char *ni_password; + char *ni_socket; + unsigned long ni_clflag; + unsigned int ni_port; + + /* Search filter processing */ + int ni_search_stack_depth; + void *ni_search_stack; + +#define DEFAULT_SEARCH_STACK_DEPTH 16 +#define MINIMUM_SEARCH_STACK_DEPTH 8 + + /* Schema config */ + NdbOcInfo *ni_opattrs; + ListNode *ni_attridxs; + ListNode *ni_attrlens; + ListNode *ni_attrsets; + ListNode *ni_attrblobs; + ldap_pvt_thread_rdwr_t ni_ai_rwlock; + Avlnode *ni_ai_tree; + ldap_pvt_thread_rdwr_t ni_oc_rwlock; + Avlnode *ni_oc_tree; + int ni_nconns; /* number of connections to open */ + int ni_nextconn; /* next conn to use */ + ldap_pvt_thread_mutex_t ni_conn_mutex; +}; + +#define NDB_MAX_RDNS 16 +#define NDB_RDN_LEN 128 +#define NDB_MAX_OCS 64 + +#define DN2ID_TABLE "OL_dn2id" +#define EID_COLUMN 0U +#define VID_COLUMN 1U +#define OCS_COLUMN 1U +#define RDN_COLUMN 2U +#define IDX_COLUMN (2U+NDB_MAX_RDNS) + +#define NEXTID_TABLE "OL_nextid" + +#define NDB_OC_BUFLEN 1026 /* 1024 data plus 2 len bytes */ + +#define INDEX_NAME "OL_index" + +typedef struct NdbRdns { + short nr_num; + char nr_buf[NDB_MAX_RDNS][NDB_RDN_LEN+1]; +} NdbRdns; + +typedef struct NdbOcs { + int no_ninfo; + int no_ntext; + int no_nitext; /* number of implicit classes */ + NdbOcInfo *no_info[NDB_MAX_OCS]; + struct berval no_text[NDB_MAX_OCS]; + struct berval no_itext[NDB_MAX_OCS]; /* implicit classes */ +} NdbOcs; + +typedef struct NdbArgs { + Ndb *ndb; + NdbTransaction *txn; + Entry *e; + NdbRdns *rdns; + struct berval *ocs; + int erdns; +} NdbArgs; + +#define NDB_NO_SUCH_OBJECT 626 +#define NDB_ALREADY_EXISTS 630 + +LDAP_END_DECL + +#include "proto-ndb.h" + +#endif diff --git a/servers/slapd/back-ndb/bind.cpp b/servers/slapd/back-ndb/bind.cpp new file mode 100644 index 0000000..871001a --- /dev/null +++ b/servers/slapd/back-ndb/bind.cpp @@ -0,0 +1,165 @@ +/* bind.cpp - ndb backend bind routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/unistd.h> + +#include "back-ndb.h" + +extern "C" int +ndb_back_bind( Operation *op, SlapReply *rs ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + Entry e = {0}; + Attribute *a; + + AttributeDescription *password = slap_schema.si_ad_userPassword; + + NdbArgs NA; + + Debug( LDAP_DEBUG_ARGS, + "==> " LDAP_XSTRING(ndb_back_bind) ": dn: %s\n", + op->o_req_dn.bv_val, 0, 0); + + /* allow noauth binds */ + switch ( be_rootdn_bind( op, NULL ) ) { + case LDAP_SUCCESS: + /* frontend will send result */ + return rs->sr_err = LDAP_SUCCESS; + + default: + /* give the database a chance */ + break; + } + + /* Get our NDB handle */ + rs->sr_err = ndb_thread_handle( op, &NA.ndb ); + + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + NA.e = &e; + +dn2entry_retry: + NA.txn = NA.ndb->startTransaction(); + rs->sr_text = NULL; + if( !NA.txn ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_bind) ": startTransaction failed: %s (%d)\n", + NA.ndb->getNdbError().message, NA.ndb->getNdbError().code, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto done; + } + + /* get entry */ + { + NdbRdns rdns; + rdns.nr_num = 0; + NA.rdns = &rdns; + NA.ocs = NULL; + rs->sr_err = ndb_entry_get_info( op, &NA, 0, NULL ); + } + switch(rs->sr_err) { + case 0: + break; + case LDAP_NO_SUCH_OBJECT: + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + case LDAP_BUSY: + rs->sr_text = "ldap_server_busy"; + goto done; +#if 0 + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto dn2entry_retry; +#endif + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto done; + } + + rs->sr_err = ndb_entry_get_data( op, &NA, 0 ); + ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx ); + ber_dupbv( &op->oq_bind.rb_edn, &e.e_name ); + + /* check for deleted */ + if ( is_entry_subentry( &e ) ) { + /* entry is an subentry, don't allow bind */ + Debug( LDAP_DEBUG_TRACE, "entry is subentry\n", 0, + 0, 0 ); + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + if ( is_entry_alias( &e ) ) { + /* entry is an alias, don't allow bind */ + Debug( LDAP_DEBUG_TRACE, "entry is alias\n", 0, 0, 0 ); + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + if ( is_entry_referral( &e ) ) { + Debug( LDAP_DEBUG_TRACE, "entry is referral\n", 0, + 0, 0 ); + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + switch ( op->oq_bind.rb_method ) { + case LDAP_AUTH_SIMPLE: + a = attr_find( e.e_attrs, password ); + if ( a == NULL ) { + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + if ( slap_passwd_check( op, &e, a, &op->oq_bind.rb_cred, + &rs->sr_text ) != 0 ) + { + /* failure; stop front end from sending result */ + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + rs->sr_err = 0; + break; + + default: + assert( 0 ); /* should not be reachable */ + rs->sr_err = LDAP_STRONG_AUTH_NOT_SUPPORTED; + rs->sr_text = "authentication method not supported"; + } + +done: + NA.txn->close(); + if ( e.e_attrs ) { + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + } + if ( rs->sr_err ) { + send_ldap_result( op, rs ); + } + /* front end will send result on success (rs->sr_err==0) */ + return rs->sr_err; +} diff --git a/servers/slapd/back-ndb/compare.cpp b/servers/slapd/back-ndb/compare.cpp new file mode 100644 index 0000000..684b97c --- /dev/null +++ b/servers/slapd/back-ndb/compare.cpp @@ -0,0 +1,169 @@ +/* compare.cpp - ndb backend compare routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-ndb.h" + +int +ndb_back_compare( Operation *op, SlapReply *rs ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + Entry e = {0}; + Attribute *a; + int manageDSAit = get_manageDSAit( op ); + + NdbArgs NA; + NdbRdns rdns; + struct berval matched; + + /* Get our NDB handle */ + rs->sr_err = ndb_thread_handle( op, &NA.ndb ); + + rdns.nr_num = 0; + NA.rdns = &rdns; + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + NA.e = &e; + +dn2entry_retry: + NA.txn = NA.ndb->startTransaction(); + rs->sr_text = NULL; + if( !NA.txn ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_compare) ": startTransaction failed: %s (%d)\n", + NA.ndb->getNdbError().message, NA.ndb->getNdbError().code, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + NA.ocs = NULL; + /* get entry */ + rs->sr_err = ndb_entry_get_info( op, &NA, 0, &matched ); + switch( rs->sr_err ) { + case 0: + break; + case LDAP_NO_SUCH_OBJECT: + rs->sr_matched = matched.bv_val; + if ( NA.ocs ) + ndb_check_referral( op, rs, &NA ); + goto return_results; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; +#if 0 + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto dn2entry_retry; +#endif + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + rs->sr_err = ndb_entry_get_data( op, &NA, 0 ); + ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx ); + if (!manageDSAit && is_entry_referral( &e ) ) { + /* return referral only if "disclose" is granted on the object */ + if ( !access_allowed( op, &e, slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } else { + /* entry is a referral, don't allow compare */ + rs->sr_ref = get_entry_referrals( op, &e ); + rs->sr_err = LDAP_REFERRAL; + rs->sr_matched = e.e_name.bv_val; + rs->sr_flags |= REP_REF_MUSTBEFREED; + } + + Debug( LDAP_DEBUG_TRACE, "entry is referral\n", 0, 0, 0 ); + goto return_results; + } + + if ( get_assert( op ) && + ( test_filter( op, &e, (Filter *)get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + if ( !access_allowed( op, &e, slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } else { + rs->sr_err = LDAP_ASSERTION_FAILED; + } + goto return_results; + } + + if ( !access_allowed( op, &e, op->oq_compare.rs_ava->aa_desc, + &op->oq_compare.rs_ava->aa_value, ACL_COMPARE, NULL ) ) + { + /* return error only if "disclose" + * is granted on the object */ + if ( !access_allowed( op, &e, slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } else { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + } + goto return_results; + } + + rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE; + + for ( a = attrs_find( e.e_attrs, op->oq_compare.rs_ava->aa_desc ); + a != NULL; + a = attrs_find( a->a_next, op->oq_compare.rs_ava->aa_desc ) ) + { + rs->sr_err = LDAP_COMPARE_FALSE; + + if ( attr_valfind( a, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + &op->oq_compare.rs_ava->aa_value, NULL, + op->o_tmpmemctx ) == 0 ) + { + rs->sr_err = LDAP_COMPARE_TRUE; + break; + } + } + +return_results: + NA.txn->close(); + if ( e.e_attrs ) { + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + } + send_ldap_result( op, rs ); + + switch ( rs->sr_err ) { + case LDAP_COMPARE_FALSE: + case LDAP_COMPARE_TRUE: + rs->sr_err = LDAP_SUCCESS; + break; + } + + return rs->sr_err; +} diff --git a/servers/slapd/back-ndb/config.cpp b/servers/slapd/back-ndb/config.cpp new file mode 100644 index 0000000..9c0dd23 --- /dev/null +++ b/servers/slapd/back-ndb/config.cpp @@ -0,0 +1,333 @@ +/* config.cpp - ndb backend configuration file routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" +#include "lutil.h" + +#include "back-ndb.h" + +#include "config.h" + +extern "C" { + static ConfigDriver ndb_cf_gen; +}; + +enum { + NDB_ATLEN = 1, + NDB_ATSET, + NDB_INDEX, + NDB_ATBLOB +}; + +static ConfigTable ndbcfg[] = { + { "dbhost", "hostname", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct ndb_info, ni_hostname), + "( OLcfgDbAt:6.1 NAME 'olcDbHost' " + "DESC 'Hostname of SQL server' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "dbname", "name", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct ndb_info, ni_dbname), + "( OLcfgDbAt:6.2 NAME 'olcDbName' " + "DESC 'Name of SQL database' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "dbuser", "username", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct ndb_info, ni_username), + "( OLcfgDbAt:6.3 NAME 'olcDbUser' " + "DESC 'Username for SQL session' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "dbpass", "password", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct ndb_info, ni_password), + "( OLcfgDbAt:6.4 NAME 'olcDbPass' " + "DESC 'Password for SQL session' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "dbport", "port", 2, 2, 0, ARG_UINT|ARG_OFFSET, + (void *)offsetof(struct ndb_info, ni_port), + "( OLcfgDbAt:6.5 NAME 'olcDbPort' " + "DESC 'Port number of SQL server' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "dbsocket", "path", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct ndb_info, ni_socket), + "( OLcfgDbAt:6.6 NAME 'olcDbSocket' " + "DESC 'Local socket path of SQL server' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "dbflag", "flag", 2, 2, 0, ARG_LONG|ARG_OFFSET, + (void *)offsetof(struct ndb_info, ni_clflag), + "( OLcfgDbAt:6.7 NAME 'olcDbFlag' " + "DESC 'Flags for SQL session' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "dbconnect", "hostname", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct ndb_info, ni_connectstr), + "( OLcfgDbAt:6.8 NAME 'olcDbConnect' " + "DESC 'Hostname of NDB server' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "dbconnections", "number", 2, 2, 0, ARG_INT|ARG_OFFSET, + (void *)offsetof(struct ndb_info, ni_nconns), + "( OLcfgDbAt:6.9 NAME 'olcDbConnections' " + "DESC 'Number of cluster connections to open' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "attrlen", "attr> <len", 3, 3, 0, ARG_MAGIC|NDB_ATLEN, + (void *)ndb_cf_gen, + "( OLcfgDbAt:6.10 NAME 'olcNdbAttrLen' " + "DESC 'Column length of a specific attribute' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "attrset", "set> <attrs", 3, 3, 0, ARG_MAGIC|NDB_ATSET, + (void *)ndb_cf_gen, + "( OLcfgDbAt:6.11 NAME 'olcNdbAttrSet' " + "DESC 'Set of common attributes' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "index", "attr", 2, 2, 0, ARG_MAGIC|NDB_INDEX, + (void *)ndb_cf_gen, "( OLcfgDbAt:0.2 NAME 'olcDbIndex' " + "DESC 'Attribute to index' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "attrblob", "attr", 2, 2, 0, ARG_MAGIC|NDB_ATBLOB, + (void *)ndb_cf_gen, "( OLcfgDbAt:6.12 NAME 'olcNdbAttrBlob' " + "DESC 'Attribute to treat as a BLOB' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "directory", "dir", 2, 2, 0, ARG_IGNORED, + NULL, "( OLcfgDbAt:0.1 NAME 'olcDbDirectory' " + "DESC 'Dummy keyword' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED, + NULL, NULL, NULL, NULL } +}; + +static ConfigOCs ndbocs[] = { + { + "( OLcfgDbOc:6.2 " + "NAME 'olcNdbConfig' " + "DESC 'NDB backend configuration' " + "SUP olcDatabaseConfig " + "MUST ( olcDbHost $ olcDbName $ olcDbConnect ) " + "MAY ( olcDbUser $ olcDbPass $ olcDbPort $ olcDbSocket $ " + "olcDbFlag $ olcDbConnections $ olcNdbAttrLen $ " + "olcDbIndex $ olcNdbAttrSet $ olcNdbAttrBlob ) )", + Cft_Database, ndbcfg }, + { NULL, Cft_Abstract, NULL } +}; + +static int +ndb_cf_gen( ConfigArgs *c ) +{ + struct ndb_info *ni = (struct ndb_info *)c->be->be_private; + int i, rc; + NdbAttrInfo *ai; + NdbOcInfo *oci; + ListNode *ln, **l2; + struct berval bv, *bva; + + if ( c->op == SLAP_CONFIG_EMIT ) { + char buf[BUFSIZ]; + rc = 0; + bv.bv_val = buf; + switch( c->type ) { + case NDB_ATLEN: + if ( ni->ni_attrlens ) { + for ( ln = ni->ni_attrlens; ln; ln=ln->ln_next ) { + ai = (NdbAttrInfo *)ln->ln_data; + bv.bv_len = snprintf( buf, sizeof(buf), + "%s %d", ai->na_name.bv_val, + ai->na_len ); + value_add_one( &c->rvalue_vals, &bv ); + } + } else { + rc = 1; + } + break; + + case NDB_ATSET: + if ( ni->ni_attrsets ) { + char *ptr, *end = buf+sizeof(buf); + for ( ln = ni->ni_attrsets; ln; ln=ln->ln_next ) { + oci = (NdbOcInfo *)ln->ln_data; + ptr = lutil_strcopy( buf, oci->no_name.bv_val ); + *ptr++ = ' '; + for ( i=0; i<oci->no_nattrs; i++ ) { + if ( end - ptr < oci->no_attrs[i]->na_name.bv_len+1 ) + break; + if ( i ) + *ptr++ = ','; + ptr = lutil_strcopy(ptr, + oci->no_attrs[i]->na_name.bv_val ); + } + bv.bv_len = ptr - buf; + value_add_one( &c->rvalue_vals, &bv ); + } + } else { + rc = 1; + } + break; + + case NDB_INDEX: + if ( ni->ni_attridxs ) { + for ( ln = ni->ni_attridxs; ln; ln=ln->ln_next ) { + ai = (NdbAttrInfo *)ln->ln_data; + value_add_one( &c->rvalue_vals, &ai->na_name ); + } + } else { + rc = 1; + } + break; + + case NDB_ATBLOB: + if ( ni->ni_attrblobs ) { + for ( ln = ni->ni_attrblobs; ln; ln=ln->ln_next ) { + ai = (NdbAttrInfo *)ln->ln_data; + value_add_one( &c->rvalue_vals, &ai->na_name ); + } + } else { + rc = 1; + } + break; + + } + return rc; + } else if ( c->op == LDAP_MOD_DELETE ) { /* FIXME */ + rc = 0; + switch( c->type ) { + case NDB_INDEX: + if ( c->valx == -1 ) { + + /* delete all */ + + } else { + + } + break; + } + return rc; + } + + switch( c->type ) { + case NDB_ATLEN: + ber_str2bv( c->argv[1], 0, 0, &bv ); + ai = ndb_ai_get( ni, &bv ); + if ( !ai ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: invalid attr %s", + c->log, c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + return -1; + } + for ( ln = ni->ni_attrlens; ln; ln = ln->ln_next ) { + if ( ln->ln_data == (void *)ai ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: attr len already set for %s", + c->log, c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + return -1; + } + } + ai->na_len = atoi( c->argv[2] ); + ai->na_flag |= NDB_INFO_ATLEN; + ln = (ListNode *)ch_malloc( sizeof(ListNode)); + ln->ln_data = ai; + ln->ln_next = NULL; + for ( l2 = &ni->ni_attrlens; *l2; l2 = &(*l2)->ln_next ); + *l2 = ln; + break; + + case NDB_INDEX: + ber_str2bv( c->argv[1], 0, 0, &bv ); + ai = ndb_ai_get( ni, &bv ); + if ( !ai ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: invalid attr %s", + c->log, c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + return -1; + } + for ( ln = ni->ni_attridxs; ln; ln = ln->ln_next ) { + if ( ln->ln_data == (void *)ai ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: attr index already set for %s", + c->log, c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + return -1; + } + } + ai->na_flag |= NDB_INFO_INDEX; + ln = (ListNode *)ch_malloc( sizeof(ListNode)); + ln->ln_data = ai; + ln->ln_next = NULL; + for ( l2 = &ni->ni_attridxs; *l2; l2 = &(*l2)->ln_next ); + *l2 = ln; + break; + + case NDB_ATSET: + ber_str2bv( c->argv[1], 0, 0, &bv ); + bva = ndb_str2bvarray( c->argv[2], strlen( c->argv[2] ), ',', NULL ); + rc = ndb_aset_get( ni, &bv, bva, &oci ); + ber_bvarray_free( bva ); + if ( rc ) { + if ( rc == LDAP_ALREADY_EXISTS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s: attrset %s already defined", + c->log, c->argv[1] ); + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s: invalid attrset %s (%d)", + c->log, c->argv[1], rc ); + } + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + return -1; + } + ln = (ListNode *)ch_malloc( sizeof(ListNode)); + ln->ln_data = oci; + ln->ln_next = NULL; + for ( l2 = &ni->ni_attrsets; *l2; l2 = &(*l2)->ln_next ); + *l2 = ln; + break; + + case NDB_ATBLOB: + ber_str2bv( c->argv[1], 0, 0, &bv ); + ai = ndb_ai_get( ni, &bv ); + if ( !ai ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: invalid attr %s", + c->log, c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + return -1; + } + for ( ln = ni->ni_attrblobs; ln; ln = ln->ln_next ) { + if ( ln->ln_data == (void *)ai ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: attr blob already set for %s", + c->log, c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + return -1; + } + } + ai->na_flag |= NDB_INFO_ATBLOB; + ln = (ListNode *)ch_malloc( sizeof(ListNode)); + ln->ln_data = ai; + ln->ln_next = NULL; + for ( l2 = &ni->ni_attrblobs; *l2; l2 = &(*l2)->ln_next ); + *l2 = ln; + break; + + } + return 0; +} + +extern "C" +int ndb_back_init_cf( BackendInfo *bi ) +{ + bi->bi_cf_ocs = ndbocs; + + return config_register_schema( ndbcfg, ndbocs ); +} diff --git a/servers/slapd/back-ndb/delete.cpp b/servers/slapd/back-ndb/delete.cpp new file mode 100644 index 0000000..99f28e5 --- /dev/null +++ b/servers/slapd/back-ndb/delete.cpp @@ -0,0 +1,322 @@ +/* delete.cpp - ndb backend delete routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "lutil.h" +#include "back-ndb.h" + +static struct berval glue_bv = BER_BVC("glue"); + +int +ndb_back_delete( Operation *op, SlapReply *rs ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + Entry e = {0}; + Entry p = {0}; + int manageDSAit = get_manageDSAit( op ); + AttributeDescription *children = slap_schema.si_ad_children; + AttributeDescription *entry = slap_schema.si_ad_entry; + + NdbArgs NA; + NdbRdns rdns; + struct berval matched; + + int num_retries = 0; + + int rc; + + LDAPControl **preread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + + Debug( LDAP_DEBUG_ARGS, "==> " LDAP_XSTRING(ndb_back_delete) ": %s\n", + op->o_req_dn.bv_val, 0, 0 ); + + ctrls[num_ctrls] = 0; + + /* allocate CSN */ + if ( BER_BVISNULL( &op->o_csn ) ) { + struct berval csn; + char csnbuf[LDAP_PVT_CSNSTR_BUFSIZE]; + + csn.bv_val = csnbuf; + csn.bv_len = sizeof(csnbuf); + slap_get_csn( op, &csn, 1 ); + } + + if ( !be_issuffix( op->o_bd, &op->o_req_ndn ) ) { + dnParent( &op->o_req_dn, &p.e_name ); + dnParent( &op->o_req_ndn, &p.e_nname ); + } + + /* Get our NDB handle */ + rs->sr_err = ndb_thread_handle( op, &NA.ndb ); + rdns.nr_num = 0; + NA.rdns = &rdns; + NA.ocs = NULL; + NA.e = &e; + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + + if( 0 ) { +retry: /* transaction retry */ + NA.txn->close(); + NA.txn = NULL; + Debug( LDAP_DEBUG_TRACE, + "==> " LDAP_XSTRING(ndb_back_delete) ": retrying...\n", + 0, 0, 0 ); + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto return_results; + } + if ( NA.ocs ) { + ber_bvarray_free( NA.ocs ); + NA.ocs = NULL; + } + ndb_trans_backoff( ++num_retries ); + } + + /* begin transaction */ + NA.txn = NA.ndb->startTransaction(); + rs->sr_text = NULL; + if( !NA.txn ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_delete) ": startTransaction failed: %s (%d)\n", + NA.ndb->getNdbError().message, NA.ndb->getNdbError().code, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + /* get entry */ + rs->sr_err = ndb_entry_get_info( op, &NA, 1, &matched ); + switch( rs->sr_err ) { + case 0: + case LDAP_NO_SUCH_OBJECT: + break; +#if 0 + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; +#endif + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + if ( rs->sr_err == LDAP_NO_SUCH_OBJECT || + ( !manageDSAit && bvmatch( NA.ocs, &glue_bv ))) { + Debug( LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(ndb_back_delete) ": no such object %s\n", + op->o_req_dn.bv_val, 0, 0); + + if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) { + rs->sr_matched = matched.bv_val; + if ( NA.ocs ) + ndb_check_referral( op, rs, &NA ); + } else { + rs->sr_matched = p.e_name.bv_val; + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } + goto return_results; + } + + /* check parent for "children" acl */ + rs->sr_err = access_allowed( op, &p, + children, NULL, ACL_WDEL, NULL ); + + if ( !rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_delete) ": no write " + "access to parent\n", 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to parent"; + goto return_results; + } + + rs->sr_err = ndb_entry_get_data( op, &NA, 1 ); + + rs->sr_err = access_allowed( op, &e, + entry, NULL, ACL_WDEL, NULL ); + + if ( !rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_delete) ": no write access " + "to entry\n", 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to entry"; + goto return_results; + } + + if ( !manageDSAit && is_entry_referral( &e ) ) { + /* entry is a referral, don't allow delete */ + rs->sr_ref = get_entry_referrals( op, &e ); + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_delete) ": entry is referral\n", + 0, 0, 0 ); + + rs->sr_err = LDAP_REFERRAL; + rs->sr_matched = e.e_name.bv_val; + rs->sr_flags = REP_REF_MUSTBEFREED; + goto return_results; + } + + if ( get_assert( op ) && + ( test_filter( op, &e, (Filter *)get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto return_results; + } + + /* pre-read */ + if( op->o_preread ) { + if( preread_ctrl == NULL ) { + preread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if( slap_read_controls( op, rs, &e, + &slap_pre_read_bv, preread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_delete) ": pre-read " + "failed!\n", 0, 0, 0 ); + if ( op->o_preread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + /* Can't do it if we have kids */ + rs->sr_err = ndb_has_children( &NA, &rc ); + if ( rs->sr_err ) { + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(ndb_back_delete) + ": has_children failed: %s (%d)\n", + NA.txn->getNdbError().message, NA.txn->getNdbError().code, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + if ( rc == LDAP_COMPARE_TRUE ) { + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(ndb_back_delete) + ": non-leaf %s\n", + op->o_req_dn.bv_val, 0, 0); + rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF; + rs->sr_text = "subordinate objects must be deleted first"; + goto return_results; + } + + /* delete info */ + rs->sr_err = ndb_entry_del_info( op->o_bd, &NA ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_delete) ": del_info failed: %s (%d)\n", + NA.txn->getNdbError().message, NA.txn->getNdbError().code, 0 ); + rs->sr_text = "DN index delete failed"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + + /* delete data */ + rs->sr_err = ndb_entry_del_data( op->o_bd, &NA ); + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_delete) ": del_data failed: %s (%d)\n", + NA.txn->getNdbError().message, NA.txn->getNdbError().code, 0 ); + rs->sr_text = "entry delete failed"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + + if( op->o_noop ) { + if (( rs->sr_err=NA.txn->execute( NdbTransaction::Rollback, + NdbOperation::AbortOnError, 1 )) != 0 ) { + rs->sr_text = "txn (no-op) failed"; + } else { + rs->sr_err = LDAP_X_NO_OPERATION; + } + } else { + if (( rs->sr_err=NA.txn->execute( NdbTransaction::Commit, + NdbOperation::AbortOnError, 1 )) != 0 ) { + rs->sr_text = "txn_commit failed"; + } else { + rs->sr_err = LDAP_SUCCESS; + } + } + + if( rs->sr_err != LDAP_SUCCESS && rs->sr_err != LDAP_X_NO_OPERATION ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_delete) ": txn_%s failed: %s (%d)\n", + op->o_noop ? "abort (no-op)" : "commit", + NA.txn->getNdbError().message, NA.txn->getNdbError().code ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "commit failed"; + + goto return_results; + } + NA.txn->close(); + NA.txn = NULL; + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_delete) ": deleted%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + e.e_id, op->o_req_dn.bv_val ); + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + if( num_ctrls ) rs->sr_ctrls = ctrls; + +return_results: + if ( NA.ocs ) { + ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx ); + NA.ocs = NULL; + } + + /* free entry */ + if( e.e_attrs != NULL ) { + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + } + + if( NA.txn != NULL ) { + NA.txn->execute( Rollback ); + NA.txn->close(); + } + + send_ldap_result( op, rs ); + slap_graduate_commit_csn( op ); + + if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) { + slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *preread_ctrl, op->o_tmpmemctx ); + } + return rs->sr_err; +} diff --git a/servers/slapd/back-ndb/init.cpp b/servers/slapd/back-ndb/init.cpp new file mode 100644 index 0000000..e611d64 --- /dev/null +++ b/servers/slapd/back-ndb/init.cpp @@ -0,0 +1,451 @@ +/* init.cpp - initialize ndb backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/unistd.h> +#include <ac/stdlib.h> +#include <ac/errno.h> +#include <sys/stat.h> +#include "back-ndb.h" +#include <lutil.h> +#include "config.h" + +extern "C" { + static BI_db_init ndb_db_init; + static BI_db_close ndb_db_close; + static BI_db_open ndb_db_open; + static BI_db_destroy ndb_db_destroy; +} + +static struct berval ndb_optable = BER_BVC("OL_opattrs"); + +static struct berval ndb_opattrs[] = { + BER_BVC("structuralObjectClass"), + BER_BVC("entryUUID"), + BER_BVC("creatorsName"), + BER_BVC("createTimestamp"), + BER_BVC("entryCSN"), + BER_BVC("modifiersName"), + BER_BVC("modifyTimestamp"), + BER_BVNULL +}; + +static int ndb_oplens[] = { + 0, /* structuralOC, default */ + 36, /* entryUUID */ + 0, /* creatorsName, default */ + 26, /* createTimestamp */ + 40, /* entryCSN */ + 0, /* modifiersName, default */ + 26, /* modifyTimestamp */ + -1 +}; + +static Uint32 ndb_lastrow[1]; +NdbInterpretedCode *ndb_lastrow_code; + +static int +ndb_db_init( BackendDB *be, ConfigReply *cr ) +{ + struct ndb_info *ni; + int rc = 0; + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_db_init) ": Initializing ndb database\n", + 0, 0, 0 ); + + /* allocate backend-database-specific stuff */ + ni = (struct ndb_info *) ch_calloc( 1, sizeof(struct ndb_info) ); + + be->be_private = ni; + be->be_cf_ocs = be->bd_info->bi_cf_ocs; + + ni->ni_search_stack_depth = DEFAULT_SEARCH_STACK_DEPTH; + + ldap_pvt_thread_rdwr_init( &ni->ni_ai_rwlock ); + ldap_pvt_thread_rdwr_init( &ni->ni_oc_rwlock ); + ldap_pvt_thread_mutex_init( &ni->ni_conn_mutex ); + +#ifdef DO_MONITORING + rc = ndb_monitor_db_init( be ); +#endif + + return rc; +} + +static int +ndb_db_close( BackendDB *be, ConfigReply *cr ); + +static int +ndb_db_open( BackendDB *be, ConfigReply *cr ) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + char sqlbuf[BUFSIZ], *ptr; + int rc, i; + + if ( be->be_suffix == NULL ) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: need suffix" ); + Debug( LDAP_DEBUG_ANY, "%s\n", + cr->msg, 0, 0 ); + return -1; + } + + Debug( LDAP_DEBUG_ARGS, + LDAP_XSTRING(ndb_db_open) ": \"%s\"\n", + be->be_suffix[0].bv_val, 0, 0 ); + + if ( ni->ni_nconns < 1 ) + ni->ni_nconns = 1; + + ni->ni_cluster = (Ndb_cluster_connection **)ch_calloc( ni->ni_nconns, sizeof( Ndb_cluster_connection *)); + for ( i=0; i<ni->ni_nconns; i++ ) { + ni->ni_cluster[i] = new Ndb_cluster_connection( ni->ni_connectstr ); + rc = ni->ni_cluster[i]->connect( 20, 5, 1 ); + if ( rc ) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: ni_cluster[%d]->connect failed (%d)", + i, rc ); + goto fail; + } + } + for ( i=0; i<ni->ni_nconns; i++ ) { + rc = ni->ni_cluster[i]->wait_until_ready( 30, 30 ); + if ( rc ) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: ni_cluster[%d]->wait failed (%d)", + i, rc ); + goto fail; + } + } + + mysql_init( &ni->ni_sql ); + if ( !mysql_real_connect( &ni->ni_sql, ni->ni_hostname, ni->ni_username, ni->ni_password, + "", ni->ni_port, ni->ni_socket, ni->ni_clflag )) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: mysql_real_connect failed, %s (%d)", + mysql_error(&ni->ni_sql), mysql_errno(&ni->ni_sql) ); + rc = -1; + goto fail; + } + + sprintf( sqlbuf, "CREATE DATABASE IF NOT EXISTS %s", ni->ni_dbname ); + rc = mysql_query( &ni->ni_sql, sqlbuf ); + if ( rc ) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: CREATE DATABASE %s failed, %s (%d)", + ni->ni_dbname, mysql_error(&ni->ni_sql), mysql_errno(&ni->ni_sql) ); + goto fail; + } + + sprintf( sqlbuf, "USE %s", ni->ni_dbname ); + rc = mysql_query( &ni->ni_sql, sqlbuf ); + if ( rc ) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: USE DATABASE %s failed, %s (%d)", + ni->ni_dbname, mysql_error(&ni->ni_sql), mysql_errno(&ni->ni_sql) ); + goto fail; + } + + ptr = sqlbuf; + ptr += sprintf( ptr, "CREATE TABLE IF NOT EXISTS " DN2ID_TABLE " (" + "eid bigint unsigned NOT NULL, " + "object_classes VARCHAR(1024) NOT NULL, " + "a0 VARCHAR(128) NOT NULL DEFAULT '', " + "a1 VARCHAR(128) NOT NULL DEFAULT '', " + "a2 VARCHAR(128) NOT NULL DEFAULT '', " + "a3 VARCHAR(128) NOT NULL DEFAULT '', " + "a4 VARCHAR(128) NOT NULL DEFAULT '', " + "a5 VARCHAR(128) NOT NULL DEFAULT '', " + "a6 VARCHAR(128) NOT NULL DEFAULT '', " + "a7 VARCHAR(128) NOT NULL DEFAULT '', " + "a8 VARCHAR(128) NOT NULL DEFAULT '', " + "a9 VARCHAR(128) NOT NULL DEFAULT '', " + "a10 VARCHAR(128) NOT NULL DEFAULT '', " + "a11 VARCHAR(128) NOT NULL DEFAULT '', " + "a12 VARCHAR(128) NOT NULL DEFAULT '', " + "a13 VARCHAR(128) NOT NULL DEFAULT '', " + "a14 VARCHAR(128) NOT NULL DEFAULT '', " + "a15 VARCHAR(128) NOT NULL DEFAULT '', " + "PRIMARY KEY (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15), " + "UNIQUE KEY eid (eid) USING HASH" ); + /* Create index columns */ + if ( ni->ni_attridxs ) { + ListNode *ln; + int newcol = 0; + + *ptr++ = ','; + *ptr++ = ' '; + for ( ln = ni->ni_attridxs; ln; ln=ln->ln_next ) { + NdbAttrInfo *ai = (NdbAttrInfo *)ln->ln_data; + ptr += sprintf( ptr, "`%s` VARCHAR(%d), ", + ai->na_name.bv_val, ai->na_len ); + } + ptr = lutil_strcopy(ptr, "KEY " INDEX_NAME " (" ); + + for ( ln = ni->ni_attridxs; ln; ln=ln->ln_next ) { + NdbAttrInfo *ai = (NdbAttrInfo *)ln->ln_data; + if ( newcol ) *ptr++ = ','; + *ptr++ = '`'; + ptr = lutil_strcopy( ptr, ai->na_name.bv_val ); + *ptr++ = '`'; + ai->na_ixcol = newcol + 18; + newcol++; + } + *ptr++ = ')'; + } + strcpy( ptr, ") ENGINE=ndb" ); + rc = mysql_query( &ni->ni_sql, sqlbuf ); + if ( rc ) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: CREATE TABLE " DN2ID_TABLE " failed, %s (%d)", + mysql_error(&ni->ni_sql), mysql_errno(&ni->ni_sql) ); + goto fail; + } + + rc = mysql_query( &ni->ni_sql, "CREATE TABLE IF NOT EXISTS " NEXTID_TABLE " (" + "a bigint unsigned AUTO_INCREMENT PRIMARY KEY ) ENGINE=ndb" ); + if ( rc ) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: CREATE TABLE " NEXTID_TABLE " failed, %s (%d)", + mysql_error(&ni->ni_sql), mysql_errno(&ni->ni_sql) ); + goto fail; + } + + { + NdbOcInfo *oci; + + rc = ndb_aset_get( ni, &ndb_optable, ndb_opattrs, &oci ); + if ( rc ) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: ndb_aset_get( %s ) failed (%d)", + ndb_optable.bv_val, rc ); + goto fail; + } + for ( i=0; ndb_oplens[i] >= 0; i++ ) { + if ( ndb_oplens[i] ) + oci->no_attrs[i]->na_len = ndb_oplens[i]; + } + rc = ndb_aset_create( ni, oci ); + if ( rc ) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: ndb_aset_create( %s ) failed (%d)", + ndb_optable.bv_val, rc ); + goto fail; + } + ni->ni_opattrs = oci; + } + /* Create attribute sets */ + { + ListNode *ln; + + for ( ln = ni->ni_attrsets; ln; ln=ln->ln_next ) { + NdbOcInfo *oci = (NdbOcInfo *)ln->ln_data; + rc = ndb_aset_create( ni, oci ); + if ( rc ) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: ndb_aset_create( %s ) failed (%d)", + oci->no_name.bv_val, rc ); + goto fail; + } + } + } + /* Initialize any currently used objectClasses */ + { + Ndb *ndb; + const NdbDictionary::Dictionary *myDict; + + ndb = new Ndb( ni->ni_cluster[0], ni->ni_dbname ); + ndb->init(1024); + + myDict = ndb->getDictionary(); + ndb_oc_read( ni, myDict ); + delete ndb; + } + +#ifdef DO_MONITORING + /* monitor setup */ + rc = ndb_monitor_db_open( be ); + if ( rc != 0 ) { + goto fail; + } +#endif + + return 0; + +fail: + Debug( LDAP_DEBUG_ANY, "%s\n", + cr->msg, 0, 0 ); + ndb_db_close( be, NULL ); + return rc; +} + +static int +ndb_db_close( BackendDB *be, ConfigReply *cr ) +{ + int i; + struct ndb_info *ni = (struct ndb_info *) be->be_private; + + mysql_close( &ni->ni_sql ); + if ( ni->ni_cluster ) { + for ( i=0; i<ni->ni_nconns; i++ ) { + if ( ni->ni_cluster[i] ) { + delete ni->ni_cluster[i]; + ni->ni_cluster[i] = NULL; + } + } + ch_free( ni->ni_cluster ); + ni->ni_cluster = NULL; + } + +#ifdef DO_MONITORING + /* monitor handling */ + (void)ndb_monitor_db_close( be ); +#endif + + return 0; +} + +static int +ndb_db_destroy( BackendDB *be, ConfigReply *cr ) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + +#ifdef DO_MONITORING + /* monitor handling */ + (void)ndb_monitor_db_destroy( be ); +#endif + + ldap_pvt_thread_mutex_destroy( &ni->ni_conn_mutex ); + ldap_pvt_thread_rdwr_destroy( &ni->ni_ai_rwlock ); + ldap_pvt_thread_rdwr_destroy( &ni->ni_oc_rwlock ); + + ch_free( ni ); + be->be_private = NULL; + + return 0; +} + +extern "C" int +ndb_back_initialize( + BackendInfo *bi ) +{ + static char *controls[] = { + LDAP_CONTROL_ASSERT, + LDAP_CONTROL_MANAGEDSAIT, + LDAP_CONTROL_NOOP, + LDAP_CONTROL_PAGEDRESULTS, + LDAP_CONTROL_PRE_READ, + LDAP_CONTROL_POST_READ, + LDAP_CONTROL_SUBENTRIES, + LDAP_CONTROL_X_PERMISSIVE_MODIFY, +#ifdef LDAP_X_TXN + LDAP_CONTROL_X_TXN_SPEC, +#endif + NULL + }; + + int rc = 0; + + /* initialize the underlying database system */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_initialize) ": initialize ndb backend\n", 0, 0, 0 ); + + ndb_init(); + + ndb_lastrow_code = new NdbInterpretedCode( NULL, ndb_lastrow, 1 ); + ndb_lastrow_code->interpret_exit_last_row(); + ndb_lastrow_code->finalise(); + + bi->bi_flags |= + SLAP_BFLAG_INCREMENT | + SLAP_BFLAG_SUBENTRIES | + SLAP_BFLAG_ALIASES | + SLAP_BFLAG_REFERRALS; + + bi->bi_controls = controls; + + bi->bi_open = 0; + bi->bi_close = 0; + bi->bi_config = 0; + bi->bi_destroy = 0; + + bi->bi_db_init = ndb_db_init; + bi->bi_db_config = config_generic_wrapper; + bi->bi_db_open = ndb_db_open; + bi->bi_db_close = ndb_db_close; + bi->bi_db_destroy = ndb_db_destroy; + + bi->bi_op_add = ndb_back_add; + bi->bi_op_bind = ndb_back_bind; + bi->bi_op_compare = ndb_back_compare; + bi->bi_op_delete = ndb_back_delete; + bi->bi_op_modify = ndb_back_modify; + bi->bi_op_modrdn = ndb_back_modrdn; + bi->bi_op_search = ndb_back_search; + + bi->bi_op_unbind = 0; + +#if 0 + bi->bi_extended = ndb_extended; + + bi->bi_chk_referrals = ndb_referrals; +#endif + bi->bi_operational = ndb_operational; + bi->bi_has_subordinates = ndb_has_subordinates; + bi->bi_entry_release_rw = 0; + bi->bi_entry_get_rw = ndb_entry_get; + + /* + * hooks for slap tools + */ + bi->bi_tool_entry_open = ndb_tool_entry_open; + bi->bi_tool_entry_close = ndb_tool_entry_close; + bi->bi_tool_entry_first = ndb_tool_entry_first; + bi->bi_tool_entry_next = ndb_tool_entry_next; + bi->bi_tool_entry_get = ndb_tool_entry_get; + bi->bi_tool_entry_put = ndb_tool_entry_put; +#if 0 + bi->bi_tool_entry_reindex = ndb_tool_entry_reindex; + bi->bi_tool_sync = 0; + bi->bi_tool_dn2id_get = ndb_tool_dn2id_get; + bi->bi_tool_entry_modify = ndb_tool_entry_modify; +#endif + + bi->bi_connection_init = 0; + bi->bi_connection_destroy = 0; + + rc = ndb_back_init_cf( bi ); + + return rc; +} + +#if SLAPD_NDB == SLAPD_MOD_DYNAMIC + +/* conditionally define the init_module() function */ +extern "C" { int init_module( int argc, char *argv[] ); } + +SLAP_BACKEND_INIT_MODULE( ndb ) + +#endif /* SLAPD_NDB == SLAPD_MOD_DYNAMIC */ + diff --git a/servers/slapd/back-ndb/modify.cpp b/servers/slapd/back-ndb/modify.cpp new file mode 100644 index 0000000..ba9c522 --- /dev/null +++ b/servers/slapd/back-ndb/modify.cpp @@ -0,0 +1,704 @@ +/* modify.cpp - ndb backend modify routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "back-ndb.h" + +/* This is a copy from slapd/mods.c, but with compaction tweaked + * to swap values from the tail into deleted slots, to reduce the + * overall update traffic. + */ +static int +ndb_modify_delete( + Entry *e, + Modification *mod, + int permissive, + const char **text, + char *textbuf, size_t textlen, + int *idx ) +{ + Attribute *a; + MatchingRule *mr = mod->sm_desc->ad_type->sat_equality; + struct berval *cvals; + int *id2 = NULL; + int i, j, rc = 0, num; + unsigned flags; + char dummy = '\0'; + + /* For ordered vals, we have no choice but to preserve order */ + if ( mod->sm_desc->ad_type->sat_flags & SLAP_AT_ORDERED_VAL ) + return modify_delete_vindex( e, mod, permissive, text, + textbuf, textlen, idx ); + + /* + * If permissive is set, then the non-existence of an + * attribute is not treated as an error. + */ + + /* delete the entire attribute */ + if ( mod->sm_values == NULL ) { + rc = attr_delete( &e->e_attrs, mod->sm_desc ); + + if( permissive ) { + rc = LDAP_SUCCESS; + } else if( rc != LDAP_SUCCESS ) { + *text = textbuf; + snprintf( textbuf, textlen, + "modify/delete: %s: no such attribute", + mod->sm_desc->ad_cname.bv_val ); + rc = LDAP_NO_SUCH_ATTRIBUTE; + } + return rc; + } + + /* FIXME: Catch old code that doesn't set sm_numvals. + */ + if ( !BER_BVISNULL( &mod->sm_values[mod->sm_numvals] )) { + for ( i = 0; !BER_BVISNULL( &mod->sm_values[i] ); i++ ); + assert( mod->sm_numvals == i ); + } + if ( !idx ) { + id2 = (int *)ch_malloc( mod->sm_numvals * sizeof( int )); + idx = id2; + } + + if( mr == NULL || !mr->smr_match ) { + /* disallow specific attributes from being deleted if + no equality rule */ + *text = textbuf; + snprintf( textbuf, textlen, + "modify/delete: %s: no equality matching rule", + mod->sm_desc->ad_cname.bv_val ); + rc = LDAP_INAPPROPRIATE_MATCHING; + goto return_result; + } + + /* delete specific values - find the attribute first */ + if ( (a = attr_find( e->e_attrs, mod->sm_desc )) == NULL ) { + if( permissive ) { + rc = LDAP_SUCCESS; + goto return_result; + } + *text = textbuf; + snprintf( textbuf, textlen, + "modify/delete: %s: no such attribute", + mod->sm_desc->ad_cname.bv_val ); + rc = LDAP_NO_SUCH_ATTRIBUTE; + goto return_result; + } + + if ( mod->sm_nvalues ) { + flags = SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX + | SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH + | SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH; + cvals = mod->sm_nvalues; + } else { + flags = SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX; + cvals = mod->sm_values; + } + + /* Locate values to delete */ + for ( i = 0; !BER_BVISNULL( &mod->sm_values[i] ); i++ ) { + unsigned sort; + rc = attr_valfind( a, flags, &cvals[i], &sort, NULL ); + if ( rc == LDAP_SUCCESS ) { + idx[i] = sort; + } else if ( rc == LDAP_NO_SUCH_ATTRIBUTE ) { + if ( permissive ) { + idx[i] = -1; + continue; + } + *text = textbuf; + snprintf( textbuf, textlen, + "modify/delete: %s: no such value", + mod->sm_desc->ad_cname.bv_val ); + goto return_result; + } else { + *text = textbuf; + snprintf( textbuf, textlen, + "modify/delete: %s: matching rule failed", + mod->sm_desc->ad_cname.bv_val ); + goto return_result; + } + } + + num = a->a_numvals; + + /* Delete the values */ + for ( i = 0; i < mod->sm_numvals; i++ ) { + /* Skip permissive values that weren't found */ + if ( idx[i] < 0 ) + continue; + /* Skip duplicate delete specs */ + if ( a->a_vals[idx[i]].bv_val == &dummy ) + continue; + /* delete value and mark it as gone */ + free( a->a_vals[idx[i]].bv_val ); + a->a_vals[idx[i]].bv_val = &dummy; + if( a->a_nvals != a->a_vals ) { + free( a->a_nvals[idx[i]].bv_val ); + a->a_nvals[idx[i]].bv_val = &dummy; + } + a->a_numvals--; + } + + /* compact array */ + for ( i=0; i<num; i++ ) { + if ( a->a_vals[i].bv_val != &dummy ) + continue; + for ( --num; num > i && a->a_vals[num].bv_val == &dummy; num-- ) + ; + a->a_vals[i] = a->a_vals[num]; + if ( a->a_nvals != a->a_vals ) + a->a_nvals[i] = a->a_nvals[num]; + } + + BER_BVZERO( &a->a_vals[num] ); + if (a->a_nvals != a->a_vals) { + BER_BVZERO( &a->a_nvals[num] ); + } + + /* if no values remain, delete the entire attribute */ + if ( !a->a_numvals ) { + if ( attr_delete( &e->e_attrs, mod->sm_desc ) ) { + /* Can never happen */ + *text = textbuf; + snprintf( textbuf, textlen, + "modify/delete: %s: no such attribute", + mod->sm_desc->ad_cname.bv_val ); + rc = LDAP_NO_SUCH_ATTRIBUTE; + } + } +return_result: + if ( id2 ) + ch_free( id2 ); + return rc; +} + +int ndb_modify_internal( + Operation *op, + NdbArgs *NA, + const char **text, + char *textbuf, + size_t textlen ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + Modification *mod; + Modifications *ml; + Modifications *modlist = op->orm_modlist; + NdbAttrInfo **modai, *atmp; + const NdbDictionary::Dictionary *myDict; + const NdbDictionary::Table *myTable; + int got_oc = 0, nmods = 0, nai = 0, i, j; + int rc, indexed = 0; + Attribute *old = NULL; + + Debug( LDAP_DEBUG_TRACE, "ndb_modify_internal: 0x%08lx: %s\n", + NA->e->e_id, NA->e->e_dn, 0); + + if ( !acl_check_modlist( op, NA->e, modlist )) { + return LDAP_INSUFFICIENT_ACCESS; + } + + old = attrs_dup( NA->e->e_attrs ); + + for ( ml = modlist; ml != NULL; ml = ml->sml_next ) { + mod = &ml->sml_mod; + nmods++; + + switch ( mod->sm_op ) { + case LDAP_MOD_ADD: + Debug(LDAP_DEBUG_ARGS, + "ndb_modify_internal: add %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + rc = modify_add_values( NA->e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + if( rc != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n", + rc, *text, 0); + } + break; + + case LDAP_MOD_DELETE: + Debug(LDAP_DEBUG_ARGS, + "ndb_modify_internal: delete %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + rc = ndb_modify_delete( NA->e, mod, get_permissiveModify(op), + text, textbuf, textlen, NULL ); + assert( rc != LDAP_TYPE_OR_VALUE_EXISTS ); + if( rc != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n", + rc, *text, 0); + } + break; + + case LDAP_MOD_REPLACE: + Debug(LDAP_DEBUG_ARGS, + "ndb_modify_internal: replace %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + rc = modify_replace_values( NA->e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + if( rc != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n", + rc, *text, 0); + } + break; + + case LDAP_MOD_INCREMENT: + Debug(LDAP_DEBUG_ARGS, + "ndb_modify_internal: increment %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + rc = modify_increment_values( NA->e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + if( rc != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, + "ndb_modify_internal: %d %s\n", + rc, *text, 0); + } + break; + + case SLAP_MOD_SOFTADD: + Debug(LDAP_DEBUG_ARGS, + "ndb_modify_internal: softadd %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + mod->sm_op = LDAP_MOD_ADD; + + rc = modify_add_values( NA->e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + + mod->sm_op = SLAP_MOD_SOFTADD; + + if ( rc == LDAP_TYPE_OR_VALUE_EXISTS ) { + rc = LDAP_SUCCESS; + } + + if( rc != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n", + rc, *text, 0); + } + break; + + case SLAP_MOD_SOFTDEL: + Debug(LDAP_DEBUG_ARGS, + "ndb_modify_internal: softdel %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + mod->sm_op = LDAP_MOD_DELETE; + + rc = modify_delete_values( NA->e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + + mod->sm_op = SLAP_MOD_SOFTDEL; + + if ( rc == LDAP_NO_SUCH_ATTRIBUTE) { + rc = LDAP_SUCCESS; + } + + if( rc != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n", + rc, *text, 0); + } + break; + + case SLAP_MOD_ADD_IF_NOT_PRESENT: + Debug(LDAP_DEBUG_ARGS, + "ndb_modify_internal: add_if_not_present %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + if ( attr_find( NA->e->e_attrs, mod->sm_desc ) ) { + rc = LDAP_SUCCESS; + break; + } + + mod->sm_op = LDAP_MOD_ADD; + + rc = modify_add_values( NA->e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + + mod->sm_op = SLAP_MOD_ADD_IF_NOT_PRESENT; + + if( rc != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n", + rc, *text, 0); + } + break; + + default: + Debug(LDAP_DEBUG_ANY, "ndb_modify_internal: invalid op %d\n", + mod->sm_op, 0, 0); + *text = "Invalid modify operation"; + rc = LDAP_OTHER; + Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n", + rc, *text, 0); + } + + if ( rc != LDAP_SUCCESS ) { + attrs_free( old ); + return rc; + } + + /* If objectClass was modified, reset the flags */ + if ( mod->sm_desc == slap_schema.si_ad_objectClass ) { + NA->e->e_ocflags = 0; + got_oc = 1; + } + } + + /* check that the entry still obeys the schema */ + rc = entry_schema_check( op, NA->e, NULL, get_relax(op), 0, NULL, + text, textbuf, textlen ); + if ( rc != LDAP_SUCCESS || op->o_noop ) { + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "entry failed schema check: %s\n", + *text, 0, 0 ); + } + attrs_free( old ); + return rc; + } + + if ( got_oc ) { + rc = ndb_entry_put_info( op->o_bd, NA, 1 ); + if ( rc ) { + attrs_free( old ); + return rc; + } + } + + /* apply modifications to DB */ + modai = (NdbAttrInfo **)op->o_tmpalloc( nmods * sizeof(NdbAttrInfo*), op->o_tmpmemctx ); + + /* Get the unique list of modified attributes */ + ldap_pvt_thread_rdwr_rlock( &ni->ni_ai_rwlock ); + for ( ml = modlist; ml != NULL; ml = ml->sml_next ) { + /* Already took care of objectclass */ + if ( ml->sml_desc == slap_schema.si_ad_objectClass ) + continue; + for ( i=0; i<nai; i++ ) { + if ( ml->sml_desc->ad_type == modai[i]->na_attr ) + break; + } + /* This attr was already updated */ + if ( i < nai ) + continue; + modai[nai] = ndb_ai_find( ni, ml->sml_desc->ad_type ); + if ( modai[nai]->na_flag & NDB_INFO_INDEX ) + indexed++; + nai++; + } + ldap_pvt_thread_rdwr_runlock( &ni->ni_ai_rwlock ); + + /* If got_oc, this was already done above */ + if ( indexed && !got_oc) { + rc = ndb_entry_put_info( op->o_bd, NA, 1 ); + if ( rc ) { + attrs_free( old ); + return rc; + } + } + + myDict = NA->ndb->getDictionary(); + + /* sort modai so that OcInfo's are contiguous */ + { + int j, k; + for ( i=0; i<nai; i++ ) { + for ( j=i+1; j<nai; j++ ) { + if ( modai[i]->na_oi == modai[j]->na_oi ) + continue; + for ( k=j+1; k<nai; k++ ) { + if ( modai[i]->na_oi == modai[k]->na_oi ) { + atmp = modai[j]; + modai[j] = modai[k]; + modai[k] = atmp; + break; + } + } + /* there are no more na_oi's that match modai[i] */ + if ( k == nai ) { + i = j; + } + } + } + } + + /* One call per table... */ + for ( i=0; i<nai; i += j ) { + atmp = modai[i]; + for ( j=i+1; j<nai; j++ ) + if ( atmp->na_oi != modai[j]->na_oi ) + break; + j -= i; + myTable = myDict->getTable( atmp->na_oi->no_table.bv_val ); + if ( !myTable ) + continue; + rc = ndb_oc_attrs( NA->txn, myTable, NA->e, atmp->na_oi, &modai[i], j, old ); + if ( rc ) break; + } + attrs_free( old ); + return rc; +} + + +int +ndb_back_modify( Operation *op, SlapReply *rs ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + Entry e = {0}; + int manageDSAit = get_manageDSAit( op ); + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof textbuf; + + int num_retries = 0; + + NdbArgs NA; + NdbRdns rdns; + struct berval matched; + + LDAPControl **preread_ctrl = NULL; + LDAPControl **postread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + + Debug( LDAP_DEBUG_ARGS, LDAP_XSTRING(ndb_back_modify) ": %s\n", + op->o_req_dn.bv_val, 0, 0 ); + + ctrls[num_ctrls] = NULL; + + slap_mods_opattrs( op, &op->orm_modlist, 1 ); + + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + + /* Get our NDB handle */ + rs->sr_err = ndb_thread_handle( op, &NA.ndb ); + rdns.nr_num = 0; + NA.rdns = &rdns; + NA.e = &e; + + if( 0 ) { +retry: /* transaction retry */ + NA.txn->close(); + NA.txn = NULL; + if( e.e_attrs ) { + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + } + Debug(LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modify) ": retrying...\n", 0, 0, 0); + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto return_results; + } + if ( NA.ocs ) { + ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx ); + } + ndb_trans_backoff( ++num_retries ); + } + NA.ocs = NULL; + + /* begin transaction */ + NA.txn = NA.ndb->startTransaction(); + rs->sr_text = NULL; + if( !NA.txn ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modify) ": startTransaction failed: %s (%d)\n", + NA.ndb->getNdbError().message, NA.ndb->getNdbError().code, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + /* get entry or ancestor */ + rs->sr_err = ndb_entry_get_info( op, &NA, 0, &matched ); + switch( rs->sr_err ) { + case 0: + break; + case LDAP_NO_SUCH_OBJECT: + Debug( LDAP_DEBUG_ARGS, + "<=- ndb_back_modify: no such object %s\n", + op->o_req_dn.bv_val, 0, 0 ); + rs->sr_matched = matched.bv_val; + if (NA.ocs ) + ndb_check_referral( op, rs, &NA ); + goto return_results; +#if 0 + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; +#endif + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + /* acquire and lock entry */ + rs->sr_err = ndb_entry_get_data( op, &NA, 1 ); + + if ( !manageDSAit && is_entry_referral( &e ) ) { + /* entry is a referral, don't allow modify */ + rs->sr_ref = get_entry_referrals( op, &e ); + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modify) ": entry is referral\n", + 0, 0, 0 ); + + rs->sr_err = LDAP_REFERRAL; + rs->sr_matched = e.e_name.bv_val; + rs->sr_flags = REP_REF_MUSTBEFREED; + goto return_results; + } + + if ( get_assert( op ) && + ( test_filter( op, &e, (Filter*)get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto return_results; + } + + if( op->o_preread ) { + if( preread_ctrl == NULL ) { + preread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if ( slap_read_controls( op, rs, &e, + &slap_pre_read_bv, preread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_modify) ": pre-read " + "failed!\n", 0, 0, 0 ); + if ( op->o_preread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + /* Modify the entry */ + rs->sr_err = ndb_modify_internal( op, &NA, &rs->sr_text, textbuf, textlen ); + + if( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modify) ": modify failed (%d)\n", + rs->sr_err, 0, 0 ); +#if 0 + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } +#endif + goto return_results; + } + + if( op->o_postread ) { + if( postread_ctrl == NULL ) { + postread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if( slap_read_controls( op, rs, &e, + &slap_post_read_bv, postread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_modify) + ": post-read failed!\n", 0, 0, 0 ); + if ( op->o_postread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + if( op->o_noop ) { + if (( rs->sr_err=NA.txn->execute( NdbTransaction::Rollback, + NdbOperation::AbortOnError, 1 )) != 0 ) { + rs->sr_text = "txn_abort (no-op) failed"; + } else { + rs->sr_err = LDAP_X_NO_OPERATION; + } + } else { + if (( rs->sr_err=NA.txn->execute( NdbTransaction::Commit, + NdbOperation::AbortOnError, 1 )) != 0 ) { + rs->sr_text = "txn_commit failed"; + } else { + rs->sr_err = LDAP_SUCCESS; + } + } + + if( rs->sr_err != LDAP_SUCCESS && rs->sr_err != LDAP_X_NO_OPERATION ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modify) ": txn_%s failed: %s (%d)\n", + op->o_noop ? "abort (no-op)" : "commit", + NA.txn->getNdbError().message, NA.txn->getNdbError().code ); + rs->sr_err = LDAP_OTHER; + goto return_results; + } + NA.txn->close(); + NA.txn = NULL; + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modify) ": updated%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + e.e_id, op->o_req_dn.bv_val ); + + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + if( num_ctrls ) rs->sr_ctrls = ctrls; + +return_results: + if ( NA.ocs ) { + ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx ); + NA.ocs = NULL; + } + + if ( e.e_attrs != NULL ) { + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + } + + if( NA.txn != NULL ) { + NA.txn->execute( Rollback ); + NA.txn->close(); + } + + send_ldap_result( op, rs ); + slap_graduate_commit_csn( op ); + + if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) { + slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *preread_ctrl, op->o_tmpmemctx ); + } + if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) { + slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *postread_ctrl, op->o_tmpmemctx ); + } + + rs->sr_text = NULL; + return rs->sr_err; +} diff --git a/servers/slapd/back-ndb/modrdn.cpp b/servers/slapd/back-ndb/modrdn.cpp new file mode 100644 index 0000000..8ff42c4 --- /dev/null +++ b/servers/slapd/back-ndb/modrdn.cpp @@ -0,0 +1,558 @@ +/* modrdn.cpp - ndb backend modrdn routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-ndb.h" + +int +ndb_back_modrdn( Operation *op, SlapReply *rs ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + AttributeDescription *children = slap_schema.si_ad_children; + AttributeDescription *entry = slap_schema.si_ad_entry; + struct berval new_dn = BER_BVNULL, new_ndn = BER_BVNULL; + Entry e = {0}; + Entry e2 = {0}; + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof textbuf; + + struct berval *np_dn = NULL; /* newSuperior dn */ + struct berval *np_ndn = NULL; /* newSuperior ndn */ + + int manageDSAit = get_manageDSAit( op ); + int num_retries = 0; + + NdbArgs NA, NA2; + NdbRdns rdns, rdn2; + struct berval matched; + + LDAPControl **preread_ctrl = NULL; + LDAPControl **postread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + + int rc; + + Debug( LDAP_DEBUG_ARGS, "==>" LDAP_XSTRING(ndb_back_modrdn) "(%s,%s,%s)\n", + op->o_req_dn.bv_val,op->oq_modrdn.rs_newrdn.bv_val, + op->oq_modrdn.rs_newSup ? op->oq_modrdn.rs_newSup->bv_val : "NULL" ); + + ctrls[num_ctrls] = NULL; + + slap_mods_opattrs( op, &op->orr_modlist, 1 ); + + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + + /* Get our NDB handle */ + rs->sr_err = ndb_thread_handle( op, &NA.ndb ); + rdns.nr_num = 0; + NA.rdns = &rdns; + NA.e = &e; + NA2.ndb = NA.ndb; + NA2.e = &e2; + NA2.rdns = &rdn2; + + if( 0 ) { +retry: /* transaction retry */ + NA.txn->close(); + NA.txn = NULL; + if ( e.e_attrs ) { + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + } + Debug( LDAP_DEBUG_TRACE, "==>" LDAP_XSTRING(ndb_back_modrdn) + ": retrying...\n", 0, 0, 0 ); + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto return_results; + } + if ( NA2.ocs ) { + ber_bvarray_free_x( NA2.ocs, op->o_tmpmemctx ); + } + if ( NA.ocs ) { + ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx ); + } + ndb_trans_backoff( ++num_retries ); + } + NA.ocs = NULL; + NA2.ocs = NULL; + + /* begin transaction */ + NA.txn = NA.ndb->startTransaction(); + rs->sr_text = NULL; + if( !NA.txn ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modrdn) ": startTransaction failed: %s (%d)\n", + NA.ndb->getNdbError().message, NA.ndb->getNdbError().code, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + NA2.txn = NA.txn; + + /* get entry */ + rs->sr_err = ndb_entry_get_info( op, &NA, 1, &matched ); + switch( rs->sr_err ) { + case 0: + break; + case LDAP_NO_SUCH_OBJECT: + Debug( LDAP_DEBUG_ARGS, + "<=- ndb_back_modrdn: no such object %s\n", + op->o_req_dn.bv_val, 0, 0 ); + rs->sr_matched = matched.bv_val; + if ( NA.ocs ) + ndb_check_referral( op, rs, &NA ); + goto return_results; +#if 0 + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; +#endif + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + /* acquire and lock entry */ + rs->sr_err = ndb_entry_get_data( op, &NA, 1 ); + if ( rs->sr_err ) + goto return_results; + + if ( !manageDSAit && is_entry_glue( &e )) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + goto return_results; + } + + if ( get_assert( op ) && + ( test_filter( op, &e, (Filter *)get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto return_results; + } + + /* check write on old entry */ + rs->sr_err = access_allowed( op, &e, entry, NULL, ACL_WRITE, NULL ); + if ( ! rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, "no access to entry\n", 0, + 0, 0 ); + rs->sr_text = "no write access to old entry"; + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto return_results; + } + + /* Can't do it if we have kids */ + rs->sr_err = ndb_has_children( &NA, &rc ); + if ( rs->sr_err ) { + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(ndb_back_modrdn) + ": has_children failed: %s (%d)\n", + NA.txn->getNdbError().message, NA.txn->getNdbError().code, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + if ( rc == LDAP_COMPARE_TRUE ) { + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(ndb_back_modrdn) + ": non-leaf %s\n", + op->o_req_dn.bv_val, 0, 0); + rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF; + rs->sr_text = "subtree rename not supported"; + goto return_results; + } + + if (!manageDSAit && is_entry_referral( &e ) ) { + /* entry is a referral, don't allow modrdn */ + rs->sr_ref = get_entry_referrals( op, &e ); + + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(ndb_back_modrdn) + ": entry %s is referral\n", e.e_dn, 0, 0 ); + + rs->sr_err = LDAP_REFERRAL, + rs->sr_matched = op->o_req_dn.bv_val; + rs->sr_flags = REP_REF_MUSTBEFREED; + goto return_results; + } + + if ( be_issuffix( op->o_bd, &e.e_nname ) ) { + /* There can only be one suffix entry */ + rs->sr_err = LDAP_NAMING_VIOLATION; + rs->sr_text = "cannot rename suffix entry"; + goto return_results; + } else { + dnParent( &e.e_nname, &e2.e_nname ); + dnParent( &e.e_name, &e2.e_name ); + } + + /* check parent for "children" acl */ + rs->sr_err = access_allowed( op, &e2, + children, NULL, + op->oq_modrdn.rs_newSup == NULL ? + ACL_WRITE : ACL_WDEL, + NULL ); + + if ( ! rs->sr_err ) { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + Debug( LDAP_DEBUG_TRACE, "no access to parent\n", 0, + 0, 0 ); + rs->sr_text = "no write access to old parent's children"; + goto return_results; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modrdn) ": wr to children " + "of entry %s OK\n", e2.e_name.bv_val, 0, 0 ); + + if ( op->oq_modrdn.rs_newSup != NULL ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modrdn) + ": new parent \"%s\" requested...\n", + op->oq_modrdn.rs_newSup->bv_val, 0, 0 ); + + /* newSuperior == oldParent? */ + if( dn_match( &e2.e_nname, op->oq_modrdn.rs_nnewSup ) ) { + Debug( LDAP_DEBUG_TRACE, "bdb_back_modrdn: " + "new parent \"%s\" same as the old parent \"%s\"\n", + op->oq_modrdn.rs_newSup->bv_val, e2.e_name.bv_val, 0 ); + op->oq_modrdn.rs_newSup = NULL; /* ignore newSuperior */ + } + } + + if ( op->oq_modrdn.rs_newSup != NULL ) { + if ( op->oq_modrdn.rs_newSup->bv_len ) { + rdn2.nr_num = 0; + np_dn = op->oq_modrdn.rs_newSup; + np_ndn = op->oq_modrdn.rs_nnewSup; + + /* newSuperior == oldParent? - checked above */ + /* newSuperior == entry being moved?, if so ==> ERROR */ + if ( dnIsSuffix( np_ndn, &e.e_nname )) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = "new superior not found"; + goto return_results; + } + /* Get Entry with dn=newSuperior. Does newSuperior exist? */ + + e2.e_name = *np_dn; + e2.e_nname = *np_ndn; + rs->sr_err = ndb_entry_get_info( op, &NA2, 1, NULL ); + switch( rs->sr_err ) { + case 0: + break; + case LDAP_NO_SUCH_OBJECT: + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modrdn) + ": newSup(ndn=%s) not here!\n", + np_ndn->bv_val, 0, 0); + rs->sr_text = "new superior not found"; + goto return_results; +#if 0 + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; +#endif + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + if ( NA2.ocs ) { + Attribute a; + int i; + + for ( i=0; !BER_BVISNULL( &NA2.ocs[i] ); i++); + a.a_numvals = i; + a.a_desc = slap_schema.si_ad_objectClass; + a.a_vals = NA2.ocs; + a.a_nvals = NA2.ocs; + a.a_next = NULL; + e2.e_attrs = &a; + + if ( is_entry_alias( &e2 )) { + /* parent is an alias, don't allow move */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modrdn) + ": entry is alias\n", + 0, 0, 0 ); + rs->sr_text = "new superior is an alias"; + rs->sr_err = LDAP_ALIAS_PROBLEM; + goto return_results; + } + + if ( is_entry_referral( &e2 ) ) { + /* parent is a referral, don't allow move */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modrdn) + ": entry is referral\n", + 0, 0, 0 ); + rs->sr_text = "new superior is a referral"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + } + } + + /* check newSuperior for "children" acl */ + rs->sr_err = access_allowed( op, &e2, children, + NULL, ACL_WADD, NULL ); + if( ! rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modrdn) + ": no wr to newSup children\n", + 0, 0, 0 ); + rs->sr_text = "no write access to new superior's children"; + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto return_results; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modrdn) + ": wr to new parent OK id=%ld\n", + (long) e2.e_id, 0, 0 ); + } + + /* Build target dn and make sure target entry doesn't exist already. */ + if (!new_dn.bv_val) { + build_new_dn( &new_dn, &e2.e_name, &op->oq_modrdn.rs_newrdn, NULL ); + } + + if (!new_ndn.bv_val) { + build_new_dn( &new_ndn, &e2.e_nname, &op->oq_modrdn.rs_nnewrdn, NULL ); + } + + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(ndb_back_modrdn) ": new ndn=%s\n", + new_ndn.bv_val, 0, 0 ); + + /* Allow rename to same DN */ + if ( !bvmatch ( &new_ndn, &e.e_nname )) { + rdn2.nr_num = 0; + e2.e_name = new_dn; + e2.e_nname = new_ndn; + NA2.ocs = &matched; + rs->sr_err = ndb_entry_get_info( op, &NA2, 1, NULL ); + NA2.ocs = NULL; + switch( rs->sr_err ) { +#if 0 + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; +#endif + case LDAP_NO_SUCH_OBJECT: + break; + case 0: + rs->sr_err = LDAP_ALREADY_EXISTS; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + } + + assert( op->orr_modlist != NULL ); + + if( op->o_preread ) { + if( preread_ctrl == NULL ) { + preread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if( slap_read_controls( op, rs, &e, + &slap_pre_read_bv, preread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_modrdn) + ": pre-read failed!\n", 0, 0, 0 ); + if ( op->o_preread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + /* delete old DN */ + rs->sr_err = ndb_entry_del_info( op->o_bd, &NA ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_modrdn) + ": dn2id del failed: %s (%d)\n", + NA.txn->getNdbError().message, NA.txn->getNdbError().code, 0 ); +#if 0 + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } +#endif + rs->sr_err = LDAP_OTHER; + rs->sr_text = "DN index delete fail"; + goto return_results; + } + + /* copy entry fields */ + e2.e_attrs = e.e_attrs; + e2.e_id = e.e_id; + + /* add new DN */ + rs->sr_err = ndb_entry_put_info( op->o_bd, &NA2, 0 ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_modrdn) + ": dn2id add failed: %s (%d)\n", + NA.txn->getNdbError().message, NA.txn->getNdbError().code, 0 ); +#if 0 + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } +#endif + rs->sr_err = LDAP_OTHER; + rs->sr_text = "DN index add failed"; + goto return_results; + } + + /* modify entry */ + rs->sr_err = ndb_modify_internal( op, &NA2, + &rs->sr_text, textbuf, textlen ); + if( rs->sr_err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_modrdn) + ": modify failed: %s (%d)\n", + NA.txn->getNdbError().message, NA.txn->getNdbError().code, 0 ); +#if 0 + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } +#endif + goto return_results; + } + + e.e_attrs = e2.e_attrs; + + if( op->o_postread ) { + if( postread_ctrl == NULL ) { + postread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if( slap_read_controls( op, rs, &e2, + &slap_post_read_bv, postread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_modrdn) + ": post-read failed!\n", 0, 0, 0 ); + if ( op->o_postread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + if( op->o_noop ) { + if (( rs->sr_err=NA.txn->execute( NdbTransaction::Rollback, + NdbOperation::AbortOnError, 1 )) != 0 ) { + rs->sr_text = "txn_abort (no-op) failed"; + } else { + rs->sr_err = LDAP_X_NO_OPERATION; + } + } else { + if (( rs->sr_err=NA.txn->execute( NdbTransaction::Commit, + NdbOperation::AbortOnError, 1 )) != 0 ) { + rs->sr_text = "txn_commit failed"; + } else { + rs->sr_err = LDAP_SUCCESS; + } + } + + if( rs->sr_err != LDAP_SUCCESS && rs->sr_err != LDAP_X_NO_OPERATION ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modrdn) ": txn_%s failed: %s (%d)\n", + op->o_noop ? "abort (no-op)" : "commit", + NA.txn->getNdbError().message, NA.txn->getNdbError().code ); + rs->sr_err = LDAP_OTHER; + goto return_results; + } + NA.txn->close(); + NA.txn = NULL; + + Debug(LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modrdn) + ": rdn modified%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + e.e_id, op->o_req_dn.bv_val ); + + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + if( num_ctrls ) rs->sr_ctrls = ctrls; + +return_results: + if ( NA2.ocs ) { + ber_bvarray_free_x( NA2.ocs, op->o_tmpmemctx ); + NA2.ocs = NULL; + } + + if ( NA.ocs ) { + ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx ); + NA.ocs = NULL; + } + + if ( e.e_attrs ) { + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + } + + if( NA.txn != NULL ) { + NA.txn->execute( Rollback ); + NA.txn->close(); + } + + send_ldap_result( op, rs ); + slap_graduate_commit_csn( op ); + + if( new_dn.bv_val != NULL ) free( new_dn.bv_val ); + if( new_ndn.bv_val != NULL ) free( new_ndn.bv_val ); + + if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) { + slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *preread_ctrl, op->o_tmpmemctx ); + } + if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) { + slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *postread_ctrl, op->o_tmpmemctx ); + } + + rs->sr_text = NULL; + return rs->sr_err; +} diff --git a/servers/slapd/back-ndb/ndbio.cpp b/servers/slapd/back-ndb/ndbio.cpp new file mode 100644 index 0000000..3c1f568 --- /dev/null +++ b/servers/slapd/back-ndb/ndbio.cpp @@ -0,0 +1,1677 @@ +/* ndbio.cpp - get/set/del data for NDB */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/errno.h> +#include <lutil.h> + +#include "back-ndb.h" + +/* For reference only */ +typedef struct MedVar { + Int16 len; /* length is always little-endian */ + char buf[1024]; +} MedVar; + +extern "C" { + static int ndb_name_cmp( const void *v1, const void *v2 ); + static int ndb_oc_dup_err( void *v1, void *v2 ); +}; + +static int +ndb_name_cmp( const void *v1, const void *v2 ) +{ + NdbOcInfo *oc1 = (NdbOcInfo *)v1, *oc2 = (NdbOcInfo *)v2; + return ber_bvstrcasecmp( &oc1->no_name, &oc2->no_name ); +} + +static int +ndb_oc_dup_err( void *v1, void *v2 ) +{ + NdbOcInfo *oc = (NdbOcInfo *)v2; + + oc->no_oc = (ObjectClass *)v1; + return -1; +} + +/* Find an existing NdbAttrInfo */ +extern "C" NdbAttrInfo * +ndb_ai_find( struct ndb_info *ni, AttributeType *at ) +{ + NdbAttrInfo atmp; + atmp.na_name = at->sat_cname; + + return (NdbAttrInfo *)avl_find( ni->ni_ai_tree, &atmp, ndb_name_cmp ); +} + +/* Find or create an NdbAttrInfo */ +extern "C" NdbAttrInfo * +ndb_ai_get( struct ndb_info *ni, struct berval *aname ) +{ + NdbAttrInfo atmp, *ai; + atmp.na_name = *aname; + + ai = (NdbAttrInfo *)avl_find( ni->ni_ai_tree, &atmp, ndb_name_cmp ); + if ( !ai ) { + const char *text; + AttributeDescription *ad = NULL; + + if ( slap_bv2ad( aname, &ad, &text )) + return NULL; + + ai = (NdbAttrInfo *)ch_malloc( sizeof( NdbAttrInfo )); + ai->na_desc = ad; + ai->na_attr = ai->na_desc->ad_type; + ai->na_name = ai->na_attr->sat_cname; + ai->na_oi = NULL; + ai->na_flag = 0; + ai->na_ixcol = 0; + ai->na_len = ai->na_attr->sat_atype.at_syntax_len; + /* Reasonable default */ + if ( !ai->na_len ) { + if ( ai->na_attr->sat_syntax == slap_schema.si_syn_distinguishedName ) + ai->na_len = 1024; + else + ai->na_len = 128; + } + /* Arbitrary limit */ + if ( ai->na_len > 1024 ) + ai->na_len = 1024; + avl_insert( &ni->ni_ai_tree, ai, ndb_name_cmp, avl_dup_error ); + } + return ai; +} + +static int +ndb_ai_check( struct ndb_info *ni, NdbOcInfo *oci, AttributeType **attrs, char **ptr, int *col, + int create ) +{ + NdbAttrInfo *ai; + int i; + + for ( i=0; attrs[i]; i++ ) { + if ( attrs[i] == slap_schema.si_ad_objectClass->ad_type ) + continue; + /* skip attrs that are in a superior */ + if ( oci->no_oc && oci->no_oc->soc_sups ) { + int j, k, found=0; + ObjectClass *oc; + for ( j=0; oci->no_oc->soc_sups[j]; j++ ) { + oc = oci->no_oc->soc_sups[j]; + if ( oc->soc_kind == LDAP_SCHEMA_ABSTRACT ) + continue; + if ( oc->soc_required ) { + for ( k=0; oc->soc_required[k]; k++ ) { + if ( attrs[i] == oc->soc_required[k] ) { + found = 1; + break; + } + } + if ( found ) break; + } + if ( oc->soc_allowed ) { + for ( k=0; oc->soc_allowed[k]; k++ ) { + if ( attrs[i] == oc->soc_allowed[k] ) { + found = 1; + break; + } + } + if ( found ) break; + } + } + if ( found ) + continue; + } + + ai = ndb_ai_get( ni, &attrs[i]->sat_cname ); + if ( !ai ) { + /* can never happen */ + return LDAP_OTHER; + } + + /* An attrset may have already been connected */ + if (( oci->no_flag & NDB_INFO_ATSET ) && ai->na_oi == oci ) + continue; + + /* An indexed attr is defined before its OC is */ + if ( !ai->na_oi ) { + ai->na_oi = oci; + ai->na_column = (*col)++; + } + + oci->no_attrs[oci->no_nattrs++] = ai; + + /* An attrset attr may already be defined */ + if ( ai->na_oi != oci ) { + int j; + for ( j=0; j<oci->no_nsets; j++ ) + if ( oci->no_sets[j] == ai->na_oi ) break; + if ( j >= oci->no_nsets ) { + /* FIXME: data loss if more sets are in use */ + if ( oci->no_nsets < NDB_MAX_OCSETS ) { + oci->no_sets[oci->no_nsets++] = ai->na_oi; + } + } + continue; + } + + if ( create ) { + if ( ai->na_flag & NDB_INFO_ATBLOB ) { + *ptr += sprintf( *ptr, ", `%s` BLOB", ai->na_attr->sat_cname.bv_val ); + } else { + *ptr += sprintf( *ptr, ", `%s` VARCHAR(%d)", ai->na_attr->sat_cname.bv_val, + ai->na_len ); + } + } + } + return 0; +} + +static int +ndb_oc_create( struct ndb_info *ni, NdbOcInfo *oci, int create ) +{ + char buf[4096], *ptr; + int i, rc = 0, col; + + if ( create ) { + ptr = buf + sprintf( buf, + "CREATE TABLE `%s` (eid bigint unsigned NOT NULL, vid int unsigned NOT NULL", + oci->no_table.bv_val ); + } + + col = 0; + if ( oci->no_oc->soc_required ) { + for ( i=0; oci->no_oc->soc_required[i]; i++ ); + col += i; + } + if ( oci->no_oc->soc_allowed ) { + for ( i=0; oci->no_oc->soc_allowed[i]; i++ ); + col += i; + } + /* assume all are present */ + oci->no_attrs = (struct ndb_attrinfo **)ch_malloc( col * sizeof(struct ndb_attrinfo *)); + + col = 2; + ldap_pvt_thread_rdwr_wlock( &ni->ni_ai_rwlock ); + if ( oci->no_oc->soc_required ) { + rc = ndb_ai_check( ni, oci, oci->no_oc->soc_required, &ptr, &col, create ); + } + if ( !rc && oci->no_oc->soc_allowed ) { + rc = ndb_ai_check( ni, oci, oci->no_oc->soc_allowed, &ptr, &col, create ); + } + ldap_pvt_thread_rdwr_wunlock( &ni->ni_ai_rwlock ); + + /* shrink down to just the needed size */ + oci->no_attrs = (struct ndb_attrinfo **)ch_realloc( oci->no_attrs, + oci->no_nattrs * sizeof(struct ndb_attrinfo *)); + + if ( create ) { + ptr = lutil_strcopy( ptr, ", PRIMARY KEY(eid, vid) ) ENGINE=ndb PARTITION BY KEY(eid)" ); + rc = mysql_real_query( &ni->ni_sql, buf, ptr - buf ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "ndb_oc_create: CREATE TABLE %s failed, %s (%d)\n", + oci->no_table.bv_val, mysql_error(&ni->ni_sql), mysql_errno(&ni->ni_sql) ); + } + } + return rc; +} + +/* Read table definitions from the DB and populate ObjectClassInfo */ +extern "C" int +ndb_oc_read( struct ndb_info *ni, const NdbDictionary::Dictionary *myDict ) +{ + const NdbDictionary::Table *myTable; + const NdbDictionary::Column *myCol; + NdbOcInfo *oci, octmp; + NdbAttrInfo *ai; + ObjectClass *oc; + NdbDictionary::Dictionary::List myList; + struct berval bv; + int i, j, rc, col; + + rc = myDict->listObjects( myList, NdbDictionary::Object::UserTable ); + /* Populate our objectClass structures */ + for ( i=0; i<myList.count; i++ ) { + /* Ignore other DBs */ + if ( strcmp( myList.elements[i].database, ni->ni_dbname )) + continue; + /* Ignore internal tables */ + if ( !strncmp( myList.elements[i].name, "OL_", 3 )) + continue; + ber_str2bv( myList.elements[i].name, 0, 0, &octmp.no_name ); + oci = (NdbOcInfo *)avl_find( ni->ni_oc_tree, &octmp, ndb_name_cmp ); + if ( oci ) + continue; + + oc = oc_bvfind( &octmp.no_name ); + if ( !oc ) { + /* undefined - shouldn't happen */ + continue; + } + myTable = myDict->getTable( myList.elements[i].name ); + oci = (NdbOcInfo *)ch_malloc( sizeof( NdbOcInfo )+oc->soc_cname.bv_len+1 ); + oci->no_table.bv_val = (char *)(oci+1); + strcpy( oci->no_table.bv_val, oc->soc_cname.bv_val ); + oci->no_table.bv_len = oc->soc_cname.bv_len; + oci->no_name = oci->no_table; + oci->no_oc = oc; + oci->no_flag = 0; + oci->no_nsets = 0; + oci->no_nattrs = 0; + col = 0; + /* Make space for all attrs, even tho sups will be dropped */ + if ( oci->no_oc->soc_required ) { + for ( j=0; oci->no_oc->soc_required[j]; j++ ); + col = j; + } + if ( oci->no_oc->soc_allowed ) { + for ( j=0; oci->no_oc->soc_allowed[j]; j++ ); + col += j; + } + oci->no_attrs = (struct ndb_attrinfo **)ch_malloc( col * sizeof(struct ndb_attrinfo *)); + avl_insert( &ni->ni_oc_tree, oci, ndb_name_cmp, avl_dup_error ); + + col = myTable->getNoOfColumns(); + /* Skip 0 and 1, eid and vid */ + for ( j = 2; j<col; j++ ) { + myCol = myTable->getColumn( j ); + ber_str2bv( myCol->getName(), 0, 0, &bv ); + ai = ndb_ai_get( ni, &bv ); + /* shouldn't happen */ + if ( !ai ) + continue; + ai->na_oi = oci; + ai->na_column = j; + ai->na_len = myCol->getLength(); + if ( myCol->getType() == NdbDictionary::Column::Blob ) + ai->na_flag |= NDB_INFO_ATBLOB; + } + } + /* Link to any attrsets */ + for ( i=0; i<myList.count; i++ ) { + /* Ignore other DBs */ + if ( strcmp( myList.elements[i].database, ni->ni_dbname )) + continue; + /* Ignore internal tables */ + if ( !strncmp( myList.elements[i].name, "OL_", 3 )) + continue; + ber_str2bv( myList.elements[i].name, 0, 0, &octmp.no_name ); + oci = (NdbOcInfo *)avl_find( ni->ni_oc_tree, &octmp, ndb_name_cmp ); + /* shouldn't happen */ + if ( !oci ) + continue; + col = 2; + if ( oci->no_oc->soc_required ) { + rc = ndb_ai_check( ni, oci, oci->no_oc->soc_required, NULL, &col, 0 ); + } + if ( oci->no_oc->soc_allowed ) { + rc = ndb_ai_check( ni, oci, oci->no_oc->soc_allowed, NULL, &col, 0 ); + } + /* shrink down to just the needed size */ + oci->no_attrs = (struct ndb_attrinfo **)ch_realloc( oci->no_attrs, + oci->no_nattrs * sizeof(struct ndb_attrinfo *)); + } + return 0; +} + +static int +ndb_oc_list( struct ndb_info *ni, const NdbDictionary::Dictionary *myDict, + struct berval *oname, int implied, NdbOcs *out ) +{ + const NdbDictionary::Table *myTable; + NdbOcInfo *oci, octmp; + ObjectClass *oc; + int i, rc; + + /* shortcut top */ + if ( ber_bvstrcasecmp( oname, &slap_schema.si_oc_top->soc_cname )) { + octmp.no_name = *oname; + oci = (NdbOcInfo *)avl_find( ni->ni_oc_tree, &octmp, ndb_name_cmp ); + if ( oci ) { + oc = oci->no_oc; + } else { + oc = oc_bvfind( oname ); + if ( !oc ) { + /* undefined - shouldn't happen */ + return LDAP_INVALID_SYNTAX; + } + } + if ( oc->soc_sups ) { + int i; + + for ( i=0; oc->soc_sups[i]; i++ ) { + rc = ndb_oc_list( ni, myDict, &oc->soc_sups[i]->soc_cname, 1, out ); + if ( rc ) return rc; + } + } + } else { + oc = slap_schema.si_oc_top; + } + /* Only insert once */ + for ( i=0; i<out->no_ntext; i++ ) + if ( out->no_text[i].bv_val == oc->soc_cname.bv_val ) + break; + if ( i == out->no_ntext ) { + for ( i=0; i<out->no_nitext; i++ ) + if ( out->no_itext[i].bv_val == oc->soc_cname.bv_val ) + break; + if ( i == out->no_nitext ) { + if ( implied ) + out->no_itext[out->no_nitext++] = oc->soc_cname; + else + out->no_text[out->no_ntext++] = oc->soc_cname; + } + } + + /* ignore top, etc... */ + if ( oc->soc_kind == LDAP_SCHEMA_ABSTRACT ) + return 0; + + if ( !oci ) { + ldap_pvt_thread_rdwr_runlock( &ni->ni_oc_rwlock ); + oci = (NdbOcInfo *)ch_malloc( sizeof( NdbOcInfo )+oc->soc_cname.bv_len+1 ); + oci->no_table.bv_val = (char *)(oci+1); + strcpy( oci->no_table.bv_val, oc->soc_cname.bv_val ); + oci->no_table.bv_len = oc->soc_cname.bv_len; + oci->no_name = oci->no_table; + oci->no_oc = oc; + oci->no_flag = 0; + oci->no_nsets = 0; + oci->no_nattrs = 0; + ldap_pvt_thread_rdwr_wlock( &ni->ni_oc_rwlock ); + if ( avl_insert( &ni->ni_oc_tree, oci, ndb_name_cmp, ndb_oc_dup_err )) { + octmp.no_oc = oci->no_oc; + ch_free( oci ); + oci = (NdbOcInfo *)octmp.no_oc; + } + /* see if the oc table already exists in the DB */ + myTable = myDict->getTable( oci->no_table.bv_val ); + rc = ndb_oc_create( ni, oci, myTable == NULL ); + ldap_pvt_thread_rdwr_wunlock( &ni->ni_oc_rwlock ); + ldap_pvt_thread_rdwr_rlock( &ni->ni_oc_rwlock ); + if ( rc ) return rc; + } + /* Only insert once */ + for ( i=0; i<out->no_ninfo; i++ ) + if ( out->no_info[i] == oci ) + break; + if ( i == out->no_ninfo ) + out->no_info[out->no_ninfo++] = oci; + return 0; +} + +extern "C" int +ndb_aset_get( struct ndb_info *ni, struct berval *sname, struct berval *attrs, NdbOcInfo **ret ) +{ + NdbOcInfo *oci, octmp; + int i, rc; + + octmp.no_name = *sname; + oci = (NdbOcInfo *)avl_find( ni->ni_oc_tree, &octmp, ndb_name_cmp ); + if ( oci ) + return LDAP_ALREADY_EXISTS; + + for ( i=0; !BER_BVISNULL( &attrs[i] ); i++ ) { + if ( !at_bvfind( &attrs[i] )) + return LDAP_NO_SUCH_ATTRIBUTE; + } + i++; + + oci = (NdbOcInfo *)ch_calloc( 1, sizeof( NdbOcInfo ) + sizeof( ObjectClass ) + + i*sizeof(AttributeType *) + sname->bv_len+1 ); + oci->no_oc = (ObjectClass *)(oci+1); + oci->no_oc->soc_required = (AttributeType **)(oci->no_oc+1); + oci->no_table.bv_val = (char *)(oci->no_oc->soc_required+i); + + for ( i=0; !BER_BVISNULL( &attrs[i] ); i++ ) + oci->no_oc->soc_required[i] = at_bvfind( &attrs[i] ); + + strcpy( oci->no_table.bv_val, sname->bv_val ); + oci->no_table.bv_len = sname->bv_len; + oci->no_name = oci->no_table; + oci->no_oc->soc_cname = oci->no_name; + oci->no_flag = NDB_INFO_ATSET; + + if ( !ber_bvcmp( sname, &slap_schema.si_oc_extensibleObject->soc_cname )) + oci->no_oc->soc_kind = slap_schema.si_oc_extensibleObject->soc_kind; + + rc = ndb_oc_create( ni, oci, 0 ); + if ( !rc ) + rc = avl_insert( &ni->ni_oc_tree, oci, ndb_name_cmp, avl_dup_error ); + if ( rc ) { + ch_free( oci ); + } else { + *ret = oci; + } + return rc; +} + +extern "C" int +ndb_aset_create( struct ndb_info *ni, NdbOcInfo *oci ) +{ + char buf[4096], *ptr; + NdbAttrInfo *ai; + int i; + + ptr = buf + sprintf( buf, + "CREATE TABLE IF NOT EXISTS `%s` (eid bigint unsigned NOT NULL, vid int unsigned NOT NULL", + oci->no_table.bv_val ); + + for ( i=0; i<oci->no_nattrs; i++ ) { + if ( oci->no_attrs[i]->na_oi != oci ) + continue; + ai = oci->no_attrs[i]; + ptr += sprintf( ptr, ", `%s` VARCHAR(%d)", ai->na_attr->sat_cname.bv_val, + ai->na_len ); + if ( ai->na_flag & NDB_INFO_INDEX ) { + ptr += sprintf( ptr, ", INDEX (`%s`)", ai->na_attr->sat_cname.bv_val ); + } + } + ptr = lutil_strcopy( ptr, ", PRIMARY KEY(eid, vid) ) ENGINE=ndb PARTITION BY KEY(eid)" ); + i = mysql_real_query( &ni->ni_sql, buf, ptr - buf ); + if ( i ) { + Debug( LDAP_DEBUG_ANY, + "ndb_aset_create: CREATE TABLE %s failed, %s (%d)\n", + oci->no_table.bv_val, mysql_error(&ni->ni_sql), mysql_errno(&ni->ni_sql) ); + } + return i; +} + +static int +ndb_oc_check( BackendDB *be, Ndb *ndb, + struct berval *ocsin, NdbOcs *out ) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + const NdbDictionary::Dictionary *myDict = ndb->getDictionary(); + + int i, rc = 0; + + out->no_ninfo = 0; + out->no_ntext = 0; + out->no_nitext = 0; + + /* Find all objectclasses and their superiors. List + * the superiors first. + */ + + ldap_pvt_thread_rdwr_rlock( &ni->ni_oc_rwlock ); + for ( i=0; !BER_BVISNULL( &ocsin[i] ); i++ ) { + rc = ndb_oc_list( ni, myDict, &ocsin[i], 0, out ); + if ( rc ) break; + } + ldap_pvt_thread_rdwr_runlock( &ni->ni_oc_rwlock ); + return rc; +} + +#define V_INS 1 +#define V_DEL 2 +#define V_REP 3 + +static int ndb_flush_blobs; + +/* set all the unique attrs of this objectclass into the table + */ +extern "C" int +ndb_oc_attrs( + NdbTransaction *txn, + const NdbDictionary::Table *myTable, + Entry *e, + NdbOcInfo *no, + NdbAttrInfo **attrs, + int nattrs, + Attribute *old +) +{ + char buf[65538], *ptr; + Attribute **an, **ao, *a; + NdbOperation *myop; + int i, j, max = 0; + int changed, rc; + Uint64 eid = e->e_id; + + if ( !nattrs ) + return 0; + + an = (Attribute **)ch_malloc( 2 * nattrs * sizeof(Attribute *)); + ao = an + nattrs; + + /* Turn lists of attrs into arrays for easier access */ + for ( i=0; i<nattrs; i++ ) { + if ( attrs[i]->na_oi != no ) { + an[i] = NULL; + ao[i] = NULL; + continue; + } + for ( a=e->e_attrs; a; a=a->a_next ) { + if ( a->a_desc == slap_schema.si_ad_objectClass ) + continue; + if ( a->a_desc->ad_type == attrs[i]->na_attr ) { + /* Don't process same attr twice */ + if ( a->a_flags & SLAP_ATTR_IXADD ) + a = NULL; + else + a->a_flags |= SLAP_ATTR_IXADD; + break; + } + } + an[i] = a; + if ( a && a->a_numvals > max ) + max = a->a_numvals; + for ( a=old; a; a=a->a_next ) { + if ( a->a_desc == slap_schema.si_ad_objectClass ) + continue; + if ( a->a_desc->ad_type == attrs[i]->na_attr ) + break; + } + ao[i] = a; + if ( a && a->a_numvals > max ) + max = a->a_numvals; + } + + for ( i=0; i<max; i++ ) { + myop = NULL; + for ( j=0; j<nattrs; j++ ) { + if ( !an[j] && !ao[j] ) + continue; + changed = 0; + if ( an[j] && an[j]->a_numvals > i ) { + /* both old and new are present, compare for changes */ + if ( ao[j] && ao[j]->a_numvals > i ) { + if ( ber_bvcmp( &ao[j]->a_nvals[i], &an[j]->a_nvals[i] )) + changed = V_REP; + } else { + changed = V_INS; + } + } else { + if ( ao[j] && ao[j]->a_numvals > i ) + changed = V_DEL; + } + if ( changed ) { + if ( !myop ) { + rc = LDAP_OTHER; + myop = txn->getNdbOperation( myTable ); + if ( !myop ) { + goto done; + } + if ( old ) { + if ( myop->writeTuple()) { + goto done; + } + } else { + if ( myop->insertTuple()) { + goto done; + } + } + if ( myop->equal( EID_COLUMN, eid )) { + goto done; + } + if ( myop->equal( VID_COLUMN, i )) { + goto done; + } + } + if ( attrs[j]->na_flag & NDB_INFO_ATBLOB ) { + NdbBlob *myBlob = myop->getBlobHandle( attrs[j]->na_column ); + rc = LDAP_OTHER; + if ( !myBlob ) { + Debug( LDAP_DEBUG_TRACE, "ndb_oc_attrs: getBlobHandle failed %s (%d)\n", + myop->getNdbError().message, myop->getNdbError().code, 0 ); + goto done; + } + if ( slapMode & SLAP_TOOL_MODE ) + ndb_flush_blobs = 1; + if ( changed & V_INS ) { + if ( myBlob->setValue( an[j]->a_vals[i].bv_val, an[j]->a_vals[i].bv_len )) { + Debug( LDAP_DEBUG_TRACE, "ndb_oc_attrs: blob->setValue failed %s (%d)\n", + myBlob->getNdbError().message, myBlob->getNdbError().code, 0 ); + goto done; + } + } else { + if ( myBlob->setValue( NULL, 0 )) { + Debug( LDAP_DEBUG_TRACE, "ndb_oc_attrs: blob->setValue failed %s (%d)\n", + myBlob->getNdbError().message, myBlob->getNdbError().code, 0 ); + goto done; + } + } + } else { + if ( changed & V_INS ) { + if ( an[j]->a_vals[i].bv_len > attrs[j]->na_len ) { + Debug( LDAP_DEBUG_ANY, "ndb_oc_attrs: attribute %s too long for column\n", + attrs[j]->na_name.bv_val, 0, 0 ); + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + ptr = buf; + *ptr++ = an[j]->a_vals[i].bv_len & 0xff; + if ( attrs[j]->na_len > 255 ) { + /* MedVar */ + *ptr++ = an[j]->a_vals[i].bv_len >> 8; + } + memcpy( ptr, an[j]->a_vals[i].bv_val, an[j]->a_vals[i].bv_len ); + ptr = buf; + } else { + ptr = NULL; + } + if ( myop->setValue( attrs[j]->na_column, ptr )) { + rc = LDAP_OTHER; + goto done; + } + } + } + } + } + rc = LDAP_SUCCESS; +done: + ch_free( an ); + if ( rc ) { + Debug( LDAP_DEBUG_TRACE, "ndb_oc_attrs: failed %s (%d)\n", + myop->getNdbError().message, myop->getNdbError().code, 0 ); + } + return rc; +} + +static int +ndb_oc_put( + const NdbDictionary::Dictionary *myDict, + NdbTransaction *txn, NdbOcInfo *no, Entry *e ) +{ + const NdbDictionary::Table *myTable; + int i, rc; + + for ( i=0; i<no->no_nsets; i++ ) { + rc = ndb_oc_put( myDict, txn, no->no_sets[i], e ); + if ( rc ) + return rc; + } + + myTable = myDict->getTable( no->no_table.bv_val ); + if ( !myTable ) + return LDAP_OTHER; + + return ndb_oc_attrs( txn, myTable, e, no, no->no_attrs, no->no_nattrs, NULL ); +} + +/* This is now only used for Adds. Modifies call ndb_oc_attrs directly. */ +extern "C" int +ndb_entry_put_data( + BackendDB *be, + NdbArgs *NA +) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + Attribute *aoc; + const NdbDictionary::Dictionary *myDict = NA->ndb->getDictionary(); + NdbOcs myOcs; + int i, rc; + + /* Get the entry's objectClass attribute */ + aoc = attr_find( NA->e->e_attrs, slap_schema.si_ad_objectClass ); + if ( !aoc ) + return LDAP_OTHER; + + ndb_oc_check( be, NA->ndb, aoc->a_nvals, &myOcs ); + myOcs.no_info[myOcs.no_ninfo++] = ni->ni_opattrs; + + /* Walk thru objectclasses, find all the attributes belonging to a class */ + for ( i=0; i<myOcs.no_ninfo; i++ ) { + rc = ndb_oc_put( myDict, NA->txn, myOcs.no_info[i], NA->e ); + if ( rc ) return rc; + } + + /* slapadd tries to batch multiple entries per txn, but entry data is + * transient and blob data is required to remain valid for the whole txn. + * So we need to flush blobs before their source data disappears. + */ + if (( slapMode & SLAP_TOOL_MODE ) && ndb_flush_blobs ) + NA->txn->execute( NdbTransaction::NoCommit ); + + return 0; +} + +static void +ndb_oc_get( Operation *op, NdbOcInfo *no, int *j, int *nocs, NdbOcInfo ***oclist ) +{ + int i; + NdbOcInfo **ol2; + + for ( i=0; i<no->no_nsets; i++ ) { + ndb_oc_get( op, no->no_sets[i], j, nocs, oclist ); + } + + /* Don't insert twice */ + ol2 = *oclist; + for ( i=0; i<*j; i++ ) + if ( ol2[i] == no ) + return; + + if ( *j >= *nocs ) { + *nocs *= 2; + ol2 = (NdbOcInfo **)op->o_tmprealloc( *oclist, *nocs * sizeof(NdbOcInfo *), op->o_tmpmemctx ); + *oclist = ol2; + } + ol2 = *oclist; + ol2[(*j)++] = no; +} + +/* Retrieve attribute data for given entry. The entry's DN and eid should + * already be populated. + */ +extern "C" int +ndb_entry_get_data( + Operation *op, + NdbArgs *NA, + int update +) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + const NdbDictionary::Dictionary *myDict = NA->ndb->getDictionary(); + const NdbDictionary::Table *myTable; + NdbIndexScanOperation **myop = NULL; + Uint64 eid; + + Attribute *a; + NdbOcs myOcs; + NdbOcInfo *oci, **oclist = NULL; + char abuf[65536], *ptr, **attrs = NULL; + struct berval bv[2]; + int *ocx = NULL; + + /* FIXME: abuf should be dynamically allocated */ + + int i, j, k, nocs, nattrs, rc = LDAP_OTHER; + + eid = NA->e->e_id; + + ndb_oc_check( op->o_bd, NA->ndb, NA->ocs, &myOcs ); + myOcs.no_info[myOcs.no_ninfo++] = ni->ni_opattrs; + nocs = myOcs.no_ninfo; + + oclist = (NdbOcInfo **)op->o_tmpcalloc( 1, nocs * sizeof(NdbOcInfo *), op->o_tmpmemctx ); + + for ( i=0, j=0; i<myOcs.no_ninfo; i++ ) { + ndb_oc_get( op, myOcs.no_info[i], &j, &nocs, &oclist ); + } + + nocs = j; + nattrs = 0; + for ( i=0; i<nocs; i++ ) + nattrs += oclist[i]->no_nattrs; + + ocx = (int *)op->o_tmpalloc( nocs * sizeof(int), op->o_tmpmemctx ); + + attrs = (char **)op->o_tmpalloc( nattrs * sizeof(char *), op->o_tmpmemctx ); + + myop = (NdbIndexScanOperation **)op->o_tmpalloc( nattrs * sizeof(NdbIndexScanOperation *), op->o_tmpmemctx ); + + k = 0; + ptr = abuf; + for ( i=0; i<nocs; i++ ) { + oci = oclist[i]; + + myop[i] = NA->txn->getNdbIndexScanOperation( "PRIMARY", oci->no_table.bv_val ); + if ( !myop[i] ) + goto leave; + if ( myop[i]->readTuples( update ? NdbOperation::LM_Exclusive : NdbOperation::LM_CommittedRead )) + goto leave; + if ( myop[i]->setBound( 0U, NdbIndexScanOperation::BoundEQ, &eid )) + goto leave; + + for ( j=0; j<oci->no_nattrs; j++ ) { + if ( oci->no_attrs[j]->na_oi != oci ) + continue; + if ( oci->no_attrs[j]->na_flag & NDB_INFO_ATBLOB ) { + NdbBlob *bi = myop[i]->getBlobHandle( oci->no_attrs[j]->na_column ); + attrs[k++] = (char *)bi; + } else { + attrs[k] = ptr; + *ptr++ = 0; + if ( oci->no_attrs[j]->na_len > 255 ) + *ptr++ = 0; + ptr += oci->no_attrs[j]->na_len + 1; + myop[i]->getValue( oci->no_attrs[j]->na_column, attrs[k++] ); + } + } + ocx[i] = k; + } + /* Must use IgnoreError, because an entry with multiple objectClasses may not + * actually have attributes defined in each class / table. + */ + if ( NA->txn->execute( NdbTransaction::NoCommit, NdbOperation::AO_IgnoreError, 1) < 0 ) + goto leave; + + /* count results */ + for ( i=0; i<nocs; i++ ) { + if (( j = myop[i]->nextResult(true) )) { + if ( j < 0 ) { + Debug( LDAP_DEBUG_TRACE, + "ndb_entry_get_data: first nextResult(%d) failed: %s (%d)\n", + i, myop[i]->getNdbError().message, myop[i]->getNdbError().code ); + } + myop[i] = NULL; + } + } + + nattrs = 0; + k = 0; + for ( i=0; i<nocs; i++ ) { + oci = oclist[i]; + for ( j=0; j<oci->no_nattrs; j++ ) { + unsigned char *buf; + int len; + if ( oci->no_attrs[j]->na_oi != oci ) + continue; + if ( !myop[i] ) { + attrs[k] = NULL; + } else if ( oci->no_attrs[j]->na_flag & NDB_INFO_ATBLOB ) { + void *vi = attrs[k]; + NdbBlob *bi = (NdbBlob *)vi; + int isNull; + bi->getNull( isNull ); + if ( !isNull ) { + nattrs++; + } else { + attrs[k] = NULL; + } + } else { + buf = (unsigned char *)attrs[k]; + len = buf[0]; + if ( oci->no_attrs[j]->na_len > 255 ) { + /* MedVar */ + len |= (buf[1] << 8); + } + if ( len ) { + nattrs++; + } else { + attrs[k] = NULL; + } + } + k++; + } + } + + a = attrs_alloc( nattrs+1 ); + NA->e->e_attrs = a; + + a->a_desc = slap_schema.si_ad_objectClass; + a->a_vals = NULL; + ber_bvarray_dup_x( &a->a_vals, NA->ocs, NULL ); + a->a_nvals = a->a_vals; + a->a_numvals = myOcs.no_ntext; + + BER_BVZERO( &bv[1] ); + + do { + a = NA->e->e_attrs->a_next; + k = 0; + for ( i=0; i<nocs; k=ocx[i], i++ ) { + oci = oclist[i]; + for ( j=0; j<oci->no_nattrs; j++ ) { + unsigned char *buf; + struct berval nbv; + if ( oci->no_attrs[j]->na_oi != oci ) + continue; + buf = (unsigned char *)attrs[k++]; + if ( !buf ) + continue; + if ( !myop[i] ) { + a=a->a_next; + continue; + } + if ( oci->no_attrs[j]->na_flag & NDB_INFO_ATBLOB ) { + void *vi = (void *)buf; + NdbBlob *bi = (NdbBlob *)vi; + Uint64 len; + Uint32 len2; + int isNull; + bi->getNull( isNull ); + if ( isNull ) { + a = a->a_next; + continue; + } + bi->getLength( len ); + bv[0].bv_len = len; + bv[0].bv_val = (char *)ch_malloc( len+1 ); + len2 = len; + if ( bi->readData( bv[0].bv_val, len2 )) { + Debug( LDAP_DEBUG_TRACE, + "ndb_entry_get_data: blob readData failed: %s (%d), len %d\n", + bi->getNdbError().message, bi->getNdbError().code, len2 ); + } + bv[0].bv_val[len] = '\0'; + ber_bvarray_add_x( &a->a_vals, bv, NULL ); + } else { + bv[0].bv_len = buf[0]; + if ( oci->no_attrs[j]->na_len > 255 ) { + /* MedVar */ + bv[0].bv_len |= (buf[1] << 8); + bv[0].bv_val = (char *)buf+2; + buf[1] = 0; + } else { + bv[0].bv_val = (char *)buf+1; + } + buf[0] = 0; + if ( bv[0].bv_len == 0 ) { + a = a->a_next; + continue; + } + bv[0].bv_val[bv[0].bv_len] = '\0'; + value_add_one( &a->a_vals, bv ); + } + a->a_desc = oci->no_attrs[j]->na_desc; + attr_normalize_one( a->a_desc, bv, &nbv, NULL ); + a->a_numvals++; + if ( !BER_BVISNULL( &nbv )) { + ber_bvarray_add_x( &a->a_nvals, &nbv, NULL ); + } else if ( !a->a_nvals ) { + a->a_nvals = a->a_vals; + } + a = a->a_next; + } + } + k = 0; + for ( i=0; i<nocs; i++ ) { + if ( !myop[i] ) + continue; + if ((j = myop[i]->nextResult(true))) { + if ( j < 0 ) { + Debug( LDAP_DEBUG_TRACE, + "ndb_entry_get_data: last nextResult(%d) failed: %s (%d)\n", + i, myop[i]->getNdbError().message, myop[i]->getNdbError().code ); + } + myop[i] = NULL; + } else { + k = 1; + } + } + } while ( k ); + + rc = 0; +leave: + if ( myop ) { + op->o_tmpfree( myop, op->o_tmpmemctx ); + } + if ( attrs ) { + op->o_tmpfree( attrs, op->o_tmpmemctx ); + } + if ( ocx ) { + op->o_tmpfree( ocx, op->o_tmpmemctx ); + } + if ( oclist ) { + op->o_tmpfree( oclist, op->o_tmpmemctx ); + } + + return rc; +} + +static int +ndb_oc_del( + NdbTransaction *txn, Uint64 eid, NdbOcInfo *no ) +{ + NdbIndexScanOperation *myop; + int i, rc; + + for ( i=0; i<no->no_nsets; i++ ) { + rc = ndb_oc_del( txn, eid, no->no_sets[i] ); + if ( rc ) return rc; + } + + myop = txn->getNdbIndexScanOperation( "PRIMARY", no->no_table.bv_val ); + if ( !myop ) + return LDAP_OTHER; + if ( myop->readTuples( NdbOperation::LM_Exclusive )) + return LDAP_OTHER; + if ( myop->setBound( 0U, NdbIndexScanOperation::BoundEQ, &eid )) + return LDAP_OTHER; + + txn->execute(NoCommit); + while ( myop->nextResult(true) == 0) { + do { + myop->deleteCurrentTuple(); + } while (myop->nextResult(false) == 0); + txn->execute(NoCommit); + } + + return 0; +} + +extern "C" int +ndb_entry_del_data( + BackendDB *be, + NdbArgs *NA +) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + Uint64 eid = NA->e->e_id; + int i; + NdbOcs myOcs; + + ndb_oc_check( be, NA->ndb, NA->ocs, &myOcs ); + myOcs.no_info[myOcs.no_ninfo++] = ni->ni_opattrs; + + for ( i=0; i<myOcs.no_ninfo; i++ ) { + if ( ndb_oc_del( NA->txn, eid, myOcs.no_info[i] )) + return LDAP_OTHER; + } + + return 0; +} + +extern "C" int +ndb_dn2rdns( + struct berval *dn, + NdbRdns *rdns +) +{ + char *beg, *end; + int i, len; + + /* Walk thru RDNs */ + end = dn->bv_val + dn->bv_len; + for ( i=0; i<NDB_MAX_RDNS; i++ ) { + for ( beg = end-1; beg > dn->bv_val; beg-- ) { + if (*beg == ',') { + beg++; + break; + } + } + if ( beg >= dn->bv_val ) { + len = end - beg; + /* RDN is too long */ + if ( len > NDB_RDN_LEN ) + return LDAP_CONSTRAINT_VIOLATION; + memcpy( rdns->nr_buf[i]+1, beg, len ); + } else { + break; + } + rdns->nr_buf[i][0] = len; + end = beg - 1; + } + /* Too many RDNs in DN */ + if ( i == NDB_MAX_RDNS && beg > dn->bv_val ) { + return LDAP_CONSTRAINT_VIOLATION; + } + rdns->nr_num = i; + return 0; +} + +static int +ndb_rdns2keys( + NdbOperation *myop, + NdbRdns *rdns +) +{ + int i; + char dummy[2] = {0,0}; + + /* Walk thru RDNs */ + for ( i=0; i<rdns->nr_num; i++ ) { + if ( myop->equal( i+RDN_COLUMN, rdns->nr_buf[i] )) + return LDAP_OTHER; + } + for ( ; i<NDB_MAX_RDNS; i++ ) { + if ( myop->equal( i+RDN_COLUMN, dummy )) + return LDAP_OTHER; + } + return 0; +} + +/* Store the DN2ID_TABLE fields */ +extern "C" int +ndb_entry_put_info( + BackendDB *be, + NdbArgs *NA, + int update +) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + const NdbDictionary::Dictionary *myDict = NA->ndb->getDictionary(); + const NdbDictionary::Table *myTable = myDict->getTable( DN2ID_TABLE ); + NdbOperation *myop; + NdbAttrInfo *ai; + Attribute *aoc, *a; + + /* Get the entry's objectClass attribute; it's ok to be + * absent on a fresh insert + */ + aoc = attr_find( NA->e->e_attrs, slap_schema.si_ad_objectClass ); + if ( update && !aoc ) + return LDAP_OBJECT_CLASS_VIOLATION; + + myop = NA->txn->getNdbOperation( myTable ); + if ( !myop ) + return LDAP_OTHER; + if ( update ) { + if ( myop->updateTuple()) + return LDAP_OTHER; + } else { + if ( myop->insertTuple()) + return LDAP_OTHER; + } + + if ( ndb_rdns2keys( myop, NA->rdns )) + return LDAP_OTHER; + + /* Set entry ID */ + { + Uint64 eid = NA->e->e_id; + if ( myop->setValue( EID_COLUMN, eid )) + return LDAP_OTHER; + } + + /* Set list of objectClasses */ + /* List is <sp> <class> <sp> <class> <sp> ... so that + * searches for " class " will yield accurate results + */ + if ( aoc ) { + char *ptr, buf[sizeof(MedVar)]; + NdbOcs myOcs; + int i; + + ndb_oc_check( be, NA->ndb, aoc->a_nvals, &myOcs ); + ptr = buf+2; + *ptr++ = ' '; + for ( i=0; i<myOcs.no_ntext; i++ ) { + /* data loss... */ + if ( ptr + myOcs.no_text[i].bv_len + 1 >= &buf[sizeof(buf)] ) + break; + ptr = lutil_strcopy( ptr, myOcs.no_text[i].bv_val ); + *ptr++ = ' '; + } + + /* implicit classes */ + if ( myOcs.no_nitext ) { + *ptr++ = '@'; + *ptr++ = ' '; + for ( i=0; i<myOcs.no_nitext; i++ ) { + /* data loss... */ + if ( ptr + myOcs.no_itext[i].bv_len + 1 >= &buf[sizeof(buf)] ) + break; + ptr = lutil_strcopy( ptr, myOcs.no_itext[i].bv_val ); + *ptr++ = ' '; + } + } + + i = ptr - buf - 2; + buf[0] = i & 0xff; + buf[1] = i >> 8; + if ( myop->setValue( OCS_COLUMN, buf )) + return LDAP_OTHER; + } + + /* Set any indexed attrs */ + for ( a = NA->e->e_attrs; a; a=a->a_next ) { + ai = ndb_ai_find( ni, a->a_desc->ad_type ); + if ( ai && ( ai->na_flag & NDB_INFO_INDEX )) { + char *ptr, buf[sizeof(MedVar)]; + int len; + + ptr = buf+1; + len = a->a_vals[0].bv_len; + /* FIXME: data loss */ + if ( len > ai->na_len ) + len = ai->na_len; + buf[0] = len & 0xff; + if ( ai->na_len > 255 ) { + *ptr++ = len >> 8; + } + memcpy( ptr, a->a_vals[0].bv_val, len ); + if ( myop->setValue( ai->na_ixcol, buf )) + return LDAP_OTHER; + } + } + + return 0; +} + +extern "C" struct berval * +ndb_str2bvarray( + char *str, + int len, + char delim, + void *ctx +) +{ + struct berval *list, tmp; + char *beg; + int i, num; + + while ( *str == delim ) { + str++; + len--; + } + + while ( str[len-1] == delim ) { + str[--len] = '\0'; + } + + for ( i = 1, beg = str;; i++ ) { + beg = strchr( beg, delim ); + if ( !beg ) + break; + if ( beg >= str + len ) + break; + beg++; + } + + num = i; + list = (struct berval *)slap_sl_malloc( (num+1)*sizeof(struct berval), ctx); + + for ( i = 0, beg = str; i<num; i++ ) { + tmp.bv_val = beg; + beg = strchr( beg, delim ); + if ( beg >= str + len ) + beg = NULL; + if ( beg ) { + tmp.bv_len = beg - tmp.bv_val; + } else { + tmp.bv_len = len - (tmp.bv_val - str); + } + ber_dupbv_x( &list[i], &tmp, ctx ); + beg++; + } + + BER_BVZERO( &list[i] ); + return list; +} + +extern "C" struct berval * +ndb_ref2oclist( + const char *ref, + void *ctx +) +{ + char *implied; + + /* MedVar */ + int len = ref[0] | (ref[1] << 8); + + /* don't return the implied classes */ + implied = (char *)memchr( ref+2, '@', len ); + if ( implied ) { + len = implied - ref - 2; + *implied = '\0'; + } + + return ndb_str2bvarray( (char *)ref+2, len, ' ', ctx ); +} + +/* Retrieve the DN2ID_TABLE fields. Can call with NULL ocs if just verifying + * the existence of a DN. + */ +extern "C" int +ndb_entry_get_info( + Operation *op, + NdbArgs *NA, + int update, + struct berval *matched +) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + const NdbDictionary::Dictionary *myDict = NA->ndb->getDictionary(); + const NdbDictionary::Table *myTable = myDict->getTable( DN2ID_TABLE ); + NdbOperation *myop[NDB_MAX_RDNS]; + NdbRecAttr *eid[NDB_MAX_RDNS], *oc[NDB_MAX_RDNS]; + char idbuf[NDB_MAX_RDNS][2*sizeof(ID)]; + char ocbuf[NDB_MAX_RDNS][NDB_OC_BUFLEN]; + + if ( matched ) { + BER_BVZERO( matched ); + } + if ( !myTable ) { + return LDAP_OTHER; + } + + myop[0] = NA->txn->getNdbOperation( myTable ); + if ( !myop[0] ) { + return LDAP_OTHER; + } + + if ( myop[0]->readTuple( update ? NdbOperation::LM_Exclusive : NdbOperation::LM_CommittedRead )) { + return LDAP_OTHER; + } + + if ( !NA->rdns->nr_num && ndb_dn2rdns( &NA->e->e_name, NA->rdns )) { + return LDAP_NO_SUCH_OBJECT; + } + + if ( ndb_rdns2keys( myop[0], NA->rdns )) { + return LDAP_OTHER; + } + + eid[0] = myop[0]->getValue( EID_COLUMN, idbuf[0] ); + if ( !eid[0] ) { + return LDAP_OTHER; + } + + ocbuf[0][0] = 0; + ocbuf[0][1] = 0; + if ( !NA->ocs ) { + oc[0] = myop[0]->getValue( OCS_COLUMN, ocbuf[0] ); + if ( !oc[0] ) { + return LDAP_OTHER; + } + } + + if ( NA->txn->execute(NdbTransaction::NoCommit, NdbOperation::AO_IgnoreError, 1) < 0 ) { + return LDAP_OTHER; + } + + switch( myop[0]->getNdbError().code ) { + case 0: + if ( !eid[0]->isNULL() && ( NA->e->e_id = eid[0]->u_64_value() )) { + /* If we didn't care about OCs, or we got them */ + if ( NA->ocs || ocbuf[0][0] || ocbuf[0][1] ) { + /* If wanted, return them */ + if ( !NA->ocs ) + NA->ocs = ndb_ref2oclist( ocbuf[0], op->o_tmpmemctx ); + break; + } + } + /* FALLTHRU */ + case NDB_NO_SUCH_OBJECT: /* no such tuple: look for closest parent */ + if ( matched ) { + int i, j, k; + char dummy[2] = {0,0}; + + /* get to last RDN, then back up 1 */ + k = NA->rdns->nr_num - 1; + + for ( i=0; i<k; i++ ) { + myop[i] = NA->txn->getNdbOperation( myTable ); + if ( !myop[i] ) + return LDAP_OTHER; + if ( myop[i]->readTuple( NdbOperation::LM_CommittedRead )) + return LDAP_OTHER; + for ( j=0; j<=i; j++ ) { + if ( myop[i]->equal( j+RDN_COLUMN, NA->rdns->nr_buf[j] )) + return LDAP_OTHER; + } + for ( ;j<NDB_MAX_RDNS; j++ ) { + if ( myop[i]->equal( j+RDN_COLUMN, dummy )) + return LDAP_OTHER; + } + eid[i] = myop[i]->getValue( EID_COLUMN, idbuf[i] ); + if ( !eid[i] ) { + return LDAP_OTHER; + } + ocbuf[i][0] = 0; + ocbuf[i][1] = 0; + if ( !NA->ocs ) { + oc[i] = myop[0]->getValue( OCS_COLUMN, ocbuf[i] ); + if ( !oc[i] ) { + return LDAP_OTHER; + } + } + } + if ( NA->txn->execute(NdbTransaction::NoCommit, NdbOperation::AO_IgnoreError, 1) < 0 ) { + return LDAP_OTHER; + } + for ( --i; i>=0; i-- ) { + if ( myop[i]->getNdbError().code == 0 ) { + for ( j=0; j<=i; j++ ) + matched->bv_len += NA->rdns->nr_buf[j][0]; + NA->erdns = NA->rdns->nr_num; + NA->rdns->nr_num = j; + matched->bv_len += i; + matched->bv_val = NA->e->e_name.bv_val + + NA->e->e_name.bv_len - matched->bv_len; + if ( !eid[i]->isNULL() ) + NA->e->e_id = eid[i]->u_64_value(); + if ( !NA->ocs ) + NA->ocs = ndb_ref2oclist( ocbuf[i], op->o_tmpmemctx ); + break; + } + } + } + return LDAP_NO_SUCH_OBJECT; + default: + return LDAP_OTHER; + } + + return 0; +} + +extern "C" int +ndb_entry_del_info( + BackendDB *be, + NdbArgs *NA +) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + const NdbDictionary::Dictionary *myDict = NA->ndb->getDictionary(); + const NdbDictionary::Table *myTable = myDict->getTable( DN2ID_TABLE ); + NdbOperation *myop; + + myop = NA->txn->getNdbOperation( myTable ); + if ( !myop ) + return LDAP_OTHER; + if ( myop->deleteTuple()) + return LDAP_OTHER; + + if ( ndb_rdns2keys( myop, NA->rdns )) + return LDAP_OTHER; + + return 0; +} + +extern "C" int +ndb_next_id( + BackendDB *be, + Ndb *ndb, + ID *id +) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + const NdbDictionary::Dictionary *myDict = ndb->getDictionary(); + const NdbDictionary::Table *myTable = myDict->getTable( NEXTID_TABLE ); + Uint64 nid = 0; + int rc; + + if ( !myTable ) { + Debug( LDAP_DEBUG_ANY, "ndb_next_id: " NEXTID_TABLE " table is missing\n", + 0, 0, 0 ); + return LDAP_OTHER; + } + + rc = ndb->getAutoIncrementValue( myTable, nid, 1000 ); + if ( !rc ) + *id = nid; + return rc; +} + +extern "C" { static void ndb_thread_hfree( void *key, void *data ); }; +static void +ndb_thread_hfree( void *key, void *data ) +{ + Ndb *ndb = (Ndb *)data; + delete ndb; +} + +extern "C" int +ndb_thread_handle( + Operation *op, + Ndb **ndb ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + void *data; + + if ( ldap_pvt_thread_pool_getkey( op->o_threadctx, ni, &data, NULL )) { + Ndb *myNdb; + int rc; + ldap_pvt_thread_mutex_lock( &ni->ni_conn_mutex ); + myNdb = new Ndb( ni->ni_cluster[ni->ni_nextconn++], ni->ni_dbname ); + if ( ni->ni_nextconn >= ni->ni_nconns ) + ni->ni_nextconn = 0; + ldap_pvt_thread_mutex_unlock( &ni->ni_conn_mutex ); + if ( !myNdb ) { + return LDAP_OTHER; + } + rc = myNdb->init(1024); + if ( rc ) { + delete myNdb; + Debug( LDAP_DEBUG_ANY, "ndb_thread_handle: err %d\n", + rc, 0, 0 ); + return rc; + } + data = (void *)myNdb; + if (( rc = ldap_pvt_thread_pool_setkey( op->o_threadctx, ni, + data, ndb_thread_hfree, NULL, NULL ))) { + delete myNdb; + Debug( LDAP_DEBUG_ANY, "ndb_thread_handle: err %d\n", + rc, 0, 0 ); + return rc; + } + } + *ndb = (Ndb *)data; + return 0; +} + +extern "C" int +ndb_entry_get( + Operation *op, + struct berval *ndn, + ObjectClass *oc, + AttributeDescription *ad, + int rw, + Entry **ent ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + NdbArgs NA; + Entry e = {0}; + int rc; + + /* Get our NDB handle */ + rc = ndb_thread_handle( op, &NA.ndb ); + + NA.txn = NA.ndb->startTransaction(); + if( !NA.txn ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_entry_get) ": startTransaction failed: %s (%d)\n", + NA.ndb->getNdbError().message, NA.ndb->getNdbError().code, 0 ); + return 1; + } + + e.e_name = *ndn; + NA.e = &e; + /* get entry */ + { + NdbRdns rdns; + rdns.nr_num = 0; + NA.ocs = NULL; + NA.rdns = &rdns; + rc = ndb_entry_get_info( op, &NA, rw, NULL ); + } + if ( rc == 0 ) { + e.e_name = *ndn; + e.e_nname = *ndn; + rc = ndb_entry_get_data( op, &NA, 0 ); + ber_bvarray_free( NA.ocs ); + if ( rc == 0 ) { + if ( oc && !is_entry_objectclass_or_sub( &e, oc )) { + attrs_free( e.e_attrs ); + rc = 1; + } + } + } + if ( rc == 0 ) { + *ent = entry_alloc(); + **ent = e; + ber_dupbv( &(*ent)->e_name, ndn ); + ber_dupbv( &(*ent)->e_nname, ndn ); + } else { + rc = 1; + } + NA.txn->close(); + return rc; +} + +/* Congestion avoidance code + * for Deadlock Rollback + */ + +extern "C" void +ndb_trans_backoff( int num_retries ) +{ + int i; + int delay = 0; + int pow_retries = 1; + unsigned long key = 0; + unsigned long max_key = -1; + struct timeval timeout; + + lutil_entropy( (unsigned char *) &key, sizeof( unsigned long )); + + for ( i = 0; i < num_retries; i++ ) { + if ( i >= 5 ) break; + pow_retries *= 4; + } + + delay = 16384 * (key * (double) pow_retries / (double) max_key); + delay = delay ? delay : 1; + + Debug( LDAP_DEBUG_TRACE, "delay = %d, num_retries = %d\n", delay, num_retries, 0 ); + + timeout.tv_sec = delay / 1000000; + timeout.tv_usec = delay % 1000000; + select( 0, NULL, NULL, NULL, &timeout ); +} + +extern "C" void +ndb_check_referral( Operation *op, SlapReply *rs, NdbArgs *NA ) +{ + struct berval dn, ndn; + int i, dif; + dif = NA->erdns - NA->rdns->nr_num; + + /* Set full DN of matched into entry */ + for ( i=0; i<dif; i++ ) { + dnParent( &NA->e->e_name, &dn ); + dnParent( &NA->e->e_nname, &ndn ); + NA->e->e_name = dn; + NA->e->e_nname = ndn; + } + + /* return referral only if "disclose" is granted on the object */ + if ( access_allowed( op, NA->e, slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL )) { + Attribute a; + for ( i=0; !BER_BVISNULL( &NA->ocs[i] ); i++ ); + a.a_numvals = i; + a.a_desc = slap_schema.si_ad_objectClass; + a.a_vals = NA->ocs; + a.a_nvals = NA->ocs; + a.a_next = NULL; + NA->e->e_attrs = &a; + if ( is_entry_referral( NA->e )) { + NA->e->e_attrs = NULL; + ndb_entry_get_data( op, NA, 0 ); + rs->sr_ref = get_entry_referrals( op, NA->e ); + if ( rs->sr_ref ) { + rs->sr_err = LDAP_REFERRAL; + rs->sr_flags |= REP_REF_MUSTBEFREED; + } + attrs_free( NA->e->e_attrs ); + } + NA->e->e_attrs = NULL; + } +} diff --git a/servers/slapd/back-ndb/proto-ndb.h b/servers/slapd/back-ndb/proto-ndb.h new file mode 100644 index 0000000..458c00f --- /dev/null +++ b/servers/slapd/back-ndb/proto-ndb.h @@ -0,0 +1,166 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#ifndef _PROTO_NDB_H +#define _PROTO_NDB_H + +LDAP_BEGIN_DECL + +extern BI_init ndb_back_initialize; + +extern BI_open ndb_back_open; +extern BI_close ndb_back_close; +extern BI_destroy ndb_back_destroy; + +extern BI_db_init ndb_back_db_init; +extern BI_db_destroy ndb_back_db_destroy; + +extern BI_op_bind ndb_back_bind; +extern BI_op_unbind ndb_back_unbind; +extern BI_op_search ndb_back_search; +extern BI_op_compare ndb_back_compare; +extern BI_op_modify ndb_back_modify; +extern BI_op_modrdn ndb_back_modrdn; +extern BI_op_add ndb_back_add; +extern BI_op_delete ndb_back_delete; + +extern BI_operational ndb_operational; +extern BI_has_subordinates ndb_has_subordinates; +extern BI_entry_get_rw ndb_entry_get; + +extern BI_tool_entry_open ndb_tool_entry_open; +extern BI_tool_entry_close ndb_tool_entry_close; +extern BI_tool_entry_first ndb_tool_entry_first; +extern BI_tool_entry_next ndb_tool_entry_next; +extern BI_tool_entry_get ndb_tool_entry_get; +extern BI_tool_entry_put ndb_tool_entry_put; +extern BI_tool_dn2id_get ndb_tool_dn2id_get; + +extern int ndb_modify_internal( + Operation *op, + NdbArgs *NA, + const char **text, + char *textbuf, + size_t textlen ); + +extern int +ndb_entry_get_data( + Operation *op, + NdbArgs *args, + int update ); + +extern int +ndb_entry_put_data( + BackendDB *be, + NdbArgs *args ); + +extern int +ndb_entry_del_data( + BackendDB *be, + NdbArgs *args ); + +extern int +ndb_entry_put_info( + BackendDB *be, + NdbArgs *args, + int update ); + +extern int +ndb_entry_get_info( + Operation *op, + NdbArgs *args, + int update, + struct berval *matched ); + +extern "C" int +ndb_entry_del_info( + BackendDB *be, + NdbArgs *args ); + +extern int +ndb_dn2rdns( + struct berval *dn, + NdbRdns *buf ); + +extern NdbAttrInfo * +ndb_ai_find( struct ndb_info *ni, AttributeType *at ); + +extern NdbAttrInfo * +ndb_ai_get( struct ndb_info *ni, struct berval *at ); + +extern int +ndb_aset_get( struct ndb_info *ni, struct berval *sname, struct berval *attrs, NdbOcInfo **ret ); + +extern int +ndb_aset_create( struct ndb_info *ni, NdbOcInfo *oci ); + +extern int +ndb_oc_read( struct ndb_info *ni, const NdbDictionary::Dictionary *dict ); + +extern int +ndb_oc_attrs( + NdbTransaction *txn, + const NdbDictionary::Table *myTable, + Entry *e, + NdbOcInfo *no, + NdbAttrInfo **attrs, + int nattrs, + Attribute *old ); + +extern int +ndb_has_children( + NdbArgs *NA, + int *hasChildren ); + +extern struct berval * +ndb_str2bvarray( + char *str, + int len, + char delim, + void *ctx ); + +extern struct berval * +ndb_ref2oclist( + const char *ref, + void *ctx ); + +extern int +ndb_next_id( + BackendDB *be, + Ndb *ndb, + ID *id ); + +extern int +ndb_thread_handle( + Operation *op, + Ndb **ndb ); + +extern int +ndb_back_init_cf( + BackendInfo *bi ); + +extern "C" void +ndb_trans_backoff( int num_retries ); + +extern "C" void +ndb_check_referral( Operation *op, SlapReply *rs, NdbArgs *NA ); + +LDAP_END_DECL + +#endif /* _PROTO_NDB_H */ diff --git a/servers/slapd/back-ndb/search.cpp b/servers/slapd/back-ndb/search.cpp new file mode 100644 index 0000000..5d5e128 --- /dev/null +++ b/servers/slapd/back-ndb/search.cpp @@ -0,0 +1,854 @@ +/* search.cpp - tools for slap tools */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/errno.h> + +#include "lutil.h" + +#include "back-ndb.h" + +static int +ndb_dn2bound( + NdbIndexScanOperation *myop, + NdbRdns *rdns +) +{ + unsigned int i; + + /* Walk thru RDNs */ + for ( i=0; i<rdns->nr_num; i++ ) { + /* Note: RDN_COLUMN offset not needed here */ + if ( myop->setBound( i, NdbIndexScanOperation::BoundEQ, rdns->nr_buf[i] )) + return LDAP_OTHER; + } + return i; +} + +/* Check that all filter terms reside in the same table. + * + * If any of the filter terms are indexed, then only an IndexScan of the OL_index + * will be performed. If none are indexed, but all the terms reside in a single + * table, a Scan can be performed with the LDAP filter transformed into a ScanFilter. + * + * Otherwise, a full scan of the DB must be done with all filtering done by slapd. + */ +static int ndb_filter_check( struct ndb_info *ni, Filter *f, + NdbOcInfo **oci, int *indexed, int *ocfilter ) +{ + AttributeDescription *ad = NULL; + ber_tag_t choice = f->f_choice; + int rc = 0, undef = 0; + + if ( choice & SLAPD_FILTER_UNDEFINED ) { + choice &= SLAPD_FILTER_MASK; + undef = 1; + } + switch( choice ) { + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: + for ( f = f->f_list; f; f=f->f_next ) { + rc = ndb_filter_check( ni, f, oci, indexed, ocfilter ); + if ( rc ) return rc; + } + break; + case LDAP_FILTER_PRESENT: + ad = f->f_desc; + break; + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_SUBSTRINGS: + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + case LDAP_FILTER_APPROX: + ad = f->f_av_desc; + break; + default: + break; + } + if ( ad && !undef ) { + NdbAttrInfo *ai; + /* ObjectClass filtering is in dn2id table */ + if ( ad == slap_schema.si_ad_objectClass ) { + if ( choice == LDAP_FILTER_EQUALITY ) + (*ocfilter)++; + return 0; + } + ai = ndb_ai_find( ni, ad->ad_type ); + if ( ai ) { + if ( ai->na_flag & NDB_INFO_INDEX ) + (*indexed)++; + if ( *oci ) { + if ( ai->na_oi != *oci ) + rc = -1; + } else { + *oci = ai->na_oi; + } + } + } + return rc; +} + +static int ndb_filter_set( Operation *op, struct ndb_info *ni, Filter *f, int indexed, + NdbIndexScanOperation *scan, NdbScanFilter *sf, int *bounds ) +{ + AttributeDescription *ad = NULL; + ber_tag_t choice = f->f_choice; + int undef = 0; + + if ( choice & SLAPD_FILTER_UNDEFINED ) { + choice &= SLAPD_FILTER_MASK; + undef = 1; + } + switch( choice ) { + case LDAP_FILTER_NOT: + /* no indexing for these */ + break; + case LDAP_FILTER_OR: + /* FIXME: these bounds aren't right. */ + if ( indexed ) { + scan->end_of_bound( (*bounds)++ ); + } + case LDAP_FILTER_AND: + if ( sf ) { + sf->begin( choice == LDAP_FILTER_OR ? NdbScanFilter::OR : NdbScanFilter::AND ); + } + for ( f = f->f_list; f; f=f->f_next ) { + if ( ndb_filter_set( op, ni, f, indexed, scan, sf, bounds )) + return -1; + } + if ( sf ) { + sf->end(); + } + break; + case LDAP_FILTER_PRESENT: + ad = f->f_desc; + break; + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_SUBSTRINGS: + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + case LDAP_FILTER_APPROX: + ad = f->f_av_desc; + break; + default: + break; + } + if ( ad && !undef ) { + NdbAttrInfo *ai; + /* ObjectClass filtering is in dn2id table */ + if ( ad == slap_schema.si_ad_objectClass ) { + return 0; + } + ai = ndb_ai_find( ni, ad->ad_type ); + if ( ai ) { + int rc; + if ( ai->na_flag & NDB_INFO_INDEX ) { + char *buf, *ptr; + NdbIndexScanOperation::BoundType bt; + + switch(choice) { + case LDAP_FILTER_PRESENT: + rc = scan->setBound( ai->na_ixcol - IDX_COLUMN, + NdbIndexScanOperation::BoundGT, NULL ); + break; + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_APPROX: + bt = NdbIndexScanOperation::BoundEQ; + goto setit; + case LDAP_FILTER_GE: + bt = NdbIndexScanOperation::BoundGE; + goto setit; + case LDAP_FILTER_LE: + bt = NdbIndexScanOperation::BoundLE; + setit: + rc = f->f_av_value.bv_len+1; + if ( ai->na_len > 255 ) + rc++; + buf = (char *)op->o_tmpalloc( rc, op->o_tmpmemctx ); + rc = f->f_av_value.bv_len; + buf[0] = rc & 0xff; + ptr = buf+1; + if ( ai->na_len > 255 ) { + buf[1] = (rc >> 8); + ptr++; + } + memcpy( ptr, f->f_av_value.bv_val, f->f_av_value.bv_len ); + rc = scan->setBound( ai->na_ixcol - IDX_COLUMN, bt, buf ); + op->o_tmpfree( buf, op->o_tmpmemctx ); + break; + default: + break; + } + } else if ( sf ) { + char *buf, *ptr; + NdbScanFilter::BinaryCondition bc; + + switch(choice) { + case LDAP_FILTER_PRESENT: + rc = sf->isnotnull( ai->na_column ); + break; + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_APPROX: + bc = NdbScanFilter::COND_EQ; + goto setf; + case LDAP_FILTER_GE: + bc = NdbScanFilter::COND_GE; + goto setf; + case LDAP_FILTER_LE: + bc = NdbScanFilter::COND_LE; + setf: + rc = sf->cmp( bc, ai->na_column, f->f_av_value.bv_val, f->f_av_value.bv_len ); + break; + case LDAP_FILTER_SUBSTRINGS: + rc = 0; + if ( f->f_sub_initial.bv_val ) + rc += f->f_sub_initial.bv_len + 1; + if ( f->f_sub_any ) { + int i; + if ( !rc ) rc++; + for (i=0; f->f_sub_any[i].bv_val; i++) + rc += f->f_sub_any[i].bv_len + 1; + } + if ( f->f_sub_final.bv_val ) { + if ( !rc ) rc++; + rc += f->f_sub_final.bv_len; + } + buf = (char *)op->o_tmpalloc( rc+1, op->o_tmpmemctx ); + ptr = buf; + if ( f->f_sub_initial.bv_val ) { + memcpy( ptr, f->f_sub_initial.bv_val, f->f_sub_initial.bv_len ); + ptr += f->f_sub_initial.bv_len; + *ptr++ = '%'; + } + if ( f->f_sub_any ) { + int i; + if ( ptr == buf ) + *ptr++ = '%'; + for (i=0; f->f_sub_any[i].bv_val; i++) { + memcpy( ptr, f->f_sub_any[i].bv_val, f->f_sub_any[i].bv_len ); + ptr += f->f_sub_any[i].bv_len; + *ptr++ = '%'; + } + } + if ( f->f_sub_final.bv_val ) { + if ( ptr == buf ) + *ptr++ = '%'; + memcpy( ptr, f->f_sub_final.bv_val, f->f_sub_final.bv_len ); + ptr += f->f_sub_final.bv_len; + } + *ptr = '\0'; + rc = sf->cmp( NdbScanFilter::COND_LIKE, ai->na_column, buf, ptr - buf ); + op->o_tmpfree( buf, op->o_tmpmemctx ); + break; + } + } + } + } + return 0; +} + +static int ndb_oc_search( Operation *op, SlapReply *rs, Ndb *ndb, NdbTransaction *txn, + NdbRdns *rbase, NdbOcInfo *oci, int indexed ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + const NdbDictionary::Dictionary *myDict = ndb->getDictionary(); + const NdbDictionary::Table *myTable; + const NdbDictionary::Index *myIndex; + NdbIndexScanOperation *scan; + NdbIndexOperation *ixop; + NdbScanFilter *sf = NULL; + struct berval *ocs; + NdbRecAttr *scanID, *scanOC, *scanDN[NDB_MAX_RDNS]; + char dnBuf[2048], *ptr; + NdbRdns rdns; + NdbArgs NA; + char idbuf[2*sizeof(ID)]; + char ocbuf[NDB_OC_BUFLEN]; + int i, rc, bounds; + Entry e = {0}; + Uint64 eid; + time_t stoptime; + int manageDSAit; + + stoptime = op->o_time + op->ors_tlimit; + manageDSAit = get_manageDSAit( op ); + + myTable = myDict->getTable( oci->no_table.bv_val ); + if ( indexed ) { + scan = txn->getNdbIndexScanOperation( INDEX_NAME, DN2ID_TABLE ); + if ( !scan ) + return LDAP_OTHER; + scan->readTuples( NdbOperation::LM_CommittedRead ); + } else { + myIndex = myDict->getIndex( "eid$unique", DN2ID_TABLE ); + if ( !myIndex ) { + Debug( LDAP_DEBUG_ANY, DN2ID_TABLE " eid index is missing!\n", 0, 0, 0 ); + rs->sr_err = LDAP_OTHER; + goto leave; + } + scan = (NdbIndexScanOperation *)txn->getNdbScanOperation( myTable ); + if ( !scan ) + return LDAP_OTHER; + scan->readTuples( NdbOperation::LM_CommittedRead ); +#if 1 + sf = new NdbScanFilter(scan); + if ( !sf ) + return LDAP_OTHER; + switch ( op->ors_filter->f_choice ) { + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: + break; + default: + if ( sf->begin() < 0 ) { + rc = LDAP_OTHER; + goto leave; + } + } +#endif + } + + bounds = 0; + rc = ndb_filter_set( op, ni, op->ors_filter, indexed, scan, sf, &bounds ); + if ( rc ) + goto leave; + if ( sf ) sf->end(); + + scanID = scan->getValue( EID_COLUMN, idbuf ); + if ( indexed ) { + scanOC = scan->getValue( OCS_COLUMN, ocbuf ); + for ( i=0; i<NDB_MAX_RDNS; i++ ) { + rdns.nr_buf[i][0] = '\0'; + scanDN[i] = scan->getValue( RDN_COLUMN+i, rdns.nr_buf[i] ); + } + } + + if ( txn->execute( NdbTransaction::NoCommit, NdbOperation::AbortOnError, 1 )) { + rs->sr_err = LDAP_OTHER; + goto leave; + } + + e.e_name.bv_val = dnBuf; + NA.e = &e; + NA.ndb = ndb; + while ( scan->nextResult( true, true ) == 0 ) { + NdbTransaction *tx2; + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + break; + } + if ( slapd_shutdown ) { + rs->sr_err = LDAP_UNAVAILABLE; + break; + } + if ( op->ors_tlimit != SLAP_NO_LIMIT && + slap_get_time() > stoptime ) { + rs->sr_err = LDAP_TIMELIMIT_EXCEEDED; + break; + } + + eid = scanID->u_64_value(); + e.e_id = eid; + if ( !indexed ) { + tx2 = ndb->startTransaction( myTable ); + if ( !tx2 ) { + rs->sr_err = LDAP_OTHER; + goto leave; + } + + ixop = tx2->getNdbIndexOperation( myIndex ); + if ( !ixop ) { + tx2->close(); + rs->sr_err = LDAP_OTHER; + goto leave; + } + ixop->readTuple( NdbOperation::LM_CommittedRead ); + ixop->equal( EID_COLUMN, eid ); + + scanOC = ixop->getValue( OCS_COLUMN, ocbuf ); + for ( i=0; i<NDB_MAX_RDNS; i++ ) { + rdns.nr_buf[i][0] = '\0'; + scanDN[i] = ixop->getValue( RDN_COLUMN+i, rdns.nr_buf[i] ); + } + rc = tx2->execute( NdbTransaction::Commit, NdbOperation::AbortOnError, 1 ); + tx2->close(); + if ( rc ) { + rs->sr_err = LDAP_OTHER; + goto leave; + } + } + + ocs = ndb_ref2oclist( ocbuf, op->o_tmpmemctx ); + for ( i=0; i<NDB_MAX_RDNS; i++ ) { + if ( scanDN[i]->isNULL() || !rdns.nr_buf[i][0] ) + break; + } + rdns.nr_num = i; + + /* entry must be subordinate to the base */ + if ( i < rbase->nr_num ) { + continue; + } + + ptr = dnBuf; + for ( --i; i>=0; i-- ) { + char *buf; + int len; + buf = rdns.nr_buf[i]; + len = *buf++; + ptr = lutil_strncopy( ptr, buf, len ); + if ( i ) *ptr++ = ','; + } + *ptr = '\0'; + e.e_name.bv_len = ptr - dnBuf; + + /* More scope checks */ + /* If indexed, these can be moved into the ScanFilter */ + switch( op->ors_scope ) { + case LDAP_SCOPE_ONELEVEL: + if ( rdns.nr_num != rbase->nr_num+1 ) + continue; + case LDAP_SCOPE_SUBORDINATE: + if ( rdns.nr_num == rbase->nr_num ) + continue; + case LDAP_SCOPE_SUBTREE: + default: + if ( e.e_name.bv_len <= op->o_req_dn.bv_len ) { + if ( op->ors_scope != LDAP_SCOPE_SUBTREE || + strcasecmp( op->o_req_dn.bv_val, e.e_name.bv_val )) + continue; + } else if ( strcasecmp( op->o_req_dn.bv_val, e.e_name.bv_val + + e.e_name.bv_len - op->o_req_dn.bv_len )) + continue; + } + + dnNormalize( 0, NULL, NULL, &e.e_name, &e.e_nname, op->o_tmpmemctx ); + { +#if 1 /* NDBapi was broken here but seems to work now */ + Ndb::Key_part_ptr keys[2]; + char xbuf[512]; + keys[0].ptr = &eid; + keys[0].len = sizeof(eid); + keys[1].ptr = NULL; + keys[1].len = 0; + tx2 = ndb->startTransaction( myTable, keys, xbuf, sizeof(xbuf)); +#else + tx2 = ndb->startTransaction( myTable ); +#endif + if ( !tx2 ) { + rs->sr_err = LDAP_OTHER; + goto leave; + } + NA.txn = tx2; + NA.ocs = ocs; + rc = ndb_entry_get_data( op, &NA, 0 ); + tx2->close(); + } + ber_bvarray_free_x( ocs, op->o_tmpmemctx ); + if ( !manageDSAit && is_entry_referral( &e )) { + BerVarray erefs = get_entry_referrals( op, &e ); + rs->sr_ref = referral_rewrite( erefs, &e.e_name, NULL, + op->ors_scope == LDAP_SCOPE_ONELEVEL ? + LDAP_SCOPE_BASE : LDAP_SCOPE_SUBTREE ); + rc = send_search_reference( op, rs ); + ber_bvarray_free( rs->sr_ref ); + ber_bvarray_free( erefs ); + rs->sr_ref = NULL; + } else if ( manageDSAit || !is_entry_glue( &e )) { + rc = test_filter( op, &e, op->ors_filter ); + if ( rc == LDAP_COMPARE_TRUE ) { + rs->sr_entry = &e; + rs->sr_attrs = op->ors_attrs; + rs->sr_flags = 0; + rc = send_search_entry( op, rs ); + rs->sr_entry = NULL; + rs->sr_attrs = NULL; + } else { + rc = 0; + } + } + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + op->o_tmpfree( e.e_nname.bv_val, op->o_tmpmemctx ); + if ( rc ) break; + } +leave: + if ( sf ) delete sf; + return rc; +} + +extern "C" +int ndb_back_search( Operation *op, SlapReply *rs ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + NdbTransaction *txn; + NdbIndexScanOperation *scan; + NdbScanFilter *sf = NULL; + Entry e = {0}; + int rc, i, ocfilter, indexed; + struct berval matched; + NdbRecAttr *scanID, *scanOC, *scanDN[NDB_MAX_RDNS]; + char dnBuf[2048], *ptr; + char idbuf[2*sizeof(ID)]; + char ocbuf[NDB_OC_BUFLEN]; + NdbRdns rdns; + NdbOcInfo *oci; + NdbArgs NA; + slap_mask_t mask; + time_t stoptime; + int manageDSAit; + + rc = ndb_thread_handle( op, &NA.ndb ); + rdns.nr_num = 0; + + manageDSAit = get_manageDSAit( op ); + + txn = NA.ndb->startTransaction(); + if ( !txn ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_search) ": startTransaction failed: %s (%d)\n", + NA.ndb->getNdbError().message, NA.ndb->getNdbError().code, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto leave; + } + + NA.txn = txn; + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + NA.e = &e; + NA.rdns = &rdns; + NA.ocs = NULL; + + rs->sr_err = ndb_entry_get_info( op, &NA, 0, &matched ); + if ( rs->sr_err ) { + if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) { + rs->sr_matched = matched.bv_val; + if ( NA.ocs ) + ndb_check_referral( op, rs, &NA ); + } + goto leave; + } + + if ( !access_allowed_mask( op, &e, slap_schema.si_ad_entry, + NULL, ACL_SEARCH, NULL, &mask )) { + if ( !ACL_GRANT( mask, ACL_DISCLOSE )) + rs->sr_err = LDAP_NO_SUCH_OBJECT; + else + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx ); + goto leave; + } + + rs->sr_err = ndb_entry_get_data( op, &NA, 0 ); + ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx ); + if ( rs->sr_err ) + goto leave; + + if ( !manageDSAit && is_entry_referral( &e )) { + rs->sr_ref = get_entry_referrals( op, &e ); + rs->sr_err = LDAP_REFERRAL; + if ( rs->sr_ref ) + rs->sr_flags |= REP_REF_MUSTBEFREED; + rs->sr_matched = e.e_name.bv_val; + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + goto leave; + } + + if ( !manageDSAit && is_entry_glue( &e )) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + goto leave; + } + + if ( get_assert( op ) && test_filter( op, &e, (Filter *)get_assertion( op )) != + LDAP_COMPARE_TRUE ) { + rs->sr_err = LDAP_ASSERTION_FAILED; + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + goto leave; + } + + /* admin ignores tlimits */ + stoptime = op->o_time + op->ors_tlimit; + + if ( op->ors_scope == LDAP_SCOPE_BASE ) { + rc = test_filter( op, &e, op->ors_filter ); + if ( rc == LDAP_COMPARE_TRUE ) { + rs->sr_entry = &e; + rs->sr_attrs = op->ors_attrs; + rs->sr_flags = 0; + send_search_entry( op, rs ); + rs->sr_entry = NULL; + } + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + rs->sr_err = LDAP_SUCCESS; + goto leave; + } else { + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + if ( rdns.nr_num == NDB_MAX_RDNS ) { + if ( op->ors_scope == LDAP_SCOPE_ONELEVEL || + op->ors_scope == LDAP_SCOPE_CHILDREN ) + rs->sr_err = LDAP_SUCCESS; + goto leave; + } + } + + /* See if we can handle the filter. Filtering on objectClass is only done + * in the DN2ID table scan. If all other filter terms reside in one table, + * then we scan the OC table instead of the DN2ID table. + */ + oci = NULL; + indexed = 0; + ocfilter = 0; + rc = ndb_filter_check( ni, op->ors_filter, &oci, &indexed, &ocfilter ); + if ( rc ) { + Debug( LDAP_DEBUG_TRACE, "ndb_back_search: " + "filter attributes from multiple tables, indexing ignored\n", + 0, 0, 0 ); + } else if ( oci ) { + rc = ndb_oc_search( op, rs, NA.ndb, txn, &rdns, oci, indexed ); + goto leave; + } + + scan = txn->getNdbIndexScanOperation( "PRIMARY", DN2ID_TABLE ); + if ( !scan ) { + rs->sr_err = LDAP_OTHER; + goto leave; + } + scan->readTuples( NdbOperation::LM_CommittedRead ); + rc = ndb_dn2bound( scan, &rdns ); + + /* TODO: if ( ocfilter ) set up scanfilter for objectclass matches + * column COND_LIKE "% <class> %" + */ + + switch( op->ors_scope ) { + case LDAP_SCOPE_ONELEVEL: + sf = new NdbScanFilter(scan); + if ( sf->begin() < 0 || + sf->cmp(NdbScanFilter::COND_NOT_LIKE, rc+3, "_%", + STRLENOF("_%")) < 0 || + sf->end() < 0 ) { + rs->sr_err = LDAP_OTHER; + goto leave; + } + /* FALLTHRU */ + case LDAP_SCOPE_CHILDREN: + /* Note: RDN_COLUMN offset not needed here */ + scan->setBound( rc, NdbIndexScanOperation::BoundLT, "\0" ); + /* FALLTHRU */ + case LDAP_SCOPE_SUBTREE: + break; + } + scanID = scan->getValue( EID_COLUMN, idbuf ); + scanOC = scan->getValue( OCS_COLUMN, ocbuf ); + for ( i=0; i<NDB_MAX_RDNS; i++ ) { + rdns.nr_buf[i][0] = '\0'; + scanDN[i] = scan->getValue( RDN_COLUMN+i, rdns.nr_buf[i] ); + } + if ( txn->execute( NdbTransaction::NoCommit, NdbOperation::AbortOnError, 1 )) { + rs->sr_err = LDAP_OTHER; + goto leave; + } + + e.e_name.bv_val = dnBuf; + while ( scan->nextResult( true, true ) == 0 ) { + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + break; + } + if ( slapd_shutdown ) { + rs->sr_err = LDAP_UNAVAILABLE; + break; + } + if ( op->ors_tlimit != SLAP_NO_LIMIT && + slap_get_time() > stoptime ) { + rs->sr_err = LDAP_TIMELIMIT_EXCEEDED; + break; + } + e.e_id = scanID->u_64_value(); + NA.ocs = ndb_ref2oclist( ocbuf, op->o_tmpmemctx ); + for ( i=0; i<NDB_MAX_RDNS; i++ ) { + if ( scanDN[i]->isNULL() || !rdns.nr_buf[i][0] ) + break; + } + ptr = dnBuf; + rdns.nr_num = i; + for ( --i; i>=0; i-- ) { + char *buf; + int len; + buf = rdns.nr_buf[i]; + len = *buf++; + ptr = lutil_strncopy( ptr, buf, len ); + if ( i ) *ptr++ = ','; + } + *ptr = '\0'; + e.e_name.bv_len = ptr - dnBuf; + dnNormalize( 0, NULL, NULL, &e.e_name, &e.e_nname, op->o_tmpmemctx ); + NA.txn = NA.ndb->startTransaction(); + rc = ndb_entry_get_data( op, &NA, 0 ); + NA.txn->close(); + ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx ); + if ( !manageDSAit && is_entry_referral( &e )) { + BerVarray erefs = get_entry_referrals( op, &e ); + rs->sr_ref = referral_rewrite( erefs, &e.e_name, NULL, + op->ors_scope == LDAP_SCOPE_ONELEVEL ? + LDAP_SCOPE_BASE : LDAP_SCOPE_SUBTREE ); + rc = send_search_reference( op, rs ); + ber_bvarray_free( rs->sr_ref ); + ber_bvarray_free( erefs ); + rs->sr_ref = NULL; + } else if ( manageDSAit || !is_entry_glue( &e )) { + rc = test_filter( op, &e, op->ors_filter ); + if ( rc == LDAP_COMPARE_TRUE ) { + rs->sr_entry = &e; + rs->sr_attrs = op->ors_attrs; + rs->sr_flags = 0; + rc = send_search_entry( op, rs ); + rs->sr_entry = NULL; + } else { + rc = 0; + } + } + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + op->o_tmpfree( e.e_nname.bv_val, op->o_tmpmemctx ); + if ( rc ) break; + } +leave: + if ( sf ) + delete sf; + if ( txn ) + txn->close(); + send_ldap_result( op, rs ); + return rs->sr_err; +} + +extern NdbInterpretedCode *ndb_lastrow_code; /* init.cpp */ + +extern "C" int +ndb_has_children( + NdbArgs *NA, + int *hasChildren +) +{ + NdbIndexScanOperation *scan; + char idbuf[2*sizeof(ID)]; + int rc; + + if ( NA->rdns->nr_num >= NDB_MAX_RDNS ) { + *hasChildren = LDAP_COMPARE_FALSE; + return 0; + } + + scan = NA->txn->getNdbIndexScanOperation( "PRIMARY", DN2ID_TABLE ); + if ( !scan ) + return LDAP_OTHER; + scan->readTuples( NdbOperation::LM_Read, 0U, 0U, 1U ); + rc = ndb_dn2bound( scan, NA->rdns ); + if ( rc < NDB_MAX_RDNS ) { + scan->setBound( rc, NdbIndexScanOperation::BoundLT, "\0" ); + } +#if 0 + scan->interpret_exit_last_row(); +#else + scan->setInterpretedCode(ndb_lastrow_code); +#endif + scan->getValue( EID_COLUMN, idbuf ); + if ( NA->txn->execute( NdbTransaction::NoCommit, NdbOperation::AO_IgnoreError, 1 )) { + return LDAP_OTHER; + } + if (rc < NDB_MAX_RDNS && scan->nextResult( true, true ) == 0 ) + *hasChildren = LDAP_COMPARE_TRUE; + else + *hasChildren = LDAP_COMPARE_FALSE; + scan->close(); + return 0; +} + +extern "C" int +ndb_has_subordinates( + Operation *op, + Entry *e, + int *hasSubordinates ) +{ + NdbArgs NA; + NdbRdns rdns; + int rc; + + NA.rdns = &rdns; + rc = ndb_dn2rdns( &e->e_nname, &rdns ); + + if ( rc == 0 ) { + rc = ndb_thread_handle( op, &NA.ndb ); + NA.txn = NA.ndb->startTransaction(); + if ( NA.txn ) { + rc = ndb_has_children( &NA, hasSubordinates ); + NA.txn->close(); + } + } + + return rc; +} + +/* + * sets the supported operational attributes (if required) + */ +extern "C" int +ndb_operational( + Operation *op, + SlapReply *rs ) +{ + Attribute **ap; + + assert( rs->sr_entry != NULL ); + + for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next ) { + if ( (*ap)->a_desc == slap_schema.si_ad_hasSubordinates ) { + break; + } + } + + if ( *ap == NULL && + attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_hasSubordinates ) == NULL && + ( SLAP_OPATTRS( rs->sr_attr_flags ) || + ad_inlist( slap_schema.si_ad_hasSubordinates, rs->sr_attrs ) ) ) + { + int hasSubordinates, rc; + + rc = ndb_has_subordinates( op, rs->sr_entry, &hasSubordinates ); + if ( rc == LDAP_SUCCESS ) { + *ap = slap_operational_hasSubordinate( hasSubordinates == LDAP_COMPARE_TRUE ); + assert( *ap != NULL ); + + ap = &(*ap)->a_next; + } + } + + return LDAP_SUCCESS; +} + diff --git a/servers/slapd/back-ndb/tools.cpp b/servers/slapd/back-ndb/tools.cpp new file mode 100644 index 0000000..82187da --- /dev/null +++ b/servers/slapd/back-ndb/tools.cpp @@ -0,0 +1,544 @@ +/* tools.cpp - tools for slap tools */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/errno.h> + +#include "lutil.h" + +#include "back-ndb.h" + +typedef struct dn_id { + ID id; + struct berval dn; +} dn_id; + +#define HOLE_SIZE 4096 +static dn_id hbuf[HOLE_SIZE], *holes = hbuf; +static unsigned nhmax = HOLE_SIZE; +static unsigned nholes; +static Avlnode *myParents; + +static Ndb *myNdb; +static NdbTransaction *myScanTxn; +static NdbIndexScanOperation *myScanOp; + +static NdbRecAttr *myScanID, *myScanOC; +static NdbRecAttr *myScanDN[NDB_MAX_RDNS]; +static char myDNbuf[2048]; +static char myIdbuf[2*sizeof(ID)]; +static char myOcbuf[NDB_OC_BUFLEN]; +static NdbRdns myRdns; + +static NdbTransaction *myPutTxn; +static int myPutCnt; + +static struct berval *myOcList; +static struct berval myDn; + +extern "C" +int ndb_tool_entry_open( + BackendDB *be, int mode ) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + + myNdb = new Ndb( ni->ni_cluster[0], ni->ni_dbname ); + return myNdb->init(1024); +} + +extern "C" +int ndb_tool_entry_close( + BackendDB *be ) +{ + if ( myPutTxn ) { + int rc = myPutTxn->execute(NdbTransaction::Commit); + if( rc != 0 ) { + char text[1024]; + snprintf( text, sizeof(text), + "txn_commit failed: %s (%d)", + myPutTxn->getNdbError().message, myPutTxn->getNdbError().code ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(ndb_tool_entry_put) ": %s\n", + text, 0, 0 ); + } + myPutTxn->close(); + myPutTxn = NULL; + } + myPutCnt = 0; + + if( nholes ) { + unsigned i; + fprintf( stderr, "Error, entries missing!\n"); + for (i=0; i<nholes; i++) { + fprintf(stderr, " entry %ld: %s\n", + holes[i].id, holes[i].dn.bv_val); + } + return -1; + } + + return 0; +} + +extern "C" +ID ndb_tool_entry_next( + BackendDB *be ) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + char *ptr; + ID id; + int i; + + assert( be != NULL ); + assert( slapMode & SLAP_TOOL_MODE ); + + if ( myScanOp->nextResult() ) { + myScanOp->close(); + myScanOp = NULL; + myScanTxn->close(); + myScanTxn = NULL; + return NOID; + } + id = myScanID->u_64_value(); + + if ( myOcList ) { + ber_bvarray_free( myOcList ); + } + myOcList = ndb_ref2oclist( myOcbuf, NULL ); + for ( i=0; i<NDB_MAX_RDNS; i++ ) { + if ( myScanDN[i]->isNULL() || !myRdns.nr_buf[i][0] ) + break; + } + myRdns.nr_num = i; + ptr = myDNbuf; + for ( --i; i>=0; i-- ) { + char *buf; + int len; + buf = myRdns.nr_buf[i]; + len = *buf++; + ptr = lutil_strncopy( ptr, buf, len ); + if ( i ) + *ptr++ = ','; + } + *ptr = '\0'; + myDn.bv_val = myDNbuf; + myDn.bv_len = ptr - myDNbuf; + + return id; +} + +extern "C" +ID ndb_tool_entry_first( + BackendDB *be ) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + int i; + + myScanTxn = myNdb->startTransaction(); + if ( !myScanTxn ) + return NOID; + + myScanOp = myScanTxn->getNdbIndexScanOperation( "PRIMARY", DN2ID_TABLE ); + if ( !myScanOp ) + return NOID; + + if ( myScanOp->readTuples( NdbOperation::LM_CommittedRead, NdbScanOperation::SF_KeyInfo )) + return NOID; + + myScanID = myScanOp->getValue( EID_COLUMN, myIdbuf ); + myScanOC = myScanOp->getValue( OCS_COLUMN, myOcbuf ); + for ( i=0; i<NDB_MAX_RDNS; i++ ) { + myScanDN[i] = myScanOp->getValue( i+RDN_COLUMN, myRdns.nr_buf[i] ); + } + if ( myScanTxn->execute( NdbTransaction::NoCommit, NdbOperation::AbortOnError, 1 )) + return NOID; + + return ndb_tool_entry_next( be ); +} + +extern "C" +ID ndb_tool_dn2id_get( + Backend *be, + struct berval *dn +) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + NdbArgs NA; + NdbRdns rdns; + Entry e; + char text[1024]; + Operation op = {0}; + Opheader ohdr = {0}; + int rc; + + if ( BER_BVISEMPTY(dn) ) + return 0; + + NA.ndb = myNdb; + NA.txn = myNdb->startTransaction(); + if ( !NA.txn ) { + snprintf( text, sizeof(text), + "startTransaction failed: %s (%d)", + myNdb->getNdbError().message, myNdb->getNdbError().code ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(ndb_tool_dn2id_get) ": %s\n", + text, 0, 0 ); + return NOID; + } + if ( myOcList ) { + ber_bvarray_free( myOcList ); + myOcList = NULL; + } + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + NA.e = &e; + e.e_name = *dn; + NA.rdns = &rdns; + NA.ocs = NULL; + rc = ndb_entry_get_info( &op, &NA, 0, NULL ); + myOcList = NA.ocs; + NA.txn->close(); + if ( rc ) + return NOID; + + myDn = *dn; + + return e.e_id; +} + +extern "C" +Entry* ndb_tool_entry_get( BackendDB *be, ID id ) +{ + NdbArgs NA; + int rc; + char text[1024]; + Operation op = {0}; + Opheader ohdr = {0}; + + assert( be != NULL ); + assert( slapMode & SLAP_TOOL_MODE ); + + NA.txn = myNdb->startTransaction(); + if ( !NA.txn ) { + snprintf( text, sizeof(text), + "start_transaction failed: %s (%d)", + myNdb->getNdbError().message, myNdb->getNdbError().code ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(ndb_tool_entry_get) ": %s\n", + text, 0, 0 ); + return NULL; + } + + NA.e = entry_alloc(); + NA.e->e_id = id; + ber_dupbv( &NA.e->e_name, &myDn ); + dnNormalize( 0, NULL, NULL, &NA.e->e_name, &NA.e->e_nname, NULL ); + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + NA.ndb = myNdb; + NA.ocs = myOcList; + rc = ndb_entry_get_data( &op, &NA, 0 ); + + if ( rc ) { + entry_free( NA.e ); + NA.e = NULL; + } + NA.txn->close(); + + return NA.e; +} + +static struct berval glueval[] = { + BER_BVC("glue"), + BER_BVNULL +}; + +static int ndb_dnid_cmp( const void *v1, const void *v2 ) +{ + struct dn_id *dn1 = (struct dn_id *)v1, + *dn2 = (struct dn_id *)v2; + return ber_bvcmp( &dn1->dn, &dn2->dn ); +} + +static int ndb_tool_next_id( + Operation *op, + NdbArgs *NA, + struct berval *text, + int hole ) +{ + struct berval ndn = NA->e->e_nname; + int rc; + + if (ndn.bv_len == 0) { + NA->e->e_id = 0; + return 0; + } + + rc = ndb_entry_get_info( op, NA, 0, NULL ); + if ( rc ) { + Attribute *a, tmp = {0}; + if ( !be_issuffix( op->o_bd, &ndn ) ) { + struct dn_id *dptr; + struct berval npdn; + dnParent( &ndn, &npdn ); + NA->e->e_nname = npdn; + NA->rdns->nr_num--; + rc = ndb_tool_next_id( op, NA, text, 1 ); + NA->e->e_nname = ndn; + NA->rdns->nr_num++; + if ( rc ) { + return rc; + } + /* If parent didn't exist, it was created just now + * and its ID is now in e->e_id. + */ + dptr = (struct dn_id *)ch_malloc( sizeof( struct dn_id ) + npdn.bv_len + 1); + dptr->id = NA->e->e_id; + dptr->dn.bv_val = (char *)(dptr+1); + strcpy(dptr->dn.bv_val, npdn.bv_val ); + dptr->dn.bv_len = npdn.bv_len; + if ( avl_insert( &myParents, dptr, ndb_dnid_cmp, avl_dup_error )) { + ch_free( dptr ); + } + } + rc = ndb_next_id( op->o_bd, myNdb, &NA->e->e_id ); + if ( rc ) { + snprintf( text->bv_val, text->bv_len, + "next_id failed: %s (%d)", + myNdb->getNdbError().message, myNdb->getNdbError().code ); + Debug( LDAP_DEBUG_ANY, + "=> ndb_tool_next_id: %s\n", text->bv_val, 0, 0 ); + return rc; + } + if ( hole ) { + a = NA->e->e_attrs; + NA->e->e_attrs = &tmp; + tmp.a_desc = slap_schema.si_ad_objectClass; + tmp.a_vals = glueval; + tmp.a_nvals = tmp.a_vals; + tmp.a_numvals = 1; + } + rc = ndb_entry_put_info( op->o_bd, NA, 0 ); + if ( hole ) { + NA->e->e_attrs = a; + } + if ( rc ) { + snprintf( text->bv_val, text->bv_len, + "ndb_entry_put_info failed: %s (%d)", + myNdb->getNdbError().message, myNdb->getNdbError().code ); + Debug( LDAP_DEBUG_ANY, + "=> ndb_tool_next_id: %s\n", text->bv_val, 0, 0 ); + } else if ( hole ) { + if ( nholes == nhmax - 1 ) { + if ( holes == hbuf ) { + holes = (dn_id *)ch_malloc( nhmax * sizeof(dn_id) * 2 ); + AC_MEMCPY( holes, hbuf, sizeof(hbuf) ); + } else { + holes = (dn_id *)ch_realloc( holes, nhmax * sizeof(dn_id) * 2 ); + } + nhmax *= 2; + } + ber_dupbv( &holes[nholes].dn, &ndn ); + holes[nholes++].id = NA->e->e_id; + } + } else if ( !hole ) { + unsigned i; + + for ( i=0; i<nholes; i++) { + if ( holes[i].id == NA->e->e_id ) { + int j; + free(holes[i].dn.bv_val); + for (j=i;j<nholes;j++) holes[j] = holes[j+1]; + holes[j].id = 0; + nholes--; + rc = ndb_entry_put_info( op->o_bd, NA, 1 ); + break; + } else if ( holes[i].id > NA->e->e_id ) { + break; + } + } + } + return rc; +} + +extern "C" +ID ndb_tool_entry_put( + BackendDB *be, + Entry *e, + struct berval *text ) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + struct dn_id dtmp, *dptr; + NdbArgs NA; + NdbRdns rdns; + int rc, slow = 0; + Operation op = {0}; + Opheader ohdr = {0}; + + assert( be != NULL ); + assert( slapMode & SLAP_TOOL_MODE ); + + assert( text != NULL ); + assert( text->bv_val != NULL ); + assert( text->bv_val[0] == '\0' ); /* overconservative? */ + + Debug( LDAP_DEBUG_TRACE, "=> " LDAP_XSTRING(ndb_tool_entry_put) + "( %ld, \"%s\" )\n", (long) e->e_id, e->e_dn, 0 ); + + if ( !be_issuffix( be, &e->e_nname )) { + dnParent( &e->e_nname, &dtmp.dn ); + dptr = (struct dn_id *)avl_find( myParents, &dtmp, ndb_dnid_cmp ); + if ( !dptr ) + slow = 1; + } + + rdns.nr_num = 0; + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + if ( !slow ) { + rc = ndb_next_id( be, myNdb, &e->e_id ); + if ( rc ) { + snprintf( text->bv_val, text->bv_len, + "next_id failed: %s (%d)", + myNdb->getNdbError().message, myNdb->getNdbError().code ); + Debug( LDAP_DEBUG_ANY, + "=> ndb_tool_next_id: %s\n", text->bv_val, 0, 0 ); + return rc; + } + } + + if ( !myPutTxn ) + myPutTxn = myNdb->startTransaction(); + if ( !myPutTxn ) { + snprintf( text->bv_val, text->bv_len, + "start_transaction failed: %s (%d)", + myNdb->getNdbError().message, myNdb->getNdbError().code ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(ndb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + return NOID; + } + + /* add dn2id indices */ + ndb_dn2rdns( &e->e_name, &rdns ); + NA.rdns = &rdns; + NA.e = e; + NA.ndb = myNdb; + NA.txn = myPutTxn; + if ( slow ) { + rc = ndb_tool_next_id( &op, &NA, text, 0 ); + if( rc != 0 ) { + goto done; + } + } else { + rc = ndb_entry_put_info( be, &NA, 0 ); + if ( rc != 0 ) { + goto done; + } + } + + /* id2entry index */ + rc = ndb_entry_put_data( be, &NA ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "ndb_entry_put_data failed: %s (%d)", + myNdb->getNdbError().message, myNdb->getNdbError().code ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(ndb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + goto done; + } + +done: + if( rc == 0 ) { + myPutCnt++; + if ( !( myPutCnt & 0x0f )) { + rc = myPutTxn->execute(NdbTransaction::Commit); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "txn_commit failed: %s (%d)", + myPutTxn->getNdbError().message, myPutTxn->getNdbError().code ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(ndb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + e->e_id = NOID; + } + myPutTxn->close(); + myPutTxn = NULL; + } + } else { + snprintf( text->bv_val, text->bv_len, + "txn_aborted! %s (%d)", + myPutTxn->getNdbError().message, myPutTxn->getNdbError().code ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(ndb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + e->e_id = NOID; + myPutTxn->close(); + } + + return e->e_id; +} + +extern "C" +int ndb_tool_entry_reindex( + BackendDB *be, + ID id, + AttributeDescription **adv ) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + + Debug( LDAP_DEBUG_ARGS, + "=> " LDAP_XSTRING(ndb_tool_entry_reindex) "( %ld )\n", + (long) id, 0, 0 ); + + return 0; +} + +extern "C" +ID ndb_tool_entry_modify( + BackendDB *be, + Entry *e, + struct berval *text ) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + int rc; + + Debug( LDAP_DEBUG_TRACE, + "=> " LDAP_XSTRING(ndb_tool_entry_modify) "( %ld, \"%s\" )\n", + (long) e->e_id, e->e_dn, 0 ); + +done: + return e->e_id; +} + diff --git a/servers/slapd/back-null/Makefile.in b/servers/slapd/back-null/Makefile.in new file mode 100644 index 0000000..9c6c161 --- /dev/null +++ b/servers/slapd/back-null/Makefile.in @@ -0,0 +1,41 @@ +# Makefile.in for back-null +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +SRCS = null.c +OBJS = null.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-null" +BUILD_MOD = @BUILD_NULL@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_NULL@_DEFS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_R_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBBASE = back_null + +XINCPATH = -I.. -I$(srcdir)/.. +XDEFS = $(MODULES_CPPFLAGS) + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ + diff --git a/servers/slapd/back-null/README b/servers/slapd/back-null/README new file mode 100644 index 0000000..300afd9 --- /dev/null +++ b/servers/slapd/back-null/README @@ -0,0 +1 @@ +The Null Backend is described in the slapd-null(5) manual page. diff --git a/servers/slapd/back-null/null.c b/servers/slapd/back-null/null.c new file mode 100644 index 0000000..6ac7b51 --- /dev/null +++ b/servers/slapd/back-null/null.c @@ -0,0 +1,470 @@ +/* null.c - the null backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2002-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by Hallvard Furuseth for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "slap.h" +#include "config.h" + +typedef struct null_info { + int ni_bind_allowed; + int ni_dosearch; + ID ni_nextid; + Entry *ni_entry; +} null_info; + +static ConfigTable nullcfg[] = { + { "bind", "true|FALSE", 1, 2, 0, ARG_ON_OFF|ARG_OFFSET, + (void *)offsetof(null_info, ni_bind_allowed), + "( OLcfgDbAt:8.1 NAME 'olcDbBindAllowed' " + "DESC 'Allow binds to this database' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "dosearch", "true|FALSE", 1, 2, 0, ARG_ON_OFF|ARG_OFFSET, + (void *)offsetof(null_info, ni_dosearch), + "( OLcfgDbAt:8.2 NAME 'olcDbDoSearch' " + "DESC 'Return an entry on searches' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED, + NULL, NULL, NULL, NULL } +}; + +static ConfigOCs nullocs[] = { + { "( OLcfgDbOc:8.1 " + "NAME 'olcNullConfig' " + "DESC 'Null backend ocnfiguration' " + "SUP olcDatabaseConfig " + "MAY ( olcDbBindAllowed $ olcDbDoSearch ) )", + Cft_Database, nullcfg }, + { NULL, 0, NULL } +}; + + +static int +null_back_db_open( BackendDB *be, ConfigReply *cr ) +{ + struct null_info *ni = (struct null_info *) be->be_private; + struct berval bv[2]; + AttributeDescription *ad = NULL; + const char *text; + Entry *e; + + if ( ni->ni_dosearch ) { + e = entry_alloc(); + e->e_name = be->be_suffix[0]; + e->e_nname = be->be_nsuffix[0]; + + dnRdn( &e->e_nname, &bv[0] ); + bv[1].bv_val = strchr(bv[0].bv_val, '=') + 1; + bv[1].bv_len = bv[0].bv_len - (bv[1].bv_val - + bv[0].bv_val); + bv[0].bv_len -= bv[1].bv_len + 1; + slap_bv2ad( &bv[0], &ad, &text ); + attr_merge_one( e, ad, &bv[1], NULL ); + + ber_str2bv("extensibleObject", 0, 0, &bv[0]); + attr_merge_one( e, slap_schema.si_ad_objectClass, &bv[0], NULL); + ni->ni_entry = e; + } + return 0; +} + +/* LDAP operations */ + +static int +null_back_bind( Operation *op, SlapReply *rs ) +{ + struct null_info *ni = (struct null_info *) op->o_bd->be_private; + + if ( ni->ni_bind_allowed || be_isroot_pw( op ) ) { + /* front end will send result on success (0) */ + return LDAP_SUCCESS; + } + + rs->sr_err = LDAP_INVALID_CREDENTIALS; + send_ldap_result( op, rs ); + + return rs->sr_err; +} + + +static int +null_back_respond( Operation *op, SlapReply *rs, int rc ) +{ + LDAPControl ctrl[SLAP_MAX_RESPONSE_CONTROLS], *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int c = 0; + + BerElementBuffer ps_berbuf; + BerElement *ps_ber = NULL; + LDAPControl **preread_ctrl = NULL, + **postread_ctrl = NULL; + + rs->sr_err = LDAP_OTHER; + + /* this comes first, as in case of assertion failure + * any further processing must stop */ + if ( get_assert( op ) ) { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto respond; + } + + if ( op->o_preread ) { + Entry e = { 0 }; + + switch ( op->o_tag ) { + case LDAP_REQ_MODIFY: + case LDAP_REQ_RENAME: + case LDAP_REQ_DELETE: + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + + preread_ctrl = &ctrls[c]; + *preread_ctrl = NULL; + + if ( slap_read_controls( op, rs, &e, + &slap_pre_read_bv, preread_ctrl ) ) + { + preread_ctrl = NULL; + + Debug( LDAP_DEBUG_TRACE, + "<=- null_back_respond: pre-read " + "failed!\n", 0, 0, 0 ); + + if ( op->o_preread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto respond; + } + + } else { + c++; + } + break; + } + } + + if ( op->o_postread ) { + Entry e = { 0 }; + + switch ( op->o_tag ) { + case LDAP_REQ_ADD: + case LDAP_REQ_MODIFY: + case LDAP_REQ_RENAME: + if ( op->o_tag == LDAP_REQ_ADD ) { + e.e_name = op->ora_e->e_name; + e.e_nname = op->ora_e->e_nname; + + } else { + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + } + + postread_ctrl = &ctrls[c]; + *postread_ctrl = NULL; + + if ( slap_read_controls( op, rs, &e, + &slap_post_read_bv, postread_ctrl ) ) + { + postread_ctrl = NULL; + + Debug( LDAP_DEBUG_TRACE, + "<=- null_back_respond: post-read " + "failed!\n", 0, 0, 0 ); + + if ( op->o_postread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto respond; + } + + } else { + c++; + } + break; + } + } + + if ( op->o_noop ) { + switch ( op->o_tag ) { + case LDAP_REQ_ADD: + case LDAP_REQ_MODIFY: + case LDAP_REQ_RENAME: + case LDAP_REQ_DELETE: + case LDAP_REQ_EXTENDED: + rc = LDAP_X_NO_OPERATION; + break; + } + } + + if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) { + struct berval cookie = BER_BVC( "" ); + + /* should not be here... */ + assert( op->o_tag == LDAP_REQ_SEARCH ); + + ctrl[c].ldctl_oid = LDAP_CONTROL_PAGEDRESULTS; + ctrl[c].ldctl_iscritical = 0; + + ps_ber = (BerElement *)&ps_berbuf; + ber_init2( ps_ber, NULL, LBER_USE_DER ); + + /* return size of 0 -- no estimate */ + ber_printf( ps_ber, "{iO}", 0, &cookie ); + + if ( ber_flatten2( ps_ber, &ctrl[c].ldctl_value, 0 ) == -1 ) { + goto done; + } + + ctrls[c] = &ctrl[c]; + c++; + } + + /* terminate controls array */ + ctrls[c] = NULL; + rs->sr_ctrls = ctrls; + rs->sr_err = rc; + +respond:; + send_ldap_result( op, rs ); + rs->sr_ctrls = NULL; + +done:; + if ( ps_ber != NULL ) { + (void) ber_free_buf( ps_ber ); + } + + if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) { + slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *preread_ctrl, op->o_tmpmemctx ); + } + + if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) { + slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *postread_ctrl, op->o_tmpmemctx ); + } + + return rs->sr_err; +} + +/* add, delete, modify, modrdn, search */ +static int +null_back_success( Operation *op, SlapReply *rs ) +{ + return null_back_respond( op, rs, LDAP_SUCCESS ); +} + +/* compare */ +static int +null_back_false( Operation *op, SlapReply *rs ) +{ + return null_back_respond( op, rs, LDAP_COMPARE_FALSE ); +} + +static int +null_back_search( Operation *op, SlapReply *rs ) +{ + struct null_info *ni = (struct null_info *) op->o_bd->be_private; + + if ( ni->ni_entry ) { + rs->sr_entry = ni->ni_entry; + rs->sr_flags = 0; + + rs->sr_attrs = op->ors_attrs; + rs->sr_operational_attrs = NULL; + send_search_entry( op, rs ); + } + return null_back_respond( op, rs, LDAP_SUCCESS ); +} + +/* for overlays */ +static int +null_back_entry_get( + Operation *op, + struct berval *ndn, + ObjectClass *oc, + AttributeDescription *at, + int rw, + Entry **ent ) +{ + /* don't admit the object isn't there */ + return oc || at ? LDAP_NO_SUCH_ATTRIBUTE : LDAP_BUSY; +} + +static int +null_back_entry_release( + Operation *op, + Entry *e, + int rw ) +{ + /* we reuse our entry, don't free it */ + return 0; +} + +/* Slap tools */ + +static int +null_tool_entry_open( BackendDB *be, int mode ) +{ + return 0; +} + +static int +null_tool_entry_close( BackendDB *be ) +{ + assert( be != NULL ); + return 0; +} + +static ID +null_tool_entry_first_x( BackendDB *be, struct berval *base, int scope, Filter *f ) +{ + return NOID; +} + +static ID +null_tool_entry_next( BackendDB *be ) +{ + return NOID; +} + +static Entry * +null_tool_entry_get( BackendDB *be, ID id ) +{ + assert( slapMode & SLAP_TOOL_MODE ); + return NULL; +} + +static ID +null_tool_entry_put( BackendDB *be, Entry *e, struct berval *text ) +{ + assert( slapMode & SLAP_TOOL_MODE ); + assert( text != NULL ); + assert( text->bv_val != NULL ); + assert( text->bv_val[0] == '\0' ); /* overconservative? */ + + e->e_id = ((struct null_info *) be->be_private)->ni_nextid++; + return e->e_id; +} + + +/* Setup */ + +static int +null_back_db_init( BackendDB *be, ConfigReply *cr ) +{ + struct null_info *ni = ch_calloc( 1, sizeof(struct null_info) ); + ni->ni_bind_allowed = 0; + ni->ni_nextid = 1; + be->be_private = ni; + be->be_cf_ocs = be->bd_info->bi_cf_ocs; + return 0; +} + +static int +null_back_db_destroy( Backend *be, ConfigReply *cr ) +{ + struct null_info *ni = be->be_private; + + if ( ni->ni_entry ) { + entry_free( ni->ni_entry ); + ni->ni_entry = NULL; + } + free( be->be_private ); + return 0; +} + + +int +null_back_initialize( BackendInfo *bi ) +{ + static char *controls[] = { + LDAP_CONTROL_ASSERT, + LDAP_CONTROL_MANAGEDSAIT, + LDAP_CONTROL_NOOP, + LDAP_CONTROL_PAGEDRESULTS, + LDAP_CONTROL_SUBENTRIES, + LDAP_CONTROL_PRE_READ, + LDAP_CONTROL_POST_READ, + LDAP_CONTROL_X_PERMISSIVE_MODIFY, + NULL + }; + + Debug( LDAP_DEBUG_TRACE, + "null_back_initialize: initialize null backend\n", 0, 0, 0 ); + + bi->bi_flags |= + SLAP_BFLAG_INCREMENT | + SLAP_BFLAG_SUBENTRIES | + SLAP_BFLAG_ALIASES | + SLAP_BFLAG_REFERRALS; + + bi->bi_controls = controls; + + bi->bi_open = 0; + bi->bi_close = 0; + bi->bi_config = 0; + bi->bi_destroy = 0; + + bi->bi_db_init = null_back_db_init; + bi->bi_db_config = config_generic_wrapper; + bi->bi_db_open = null_back_db_open; + bi->bi_db_close = 0; + bi->bi_db_destroy = null_back_db_destroy; + + bi->bi_op_bind = null_back_bind; + bi->bi_op_unbind = 0; + bi->bi_op_search = null_back_search; + bi->bi_op_compare = null_back_false; + bi->bi_op_modify = null_back_success; + bi->bi_op_modrdn = null_back_success; + bi->bi_op_add = null_back_success; + bi->bi_op_delete = null_back_success; + bi->bi_op_abandon = 0; + + bi->bi_extended = 0; + + bi->bi_chk_referrals = 0; + + bi->bi_connection_init = 0; + bi->bi_connection_destroy = 0; + + bi->bi_entry_get_rw = null_back_entry_get; + bi->bi_entry_release_rw = null_back_entry_release; + + bi->bi_tool_entry_open = null_tool_entry_open; + bi->bi_tool_entry_close = null_tool_entry_close; + bi->bi_tool_entry_first = backend_tool_entry_first; + bi->bi_tool_entry_first_x = null_tool_entry_first_x; + bi->bi_tool_entry_next = null_tool_entry_next; + bi->bi_tool_entry_get = null_tool_entry_get; + bi->bi_tool_entry_put = null_tool_entry_put; + + bi->bi_cf_ocs = nullocs; + return config_register_schema( nullcfg, nullocs ); +} + +#if SLAPD_NULL == SLAPD_MOD_DYNAMIC + +/* conditionally define the init_module() function */ +SLAP_BACKEND_INIT_MODULE( null ) + +#endif /* SLAPD_NULL == SLAPD_MOD_DYNAMIC */ diff --git a/servers/slapd/back-passwd/Makefile.in b/servers/slapd/back-passwd/Makefile.in new file mode 100644 index 0000000..a3f1090 --- /dev/null +++ b/servers/slapd/back-passwd/Makefile.in @@ -0,0 +1,41 @@ +# Makefile.in for back-passwd +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +SRCS = search.c config.c init.c +OBJS = search.lo config.lo init.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-passwd" +BUILD_MOD = @BUILD_PASSWD@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_PASSWD@_DEFS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_R_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBBASE = back_passwd + +XINCPATH = -I.. -I$(srcdir)/.. +XDEFS = $(MODULES_CPPFLAGS) + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ + diff --git a/servers/slapd/back-passwd/back-passwd.h b/servers/slapd/back-passwd/back-passwd.h new file mode 100644 index 0000000..5b1be46 --- /dev/null +++ b/servers/slapd/back-passwd/back-passwd.h @@ -0,0 +1,31 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#ifndef _BACK_PASSWD_H +#define _BACK_PASSWD_H + +#include "proto-passwd.h" + +LDAP_BEGIN_DECL + +extern ldap_pvt_thread_mutex_t passwd_mutex; + +extern BI_destroy passwd_back_destroy; + +extern BI_op_search passwd_back_search; + +LDAP_END_DECL + +#endif /* _BACK_PASSWD_H */ diff --git a/servers/slapd/back-passwd/config.c b/servers/slapd/back-passwd/config.c new file mode 100644 index 0000000..3a72d37 --- /dev/null +++ b/servers/slapd/back-passwd/config.c @@ -0,0 +1,73 @@ +/* config.c - passwd backend configuration file routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by the University of Michigan + * (as part of U-MICH LDAP). + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "slap.h" +#include "back-passwd.h" +#include "config.h" + +static ConfigTable passwdcfg[] = { + { "file", "filename", 2, 2, 0, +#ifdef HAVE_SETPWFILE + ARG_STRING|ARG_OFFSET, NULL, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgDbAt:9.1 NAME 'olcPasswdFile' " + "DESC 'File containing passwd records' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED, + NULL, NULL, NULL, NULL } +}; + +static ConfigOCs passwdocs[] = { + { "( OLcfgDbOc:9.1 " + "NAME 'olcPasswdConfig' " + "DESC 'Passwd backend configuration' " + "SUP olcDatabaseConfig " + "MAY olcPasswdFile )", + Cft_Database, passwdcfg }, + { NULL, 0, NULL } +}; + +int +passwd_back_init_cf( BackendInfo *bi ) +{ + bi->bi_cf_ocs = passwdocs; + return config_register_schema( passwdcfg, passwdocs ); +} diff --git a/servers/slapd/back-passwd/init.c b/servers/slapd/back-passwd/init.c new file mode 100644 index 0000000..fa45117 --- /dev/null +++ b/servers/slapd/back-passwd/init.c @@ -0,0 +1,122 @@ +/* init.c - initialize passwd backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> + +#include "slap.h" +#include "back-passwd.h" + +ldap_pvt_thread_mutex_t passwd_mutex; + +AttributeDescription *ad_sn; +AttributeDescription *ad_desc; + +static BI_db_init passwd_back_db_init; + +int +passwd_back_initialize( + BackendInfo *bi +) +{ + ldap_pvt_thread_mutex_init( &passwd_mutex ); + + bi->bi_open = passwd_back_open; + bi->bi_config = 0; + bi->bi_close = 0; + bi->bi_destroy = passwd_back_destroy; + + bi->bi_db_init = passwd_back_db_init; + bi->bi_db_config = 0; + bi->bi_db_open = 0; + bi->bi_db_close = 0; + bi->bi_db_destroy = 0; + + bi->bi_op_bind = 0; + bi->bi_op_unbind = 0; + bi->bi_op_search = passwd_back_search; + bi->bi_op_compare = 0; + bi->bi_op_modify = 0; + bi->bi_op_modrdn = 0; + bi->bi_op_add = 0; + bi->bi_op_delete = 0; + bi->bi_op_abandon = 0; + + bi->bi_extended = 0; + + bi->bi_chk_referrals = 0; + + bi->bi_connection_init = 0; + bi->bi_connection_destroy = 0; + + return passwd_back_init_cf( bi ); +} + +int +passwd_back_open( + BackendInfo *bi +) +{ + const char *text; + int rc; + + rc = slap_str2ad( "sn", &ad_sn, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "passwd_back_open: " + "slap_str2ad(\"%s\") returned %d: %s\n", + "sn", rc, text ); + return -1; + } + rc = slap_str2ad( "description", &ad_desc, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "passwd_back_open: " + "slap_str2ad(\"%s\") returned %d: %s\n", + "description", rc, text ); + return -1; + } + + return 0; +} + +int +passwd_back_destroy( + BackendInfo *bi +) +{ + ldap_pvt_thread_mutex_destroy( &passwd_mutex ); + return 0; +} + +static int +passwd_back_db_init( + Backend *be, + struct config_reply_s *cr +) +{ + be->be_cf_ocs = be->bd_info->bi_cf_ocs; + return 0; +} + +#if SLAPD_PASSWD == SLAPD_MOD_DYNAMIC + +/* conditionally define the init_module() function */ +SLAP_BACKEND_INIT_MODULE( passwd ) + +#endif /* SLAPD_PASSWD == SLAPD_MOD_DYNAMIC */ + diff --git a/servers/slapd/back-passwd/proto-passwd.h b/servers/slapd/back-passwd/proto-passwd.h new file mode 100644 index 0000000..e328aee --- /dev/null +++ b/servers/slapd/back-passwd/proto-passwd.h @@ -0,0 +1,33 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#ifndef PROTO_PASSWD_H +#define PROTO_PASSWD_H + +LDAP_BEGIN_DECL + +extern BI_init passwd_back_initialize; +extern BI_open passwd_back_open; +extern BI_destroy passwd_back_destroy; +extern BI_op_search passwd_back_search; + +extern int passwd_back_init_cf( BackendInfo *bi ); + +extern AttributeDescription *ad_sn; +extern AttributeDescription *ad_desc; + +LDAP_END_DECL + +#endif /* PROTO_PASSWD_H */ diff --git a/servers/slapd/back-passwd/search.c b/servers/slapd/back-passwd/search.c new file mode 100644 index 0000000..d5bb9dc --- /dev/null +++ b/servers/slapd/back-passwd/search.c @@ -0,0 +1,365 @@ +/* search.c - /etc/passwd backend search function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by the University of Michigan + * (as part of U-MICH LDAP). Additional significant contributors + * include: + * Hallvard B. Furuseth + * Howard Chu + * Kurt D. Zeilenga + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include <pwd.h> + +#include "slap.h" +#include "back-passwd.h" + +static void pw_start( Backend *be ); + +static int pw2entry( + Backend *be, + struct passwd *pw, + Entry *ep ); + +int +passwd_back_search( + Operation *op, + SlapReply *rs ) +{ + struct passwd *pw; + time_t stoptime = (time_t)-1; + + LDAPRDN rdn = NULL; + struct berval parent = BER_BVNULL; + + AttributeDescription *ad_objectClass = slap_schema.si_ad_objectClass; + + if ( op->ors_tlimit != SLAP_NO_LIMIT ) { + stoptime = op->o_time + op->ors_tlimit; + } + + /* Handle a query for the base of this backend */ + if ( be_issuffix( op->o_bd, &op->o_req_ndn ) ) { + struct berval val; + + rs->sr_matched = op->o_req_dn.bv_val; + + if( op->ors_scope != LDAP_SCOPE_ONELEVEL ) { + AttributeDescription *desc = NULL; + char *next; + Entry e = { 0 }; + + /* Create an entry corresponding to the base DN */ + e.e_name.bv_val = ch_strdup( op->o_req_dn.bv_val ); + e.e_name.bv_len = op->o_req_dn.bv_len; + e.e_nname.bv_val = ch_strdup( op->o_req_ndn.bv_val ); + e.e_nname.bv_len = op->o_req_ndn.bv_len; + + /* Use the first attribute of the DN + * as an attribute within the entry itself. + */ + if( ldap_bv2rdn( &op->o_req_dn, &rdn, &next, + LDAP_DN_FORMAT_LDAP ) ) + { + rs->sr_err = LDAP_INVALID_DN_SYNTAX; + goto done; + } + + if( slap_bv2ad( &rdn[0]->la_attr, &desc, &rs->sr_text )) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + ldap_rdnfree(rdn); + goto done; + } + + attr_merge_normalize_one( &e, desc, &rdn[0]->la_value, NULL ); + + ldap_rdnfree(rdn); + rdn = NULL; + + /* Every entry needs an objectclass. We don't really + * know if our hardcoded choice here agrees with the + * DN that was configured for this backend, but it's + * better than nothing. + * + * should be a configuratable item + */ + BER_BVSTR( &val, "organizationalUnit" ); + attr_merge_one( &e, ad_objectClass, &val, NULL ); + + if ( test_filter( op, &e, op->ors_filter ) == LDAP_COMPARE_TRUE ) { + rs->sr_entry = &e; + rs->sr_attrs = op->ors_attrs; + rs->sr_flags = REP_ENTRY_MODIFIABLE; + send_search_entry( op, rs ); + rs->sr_flags = 0; + rs->sr_attrs = NULL; + } + + entry_clean( &e ); + } + + if ( op->ors_scope != LDAP_SCOPE_BASE ) { + /* check all our "children" */ + + ldap_pvt_thread_mutex_lock( &passwd_mutex ); + pw_start( op->o_bd ); + for ( pw = getpwent(); pw != NULL; pw = getpwent() ) { + Entry e = { 0 }; + + /* check for abandon */ + if ( op->o_abandon ) { + endpwent(); + ldap_pvt_thread_mutex_unlock( &passwd_mutex ); + return( SLAPD_ABANDON ); + } + + /* check time limit */ + if ( op->ors_tlimit != SLAP_NO_LIMIT + && slap_get_time() > stoptime ) + { + send_ldap_error( op, rs, LDAP_TIMELIMIT_EXCEEDED, NULL ); + endpwent(); + ldap_pvt_thread_mutex_unlock( &passwd_mutex ); + return( 0 ); + } + + if ( pw2entry( op->o_bd, pw, &e ) ) { + rs->sr_err = LDAP_OTHER; + endpwent(); + ldap_pvt_thread_mutex_unlock( &passwd_mutex ); + goto done; + } + + if ( test_filter( op, &e, op->ors_filter ) == LDAP_COMPARE_TRUE ) { + /* check size limit */ + if ( --op->ors_slimit == -1 ) { + send_ldap_error( op, rs, LDAP_SIZELIMIT_EXCEEDED, NULL ); + endpwent(); + ldap_pvt_thread_mutex_unlock( &passwd_mutex ); + return( 0 ); + } + + rs->sr_entry = &e; + rs->sr_attrs = op->ors_attrs; + rs->sr_flags = REP_ENTRY_MODIFIABLE; + send_search_entry( op, rs ); + rs->sr_flags = 0; + rs->sr_entry = NULL; + } + + entry_clean( &e ); + } + endpwent(); + ldap_pvt_thread_mutex_unlock( &passwd_mutex ); + } + + } else { + char *next; + Entry e = { 0 }; + int rc; + + if (! be_issuffix( op->o_bd, &op->o_req_ndn ) ) { + dnParent( &op->o_req_ndn, &parent ); + } + + /* This backend is only one layer deep. Don't answer requests for + * anything deeper than that. + */ + if( !be_issuffix( op->o_bd, &parent ) ) { + int i; + for( i=0; op->o_bd->be_nsuffix[i].bv_val != NULL; i++ ) { + if( dnIsSuffix( &op->o_req_ndn, &op->o_bd->be_nsuffix[i] ) ) { + rs->sr_matched = op->o_bd->be_suffix[i].bv_val; + break; + } + } + rs->sr_err = LDAP_NO_SUCH_OBJECT; + goto done; + } + + if( op->ors_scope == LDAP_SCOPE_ONELEVEL ) { + goto done; + } + + if ( ldap_bv2rdn( &op->o_req_dn, &rdn, &next, + LDAP_DN_FORMAT_LDAP )) + { + rs->sr_err = LDAP_OTHER; + goto done; + } + + ldap_pvt_thread_mutex_lock( &passwd_mutex ); + pw_start( op->o_bd ); + pw = getpwnam( rdn[0]->la_value.bv_val ); + if ( pw == NULL ) { + rs->sr_matched = parent.bv_val; + rs->sr_err = LDAP_NO_SUCH_OBJECT; + ldap_pvt_thread_mutex_unlock( &passwd_mutex ); + goto done; + } + + rc = pw2entry( op->o_bd, pw, &e ); + ldap_pvt_thread_mutex_unlock( &passwd_mutex ); + if ( rc ) { + rs->sr_err = LDAP_OTHER; + goto done; + } + + if ( test_filter( op, &e, op->ors_filter ) == LDAP_COMPARE_TRUE ) { + rs->sr_entry = &e; + rs->sr_attrs = op->ors_attrs; + rs->sr_flags = REP_ENTRY_MODIFIABLE; + send_search_entry( op, rs ); + rs->sr_flags = 0; + rs->sr_entry = NULL; + rs->sr_attrs = NULL; + } + + entry_clean( &e ); + } + +done: + if( rs->sr_err != LDAP_NO_SUCH_OBJECT ) rs->sr_matched = NULL; + send_ldap_result( op, rs ); + + if( rdn != NULL ) ldap_rdnfree( rdn ); + + return( 0 ); +} + +static void +pw_start( + Backend *be +) +{ + endpwent(); + +#ifdef HAVE_SETPWFILE + if ( be->be_private != NULL ) { + (void) setpwfile( (char *) be->be_private ); + } +#endif /* HAVE_SETPWFILE */ +} + +static int +pw2entry( Backend *be, struct passwd *pw, Entry *e ) +{ + size_t pwlen; + struct berval val; + struct berval bv; + + int rc; + + /* + * from pw we get pw_name and make it cn + * give it an objectclass of person. + */ + + pwlen = strlen( pw->pw_name ); + val.bv_len = STRLENOF("uid=,") + ( pwlen + be->be_suffix[0].bv_len ); + val.bv_val = ch_malloc( val.bv_len + 1 ); + + /* rdn attribute type should be a configuratable item */ + sprintf( val.bv_val, "uid=%s,%s", + pw->pw_name, be->be_suffix[0].bv_val ); + + rc = dnNormalize( 0, NULL, NULL, &val, &bv, NULL ); + if( rc != LDAP_SUCCESS ) { + free( val.bv_val ); + return( -1 ); + } + + e->e_name = val; + e->e_nname = bv; + + e->e_attrs = NULL; + + /* objectclasses should be configurable items */ + BER_BVSTR( &val, "person" ); + attr_merge_one( e, slap_schema.si_ad_objectClass, &val, NULL ); + + BER_BVSTR( &val, "uidObject" ); + attr_merge_one( e, slap_schema.si_ad_objectClass, &val, NULL ); + + val.bv_val = pw->pw_name; + val.bv_len = pwlen; + attr_merge_normalize_one( e, slap_schema.si_ad_uid, &val, NULL ); /* required by uidObject */ + attr_merge_normalize_one( e, slap_schema.si_ad_cn, &val, NULL ); /* required by person */ + attr_merge_normalize_one( e, ad_sn, &val, NULL ); /* required by person */ + +#ifdef HAVE_STRUCT_PASSWD_PW_GECOS + /* + * if gecos is present, add it as a cn. first process it + * according to standard BSD usage. If the processed cn has + * a space, use the tail as the surname. + */ + if (pw->pw_gecos[0]) { + char *s; + + ber_str2bv( pw->pw_gecos, 0, 0, &val ); + attr_merge_normalize_one( e, ad_desc, &val, NULL ); + + s = ber_bvchr( &val, ',' ); + if ( s ) *s = '\0'; + + s = ber_bvchr( &val, '&' ); + if ( s ) { + char buf[1024]; + + if( val.bv_len + pwlen < sizeof(buf) ) { + int i = s - val.bv_val; + strncpy( buf, val.bv_val, i ); + s = buf + i; + strcpy( s, pw->pw_name ); + *s = TOUPPER((unsigned char)*s); + strcat( s, val.bv_val + i + 1 ); + val.bv_val = buf; + } + } + val.bv_len = strlen( val.bv_val ); + + if ( val.bv_len && strcasecmp( val.bv_val, pw->pw_name ) ) { + attr_merge_normalize_one( e, slap_schema.si_ad_cn, &val, NULL ); + } + + if ( ( s = strrchr(val.bv_val, ' ' ) ) ) { + ber_str2bv( s + 1, 0, 0, &val ); + attr_merge_normalize_one( e, ad_sn, &val, NULL ); + } + } +#endif /* HAVE_STRUCT_PASSWD_PW_GECOS */ + + return( 0 ); +} diff --git a/servers/slapd/back-perl/Makefile.in b/servers/slapd/back-perl/Makefile.in new file mode 100644 index 0000000..31e2715 --- /dev/null +++ b/servers/slapd/back-perl/Makefile.in @@ -0,0 +1,46 @@ +# Makefile.in for back-perl +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## Portions Copyright 1999 John C. Quillan. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +SRCS = init.c search.c close.c config.c bind.c compare.c \ + modify.c add.c modrdn.c delete.c +OBJS = init.lo search.lo close.lo config.lo bind.lo compare.lo \ + modify.lo add.lo modrdn.lo delete.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-perl" +BUILD_MOD = @BUILD_PERL@ +PERL_CPPFLAGS = @PERL_CPPFLAGS@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_PERL@_DEFS) +MOD_LIBS = @MOD_PERL_LDFLAGS@ + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_R_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBBASE = back_perl + +XINCPATH = -I.. -I$(srcdir)/.. +XDEFS = $(PERL_CPPFLAGS) $(MODULES_CPPFLAGS) + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ + diff --git a/servers/slapd/back-perl/README b/servers/slapd/back-perl/README new file mode 100644 index 0000000..0959b44 --- /dev/null +++ b/servers/slapd/back-perl/README @@ -0,0 +1,24 @@ +Differences from 2.0 Perl API: + +- Perl 5.6 is supported + +- backend methods return actual LDAP result codes, not + true/false; this gives the Perl module finer control + of the error returned to the client + +- a filterSearchResults configuration file directive was + added to tell the backend glue that the results returned + from the Perl module are candidates only + +- the "init" method is called after the backend has been + initialized - this lets you do some initialization after + *all* configuration file directives have been read + +- the interface for the search method is improved to + pass the scope, deferencing policy, size limit, etc. + See SampleLDAP.pm for details. + +These changes were sponsored by myinternet Limited. + +Luke Howard <lukeh@padl.com> + diff --git a/servers/slapd/back-perl/SampleLDAP.pm b/servers/slapd/back-perl/SampleLDAP.pm new file mode 100644 index 0000000..bd25913 --- /dev/null +++ b/servers/slapd/back-perl/SampleLDAP.pm @@ -0,0 +1,171 @@ +# This is a sample Perl module for the OpenLDAP server slapd. +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## Portions Copyright 1999 John C. Quillan. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +# Usage: Add something like this to slapd.conf: +# +# database perl +# suffix "o=AnyOrg,c=US" +# perlModulePath /directory/containing/this/module +# perlModule SampleLDAP +# +# See the slapd-perl(5) manual page for details. +# +# This demo module keeps an in-memory hash {"DN" => "LDIF entry", ...} +# built in sub add{} & co. The data is lost when slapd shuts down. + +package SampleLDAP; +use strict; +use warnings; +use POSIX; + +$SampleLDAP::VERSION = '1.01'; + +sub new { + my $class = shift; + + my $this = {}; + bless $this, $class; + print {*STDERR} "Here in new\n"; + print {*STDERR} 'Posix Var ' . BUFSIZ . ' and ' . FILENAME_MAX . "\n"; + return $this; +} + +sub init { + return 0; +} + +sub search { + my $this = shift; + my ( $base, $scope, $deref, $sizeLim, $timeLim, $filterStr, $attrOnly, + @attrs ) + = @_; + print {*STDERR} "====$filterStr====\n"; + $filterStr =~ s/\(|\)//gm; + $filterStr =~ s/=/: /m; + + my @match_dn = (); + for my $dn ( keys %{$this} ) { + if ( $this->{$dn} =~ /$filterStr/imx ) { + push @match_dn, $dn; + last if ( scalar @match_dn == $sizeLim ); + + } + } + + my @match_entries = (); + + for my $dn (@match_dn) { + push @match_entries, $this->{$dn}; + } + + return ( 0, @match_entries ); + +} + +sub compare { + my $this = shift; + my ( $dn, $avaStr ) = @_; + my $rc = 5; # LDAP_COMPARE_FALSE + + $avaStr =~ s/=/: /m; + + if ( $this->{$dn} =~ /$avaStr/im ) { + $rc = 6; # LDAP_COMPARE_TRUE + } + + return $rc; +} + +sub modify { + my $this = shift; + + my ( $dn, @list ) = @_; + + while ( @list > 0 ) { + my $action = shift @list; + my $key = shift @list; + my $value = shift @list; + + if ( $action eq 'ADD' ) { + $this->{$dn} .= "$key: $value\n"; + + } + elsif ( $action eq 'DELETE' ) { + $this->{$dn} =~ s/^$key:\s*$value\n//im; + + } + elsif ( $action eq 'REPLACE' ) { + $this->{$dn} =~ s/$key: .*$/$key: $value/im; + } + } + + return 0; +} + +sub add { + my $this = shift; + + my ($entryStr) = @_; + + my ($dn) = ( $entryStr =~ /dn:\s(.*)$/m ); + + # + # This needs to be here until a normalized dn is + # passed to this routine. + # + $dn = uc $dn; + $dn =~ s/\s*//gm; + + $this->{$dn} = $entryStr; + + return 0; +} + +sub modrdn { + my $this = shift; + + my ( $dn, $newdn, $delFlag ) = @_; + + $this->{$newdn} = $this->{$dn}; + + if ($delFlag) { + delete $this->{$dn}; + } + return 0; + +} + +sub delete { + my $this = shift; + + my ($dn) = @_; + + print {*STDERR} "XXXXXX $dn XXXXXXX\n"; + delete $this->{$dn}; + return 0; +} + +sub config { + my $this = shift; + + my (@args) = @_; + local $, = ' - '; + print {*STDERR} @args; + print {*STDERR} "\n"; + return 0; +} + +1; diff --git a/servers/slapd/back-perl/add.c b/servers/slapd/back-perl/add.c new file mode 100644 index 0000000..a7cf33a --- /dev/null +++ b/servers/slapd/back-perl/add.c @@ -0,0 +1,62 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 John C. Quillan. + * Portions Copyright 2002 myinternet Limited. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "perl_back.h" + +int +perl_back_add( + Operation *op, + SlapReply *rs ) +{ + PerlBackend *perl_back = (PerlBackend *) op->o_bd->be_private; + int len; + int count; + + PERL_SET_CONTEXT( PERL_INTERPRETER ); + ldap_pvt_thread_mutex_lock( &perl_interpreter_mutex ); + ldap_pvt_thread_mutex_lock( &entry2str_mutex ); + + { + dSP; ENTER; SAVETMPS; + + PUSHMARK(sp); + XPUSHs( perl_back->pb_obj_ref ); + XPUSHs(sv_2mortal(newSVpv( entry2str( op->ora_e, &len ), 0 ))); + + PUTBACK; + + count = call_method("add", G_SCALAR); + + SPAGAIN; + + if (count != 1) { + croak("Big trouble in back_add\n"); + } + + rs->sr_err = POPi; + + PUTBACK; FREETMPS; LEAVE; + } + + ldap_pvt_thread_mutex_unlock( &entry2str_mutex ); + ldap_pvt_thread_mutex_unlock( &perl_interpreter_mutex ); + + send_ldap_result( op, rs ); + + Debug( LDAP_DEBUG_ANY, "Perl ADD\n", 0, 0, 0 ); + return( 0 ); +} diff --git a/servers/slapd/back-perl/asperl_undefs.h b/servers/slapd/back-perl/asperl_undefs.h new file mode 100644 index 0000000..73db78c --- /dev/null +++ b/servers/slapd/back-perl/asperl_undefs.h @@ -0,0 +1,38 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +/* This file is probably obsolete. If it is not, */ +/* #inclusion of it may have to be moved. See ITS#2513. */ + +/* This file is necessary because both PERL headers */ +/* and OpenLDAP define a number of macros without */ +/* checking wether they're already defined */ + +#ifndef ASPERL_UNDEFS_H +#define ASPERL_UNDEFS_H + +/* ActiveState Win32 PERL port support */ +/* set in ldap/include/portable.h */ +# ifdef HAVE_WIN32_ASPERL +/* The following macros are undefined to prevent */ +/* redefinition in PERL headers*/ +# undef gid_t +# undef uid_t +# undef mode_t +# undef caddr_t +# undef WIN32_LEAN_AND_MEAN +# endif +#endif + diff --git a/servers/slapd/back-perl/bind.c b/servers/slapd/back-perl/bind.c new file mode 100644 index 0000000..beefd4c --- /dev/null +++ b/servers/slapd/back-perl/bind.c @@ -0,0 +1,80 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 John C. Quillan. + * Portions Copyright 2002 myinternet Limited. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "perl_back.h" + + +/********************************************************** + * + * Bind + * + **********************************************************/ +int +perl_back_bind( + Operation *op, + SlapReply *rs ) +{ + int count; + + PerlBackend *perl_back = (PerlBackend *) op->o_bd->be_private; + + /* allow rootdn as a means to auth without the need to actually + * contact the proxied DSA */ + switch ( be_rootdn_bind( op, rs ) ) { + case SLAP_CB_CONTINUE: + break; + + default: + return rs->sr_err; + } + + PERL_SET_CONTEXT( PERL_INTERPRETER ); + ldap_pvt_thread_mutex_lock( &perl_interpreter_mutex ); + + { + dSP; ENTER; SAVETMPS; + + PUSHMARK(SP); + XPUSHs( perl_back->pb_obj_ref ); + XPUSHs(sv_2mortal(newSVpv( op->o_req_dn.bv_val , op->o_req_dn.bv_len))); + XPUSHs(sv_2mortal(newSVpv( op->orb_cred.bv_val , op->orb_cred.bv_len))); + PUTBACK; + + count = call_method("bind", G_SCALAR); + + SPAGAIN; + + if (count != 1) { + croak("Big trouble in back_bind\n"); + } + + rs->sr_err = POPi; + + + PUTBACK; FREETMPS; LEAVE; + } + + ldap_pvt_thread_mutex_unlock( &perl_interpreter_mutex ); + + Debug( LDAP_DEBUG_ANY, "Perl BIND returned 0x%04x\n", rs->sr_err, 0, 0 ); + + /* frontend will send result on success (0) */ + if( rs->sr_err != LDAP_SUCCESS ) + send_ldap_result( op, rs ); + + return ( rs->sr_err ); +} diff --git a/servers/slapd/back-perl/close.c b/servers/slapd/back-perl/close.c new file mode 100644 index 0000000..6a64523 --- /dev/null +++ b/servers/slapd/back-perl/close.c @@ -0,0 +1,59 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 John C. Quillan. + * Portions Copyright 2002 myinternet Limited. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "perl_back.h" +#include "../config.h" +/********************************************************** + * + * Close + * + **********************************************************/ + +int +perl_back_close( + BackendInfo *bd +) +{ + perl_destruct(PERL_INTERPRETER); + perl_free(PERL_INTERPRETER); + PERL_INTERPRETER = NULL; +#ifdef PERL_SYS_TERM + PERL_SYS_TERM(); +#endif + + ldap_pvt_thread_mutex_destroy( &perl_interpreter_mutex ); + + return 0; +} + +int +perl_back_db_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + PerlBackend *pb = be->be_private; + + ch_free( pb->pb_module_name ); + ber_bvarray_free( pb->pb_module_path ); + ber_bvarray_free( pb->pb_module_config ); + + free( be->be_private ); + be->be_private = NULL; + + return 0; +} diff --git a/servers/slapd/back-perl/compare.c b/servers/slapd/back-perl/compare.c new file mode 100644 index 0000000..193b9b3 --- /dev/null +++ b/servers/slapd/back-perl/compare.c @@ -0,0 +1,80 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 John C. Quillan. + * Portions Copyright 2002 myinternet Limited. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "perl_back.h" +#include "lutil.h" + +/********************************************************** + * + * Compare + * + **********************************************************/ + +int +perl_back_compare( + Operation *op, + SlapReply *rs ) +{ + int count, avalen; + char *avastr; + + PerlBackend *perl_back = (PerlBackend *)op->o_bd->be_private; + + avalen = op->orc_ava->aa_desc->ad_cname.bv_len + 1 + + op->orc_ava->aa_value.bv_len; + avastr = ch_malloc( avalen + 1 ); + + lutil_strcopy( lutil_strcopy( lutil_strcopy( avastr, + op->orc_ava->aa_desc->ad_cname.bv_val ), "=" ), + op->orc_ava->aa_value.bv_val ); + + PERL_SET_CONTEXT( PERL_INTERPRETER ); + ldap_pvt_thread_mutex_lock( &perl_interpreter_mutex ); + + { + dSP; ENTER; SAVETMPS; + + PUSHMARK(sp); + XPUSHs( perl_back->pb_obj_ref ); + XPUSHs(sv_2mortal(newSVpv( op->o_req_dn.bv_val , op->o_req_dn.bv_len))); + XPUSHs(sv_2mortal(newSVpv( avastr , avalen))); + PUTBACK; + + count = call_method("compare", G_SCALAR); + + SPAGAIN; + + if (count != 1) { + croak("Big trouble in back_compare\n"); + } + + rs->sr_err = POPi; + + PUTBACK; FREETMPS; LEAVE; + } + + ldap_pvt_thread_mutex_unlock( &perl_interpreter_mutex ); + + ch_free( avastr ); + + send_ldap_result( op, rs ); + + Debug( LDAP_DEBUG_ANY, "Perl COMPARE\n", 0, 0, 0 ); + + return (0); +} + diff --git a/servers/slapd/back-perl/config.c b/servers/slapd/back-perl/config.c new file mode 100644 index 0000000..6caf446 --- /dev/null +++ b/servers/slapd/back-perl/config.c @@ -0,0 +1,255 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 John C. Quillan. + * Portions Copyright 2002 myinternet Limited. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "perl_back.h" +#include "../config.h" + +static ConfigDriver perl_cf; + +enum { + PERL_MODULE = 1, + PERL_PATH, + PERL_CONFIG +}; + +static ConfigTable perlcfg[] = { + { "perlModule", "module", 2, 2, 0, + ARG_STRING|ARG_MAGIC|PERL_MODULE, perl_cf, + "( OLcfgDbAt:11.1 NAME 'olcPerlModule' " + "DESC 'Perl module name' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "perlModulePath", "path", 2, 2, 0, + ARG_MAGIC|PERL_PATH, perl_cf, + "( OLcfgDbAt:11.2 NAME 'olcPerlModulePath' " + "DESC 'Perl module path' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "filterSearchResults", "on|off", 2, 2, 0, ARG_ON_OFF|ARG_OFFSET, + (void *)offsetof(PerlBackend, pb_filter_search_results), + "( OLcfgDbAt:11.3 NAME 'olcPerlFilterSearchResults' " + "DESC 'Filter search results before returning to client' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "perlModuleConfig", "args", 2, 0, 0, + ARG_MAGIC|PERL_CONFIG, perl_cf, + "( OLcfgDbAt:11.4 NAME 'olcPerlModuleConfig' " + "DESC 'Perl module config directives' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { NULL } +}; + +static ConfigOCs perlocs[] = { + { "( OLcfgDbOc:11.1 " + "NAME 'olcDbPerlConfig' " + "DESC 'Perl DB configuration' " + "SUP olcDatabaseConfig " + "MUST ( olcPerlModulePath $ olcPerlModule ) " + "MAY ( olcPerlFilterSearchResults $ olcPerlModuleConfig ) )", + Cft_Database, perlcfg, NULL, NULL }, + { NULL } +}; + +static ConfigOCs ovperlocs[] = { + { "( OLcfgDbOc:11.2 " + "NAME 'olcovPerlConfig' " + "DESC 'Perl overlay configuration' " + "SUP olcOverlayConfig " + "MUST ( olcPerlModulePath $ olcPerlModule ) " + "MAY ( olcPerlFilterSearchResults $ olcPerlModuleConfig ) )", + Cft_Overlay, perlcfg, NULL, NULL }, + { NULL } +}; + +/********************************************************** + * + * Config + * + **********************************************************/ +int +perl_back_db_config( + BackendDB *be, + const char *fname, + int lineno, + int argc, + char **argv +) +{ + int rc = config_generic_wrapper( be, fname, lineno, argc, argv ); + /* backward compatibility: map unknown directives to perlModuleConfig */ + if ( rc == SLAP_CONF_UNKNOWN ) { + char **av = ch_malloc( (argc+2) * sizeof(char *)); + int i; + av[0] = "perlModuleConfig"; + av++; + for ( i=0; i<argc; i++ ) + av[i] = argv[i]; + av[i] = NULL; + av--; + rc = config_generic_wrapper( be, fname, lineno, argc+1, av ); + ch_free( av ); + } + return rc; +} + +static int +perl_cf( + ConfigArgs *c +) +{ + PerlBackend *pb = (PerlBackend *) c->be->be_private; + SV* loc_sv; + int count ; + int args; + int rc = 0; + char eval_str[EVAL_BUF_SIZE]; + struct berval bv; + + if ( c->op == SLAP_CONFIG_EMIT ) { + switch( c-> type ) { + case PERL_MODULE: + if ( !pb->pb_module_name ) + return 1; + c->value_string = ch_strdup( pb->pb_module_name ); + break; + case PERL_PATH: + if ( !pb->pb_module_path ) + return 1; + ber_bvarray_dup_x( &c->rvalue_vals, pb->pb_module_path, NULL ); + break; + case PERL_CONFIG: + if ( !pb->pb_module_config ) + return 1; + ber_bvarray_dup_x( &c->rvalue_vals, pb->pb_module_config, NULL ); + break; + } + } else if ( c->op == LDAP_MOD_DELETE ) { + /* FIXME: none of this affects the state of the perl + * interpreter at all. We should probably destroy it + * and recreate it... + */ + switch( c-> type ) { + case PERL_MODULE: + ch_free( pb->pb_module_name ); + pb->pb_module_name = NULL; + break; + case PERL_PATH: + if ( c->valx < 0 ) { + ber_bvarray_free( pb->pb_module_path ); + pb->pb_module_path = NULL; + } else { + int i = c->valx; + ch_free( pb->pb_module_path[i].bv_val ); + for (; pb->pb_module_path[i].bv_val; i++ ) + pb->pb_module_path[i] = pb->pb_module_path[i+1]; + } + break; + case PERL_CONFIG: + if ( c->valx < 0 ) { + ber_bvarray_free( pb->pb_module_config ); + pb->pb_module_config = NULL; + } else { + int i = c->valx; + ch_free( pb->pb_module_config[i].bv_val ); + for (; pb->pb_module_config[i].bv_val; i++ ) + pb->pb_module_config[i] = pb->pb_module_config[i+1]; + } + break; + } + } else { + PERL_SET_CONTEXT( PERL_INTERPRETER ); + switch( c->type ) { + case PERL_MODULE: + snprintf( eval_str, EVAL_BUF_SIZE, "use %s;", c->argv[1] ); + eval_pv( eval_str, 0 ); + + if (SvTRUE(ERRSV)) { + STRLEN len; + + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: error %s", + c->log, SvPV(ERRSV, len )); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + rc = 1; + } else { + dSP; ENTER; SAVETMPS; + PUSHMARK(sp); + XPUSHs(sv_2mortal(newSVpv(c->argv[1], 0))); + PUTBACK; + + count = call_method("new", G_SCALAR); + + SPAGAIN; + + if (count != 1) { + croak("Big trouble in config\n") ; + } + + pb->pb_obj_ref = newSVsv(POPs); + + PUTBACK; FREETMPS; LEAVE ; + pb->pb_module_name = ch_strdup( c->argv[1] ); + } + break; + + case PERL_PATH: + snprintf( eval_str, EVAL_BUF_SIZE, "push @INC, '%s';", c->argv[1] ); + loc_sv = eval_pv( eval_str, 0 ); + /* XXX loc_sv return value is ignored. */ + ber_str2bv( c->argv[1], 0, 0, &bv ); + value_add_one( &pb->pb_module_path, &bv ); + break; + + case PERL_CONFIG: { + dSP ; ENTER ; SAVETMPS; + + PUSHMARK(sp) ; + XPUSHs( pb->pb_obj_ref ); + + /* Put all arguments on the perl stack */ + for( args = 1; args < c->argc; args++ ) + XPUSHs(sv_2mortal(newSVpv(c->argv[args], 0))); + + ber_str2bv( c->line + STRLENOF("perlModuleConfig "), 0, 0, &bv ); + value_add_one( &pb->pb_module_config, &bv ); + + PUTBACK ; + + count = call_method("config", G_SCALAR); + + SPAGAIN ; + + if (count != 1) { + croak("Big trouble in config\n") ; + } + + rc = POPi; + + PUTBACK ; FREETMPS ; LEAVE ; + } + break; + } + } + return rc; +} + +int +perl_back_init_cf( BackendInfo *bi ) +{ + bi->bi_cf_ocs = perlocs; + + return config_register_schema( perlcfg, perlocs ); +} diff --git a/servers/slapd/back-perl/delete.c b/servers/slapd/back-perl/delete.c new file mode 100644 index 0000000..cb3b114 --- /dev/null +++ b/servers/slapd/back-perl/delete.c @@ -0,0 +1,59 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 John C. Quillan. + * Portions Copyright 2002 myinternet Limited. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "perl_back.h" + +int +perl_back_delete( + Operation *op, + SlapReply *rs ) +{ + PerlBackend *perl_back = (PerlBackend *) op->o_bd->be_private; + int count; + + PERL_SET_CONTEXT( PERL_INTERPRETER ); + ldap_pvt_thread_mutex_lock( &perl_interpreter_mutex ); + + { + dSP; ENTER; SAVETMPS; + + PUSHMARK(sp); + XPUSHs( perl_back->pb_obj_ref ); + XPUSHs(sv_2mortal(newSVpv( op->o_req_dn.bv_val , op->o_req_dn.bv_len ))); + + PUTBACK; + + count = call_method("delete", G_SCALAR); + + SPAGAIN; + + if (count != 1) { + croak("Big trouble in perl-back_delete\n"); + } + + rs->sr_err = POPi; + + PUTBACK; FREETMPS; LEAVE; + } + + ldap_pvt_thread_mutex_unlock( &perl_interpreter_mutex ); + + send_ldap_result( op, rs ); + + Debug( LDAP_DEBUG_ANY, "Perl DELETE\n", 0, 0, 0 ); + return( 0 ); +} diff --git a/servers/slapd/back-perl/init.c b/servers/slapd/back-perl/init.c new file mode 100644 index 0000000..9715eb7 --- /dev/null +++ b/servers/slapd/back-perl/init.c @@ -0,0 +1,177 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 John C. Quillan. + * Portions Copyright 2002 myinternet Limited. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "perl_back.h" +#include "../config.h" + +#ifdef PERL_SYS_INIT3 +#include <ac/unistd.h> /* maybe get environ */ +extern char **environ; +#endif + +static void perl_back_xs_init LDAP_P((PERL_BACK_XS_INIT_PARAMS)); +EXT void boot_DynaLoader LDAP_P((PERL_BACK_BOOT_DYNALOADER_PARAMS)); + +PerlInterpreter *PERL_INTERPRETER = NULL; +ldap_pvt_thread_mutex_t perl_interpreter_mutex; + + +/********************************************************** + * + * Init + * + **********************************************************/ + +int +perl_back_initialize( + BackendInfo *bi +) +{ + char *embedding[] = { "", "-e", "0", NULL }, **argv = embedding; + int argc = 3; +#ifdef PERL_SYS_INIT3 + char **env = environ; +#else + char **env = NULL; +#endif + + bi->bi_open = NULL; + bi->bi_config = 0; + bi->bi_close = perl_back_close; + bi->bi_destroy = 0; + + bi->bi_db_init = perl_back_db_init; + bi->bi_db_config = perl_back_db_config; + bi->bi_db_open = perl_back_db_open; + bi->bi_db_close = 0; + bi->bi_db_destroy = perl_back_db_destroy; + + bi->bi_op_bind = perl_back_bind; + bi->bi_op_unbind = 0; + bi->bi_op_search = perl_back_search; + bi->bi_op_compare = perl_back_compare; + bi->bi_op_modify = perl_back_modify; + bi->bi_op_modrdn = perl_back_modrdn; + bi->bi_op_add = perl_back_add; + bi->bi_op_delete = perl_back_delete; + bi->bi_op_abandon = 0; + + bi->bi_extended = 0; + + bi->bi_chk_referrals = 0; + + bi->bi_connection_init = 0; + bi->bi_connection_destroy = 0; + + /* injecting code from perl_back_open, because using fonction reference (bi->bi_open) is not functional */ + Debug( LDAP_DEBUG_TRACE, "perl backend open\n", 0, 0, 0 ); + + if( PERL_INTERPRETER != NULL ) { + Debug( LDAP_DEBUG_ANY, "perl backend open: already opened\n", + 0, 0, 0 ); + return 1; + } + + ldap_pvt_thread_mutex_init( &perl_interpreter_mutex ); + +#ifdef PERL_SYS_INIT3 + PERL_SYS_INIT3(&argc, &argv, &env); +#endif + PERL_INTERPRETER = perl_alloc(); + perl_construct(PERL_INTERPRETER); +#ifdef PERL_EXIT_DESTRUCT_END + PL_exit_flags |= PERL_EXIT_DESTRUCT_END; +#endif + perl_parse(PERL_INTERPRETER, perl_back_xs_init, argc, argv, env); + perl_run(PERL_INTERPRETER); + return perl_back_init_cf( bi ); +} + +int +perl_back_db_init( + BackendDB *be, + ConfigReply *cr +) +{ + be->be_private = (PerlBackend *) ch_malloc( sizeof(PerlBackend) ); + memset( be->be_private, '\0', sizeof(PerlBackend)); + + ((PerlBackend *)be->be_private)->pb_filter_search_results = 0; + + Debug( LDAP_DEBUG_TRACE, "perl backend db init\n", 0, 0, 0 ); + + be->be_cf_ocs = be->bd_info->bi_cf_ocs; + + return 0; +} + +int +perl_back_db_open( + BackendDB *be, + ConfigReply *cr +) +{ + int count; + int return_code; + + PerlBackend *perl_back = (PerlBackend *) be->be_private; + + ldap_pvt_thread_mutex_lock( &perl_interpreter_mutex ); + + { + dSP; ENTER; SAVETMPS; + + PUSHMARK(sp); + XPUSHs( perl_back->pb_obj_ref ); + + PUTBACK; + + count = call_method("init", G_SCALAR); + + SPAGAIN; + + if (count != 1) { + croak("Big trouble in perl_back_db_open\n"); + } + + return_code = POPi; + + PUTBACK; FREETMPS; LEAVE; + } + + ldap_pvt_thread_mutex_unlock( &perl_interpreter_mutex ); + + return return_code; +} + + +static void +perl_back_xs_init(PERL_BACK_XS_INIT_PARAMS) +{ + char *file = __FILE__; + dXSUB_SYS; + newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file); +} + +#if SLAPD_PERL == SLAPD_MOD_DYNAMIC + +/* conditionally define the init_module() function */ +SLAP_BACKEND_INIT_MODULE( perl ) + +#endif /* SLAPD_PERL == SLAPD_MOD_DYNAMIC */ + + diff --git a/servers/slapd/back-perl/modify.c b/servers/slapd/back-perl/modify.c new file mode 100644 index 0000000..39eb563 --- /dev/null +++ b/servers/slapd/back-perl/modify.c @@ -0,0 +1,97 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 John C. Quillan. + * Portions Copyright 2002 myinternet Limited. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "perl_back.h" +#include <ac/string.h> + +int +perl_back_modify( + Operation *op, + SlapReply *rs ) +{ + PerlBackend *perl_back = (PerlBackend *)op->o_bd->be_private; + Modifications *modlist = op->orm_modlist; + int count; + int i; + + PERL_SET_CONTEXT( PERL_INTERPRETER ); + ldap_pvt_thread_mutex_lock( &perl_interpreter_mutex ); + + { + dSP; ENTER; SAVETMPS; + + PUSHMARK(sp); + XPUSHs( perl_back->pb_obj_ref ); + XPUSHs(sv_2mortal(newSVpv( op->o_req_dn.bv_val , 0))); + + for (; modlist != NULL; modlist = modlist->sml_next ) { + Modification *mods = &modlist->sml_mod; + + switch ( mods->sm_op & ~LDAP_MOD_BVALUES ) { + case LDAP_MOD_ADD: + XPUSHs(sv_2mortal(newSVpv("ADD", STRLENOF("ADD") ))); + break; + + case LDAP_MOD_DELETE: + XPUSHs(sv_2mortal(newSVpv("DELETE", STRLENOF("DELETE") ))); + break; + + case LDAP_MOD_REPLACE: + XPUSHs(sv_2mortal(newSVpv("REPLACE", STRLENOF("REPLACE") ))); + break; + } + + + XPUSHs(sv_2mortal(newSVpv( mods->sm_desc->ad_cname.bv_val, + mods->sm_desc->ad_cname.bv_len ))); + + for ( i = 0; + mods->sm_values != NULL && mods->sm_values[i].bv_val != NULL; + i++ ) + { + XPUSHs(sv_2mortal(newSVpv( mods->sm_values[i].bv_val, mods->sm_values[i].bv_len ))); + } + + /* Fix delete attrib without value. */ + if ( i == 0) { + XPUSHs(sv_newmortal()); + } + } + + PUTBACK; + + count = call_method("modify", G_SCALAR); + + SPAGAIN; + + if (count != 1) { + croak("Big trouble in back_modify\n"); + } + + rs->sr_err = POPi; + + PUTBACK; FREETMPS; LEAVE; + } + + ldap_pvt_thread_mutex_unlock( &perl_interpreter_mutex ); + + send_ldap_result( op, rs ); + + Debug( LDAP_DEBUG_ANY, "Perl MODIFY\n", 0, 0, 0 ); + return( 0 ); +} + diff --git a/servers/slapd/back-perl/modrdn.c b/servers/slapd/back-perl/modrdn.c new file mode 100644 index 0000000..72708c3 --- /dev/null +++ b/servers/slapd/back-perl/modrdn.c @@ -0,0 +1,63 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 John C. Quillan. + * Portions Copyright 2002 myinternet Limited. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "perl_back.h" + +int +perl_back_modrdn( + Operation *op, + SlapReply *rs ) +{ + PerlBackend *perl_back = (PerlBackend *) op->o_bd->be_private; + int count; + + PERL_SET_CONTEXT( PERL_INTERPRETER ); + ldap_pvt_thread_mutex_lock( &perl_interpreter_mutex ); + + { + dSP; ENTER; SAVETMPS; + + PUSHMARK(sp) ; + XPUSHs( perl_back->pb_obj_ref ); + XPUSHs(sv_2mortal(newSVpv( op->o_req_dn.bv_val , op->o_req_dn.bv_len ))); + XPUSHs(sv_2mortal(newSVpv( op->orr_newrdn.bv_val , op->orr_newrdn.bv_len ))); + XPUSHs(sv_2mortal(newSViv( op->orr_deleteoldrdn ))); + if ( op->orr_newSup != NULL ) { + XPUSHs(sv_2mortal(newSVpv( op->orr_newSup->bv_val , op->orr_newSup->bv_len ))); + } + PUTBACK ; + + count = call_method("modrdn", G_SCALAR); + + SPAGAIN ; + + if (count != 1) { + croak("Big trouble in back_modrdn\n") ; + } + + rs->sr_err = POPi; + + PUTBACK; FREETMPS; LEAVE ; + } + + ldap_pvt_thread_mutex_unlock( &perl_interpreter_mutex ); + + send_ldap_result( op, rs ); + + Debug( LDAP_DEBUG_ANY, "Perl MODRDN\n", 0, 0, 0 ); + return( 0 ); +} diff --git a/servers/slapd/back-perl/perl_back.h b/servers/slapd/back-perl/perl_back.h new file mode 100644 index 0000000..ac8f54a --- /dev/null +++ b/servers/slapd/back-perl/perl_back.h @@ -0,0 +1,82 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 John C. Quillan. + * Portions Copyright 2002 myinternet Limited. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#ifndef PERL_BACK_H +#define PERL_BACK_H 1 + +#include <EXTERN.h> +#include <perl.h> +#undef _ /* #defined by both Perl and ac/localize.h */ +#include "asperl_undefs.h" + +#include "portable.h" + +#include "slap.h" + +LDAP_BEGIN_DECL + +/* + * From Apache mod_perl: test for Perl version. + */ + +#if defined(pTHX_) || (PERL_REVISION > 5 || (PERL_REVISION == 5 && PERL_VERSION >= 6)) +#define PERL_IS_5_6 +#endif + +#define EVAL_BUF_SIZE 500 + +extern ldap_pvt_thread_mutex_t perl_interpreter_mutex; + +#ifdef PERL_IS_5_6 +/* We should be using the PL_errgv, I think */ +/* All the old style variables are prefixed with PL_ now */ +# define errgv PL_errgv +# define na PL_na +#else +# define call_method(m, f) perl_call_method(m, f) +# define eval_pv(m, f) perl_eval_pv(m, f) +# define ERRSV GvSV(errgv) +#endif + +#if defined( HAVE_WIN32_ASPERL ) || defined( USE_ITHREADS ) +/* pTHX is needed often now */ +# define PERL_INTERPRETER my_perl +# define PERL_BACK_XS_INIT_PARAMS pTHX +# define PERL_BACK_BOOT_DYNALOADER_PARAMS pTHX, CV *cv +#else +# define PERL_INTERPRETER perl_interpreter +# define PERL_BACK_XS_INIT_PARAMS void +# define PERL_BACK_BOOT_DYNALOADER_PARAMS CV *cv +# define PERL_SET_CONTEXT(i) +#endif + +extern PerlInterpreter *PERL_INTERPRETER; + + +typedef struct perl_backend_instance { + char *pb_module_name; + BerVarray pb_module_path; + BerVarray pb_module_config; + SV *pb_obj_ref; + int pb_filter_search_results; +} PerlBackend; + +LDAP_END_DECL + +#include "proto-perl.h" + +#endif /* PERL_BACK_H */ diff --git a/servers/slapd/back-perl/proto-perl.h b/servers/slapd/back-perl/proto-perl.h new file mode 100644 index 0000000..a770270 --- /dev/null +++ b/servers/slapd/back-perl/proto-perl.h @@ -0,0 +1,43 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 John C. Quillan. + * Portions Copyright 2002 myinternet Limited. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#ifndef PROTO_PERL_H +#define PROTO_PERL_H + +LDAP_BEGIN_DECL + +extern BI_init perl_back_initialize; + +extern BI_close perl_back_close; + +extern BI_db_init perl_back_db_init; +extern BI_db_open perl_back_db_open; +extern BI_db_destroy perl_back_db_destroy; +extern BI_db_config perl_back_db_config; + +extern BI_op_bind perl_back_bind; +extern BI_op_search perl_back_search; +extern BI_op_compare perl_back_compare; +extern BI_op_modify perl_back_modify; +extern BI_op_modrdn perl_back_modrdn; +extern BI_op_add perl_back_add; +extern BI_op_delete perl_back_delete; + +extern int perl_back_init_cf( BackendInfo *bi ); +LDAP_END_DECL + +#endif /* PROTO_PERL_H */ diff --git a/servers/slapd/back-perl/search.c b/servers/slapd/back-perl/search.c new file mode 100644 index 0000000..7c761ee --- /dev/null +++ b/servers/slapd/back-perl/search.c @@ -0,0 +1,122 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 John C. Quillan. + * Portions Copyright 2002 myinternet Limited. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "perl_back.h" + +/********************************************************** + * + * Search + * + **********************************************************/ +int +perl_back_search( + Operation *op, + SlapReply *rs ) +{ + PerlBackend *perl_back = (PerlBackend *)op->o_bd->be_private; + int count ; + AttributeName *an; + Entry *e; + char *buf; + int i; + + PERL_SET_CONTEXT( PERL_INTERPRETER ); + ldap_pvt_thread_mutex_lock( &perl_interpreter_mutex ); + + { + dSP; ENTER; SAVETMPS; + + PUSHMARK(sp) ; + XPUSHs( perl_back->pb_obj_ref ); + XPUSHs(sv_2mortal(newSVpv( op->o_req_ndn.bv_val , op->o_req_ndn.bv_len))); + XPUSHs(sv_2mortal(newSViv( op->ors_scope ))); + XPUSHs(sv_2mortal(newSViv( op->ors_deref ))); + XPUSHs(sv_2mortal(newSViv( op->ors_slimit ))); + XPUSHs(sv_2mortal(newSViv( op->ors_tlimit ))); + XPUSHs(sv_2mortal(newSVpv( op->ors_filterstr.bv_val , op->ors_filterstr.bv_len))); + XPUSHs(sv_2mortal(newSViv( op->ors_attrsonly ))); + + for ( an = op->ors_attrs; an && an->an_name.bv_val; an++ ) { + XPUSHs(sv_2mortal(newSVpv( an->an_name.bv_val , an->an_name.bv_len))); + } + PUTBACK; + + count = call_method("search", G_ARRAY ); + + SPAGAIN; + + if (count < 1) { + croak("Big trouble in back_search\n") ; + } + + if ( count > 1 ) { + + for ( i = 1; i < count; i++ ) { + + buf = POPp; + + if ( (e = str2entry( buf )) == NULL ) { + Debug( LDAP_DEBUG_ANY, "str2entry(%s) failed\n", buf, 0, 0 ); + + } else { + int send_entry; + + if (perl_back->pb_filter_search_results) + send_entry = (test_filter( op, e, op->ors_filter ) == LDAP_COMPARE_TRUE); + else + send_entry = 1; + + if (send_entry) { + rs->sr_entry = e; + rs->sr_attrs = op->ors_attrs; + rs->sr_flags = REP_ENTRY_MODIFIABLE; + rs->sr_err = LDAP_SUCCESS; + rs->sr_err = send_search_entry( op, rs ); + rs->sr_flags = 0; + rs->sr_attrs = NULL; + rs->sr_entry = NULL; + if ( rs->sr_err == LDAP_SIZELIMIT_EXCEEDED || rs->sr_err == LDAP_BUSY ) { + goto done; + } + } + + entry_free( e ); + } + } + } + + /* + * We grab the return code last because the stack comes + * from perl in reverse order. + * + * ex perl: return ( 0, $res_1, $res_2 ); + * + * ex stack: <$res_2> <$res_1> <0> + */ + + rs->sr_err = POPi; + +done:; + PUTBACK; FREETMPS; LEAVE; + } + + ldap_pvt_thread_mutex_unlock( &perl_interpreter_mutex ); + + send_ldap_result( op, rs ); + + return 0; +} diff --git a/servers/slapd/back-relay/Makefile.in b/servers/slapd/back-relay/Makefile.in new file mode 100644 index 0000000..15100af --- /dev/null +++ b/servers/slapd/back-relay/Makefile.in @@ -0,0 +1,41 @@ +# Makefile.in for back-relay +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +SRCS = init.c op.c +OBJS = init.lo op.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-relay" +BUILD_MOD = @BUILD_RELAY@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_RELAY@_DEFS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_R_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) $(REWRITE) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) $(REWRITE) + +LIBBASE = back_relay + +XINCPATH = -I.. -I$(srcdir)/.. +XDEFS = $(MODULES_CPPFLAGS) + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ + diff --git a/servers/slapd/back-relay/README b/servers/slapd/back-relay/README new file mode 100644 index 0000000..81f152c --- /dev/null +++ b/servers/slapd/back-relay/README @@ -0,0 +1,83 @@ +Relay backend sets up a relay virtual database that allows +to access other databases in the same instance of slapd +through different naming contexts and remapping attribute +values. + +The DN rewrite, filter rewrite and attributeType/objectClass +mapping is done by means of the rewrite-remap overlay. + +The database containing the real naming context can be +explicitly selected by means of the "relay" directive, +which must contain the naming context of the target +database. This also causes the rewrite-remap overlay +to be automatically instantiated. If the optional keyword +"massage" is present, the rewrite-remap overlay is +automatically configured to map the virtual to the real +naming context and vice-versa. + +Otherwise, the rewrite-remap overlay must be explicitly +instantiated, by using the "overlay" directive, as +illustrated below. This allows much more freedom in target +database selection and DN rewriting. + +If the "relay" directive is not present, the backend is +not bound to a single target database; on the contrary, +the target database is selected on a per-operation basis. + +This allows, for instance, to relay one database for +authentication and anotheir for search/modify, or allows +to use one target for persons and another for groups +and so on. + +To summarize: the "relay" directive: +- explicitly bounds the database to a single database + holding the real naming context; +- automatically instantiates the rewrite-remap overlay; +- automatically configures the naming context massaging + if the optional "massage" keyword is added + +If the "relay" directive is not used, the rewrite-remap +overlay must be explicitly instantiated and the massaging +must be configured, either by using the "suffixmassage" +directive, or by issuing more sophisticate rewrite +instructions. + +AttributeType/objectClass mapping must be explicitly +required. + +Note that the rewrite-remap overlay is not complete nor +production- ready yet. +Examples are given of all the suggested usages. + +# automatically massage from virtual to real naming context +database relay +suffix "dc=virtual,dc=naming,dc=context" +relay "dc=real,dc=naming,dc=context" massage + +# explicitly massage (same as above) +database relay +suffix "dc=virtual,dc=naming,dc=context" +relay "dc=real,dc=naming,dc=context" +suffixmassage "dc=virtual,dc=naming,dc=context" \ + "dc=real,dc=naming,dc=context" + +# explicitly massage (same as above, but dynamic backend resolution) +database relay +suffix "dc=virtual,dc=naming,dc=context" +overlay rewrite-remap +suffixmassage "dc=virtual,dc=naming,dc=context" \ + "dc=real,dc=naming,dc=context" + +# old fashioned suffixalias, applied also to DN-valued attributes +# from virtual to real naming context, but not the reverse... +database relay +suffix "dc=virtual,dc=naming,dc=context" +relay "dc=real,dc=naming,dc=context" +rewriteContext default +rewriteRule "(.*)dc=virtual,dc=naming,dc=context$" \ + "$1dc=real,dc=naming,dc=context" +rewriteContext searchFilter +rewriteContext searchResult +rewriteContext searchResultAttrDN +rewriteContext matchedDN + diff --git a/servers/slapd/back-relay/back-relay.h b/servers/slapd/back-relay/back-relay.h new file mode 100644 index 0000000..0a0a9bc --- /dev/null +++ b/servers/slapd/back-relay/back-relay.h @@ -0,0 +1,49 @@ +/* back-relay.h - relay backend header file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2021 The OpenLDAP Foundation. + * Portions Copyright 2004 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#ifndef SLAPD_RELAY_H +#define SLAPD_RELAY_H + +#include "proto-back-relay.h" + +LDAP_BEGIN_DECL + +typedef enum relay_operation_e { + relay_op_entry_get = op_last, + relay_op_entry_release, + relay_op_has_subordinates, + relay_op_last +} relay_operation_t; + +typedef struct relay_back_info { + BackendDB *ri_bd; + struct berval ri_realsuffix; + int ri_massage; +} relay_back_info; + +/* Pad relay_back_info if needed to create valid OpExtra key addresses */ +#define RELAY_INFO_SIZE \ + (sizeof(relay_back_info) > (size_t) relay_op_last ? \ + sizeof(relay_back_info) : (size_t) relay_op_last ) + +LDAP_END_DECL + +#endif /* SLAPD_RELAY_H */ diff --git a/servers/slapd/back-relay/init.c b/servers/slapd/back-relay/init.c new file mode 100644 index 0000000..4e18381 --- /dev/null +++ b/servers/slapd/back-relay/init.c @@ -0,0 +1,254 @@ +/* init.c - initialize relay backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2021 The OpenLDAP Foundation. + * Portions Copyright 2004 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "slap.h" +#include "config.h" +#include "back-relay.h" + +static ConfigDriver relay_back_cf; + +static ConfigTable relaycfg[] = { + { "relay", "relay", 2, 2, 0, + ARG_MAGIC|ARG_DN|ARG_QUOTE, + relay_back_cf, "( OLcfgDbAt:5.1 " + "NAME 'olcRelay' " + "DESC 'Relay DN' " + "SYNTAX OMsDN " + "SINGLE-VALUE )", + NULL, NULL }, + { NULL } +}; + +static ConfigOCs relayocs[] = { + { "( OLcfgDbOc:5.1 " + "NAME 'olcRelayConfig' " + "DESC 'Relay backend configuration' " + "SUP olcDatabaseConfig " + "MAY ( olcRelay " + ") )", + Cft_Database, relaycfg}, + { NULL, 0, NULL } +}; + +static int +relay_back_cf( ConfigArgs *c ) +{ + relay_back_info *ri = ( relay_back_info * )c->be->be_private; + int rc = 0; + + if ( c->op == SLAP_CONFIG_EMIT ) { + if ( ri != NULL && !BER_BVISNULL( &ri->ri_realsuffix ) ) { + value_add_one( &c->rvalue_vals, &ri->ri_realsuffix ); + return 0; + } + return 1; + + } else if ( c->op == LDAP_MOD_DELETE ) { + if ( !BER_BVISNULL( &ri->ri_realsuffix ) ) { + ch_free( ri->ri_realsuffix.bv_val ); + BER_BVZERO( &ri->ri_realsuffix ); + ri->ri_bd = NULL; + return 0; + } + return 1; + + } else { + BackendDB *bd; + + assert( ri != NULL ); + assert( BER_BVISNULL( &ri->ri_realsuffix ) ); + + if ( c->be->be_nsuffix == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "\"relay\" directive " + "must appear after \"suffix\"" ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + rc = 1; + goto relay_done; + } + + if ( !BER_BVISNULL( &c->be->be_nsuffix[ 1 ] ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "relaying of multiple suffix " + "database not supported" ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + rc = 1; + goto relay_done; + } + + bd = select_backend( &c->value_ndn, 1 ); + if ( bd == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "cannot find database " + "of relay dn \"%s\" " + "in \"olcRelay <dn>\"\n", + c->value_dn.bv_val ); + Log2( LDAP_DEBUG_CONFIG, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + + } else if ( bd->be_private == c->be->be_private ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "relay dn \"%s\" would call self " + "in \"relay <dn>\" line\n", + c->value_dn.bv_val ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + rc = 1; + goto relay_done; + } + + ri->ri_realsuffix = c->value_ndn; + BER_BVZERO( &c->value_ndn ); + +relay_done:; + ch_free( c->value_dn.bv_val ); + ch_free( c->value_ndn.bv_val ); + } + + return rc; +} + +int +relay_back_initialize( BackendInfo *bi ) +{ + bi->bi_init = 0; + bi->bi_open = 0; + bi->bi_config = 0; + bi->bi_close = 0; + bi->bi_destroy = 0; + + bi->bi_db_init = relay_back_db_init; + bi->bi_db_config = config_generic_wrapper; + bi->bi_db_open = relay_back_db_open; +#if 0 + bi->bi_db_close = relay_back_db_close; +#endif + bi->bi_db_destroy = relay_back_db_destroy; + + bi->bi_op_bind = relay_back_op_bind; + bi->bi_op_search = relay_back_op_search; + bi->bi_op_compare = relay_back_op_compare; + bi->bi_op_modify = relay_back_op_modify; + bi->bi_op_modrdn = relay_back_op_modrdn; + bi->bi_op_add = relay_back_op_add; + bi->bi_op_delete = relay_back_op_delete; + bi->bi_extended = relay_back_op_extended; + bi->bi_entry_release_rw = relay_back_entry_release_rw; + bi->bi_entry_get_rw = relay_back_entry_get_rw; + bi->bi_operational = relay_back_operational; + bi->bi_has_subordinates = relay_back_has_subordinates; + + bi->bi_cf_ocs = relayocs; + + return config_register_schema( relaycfg, relayocs ); +} + +int +relay_back_db_init( Backend *be, ConfigReply *cr) +{ + relay_back_info *ri; + + be->be_private = NULL; + + ri = (relay_back_info *) ch_calloc( 1, RELAY_INFO_SIZE ); + if ( ri == NULL ) { + return -1; + } + + ri->ri_bd = NULL; + BER_BVZERO( &ri->ri_realsuffix ); + ri->ri_massage = 0; + + be->be_cf_ocs = be->bd_info->bi_cf_ocs; + + be->be_private = (void *)ri; + + return 0; +} + +int +relay_back_db_open( Backend *be, ConfigReply *cr ) +{ + relay_back_info *ri = (relay_back_info *)be->be_private; + + assert( ri != NULL ); + + if ( !BER_BVISNULL( &ri->ri_realsuffix ) ) { + ri->ri_bd = select_backend( &ri->ri_realsuffix, 1 ); + + /* must be there: it was during config! */ + if ( ri->ri_bd == NULL ) { + snprintf( cr->msg, sizeof( cr->msg), + "cannot find database " + "of relay dn \"%s\" " + "in \"olcRelay <dn>\"\n", + ri->ri_realsuffix.bv_val ); + Log1( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "relay_back_db_open: %s.\n", cr->msg ); + + return 1; + } + + /* inherit controls */ + AC_MEMCPY( be->bd_self->be_ctrls, ri->ri_bd->be_ctrls, sizeof( be->be_ctrls ) ); + + } else { + /* inherit all? */ + AC_MEMCPY( be->bd_self->be_ctrls, frontendDB->be_ctrls, sizeof( be->be_ctrls ) ); + } + + return 0; +} + +int +relay_back_db_close( Backend *be, ConfigReply *cr ) +{ + return 0; +} + +int +relay_back_db_destroy( Backend *be, ConfigReply *cr) +{ + relay_back_info *ri = (relay_back_info *)be->be_private; + + if ( ri ) { + if ( !BER_BVISNULL( &ri->ri_realsuffix ) ) { + ch_free( ri->ri_realsuffix.bv_val ); + } + ch_free( ri ); + } + + return 0; +} + +#if SLAPD_RELAY == SLAPD_MOD_DYNAMIC + +/* conditionally define the init_module() function */ +SLAP_BACKEND_INIT_MODULE( relay ) + +#endif /* SLAPD_RELAY == SLAPD_MOD_DYNAMIC */ diff --git a/servers/slapd/back-relay/op.c b/servers/slapd/back-relay/op.c new file mode 100644 index 0000000..37112ae --- /dev/null +++ b/servers/slapd/back-relay/op.c @@ -0,0 +1,331 @@ +/* op.c - relay backend operations */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2021 The OpenLDAP Foundation. + * Portions Copyright 2004 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include "slap.h" +#include "back-relay.h" + +/* Results when no real database (.rf_bd) or operation handler (.rf_op) */ +static const struct relay_fail_modes_s { + slap_mask_t rf_bd, rf_op; +#define RB_ERR_MASK 0x0000FFFFU /* bitmask for default return value */ +#define RB_BDERR 0x80000000U /* use .rf_bd's default return value */ +#define RB_OPERR 0x40000000U /* set rs->sr_err = .rf_op return value */ +#define RB_REF 0x20000000U /* use default_referral if available */ +#define RB_SEND 0x10000000U /* send result; RB_??ERR is also set */ +#define RB_SENDREF 0/*unused*/ /* like RB_SEND when referral found */ +#define RB_NO_BIND (RB_OPERR | LDAP_INVALID_CREDENTIALS) +#define RB_NOT_SUPP (RB_OPERR | LDAP_UNWILLING_TO_PERFORM) +#define RB_NO_OBJ (RB_REF | LDAP_NO_SUCH_OBJECT) +#define RB_CHK_REF (RB_REF | RB_SENDREF | LDAP_SUCCESS) +} relay_fail_modes[relay_op_last] = { + /* .rf_bd is unused when zero, otherwise both fields have RB_BDERR */ +# define RB_OP(b, o) { (b) | RB_BD2ERR(b), (o) | RB_BD2ERR(b) } +# define RB_BD2ERR(b) ((b) ? RB_BDERR : 0) + /* indexed by slap_operation_t: */ + RB_OP(RB_NO_BIND|RB_SEND, RB_NO_BIND |RB_SEND), /* Bind */ + RB_OP(0, LDAP_SUCCESS), /* Unbind: unused */ + RB_OP(RB_NO_OBJ |RB_SEND, RB_NOT_SUPP |RB_SEND), /* Search */ + RB_OP(RB_NO_OBJ |RB_SEND, SLAP_CB_CONTINUE), /* Compare */ + RB_OP(RB_NO_OBJ |RB_SEND, RB_NOT_SUPP |RB_SEND), /* Modify */ + RB_OP(RB_NO_OBJ |RB_SEND, RB_NOT_SUPP |RB_SEND), /* Modrdn */ + RB_OP(RB_NO_OBJ |RB_SEND, RB_NOT_SUPP |RB_SEND), /* Add */ + RB_OP(RB_NO_OBJ |RB_SEND, RB_NOT_SUPP |RB_SEND), /* Delete */ + RB_OP(0, LDAP_SUCCESS), /* Abandon:unused */ + RB_OP(RB_NO_OBJ, RB_NOT_SUPP), /* Extended */ + RB_OP(0, SLAP_CB_CONTINUE), /* Cancel: unused */ + RB_OP(0, LDAP_SUCCESS), /* operational */ + RB_OP(RB_CHK_REF, LDAP_SUCCESS), /* chk_referrals:unused*/ + RB_OP(0, SLAP_CB_CONTINUE),/* chk_controls:unused */ + /* additional relay_operation_t indexes from back-relay.h: */ + RB_OP(0, 0/*unused*/), /* entry_get = op_last */ + RB_OP(0, 0/*unused*/), /* entry_release */ + RB_OP(0, 0/*unused*/), /* has_subordinates */ +}; + +/* + * Callbacks: Caller changed op->o_bd from Relay to underlying + * BackendDB. sc_response sets it to Relay BackendDB, sc_cleanup puts + * back underlying BackendDB. Caller will restore Relay BackendDB. + */ + +typedef struct relay_callback { + slap_callback rcb_sc; + BackendDB *rcb_bd; +} relay_callback; + +static int +relay_back_cleanup_cb( Operation *op, SlapReply *rs ) +{ + op->o_bd = ((relay_callback *) op->o_callback)->rcb_bd; + return SLAP_CB_CONTINUE; +} + +static int +relay_back_response_cb( Operation *op, SlapReply *rs ) +{ + relay_callback *rcb = (relay_callback *) op->o_callback; + + rcb->rcb_sc.sc_cleanup = relay_back_cleanup_cb; + rcb->rcb_bd = op->o_bd; + op->o_bd = op->o_callback->sc_private; + return SLAP_CB_CONTINUE; +} + +#define relay_back_add_cb( rcb, op ) { \ + (rcb)->rcb_sc.sc_next = (op)->o_callback; \ + (rcb)->rcb_sc.sc_response = relay_back_response_cb; \ + (rcb)->rcb_sc.sc_cleanup = 0; \ + (rcb)->rcb_sc.sc_writewait = 0; \ + (rcb)->rcb_sc.sc_private = (op)->o_bd; \ + (op)->o_callback = (slap_callback *) (rcb); \ +} + +#define relay_back_remove_cb( rcb, op ) { \ + slap_callback **sc = &(op)->o_callback; \ + for ( ;; sc = &(*sc)->sc_next ) \ + if ( *sc == (slap_callback *) (rcb) ) { \ + *sc = (*sc)->sc_next; break; \ + } else if ( *sc == NULL ) break; \ +} + +/* + * Select the backend database with the operation's DN. On failure, + * set/send results depending on operation type <which>'s fail_modes. + */ +static BackendDB * +relay_back_select_backend( Operation *op, SlapReply *rs, int which ) +{ + OpExtra *oex; + char *key = (char *) op->o_bd->be_private; + BackendDB *bd = ((relay_back_info *) key)->ri_bd; + slap_mask_t fail_mode = relay_fail_modes[which].rf_bd; + int useDN = 0, rc = ( fail_mode & RB_ERR_MASK ); + + if ( bd == NULL && !BER_BVISNULL( &op->o_req_ndn ) ) { + useDN = 1; + bd = select_backend( &op->o_req_ndn, 1 ); + } + + if ( bd != NULL ) { + key += which; /* <relay, op type> key from RELAY_WRAP_OP() */ + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == key ) + break; + } + if ( oex == NULL ) { + return bd; + } + + Debug( LDAP_DEBUG_ANY, + "%s: back-relay for DN=\"%s\" would call self.\n", + op->o_log_prefix, op->o_req_dn.bv_val, 0 ); + + } else if ( useDN && ( fail_mode & RB_REF ) && default_referral ) { + rc = LDAP_REFERRAL; + + /* if we set sr_err to LDAP_REFERRAL, we must provide one */ + rs->sr_ref = referral_rewrite( + default_referral, NULL, &op->o_req_dn, + op->o_tag == LDAP_REQ_SEARCH ? + op->ors_scope : LDAP_SCOPE_DEFAULT ); + if ( rs->sr_ref != NULL ) { + rs->sr_flags |= REP_REF_MUSTBEFREED; + } else { + rs->sr_ref = default_referral; + } + + if ( fail_mode & RB_SENDREF ) + fail_mode = (RB_BDERR | RB_SEND); + } + + if ( fail_mode & RB_BDERR ) { + rs->sr_err = rc; + if ( fail_mode & RB_SEND ) { + send_ldap_result( op, rs ); + } + } + + return NULL; +} + +/* + * Forward <act> on <op> to database <bd>, with <relay, op type>-specific + * key in op->o_extra so relay_back_select_backend() can catch recursion. + */ +#define RELAY_WRAP_OP( op, bd, which, act ) { \ + OpExtraDB wrap_oex; \ + BackendDB *const wrap_bd = (op)->o_bd; \ + wrap_oex.oe_db = wrap_bd; \ + wrap_oex.oe.oe_key = (char *) wrap_bd->be_private + (which); \ + LDAP_SLIST_INSERT_HEAD( &(op)->o_extra, &wrap_oex.oe, oe_next ); \ + (op)->o_bd = (bd); \ + act; \ + (op)->o_bd = wrap_bd; \ + LDAP_SLIST_REMOVE( &(op)->o_extra, &wrap_oex.oe, OpExtra, oe_next ); \ +} + +/* + * Forward backend function #<which> on <op> to operation DN's database + * like RELAY_WRAP_OP, after setting up callbacks. If no database or no + * backend function, set/send results depending on <which>'s fail_modes. + */ +static int +relay_back_op( Operation *op, SlapReply *rs, int which ) +{ + BackendDB *bd; + BI_op_bind *func; + slap_mask_t fail_mode = relay_fail_modes[which].rf_op; + int rc = ( fail_mode & RB_ERR_MASK ); + + bd = relay_back_select_backend( op, rs, which ); + if ( bd == NULL ) { + if ( fail_mode & RB_BDERR ) + return rs->sr_err; /* sr_err was set above */ + + } else if ( (func = (&bd->be_bind)[which]) != 0 ) { + relay_callback rcb; + + relay_back_add_cb( &rcb, op ); + RELAY_WRAP_OP( op, bd, which, { + rc = func( op, rs ); + }); + relay_back_remove_cb( &rcb, op ); + + } else if ( fail_mode & RB_OPERR ) { + rs->sr_err = rc; + if ( rc == LDAP_UNWILLING_TO_PERFORM ) { + rs->sr_text = "operation not supported within naming context"; + } + + if ( fail_mode & RB_SEND ) { + send_ldap_result( op, rs ); + } + } + + return rc; +} + + +int +relay_back_op_bind( Operation *op, SlapReply *rs ) +{ + /* allow rootdn as a means to auth without the need to actually + * contact the proxied DSA */ + switch ( be_rootdn_bind( op, rs ) ) { + case SLAP_CB_CONTINUE: + break; + + default: + return rs->sr_err; + } + + return relay_back_op( op, rs, op_bind ); +} + +#define RELAY_DEFOP(func, which) \ + int func( Operation *op, SlapReply *rs ) \ + { return relay_back_op( op, rs, which ); } + +RELAY_DEFOP( relay_back_op_search, op_search ) +RELAY_DEFOP( relay_back_op_compare, op_compare ) +RELAY_DEFOP( relay_back_op_modify, op_modify ) +RELAY_DEFOP( relay_back_op_modrdn, op_modrdn ) +RELAY_DEFOP( relay_back_op_add, op_add ) +RELAY_DEFOP( relay_back_op_delete, op_delete ) +RELAY_DEFOP( relay_back_op_extended, op_extended ) +RELAY_DEFOP( relay_back_operational, op_aux_operational ) + +/* Abandon, Cancel, Unbind and some DN-less calls like be_connection_init + * need no extra handling: slapd already calls them for all databases. + */ + + +int +relay_back_entry_release_rw( Operation *op, Entry *e, int rw ) +{ + BackendDB *bd; + int rc = LDAP_UNWILLING_TO_PERFORM; + + bd = relay_back_select_backend( op, NULL, relay_op_entry_release ); + if ( bd && bd->be_release ) { + RELAY_WRAP_OP( op, bd, relay_op_entry_release, { + rc = bd->be_release( op, e, rw ); + }); + } else if ( e->e_private == NULL ) { + entry_free( e ); + rc = LDAP_SUCCESS; + } + + return rc; +} + +int +relay_back_entry_get_rw( Operation *op, struct berval *ndn, + ObjectClass *oc, AttributeDescription *at, int rw, Entry **e ) +{ + BackendDB *bd; + int rc = LDAP_NO_SUCH_OBJECT; + + bd = relay_back_select_backend( op, NULL, relay_op_entry_get ); + if ( bd && bd->be_fetch ) { + RELAY_WRAP_OP( op, bd, relay_op_entry_get, { + rc = bd->be_fetch( op, ndn, oc, at, rw, e ); + }); + } + + return rc; +} + +#if 0 /* Give the RB_SENDREF flag a nonzero value if implementing this */ +/* + * NOTE: even the existence of this function is questionable: we cannot + * pass the bi_chk_referrals() call thru the rwm overlay because there + * is no way to rewrite the req_dn back; but then relay_back_chk_referrals() + * is passing the target database a DN that likely does not belong to its + * naming context... mmmh. + */ +RELAY_DEFOP( relay_back_chk_referrals, op_aux_chk_referrals ) +#endif /*0*/ + +int +relay_back_has_subordinates( Operation *op, Entry *e, int *hasSubs ) +{ + BackendDB *bd; + int rc = LDAP_OTHER; + + bd = relay_back_select_backend( op, NULL, relay_op_has_subordinates ); + if ( bd && bd->be_has_subordinates ) { + RELAY_WRAP_OP( op, bd, relay_op_has_subordinates, { + rc = bd->be_has_subordinates( op, e, hasSubs ); + }); + } + + return rc; +} + + +/* + * FIXME: must implement tools as well + */ diff --git a/servers/slapd/back-relay/proto-back-relay.h b/servers/slapd/back-relay/proto-back-relay.h new file mode 100644 index 0000000..5ed71aa --- /dev/null +++ b/servers/slapd/back-relay/proto-back-relay.h @@ -0,0 +1,52 @@ +/* proto-back-relay.h - relay backend header file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2021 The OpenLDAP Foundation. + * Portions Copyright 2004 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#ifndef PROTO_BACK_RELAY +#define PROTO_BACK_RELAY + +#include <ldap_cdefs.h> + +LDAP_BEGIN_DECL + +extern BI_init relay_back_initialize; + +extern BI_db_init relay_back_db_init; +extern BI_db_open relay_back_db_open; +extern BI_db_close relay_back_db_close; +extern BI_db_destroy relay_back_db_destroy; + +extern BI_op_bind relay_back_op_bind; +extern BI_op_search relay_back_op_search; +extern BI_op_compare relay_back_op_compare; +extern BI_op_modify relay_back_op_modify; +extern BI_op_modrdn relay_back_op_modrdn; +extern BI_op_add relay_back_op_add; +extern BI_op_delete relay_back_op_delete; +extern BI_op_extended relay_back_op_extended; +extern BI_entry_release_rw relay_back_entry_release_rw; +extern BI_entry_get_rw relay_back_entry_get_rw; +extern BI_operational relay_back_operational; +extern BI_has_subordinates relay_back_has_subordinates; + +LDAP_END_DECL + +#endif /* PROTO_BACK_RELAY */ + diff --git a/servers/slapd/back-shell/Makefile.in b/servers/slapd/back-shell/Makefile.in new file mode 100644 index 0000000..1fed787 --- /dev/null +++ b/servers/slapd/back-shell/Makefile.in @@ -0,0 +1,43 @@ +# Makefile.in for back-shell +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +SRCS = init.c config.c fork.c search.c bind.c unbind.c add.c \ + delete.c modify.c modrdn.c compare.c result.c +OBJS = init.lo config.lo fork.lo search.lo bind.lo unbind.lo add.lo \ + delete.lo modify.lo modrdn.lo compare.lo result.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-shell" +BUILD_MOD = @BUILD_SHELL@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_SHELL@_DEFS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_R_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBBASE = back_shell + +XINCPATH = -I.. -I$(srcdir)/.. +XDEFS = $(MODULES_CPPFLAGS) + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ + diff --git a/servers/slapd/back-shell/add.c b/servers/slapd/back-shell/add.c new file mode 100644 index 0000000..19a96d1 --- /dev/null +++ b/servers/slapd/back-shell/add.c @@ -0,0 +1,84 @@ +/* add.c - shell backend add function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by the University of Michigan + * (as part of U-MICH LDAP). + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "shell.h" + +int +shell_back_add( + Operation *op, + SlapReply *rs ) +{ + struct shellinfo *si = (struct shellinfo *) op->o_bd->be_private; + AttributeDescription *entry = slap_schema.si_ad_entry; + FILE *rfp, *wfp; + int len; + + if ( si->si_add == NULL ) { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "add not implemented" ); + return( -1 ); + } + + if ( ! access_allowed( op, op->oq_add.rs_e, + entry, NULL, ACL_WADD, NULL ) ) + { + send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, NULL ); + return -1; + } + + if ( forkandexec( si->si_add, &rfp, &wfp ) == (pid_t)-1 ) { + send_ldap_error( op, rs, LDAP_OTHER, + "could not fork/exec" ); + return( -1 ); + } + + /* write out the request to the add process */ + fprintf( wfp, "ADD\n" ); + fprintf( wfp, "msgid: %ld\n", (long) op->o_msgid ); + print_suffixes( wfp, op->o_bd ); + ldap_pvt_thread_mutex_lock( &entry2str_mutex ); + fprintf( wfp, "%s", entry2str( op->oq_add.rs_e, &len ) ); + ldap_pvt_thread_mutex_unlock( &entry2str_mutex ); + fclose( wfp ); + + /* read in the result and send it along */ + read_and_send_results( op, rs, rfp ); + + fclose( rfp ); + return( 0 ); +} diff --git a/servers/slapd/back-shell/bind.c b/servers/slapd/back-shell/bind.c new file mode 100644 index 0000000..28b9e05 --- /dev/null +++ b/servers/slapd/back-shell/bind.c @@ -0,0 +1,105 @@ +/* bind.c - shell backend bind function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by the University of Michigan + * (as part of U-MICH LDAP). + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "shell.h" + +int +shell_back_bind( + Operation *op, + SlapReply *rs ) +{ + struct shellinfo *si = (struct shellinfo *) op->o_bd->be_private; + AttributeDescription *entry = slap_schema.si_ad_entry; + Entry e; + FILE *rfp, *wfp; + int rc; + + /* allow rootdn as a means to auth without the need to actually + * contact the proxied DSA */ + switch ( be_rootdn_bind( op, rs ) ) { + case SLAP_CB_CONTINUE: + break; + + default: + return rs->sr_err; + } + + if ( si->si_bind == NULL ) { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "bind not implemented" ); + return( -1 ); + } + + e.e_id = NOID; + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + e.e_attrs = NULL; + e.e_ocflags = 0; + e.e_bv.bv_len = 0; + e.e_bv.bv_val = NULL; + e.e_private = NULL; + + if ( ! access_allowed( op, &e, + entry, NULL, ACL_AUTH, NULL ) ) + { + send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, NULL ); + return -1; + } + + if ( forkandexec( si->si_bind, &rfp, &wfp ) == (pid_t)-1 ) { + send_ldap_error( op, rs, LDAP_OTHER, + "could not fork/exec" ); + return( -1 ); + } + + /* write out the request to the bind process */ + fprintf( wfp, "BIND\n" ); + fprintf( wfp, "msgid: %ld\n", (long) op->o_msgid ); + print_suffixes( wfp, op->o_bd ); + fprintf( wfp, "dn: %s\n", op->o_req_dn.bv_val ); + fprintf( wfp, "method: %d\n", op->oq_bind.rb_method ); + fprintf( wfp, "credlen: %lu\n", op->oq_bind.rb_cred.bv_len ); + fprintf( wfp, "cred: %s\n", op->oq_bind.rb_cred.bv_val ); /* XXX */ + fclose( wfp ); + + /* read in the results and send them along */ + rc = read_and_send_results( op, rs, rfp ); + fclose( rfp ); + + return( rc ); +} diff --git a/servers/slapd/back-shell/compare.c b/servers/slapd/back-shell/compare.c new file mode 100644 index 0000000..2b1949a --- /dev/null +++ b/servers/slapd/back-shell/compare.c @@ -0,0 +1,99 @@ +/* compare.c - shell backend compare function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by the University of Michigan + * (as part of U-MICH LDAP). + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "shell.h" + +int +shell_back_compare( + Operation *op, + SlapReply *rs ) +{ + struct shellinfo *si = (struct shellinfo *) op->o_bd->be_private; + AttributeDescription *entry = slap_schema.si_ad_entry; + Entry e; + FILE *rfp, *wfp; + + if ( si->si_compare == NULL ) { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "compare not implemented" ); + return( -1 ); + } + + e.e_id = NOID; + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + e.e_attrs = NULL; + e.e_ocflags = 0; + e.e_bv.bv_len = 0; + e.e_bv.bv_val = NULL; + e.e_private = NULL; + + if ( ! access_allowed( op, &e, + entry, NULL, ACL_READ, NULL ) ) + { + send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, NULL ); + return -1; + } + + if ( forkandexec( si->si_compare, &rfp, &wfp ) == (pid_t)-1 ) { + send_ldap_error( op, rs, LDAP_OTHER, + "could not fork/exec" ); + return( -1 ); + } + + /* + * FIX ME: This should use LDIF routines so that binary + * values are properly dealt with + */ + + /* write out the request to the compare process */ + fprintf( wfp, "COMPARE\n" ); + fprintf( wfp, "msgid: %ld\n", (long) op->o_msgid ); + print_suffixes( wfp, op->o_bd ); + fprintf( wfp, "dn: %s\n", op->o_req_dn.bv_val ); + fprintf( wfp, "%s: %s\n", + op->oq_compare.rs_ava->aa_desc->ad_cname.bv_val, + op->oq_compare.rs_ava->aa_value.bv_val /* could be binary! */ ); + fclose( wfp ); + + /* read in the result and send it along */ + read_and_send_results( op, rs, rfp ); + + fclose( rfp ); + return( 0 ); +} diff --git a/servers/slapd/back-shell/config.c b/servers/slapd/back-shell/config.c new file mode 100644 index 0000000..3b99e35 --- /dev/null +++ b/servers/slapd/back-shell/config.c @@ -0,0 +1,137 @@ +/* config.c - shell backend configuration file routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by the University of Michigan + * (as part of U-MICH LDAP). + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "shell.h" +#include "config.h" + +static ConfigDriver shell_cf; + +enum { + SHELL_BIND = 0, + SHELL_UNBIND = 1, + SHELL_SEARCH, + SHELL_COMPARE, + SHELL_MODIFY, + SHELL_MODRDN, + SHELL_ADD, + SHELL_DELETE +}; + +static ConfigTable shellcfg[] = { + { "bind", "args", 2, 0, 0, ARG_MAGIC|SHELL_BIND, shell_cf, + "( OLcfgDbAt:10.1 NAME 'olcShellBind' " + "DESC 'Bind command and arguments' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE ) ", NULL, NULL }, + { "unbind", "args", 2, 0, 0, ARG_MAGIC|SHELL_UNBIND, shell_cf, + "( OLcfgDbAt:10.2 NAME 'olcShellUnbind' " + "DESC 'Unbind command and arguments' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE ) ", NULL, NULL }, + { "search", "args", 2, 0, 0, ARG_MAGIC|SHELL_SEARCH, shell_cf, + "( OLcfgDbAt:10.3 NAME 'olcShellSearch' " + "DESC 'Search command and arguments' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE ) ", NULL, NULL }, + { "compare", "args", 2, 0, 0, ARG_MAGIC|SHELL_COMPARE, shell_cf, + "( OLcfgDbAt:10.4 NAME 'olcShellCompare' " + "DESC 'Compare command and arguments' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE ) ", NULL, NULL }, + { "modify", "args", 2, 0, 0, ARG_MAGIC|SHELL_MODIFY, shell_cf, + "( OLcfgDbAt:10.5 NAME 'olcShellModify' " + "DESC 'Modify command and arguments' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE ) ", NULL, NULL }, + { "modrdn", "args", 2, 0, 0, ARG_MAGIC|SHELL_MODRDN, shell_cf, + "( OLcfgDbAt:10.6 NAME 'olcShellModRDN' " + "DESC 'ModRDN command and arguments' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE ) ", NULL, NULL }, + { "add", "args", 2, 0, 0, ARG_MAGIC|SHELL_ADD, shell_cf, + "( OLcfgDbAt:10.7 NAME 'olcShellAdd' " + "DESC 'Add command and arguments' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE ) ", NULL, NULL }, + { "delete", "args", 2, 0, 0, ARG_MAGIC|SHELL_DELETE, shell_cf, + "( OLcfgDbAt:10.8 NAME 'olcShellDelete' " + "DESC 'Delete command and arguments' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE ) ", NULL, NULL }, + { NULL } +}; + +static ConfigOCs shellocs[] = { + { "( OLcfgDbOc:10.1 " + "NAME 'olcShellConfig' " + "DESC 'Shell backend configuration' " + "SUP olcDatabaseConfig " + "MAY ( olcShellBind $ olcShellUnbind $ olcShellSearch $ " + "olcShellCompare $ olcShellModify $ olcShellModRDN $ " + "olcShellAdd $ olcShellDelete ) )", + Cft_Database, shellcfg }, + { NULL } +}; + +static int +shell_cf( ConfigArgs *c ) +{ + struct shellinfo *si = (struct shellinfo *) c->be->be_private; + char ***arr = &si->si_bind; + + if ( c->op == SLAP_CONFIG_EMIT ) { + struct berval bv; + if ( !arr[c->type] ) return 1; + bv.bv_val = ldap_charray2str( arr[c->type], " " ); + bv.bv_len = strlen( bv.bv_val ); + ber_bvarray_add( &c->rvalue_vals, &bv ); + } else if ( c->op == LDAP_MOD_DELETE ) { + ldap_charray_free( arr[c->type] ); + arr[c->type] = NULL; + } else { + arr[c->type] = ldap_charray_dup( &c->argv[1] ); + } + return 0; +} + +int +shell_back_init_cf( BackendInfo *bi ) +{ + bi->bi_cf_ocs = shellocs; + return config_register_schema( shellcfg, shellocs ); +} diff --git a/servers/slapd/back-shell/delete.c b/servers/slapd/back-shell/delete.c new file mode 100644 index 0000000..bb2f317 --- /dev/null +++ b/servers/slapd/back-shell/delete.c @@ -0,0 +1,90 @@ +/* delete.c - shell backend delete function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by the University of Michigan + * (as part of U-MICH LDAP). + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "shell.h" + +int +shell_back_delete( + Operation *op, + SlapReply *rs ) +{ + struct shellinfo *si = (struct shellinfo *) op->o_bd->be_private; + AttributeDescription *entry = slap_schema.si_ad_entry; + Entry e; + FILE *rfp, *wfp; + + if ( si->si_delete == NULL ) { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "delete not implemented" ); + return( -1 ); + } + + e.e_id = NOID; + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + e.e_attrs = NULL; + e.e_ocflags = 0; + e.e_bv.bv_len = 0; + e.e_bv.bv_val = NULL; + e.e_private = NULL; + + if ( ! access_allowed( op, &e, + entry, NULL, ACL_WDEL, NULL ) ) + { + send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, NULL ); + return -1; + } + + if ( forkandexec( si->si_delete, &rfp, &wfp ) == (pid_t)-1 ) { + send_ldap_error( op, rs, LDAP_OTHER, + "could not fork/exec" ); + return( -1 ); + } + + /* write out the request to the delete process */ + fprintf( wfp, "DELETE\n" ); + fprintf( wfp, "msgid: %ld\n", (long) op->o_msgid ); + print_suffixes( wfp, op->o_bd ); + fprintf( wfp, "dn: %s\n", op->o_req_dn.bv_val ); + fclose( wfp ); + + /* read in the results and send them along */ + read_and_send_results( op, rs, rfp ); + fclose( rfp ); + return( 0 ); +} diff --git a/servers/slapd/back-shell/fork.c b/servers/slapd/back-shell/fork.c new file mode 100644 index 0000000..54100f7 --- /dev/null +++ b/servers/slapd/back-shell/fork.c @@ -0,0 +1,118 @@ +/* fork.c - fork and exec a process, connecting stdin/out w/pipes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by the University of Michigan + * (as part of U-MICH LDAP). + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/errno.h> +#include <ac/string.h> +#include <ac/socket.h> +#include <ac/unistd.h> + +#include "slap.h" +#include "shell.h" + +pid_t +forkandexec( + char **args, + FILE **rfp, + FILE **wfp +) +{ + int p2c[2] = { -1, -1 }, c2p[2]; + pid_t pid; + + if ( pipe( p2c ) != 0 || pipe( c2p ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "pipe failed\n", 0, 0, 0 ); + close( p2c[0] ); + close( p2c[1] ); + return( -1 ); + } + + /* + * what we're trying to set up looks like this: + * parent *wfp -> p2c[1] | p2c[0] -> stdin child + * parent *rfp <- c2p[0] | c2p[1] <- stdout child + */ + + fflush( NULL ); +# ifdef HAVE_THR + pid = fork1(); +# else + pid = fork(); +# endif + if ( pid == 0 ) { /* child */ + /* + * child could deadlock here due to resources locked + * by our parent + * + * If so, configure --without-threads. + */ + if ( dup2( p2c[0], 0 ) == -1 || dup2( c2p[1], 1 ) == -1 ) { + Debug( LDAP_DEBUG_ANY, "dup2 failed\n", 0, 0, 0 ); + exit( EXIT_FAILURE ); + } + } + close( p2c[0] ); + close( c2p[1] ); + if ( pid <= 0 ) { + close( p2c[1] ); + close( c2p[0] ); + } + switch ( pid ) { + case 0: + execv( args[0], args ); + + Debug( LDAP_DEBUG_ANY, "execv failed\n", 0, 0, 0 ); + exit( EXIT_FAILURE ); + + case -1: /* trouble */ + Debug( LDAP_DEBUG_ANY, "fork failed\n", 0, 0, 0 ); + return( -1 ); + } + + /* parent */ + if ( (*rfp = fdopen( c2p[0], "r" )) == NULL || (*wfp = fdopen( p2c[1], + "w" )) == NULL ) { + Debug( LDAP_DEBUG_ANY, "fdopen failed\n", 0, 0, 0 ); + if ( *rfp ) { + fclose( *rfp ); + *rfp = NULL; + } else { + close( c2p[0] ); + } + close( p2c[1] ); + + return( -1 ); + } + + return( pid ); +} diff --git a/servers/slapd/back-shell/init.c b/servers/slapd/back-shell/init.c new file mode 100644 index 0000000..e60cd6e --- /dev/null +++ b/servers/slapd/back-shell/init.c @@ -0,0 +1,111 @@ +/* init.c - initialize shell backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by the University of Michigan + * (as part of U-MICH LDAP). + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> + +#include "slap.h" + +#include "config.h" + +#include "shell.h" + +int +shell_back_initialize( + BackendInfo *bi +) +{ + bi->bi_open = 0; + bi->bi_config = 0; + bi->bi_close = 0; + bi->bi_destroy = 0; + + bi->bi_db_init = shell_back_db_init; + bi->bi_db_config = 0; + bi->bi_db_open = 0; + bi->bi_db_close = 0; + bi->bi_db_destroy = shell_back_db_destroy; + + bi->bi_op_bind = shell_back_bind; + bi->bi_op_unbind = shell_back_unbind; + bi->bi_op_search = shell_back_search; + bi->bi_op_compare = shell_back_compare; + bi->bi_op_modify = shell_back_modify; + bi->bi_op_modrdn = shell_back_modrdn; + bi->bi_op_add = shell_back_add; + bi->bi_op_delete = shell_back_delete; + bi->bi_op_abandon = 0; + + bi->bi_extended = 0; + + bi->bi_chk_referrals = 0; + + bi->bi_connection_init = 0; + bi->bi_connection_destroy = 0; + + return shell_back_init_cf( bi ); +} + +int +shell_back_db_init( + Backend *be, + ConfigReply *cr +) +{ + struct shellinfo *si; + + si = (struct shellinfo *) ch_calloc( 1, sizeof(struct shellinfo) ); + + be->be_private = si; + be->be_cf_ocs = be->bd_info->bi_cf_ocs; + + return si == NULL; +} + +int +shell_back_db_destroy( + Backend *be, + ConfigReply *cr +) +{ + free( be->be_private ); + return 0; +} + +#if SLAPD_SHELL == SLAPD_MOD_DYNAMIC + +/* conditionally define the init_module() function */ +SLAP_BACKEND_INIT_MODULE( shell ) + +#endif /* SLAPD_SHELL == SLAPD_MOD_DYNAMIC */ + diff --git a/servers/slapd/back-shell/modify.c b/servers/slapd/back-shell/modify.c new file mode 100644 index 0000000..8ddb674 --- /dev/null +++ b/servers/slapd/back-shell/modify.c @@ -0,0 +1,126 @@ +/* modify.c - shell backend modify function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by the University of Michigan + * (as part of U-MICH LDAP). + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "shell.h" +#include "ldif.h" + +int +shell_back_modify( + Operation *op, + SlapReply *rs ) +{ + Modification *mod; + struct shellinfo *si = (struct shellinfo *) op->o_bd->be_private; + AttributeDescription *entry = slap_schema.si_ad_entry; + Modifications *ml = op->orm_modlist; + Entry e; + FILE *rfp, *wfp; + int i; + + if ( si->si_modify == NULL ) { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "modify not implemented" ); + return( -1 ); + } + + e.e_id = NOID; + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + e.e_attrs = NULL; + e.e_ocflags = 0; + e.e_bv.bv_len = 0; + e.e_bv.bv_val = NULL; + e.e_private = NULL; + + if ( ! access_allowed( op, &e, + entry, NULL, ACL_WRITE, NULL ) ) + { + send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, NULL ); + return -1; + } + + if ( forkandexec( si->si_modify, &rfp, &wfp ) == (pid_t)-1 ) { + send_ldap_error( op, rs, LDAP_OTHER, + "could not fork/exec" ); + return( -1 ); + } + + /* write out the request to the modify process */ + fprintf( wfp, "MODIFY\n" ); + fprintf( wfp, "msgid: %ld\n", (long) op->o_msgid ); + print_suffixes( wfp, op->o_bd ); + fprintf( wfp, "dn: %s\n", op->o_req_dn.bv_val ); + for ( ; ml != NULL; ml = ml->sml_next ) { + mod = &ml->sml_mod; + + switch ( mod->sm_op ) { + case LDAP_MOD_ADD: + fprintf( wfp, "add: %s\n", mod->sm_desc->ad_cname.bv_val ); + break; + + case LDAP_MOD_DELETE: + fprintf( wfp, "delete: %s\n", mod->sm_desc->ad_cname.bv_val ); + break; + + case LDAP_MOD_REPLACE: + fprintf( wfp, "replace: %s\n", mod->sm_desc->ad_cname.bv_val ); + break; + } + + if( mod->sm_values != NULL ) { + for ( i = 0; mod->sm_values[i].bv_val != NULL; i++ ) { + char *out = ldif_put( LDIF_PUT_VALUE, + mod->sm_desc->ad_cname.bv_val, + mod->sm_values[i].bv_val, + mod->sm_values[i].bv_len ); + if ( out ) { + fprintf( wfp, "%s", out ); + ber_memfree( out ); + } + } + } + + fprintf( wfp, "-\n" ); + } + fclose( wfp ); + + /* read in the results and send them along */ + read_and_send_results( op, rs, rfp ); + fclose( rfp ); + return( 0 ); +} diff --git a/servers/slapd/back-shell/modrdn.c b/servers/slapd/back-shell/modrdn.c new file mode 100644 index 0000000..7c56fa5 --- /dev/null +++ b/servers/slapd/back-shell/modrdn.c @@ -0,0 +1,96 @@ +/* modrdn.c - shell backend modrdn function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by the University of Michigan + * (as part of U-MICH LDAP). + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "shell.h" + +int +shell_back_modrdn( + Operation *op, + SlapReply *rs ) +{ + struct shellinfo *si = (struct shellinfo *) op->o_bd->be_private; + AttributeDescription *entry = slap_schema.si_ad_entry; + Entry e; + FILE *rfp, *wfp; + + if ( si->si_modrdn == NULL ) { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "modrdn not implemented" ); + return( -1 ); + } + + e.e_id = NOID; + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + e.e_attrs = NULL; + e.e_ocflags = 0; + e.e_bv.bv_len = 0; + e.e_bv.bv_val = NULL; + e.e_private = NULL; + + if ( ! access_allowed( op, &e, entry, NULL, + op->oq_modrdn.rs_newSup ? ACL_WDEL : ACL_WRITE, + NULL ) ) + { + send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, NULL ); + return -1; + } + + if ( forkandexec( si->si_modrdn, &rfp, &wfp ) == (pid_t)-1 ) { + send_ldap_error( op, rs, LDAP_OTHER, + "could not fork/exec" ); + return( -1 ); + } + + /* write out the request to the modrdn process */ + fprintf( wfp, "MODRDN\n" ); + fprintf( wfp, "msgid: %ld\n", (long) op->o_msgid ); + print_suffixes( wfp, op->o_bd ); + fprintf( wfp, "dn: %s\n", op->o_req_dn.bv_val ); + fprintf( wfp, "newrdn: %s\n", op->oq_modrdn.rs_newrdn.bv_val ); + fprintf( wfp, "deleteoldrdn: %d\n", op->oq_modrdn.rs_deleteoldrdn ? 1 : 0 ); + if ( op->oq_modrdn.rs_newSup != NULL ) { + fprintf( wfp, "newSuperior: %s\n", op->oq_modrdn.rs_newSup->bv_val ); + } + fclose( wfp ); + + /* read in the results and send them along */ + read_and_send_results( op, rs, rfp ); + fclose( rfp ); + return( 0 ); +} diff --git a/servers/slapd/back-shell/proto-shell.h b/servers/slapd/back-shell/proto-shell.h new file mode 100644 index 0000000..f6a9aad --- /dev/null +++ b/servers/slapd/back-shell/proto-shell.h @@ -0,0 +1,56 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by the University of Michigan + * (as part of U-MICH LDAP). + */ + +#ifndef PROTO_SHELL_H +#define PROTO_SHELL_H + +LDAP_BEGIN_DECL + +extern BI_init shell_back_initialize; + +extern BI_open shell_back_open; +extern BI_close shell_back_close; +extern BI_destroy shell_back_destroy; + +extern BI_db_init shell_back_db_init; +extern BI_db_destroy shell_back_db_destroy; + +extern BI_op_bind shell_back_bind; +extern BI_op_unbind shell_back_unbind; +extern BI_op_search shell_back_search; +extern BI_op_compare shell_back_compare; +extern BI_op_modify shell_back_modify; +extern BI_op_modrdn shell_back_modrdn; +extern BI_op_add shell_back_add; +extern BI_op_delete shell_back_delete; + +extern int shell_back_init_cf( BackendInfo *bi ); +LDAP_END_DECL + +#endif /* PROTO_SHELL_H */ diff --git a/servers/slapd/back-shell/result.c b/servers/slapd/back-shell/result.c new file mode 100644 index 0000000..9133eaf --- /dev/null +++ b/servers/slapd/back-shell/result.c @@ -0,0 +1,135 @@ +/* result.c - shell backend result reading function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by the University of Michigan + * (as part of U-MICH LDAP). + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/errno.h> +#include <ac/string.h> +#include <ac/socket.h> +#include <ac/unistd.h> + +#include "slap.h" +#include "shell.h" + +int +read_and_send_results( + Operation *op, + SlapReply *rs, + FILE *fp ) +{ + int bsize, len; + char *buf, *bp; + char line[BUFSIZ]; + char ebuf[128]; + + /* read in the result and send it along */ + buf = (char *) ch_malloc( BUFSIZ ); + buf[0] = '\0'; + bsize = BUFSIZ; + bp = buf; + while ( !feof(fp) ) { + errno = 0; + if ( fgets( line, sizeof(line), fp ) == NULL ) { + if ( errno == EINTR ) continue; + + Debug( LDAP_DEBUG_ANY, "shell: fgets failed: %s (%d)\n", + AC_STRERROR_R(errno, ebuf, sizeof ebuf), errno, 0 ); + break; + } + + Debug( LDAP_DEBUG_SHELL, "shell search reading line (%s)\n", + line, 0, 0 ); + + /* ignore lines beginning with # (LDIFv1 comments) */ + if ( *line == '#' ) { + continue; + } + + /* ignore lines beginning with DEBUG: */ + if ( strncasecmp( line, "DEBUG:", 6 ) == 0 ) { + continue; + } + + len = strlen( line ); + while ( bp + len + 1 - buf > bsize ) { + size_t offset = bp - buf; + bsize += BUFSIZ; + buf = (char *) ch_realloc( buf, bsize ); + bp = &buf[offset]; + } + strcpy( bp, line ); + bp += len; + + /* line marked the end of an entry or result */ + if ( *line == '\n' ) { + if ( strncasecmp( buf, "RESULT", 6 ) == 0 ) { + break; + } + + if ( (rs->sr_entry = str2entry( buf )) == NULL ) { + Debug( LDAP_DEBUG_ANY, "str2entry(%s) failed\n", + buf, 0, 0 ); + } else { + rs->sr_attrs = op->oq_search.rs_attrs; + rs->sr_flags = REP_ENTRY_MODIFIABLE; + send_search_entry( op, rs ); + entry_free( rs->sr_entry ); + rs->sr_attrs = NULL; + } + + bp = buf; + } + } + (void) str2result( buf, &rs->sr_err, (char **)&rs->sr_matched, (char **)&rs->sr_text ); + + /* otherwise, front end will send this result */ + if ( rs->sr_err != 0 || op->o_tag != LDAP_REQ_BIND ) { + send_ldap_result( op, rs ); + } + + free( buf ); + + return( rs->sr_err ); +} + +void +print_suffixes( + FILE *fp, + Backend *be +) +{ + int i; + + for ( i = 0; be->be_suffix[i].bv_val != NULL; i++ ) { + fprintf( fp, "suffix: %s\n", be->be_suffix[i].bv_val ); + } +} diff --git a/servers/slapd/back-shell/search.c b/servers/slapd/back-shell/search.c new file mode 100644 index 0000000..b628b9f --- /dev/null +++ b/servers/slapd/back-shell/search.c @@ -0,0 +1,86 @@ +/* search.c - shell backend search function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by the University of Michigan + * (as part of U-MICH LDAP). + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "shell.h" + +int +shell_back_search( + Operation *op, + SlapReply *rs ) +{ + struct shellinfo *si = (struct shellinfo *) op->o_bd->be_private; + FILE *rfp, *wfp; + AttributeName *an; + + if ( si->si_search == NULL ) { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "search not implemented" ); + return( -1 ); + } + + if ( forkandexec( si->si_search, &rfp, &wfp ) == (pid_t)-1 ) { + send_ldap_error( op, rs, LDAP_OTHER, + "could not fork/exec" ); + return( -1 ); + } + + /* write out the request to the search process */ + fprintf( wfp, "SEARCH\n" ); + fprintf( wfp, "msgid: %ld\n", (long) op->o_msgid ); + print_suffixes( wfp, op->o_bd ); + fprintf( wfp, "base: %s\n", op->o_req_dn.bv_val ); + fprintf( wfp, "scope: %d\n", op->oq_search.rs_scope ); + fprintf( wfp, "deref: %d\n", op->oq_search.rs_deref ); + fprintf( wfp, "sizelimit: %d\n", op->oq_search.rs_slimit ); + fprintf( wfp, "timelimit: %d\n", op->oq_search.rs_tlimit ); + fprintf( wfp, "filter: %s\n", op->oq_search.rs_filterstr.bv_val ); + fprintf( wfp, "attrsonly: %d\n", op->oq_search.rs_attrsonly ? 1 : 0 ); + fprintf( wfp, "attrs:%s", op->oq_search.rs_attrs == NULL ? " all" : "" ); + for ( an = op->oq_search.rs_attrs; an && an->an_name.bv_val; an++ ) { + fprintf( wfp, " %s", an->an_name.bv_val ); + } + fprintf( wfp, "\n" ); + fclose( wfp ); + + /* read in the results and send them along */ + rs->sr_attrs = op->oq_search.rs_attrs; + read_and_send_results( op, rs, rfp ); + + fclose( rfp ); + return( 0 ); +} diff --git a/servers/slapd/back-shell/searchexample.conf b/servers/slapd/back-shell/searchexample.conf new file mode 100644 index 0000000..a450ad3 --- /dev/null +++ b/servers/slapd/back-shell/searchexample.conf @@ -0,0 +1,29 @@ +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +# +## Portions Copyright (c) 1995 Regents of the University of Michigan. +## All rights reserved. +## +## Redistribution and use in source and binary forms are permitted +## provided that this notice is preserved and that due credit is given +## to the University of Michigan at Ann Arbor. The name of the University +## may not be used to endorse or promote products derived from this +## software without specific prior written permission. This software +## is provided ``as is'' without express or implied warranty. + +include /usr/local/etc/openldap/schema/core.schema + +database shell +suffix "dc=example,dc=com" +search /usr/local/etc/searchexample.sh diff --git a/servers/slapd/back-shell/searchexample.sh b/servers/slapd/back-shell/searchexample.sh new file mode 100644 index 0000000..9ded406 --- /dev/null +++ b/servers/slapd/back-shell/searchexample.sh @@ -0,0 +1,65 @@ +#! /bin/sh +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +# +## Portions Copyright (c) 1995 Regents of the University of Michigan. +## All rights reserved. +## +## Redistribution and use in source and binary forms are permitted +## provided that this notice is preserved and that due credit is given +## to the University of Michigan at Ann Arbor. The name of the University +## may not be used to endorse or promote products derived from this +## software without specific prior written permission. This software +## is provided ``as is'' without express or implied warranty. + +while [ 1 ]; do + read TAG VALUE + if [ $? -ne 0 ]; then + break + fi + case "$TAG" in + base:) + BASE=$VALUE + ;; + filter:) + FILTER=$VALUE + ;; + # include other parameters here + esac +done + +LOGIN=`echo $FILTER | sed -e 's/.*=\(.*\))/\1/'` + +PWLINE=`grep -i "^$LOGIN" /etc/passwd` + +#sleep 60 +# if we found an entry that matches +if [ $? = 0 ]; then + echo $PWLINE | awk -F: '{ + printf("dn: cn=%s,%s\n", $1, base); + printf("objectclass: top\n"); + printf("objectclass: person\n"); + printf("cn: %s\n", $1); + printf("cn: %s\n", $5); + printf("sn: %s\n", $1); + printf("uid: %s\n", $1); + }' base="$BASE" + echo "" +fi + +# result +echo "RESULT" +echo "code: 0" + +exit 0 diff --git a/servers/slapd/back-shell/shell.h b/servers/slapd/back-shell/shell.h new file mode 100644 index 0000000..ec239b3 --- /dev/null +++ b/servers/slapd/back-shell/shell.h @@ -0,0 +1,65 @@ +/* shell.h - shell backend header file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by the University of Michigan + * (as part of U-MICH LDAP). + */ + +#ifndef SLAPD_SHELL_H +#define SLAPD_SHELL_H + +#include "proto-shell.h" + +LDAP_BEGIN_DECL + +struct shellinfo { + char **si_bind; /* cmd + args to exec for bind */ + char **si_unbind; /* cmd + args to exec for unbind */ + char **si_search; /* cmd + args to exec for search */ + char **si_compare; /* cmd + args to exec for compare */ + char **si_modify; /* cmd + args to exec for modify */ + char **si_modrdn; /* cmd + args to exec for modrdn */ + char **si_add; /* cmd + args to exec for add */ + char **si_delete; /* cmd + args to exec for delete */ +}; + +extern pid_t forkandexec LDAP_P(( + char **args, + FILE **rfp, + FILE **wfp)); + +extern void print_suffixes LDAP_P(( + FILE *fp, + BackendDB *bd)); + +extern int read_and_send_results LDAP_P(( + Operation *op, + SlapReply *rs, + FILE *fp)); + +LDAP_END_DECL + +#endif diff --git a/servers/slapd/back-shell/unbind.c b/servers/slapd/back-shell/unbind.c new file mode 100644 index 0000000..77016e2 --- /dev/null +++ b/servers/slapd/back-shell/unbind.c @@ -0,0 +1,69 @@ +/* unbind.c - shell backend unbind function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by the University of Michigan + * (as part of U-MICH LDAP). + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "shell.h" + +int +shell_back_unbind( + Operation *op, + SlapReply *rs +) +{ + struct shellinfo *si = (struct shellinfo *) op->o_bd->be_private; + FILE *rfp, *wfp; + + if ( si->si_unbind == NULL ) { + return 0; + } + + if ( forkandexec( si->si_unbind, &rfp, &wfp ) == (pid_t)-1 ) { + return 0; + } + + /* write out the request to the unbind process */ + fprintf( wfp, "UNBIND\n" ); + fprintf( wfp, "msgid: %ld\n", (long) op->o_msgid ); + print_suffixes( wfp, op->o_bd ); + fprintf( wfp, "dn: %s\n", (op->o_conn->c_dn.bv_len ? op->o_conn->c_dn.bv_val : "") ); + fclose( wfp ); + + /* no response to unbind */ + fclose( rfp ); + + return 0; +} diff --git a/servers/slapd/back-sock/Makefile.in b/servers/slapd/back-sock/Makefile.in new file mode 100644 index 0000000..bb4e94f --- /dev/null +++ b/servers/slapd/back-sock/Makefile.in @@ -0,0 +1,47 @@ +# Makefile.in for back-sock +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 2007-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +## +## ACKNOWLEDGEMENTS: +## This work was initially developed by Brian Candler for inclusion +## in OpenLDAP Software. + +SRCS = init.c config.c opensock.c search.c bind.c unbind.c add.c \ + delete.c modify.c modrdn.c compare.c result.c extended.c +OBJS = init.lo config.lo opensock.lo search.lo bind.lo unbind.lo add.lo \ + delete.lo modify.lo modrdn.lo compare.lo result.lo extended.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-sock" +BUILD_MOD = @BUILD_SOCK@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_SOCK@_DEFS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_R_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBBASE = back_sock + +XINCPATH = -I.. -I$(srcdir)/.. +XDEFS = $(MODULES_CPPFLAGS) + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ + diff --git a/servers/slapd/back-sock/add.c b/servers/slapd/back-sock/add.c new file mode 100644 index 0000000..c86c8a9 --- /dev/null +++ b/servers/slapd/back-sock/add.c @@ -0,0 +1,69 @@ +/* add.c - sock backend add function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2007-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Brian Candler for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-sock.h" + +int +sock_back_add( + Operation *op, + SlapReply *rs ) +{ + struct sockinfo *si = (struct sockinfo *) op->o_bd->be_private; + AttributeDescription *entry = slap_schema.si_ad_entry; + FILE *fp; + int len; + + if ( ! access_allowed( op, op->oq_add.rs_e, + entry, NULL, ACL_WADD, NULL ) ) + { + send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, NULL ); + return -1; + } + + if ( (fp = opensock( si->si_sockpath )) == NULL ) { + send_ldap_error( op, rs, LDAP_OTHER, + "could not open socket" ); + return( -1 ); + } + + /* write out the request to the add process */ + fprintf( fp, "ADD\n" ); + fprintf( fp, "msgid: %ld\n", (long) op->o_msgid ); + sock_print_conn( fp, op->o_conn, si ); + sock_print_suffixes( fp, op->o_bd ); + ldap_pvt_thread_mutex_lock( &entry2str_mutex ); + fprintf( fp, "%s", entry2str( op->oq_add.rs_e, &len ) ); + ldap_pvt_thread_mutex_unlock( &entry2str_mutex ); + fprintf (fp, "\n" ); + + /* read in the result and send it along */ + sock_read_and_send_results( op, rs, fp ); + + fclose( fp ); + return( 0 ); +} diff --git a/servers/slapd/back-sock/back-sock.h b/servers/slapd/back-sock/back-sock.h new file mode 100644 index 0000000..68abca8 --- /dev/null +++ b/servers/slapd/back-sock/back-sock.h @@ -0,0 +1,61 @@ +/* sock.h - socket backend header file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2007-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Brian Candler for inclusion + * in OpenLDAP Software. + */ + +#ifndef SLAPD_SOCK_H +#define SLAPD_SOCK_H + +#include "proto-sock.h" + +LDAP_BEGIN_DECL + +struct sockinfo { + const char *si_sockpath; + slap_mask_t si_extensions; + slap_mask_t si_ops; /* overlay: operations to act on */ + slap_mask_t si_resps; /* overlay: responses to forward */ + regex_t si_dnpat; /* overlay: DN pattern to match */ + struct berval si_dnpatstr; +}; + +#define SOCK_EXT_BINDDN 1 +#define SOCK_EXT_PEERNAME 2 +#define SOCK_EXT_SSF 4 +#define SOCK_EXT_CONNID 8 + +extern FILE *opensock LDAP_P(( + const char *sockpath)); + +extern void sock_print_suffixes LDAP_P(( + FILE *fp, + BackendDB *bd)); + +extern void sock_print_conn LDAP_P(( + FILE *fp, + Connection *conn, + struct sockinfo *si)); + +extern int sock_read_and_send_results LDAP_P(( + Operation *op, + SlapReply *rs, + FILE *fp)); + +LDAP_END_DECL + +#endif diff --git a/servers/slapd/back-sock/bind.c b/servers/slapd/back-sock/bind.c new file mode 100644 index 0000000..83982bf --- /dev/null +++ b/servers/slapd/back-sock/bind.c @@ -0,0 +1,80 @@ +/* bind.c - sock backend bind function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2007-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Brian Candler for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-sock.h" + +int +sock_back_bind( + Operation *op, + SlapReply *rs ) +{ + struct sockinfo *si = (struct sockinfo *) op->o_bd->be_private; + AttributeDescription *entry = slap_schema.si_ad_entry; + Entry e; + FILE *fp; + int rc; + + e.e_id = NOID; + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + e.e_attrs = NULL; + e.e_ocflags = 0; + e.e_bv.bv_len = 0; + e.e_bv.bv_val = NULL; + e.e_private = NULL; + + if ( ! access_allowed( op, &e, + entry, NULL, ACL_AUTH, NULL ) ) + { + send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, NULL ); + return -1; + } + + if ( (fp = opensock( si->si_sockpath )) == NULL ) { + send_ldap_error( op, rs, LDAP_OTHER, + "could not open socket" ); + return( -1 ); + } + + /* write out the request to the bind process */ + fprintf( fp, "BIND\n" ); + fprintf( fp, "msgid: %ld\n", (long) op->o_msgid ); + sock_print_conn( fp, op->o_conn, si ); + sock_print_suffixes( fp, op->o_bd ); + fprintf( fp, "dn: %s\n", op->o_req_dn.bv_val ); + fprintf( fp, "method: %d\n", op->oq_bind.rb_method ); + fprintf( fp, "credlen: %lu\n", op->oq_bind.rb_cred.bv_len ); + fprintf( fp, "cred: %s\n", op->oq_bind.rb_cred.bv_val ); /* XXX */ + fprintf( fp, "\n" ); + + /* read in the results and send them along */ + rc = sock_read_and_send_results( op, rs, fp ); + fclose( fp ); + + return( rc ); +} diff --git a/servers/slapd/back-sock/compare.c b/servers/slapd/back-sock/compare.c new file mode 100644 index 0000000..72ead88 --- /dev/null +++ b/servers/slapd/back-sock/compare.c @@ -0,0 +1,88 @@ +/* compare.c - sock backend compare function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Brian Candler for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-sock.h" +#include "ldif.h" + +int +sock_back_compare( + Operation *op, + SlapReply *rs ) +{ + struct sockinfo *si = (struct sockinfo *) op->o_bd->be_private; + AttributeDescription *entry = slap_schema.si_ad_entry; + Entry e; + FILE *fp; + char *text; + + e.e_id = NOID; + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + e.e_attrs = NULL; + e.e_ocflags = 0; + e.e_bv.bv_len = 0; + e.e_bv.bv_val = NULL; + e.e_private = NULL; + + if ( ! access_allowed( op, &e, + entry, NULL, ACL_COMPARE, NULL ) ) + { + send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, NULL ); + return -1; + } + + if ( (fp = opensock( si->si_sockpath )) == NULL ) { + send_ldap_error( op, rs, LDAP_OTHER, + "could not open socket" ); + return( -1 ); + } + + /* write out the request to the compare process */ + fprintf( fp, "COMPARE\n" ); + fprintf( fp, "msgid: %ld\n", (long) op->o_msgid ); + sock_print_conn( fp, op->o_conn, si ); + sock_print_suffixes( fp, op->o_bd ); + fprintf( fp, "dn: %s\n", op->o_req_dn.bv_val ); + /* could be binary */ + text = ldif_put_wrap( LDIF_PUT_VALUE, + op->orc_ava->aa_desc->ad_cname.bv_val, + op->orc_ava->aa_value.bv_val, + op->orc_ava->aa_value.bv_len, LDIF_LINE_WIDTH_MAX ); + if ( text ) { + fprintf( fp, "%s\n", text ); + ber_memfree( text ); + } else { + fprintf( fp, "\n\n" ); + } + + /* read in the result and send it along */ + sock_read_and_send_results( op, rs, fp ); + + fclose( fp ); + return( 0 ); +} diff --git a/servers/slapd/back-sock/config.c b/servers/slapd/back-sock/config.c new file mode 100644 index 0000000..a340239 --- /dev/null +++ b/servers/slapd/back-sock/config.c @@ -0,0 +1,420 @@ +/* config.c - sock backend configuration file routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2007-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Brian Candler for inclusion + * in OpenLDAP Software. Dynamic config support by Howard Chu. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "config.h" +#include "back-sock.h" + +static ConfigDriver bs_cf_gen; +static int sock_over_setup(); +static slap_response sock_over_response; + +enum { + BS_EXT = 1, + BS_OPS, + BS_RESP, + BS_DNPAT +}; + +/* The number of overlay-only config attrs */ +#define NUM_OV_ATTRS 3 + +static ConfigTable bscfg[] = { + { "sockops", "ops", 2, 0, 0, ARG_MAGIC|BS_OPS, + bs_cf_gen, "( OLcfgDbAt:7.3 NAME 'olcOvSocketOps' " + "DESC 'Operation types to forward' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "sockresps", "resps", 2, 0, 0, ARG_MAGIC|BS_RESP, + bs_cf_gen, "( OLcfgDbAt:7.4 NAME 'olcOvSocketResps' " + "DESC 'Response types to forward' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "sockdnpat", "regexp", 2, 2, 0, ARG_MAGIC|BS_DNPAT, + bs_cf_gen, "( OLcfgDbAt:7.5 NAME 'olcOvSocketDNpat' " + "DESC 'DN pattern to match' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + + { "socketpath", "pathname", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct sockinfo, si_sockpath), + "( OLcfgDbAt:7.1 NAME 'olcDbSocketPath' " + "DESC 'Pathname for Unix domain socket' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "extensions", "ext", 2, 0, 0, ARG_MAGIC|BS_EXT, + bs_cf_gen, "( OLcfgDbAt:7.2 NAME 'olcDbSocketExtensions' " + "DESC 'binddn, peername, or ssf' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { NULL, NULL } +}; + +static ConfigOCs bsocs[] = { + { "( OLcfgDbOc:7.1 " + "NAME 'olcDbSocketConfig' " + "DESC 'Socket backend configuration' " + "SUP olcDatabaseConfig " + "MUST olcDbSocketPath " + "MAY olcDbSocketExtensions )", + Cft_Database, bscfg+NUM_OV_ATTRS }, + { NULL, 0, NULL } +}; + +static ConfigOCs osocs[] = { + { "( OLcfgDbOc:7.2 " + "NAME 'olcOvSocketConfig' " + "DESC 'Socket overlay configuration' " + "SUP olcOverlayConfig " + "MUST olcDbSocketPath " + "MAY ( olcDbSocketExtensions $ " + " olcOvSocketOps $ olcOvSocketResps $ " + " olcOvSocketDNpat ) )", + Cft_Overlay, bscfg }, + { NULL, 0, NULL } +}; + +#define SOCK_OP_BIND 0x001 +#define SOCK_OP_UNBIND 0x002 +#define SOCK_OP_SEARCH 0x004 +#define SOCK_OP_COMPARE 0x008 +#define SOCK_OP_MODIFY 0x010 +#define SOCK_OP_MODRDN 0x020 +#define SOCK_OP_ADD 0x040 +#define SOCK_OP_DELETE 0x080 +#define SOCK_OP_EXTENDED 0x100 + +#define SOCK_REP_RESULT 0x001 +#define SOCK_REP_SEARCH 0x002 + +static slap_verbmasks bs_exts[] = { + { BER_BVC("binddn"), SOCK_EXT_BINDDN }, + { BER_BVC("peername"), SOCK_EXT_PEERNAME }, + { BER_BVC("ssf"), SOCK_EXT_SSF }, + { BER_BVC("connid"), SOCK_EXT_CONNID }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks ov_ops[] = { + { BER_BVC("bind"), SOCK_OP_BIND }, + { BER_BVC("unbind"), SOCK_OP_UNBIND }, + { BER_BVC("search"), SOCK_OP_SEARCH }, + { BER_BVC("compare"), SOCK_OP_COMPARE }, + { BER_BVC("modify"), SOCK_OP_MODIFY }, + { BER_BVC("modrdn"), SOCK_OP_MODRDN }, + { BER_BVC("add"), SOCK_OP_ADD }, + { BER_BVC("delete"), SOCK_OP_DELETE }, + { BER_BVC("extended"), SOCK_OP_EXTENDED }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks ov_resps[] = { + { BER_BVC("result"), SOCK_REP_RESULT }, + { BER_BVC("search"), SOCK_REP_SEARCH }, + { BER_BVNULL, 0 } +}; + +static int +bs_cf_gen( ConfigArgs *c ) +{ + struct sockinfo *si; + int rc; + + if ( c->be && c->table == Cft_Database ) + si = c->be->be_private; + else if ( c->bi ) + si = c->bi->bi_private; + else + return ARG_BAD_CONF; + + if ( c->op == SLAP_CONFIG_EMIT ) { + switch( c->type ) { + case BS_EXT: + return mask_to_verbs( bs_exts, si->si_extensions, &c->rvalue_vals ); + case BS_OPS: + return mask_to_verbs( ov_ops, si->si_ops, &c->rvalue_vals ); + case BS_RESP: + return mask_to_verbs( ov_resps, si->si_resps, &c->rvalue_vals ); + case BS_DNPAT: + value_add_one( &c->rvalue_vals, &si->si_dnpatstr ); + return 0; + } + } else if ( c->op == LDAP_MOD_DELETE ) { + switch( c->type ) { + case BS_EXT: + if ( c->valx < 0 ) { + si->si_extensions = 0; + rc = 0; + } else { + slap_mask_t dels = 0; + rc = verbs_to_mask( c->argc, c->argv, bs_exts, &dels ); + if ( rc == 0 ) + si->si_extensions ^= dels; + } + return rc; + case BS_OPS: + if ( c->valx < 0 ) { + si->si_ops = 0; + rc = 0; + } else { + slap_mask_t dels = 0; + rc = verbs_to_mask( c->argc, c->argv, ov_ops, &dels ); + if ( rc == 0 ) + si->si_ops ^= dels; + } + return rc; + case BS_RESP: + if ( c->valx < 0 ) { + si->si_resps = 0; + rc = 0; + } else { + slap_mask_t dels = 0; + rc = verbs_to_mask( c->argc, c->argv, ov_resps, &dels ); + if ( rc == 0 ) + si->si_resps ^= dels; + } + return rc; + case BS_DNPAT: + regfree( &si->si_dnpat ); + ch_free( si->si_dnpatstr.bv_val ); + BER_BVZERO( &si->si_dnpatstr ); + return 0; + } + + } else { + switch( c->type ) { + case BS_EXT: + return verbs_to_mask( c->argc, c->argv, bs_exts, &si->si_extensions ); + case BS_OPS: + return verbs_to_mask( c->argc, c->argv, ov_ops, &si->si_ops ); + case BS_RESP: + return verbs_to_mask( c->argc, c->argv, ov_resps, &si->si_resps ); + case BS_DNPAT: + if ( !regcomp( &si->si_dnpat, c->argv[1], REG_EXTENDED|REG_ICASE|REG_NOSUB )) { + ber_str2bv( c->argv[1], 0, 1, &si->si_dnpatstr ); + return 0; + } else { + return 1; + } + } + } + return 1; +} + +int +sock_back_init_cf( BackendInfo *bi ) +{ + int rc; + bi->bi_cf_ocs = bsocs; + + rc = config_register_schema( bscfg, bsocs ); + if ( !rc ) + rc = sock_over_setup(); + return rc; +} + +/* sock overlay wrapper */ +static slap_overinst sockover; + +static int sock_over_db_init( Backend *be, struct config_reply_s *cr ); +static int sock_over_db_destroy( Backend *be, struct config_reply_s *cr ); + +static BI_op_bind *sockfuncs[] = { + sock_back_bind, + sock_back_unbind, + sock_back_search, + sock_back_compare, + sock_back_modify, + sock_back_modrdn, + sock_back_add, + sock_back_delete, + 0, /* abandon not supported */ + sock_back_extended +}; + +static const int sockopflags[] = { + SOCK_OP_BIND, + SOCK_OP_UNBIND, + SOCK_OP_SEARCH, + SOCK_OP_COMPARE, + SOCK_OP_MODIFY, + SOCK_OP_MODRDN, + SOCK_OP_ADD, + SOCK_OP_DELETE, + 0, /* abandon not supported */ + SOCK_OP_EXTENDED +}; + +static int sock_over_op( + Operation *op, + SlapReply *rs +) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + void *private = op->o_bd->be_private; + slap_callback *sc; + struct sockinfo *si; + slap_operation_t which; + + switch (op->o_tag) { + case LDAP_REQ_BIND: which = op_bind; break; + case LDAP_REQ_UNBIND: which = op_unbind; break; + case LDAP_REQ_SEARCH: which = op_search; break; + case LDAP_REQ_COMPARE: which = op_compare; break; + case LDAP_REQ_MODIFY: which = op_modify; break; + case LDAP_REQ_MODRDN: which = op_modrdn; break; + case LDAP_REQ_ADD: which = op_add; break; + case LDAP_REQ_DELETE: which = op_delete; break; + case LDAP_REQ_EXTENDED: which = op_extended; break; + default: + return SLAP_CB_CONTINUE; + } + si = on->on_bi.bi_private; + if ( !(si->si_ops & sockopflags[which])) + return SLAP_CB_CONTINUE; + + if ( !BER_BVISEMPTY( &si->si_dnpatstr ) && + regexec( &si->si_dnpat, op->o_req_ndn.bv_val, 0, NULL, 0 )) + return SLAP_CB_CONTINUE; + + op->o_bd->be_private = si; + sc = op->o_callback; + op->o_callback = NULL; + sockfuncs[which]( op, rs ); + op->o_bd->be_private = private; + op->o_callback = sc; + return rs->sr_err; +} + +static int +sock_over_response( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + struct sockinfo *si = (struct sockinfo *)on->on_bi.bi_private; + FILE *fp; + + if ( rs->sr_type == REP_RESULT ) { + if ( !( si->si_resps & SOCK_REP_RESULT )) + return SLAP_CB_CONTINUE; + } else if ( rs->sr_type == REP_SEARCH ) { + if ( !( si->si_resps & SOCK_REP_SEARCH )) + return SLAP_CB_CONTINUE; + } else + return SLAP_CB_CONTINUE; + + if (( fp = opensock( si->si_sockpath )) == NULL ) + return SLAP_CB_CONTINUE; + + if ( rs->sr_type == REP_RESULT ) { + /* write out the result */ + fprintf( fp, "RESULT\n" ); + fprintf( fp, "msgid: %ld\n", (long) op->o_msgid ); + sock_print_conn( fp, op->o_conn, si ); + fprintf( fp, "code: %d\n", rs->sr_err ); + if ( rs->sr_matched ) + fprintf( fp, "matched: %s\n", rs->sr_matched ); + if (rs->sr_text ) + fprintf( fp, "info: %s\n", rs->sr_text ); + } else { + /* write out the search entry */ + int len; + fprintf( fp, "ENTRY\n" ); + fprintf( fp, "msgid: %ld\n", (long) op->o_msgid ); + sock_print_conn( fp, op->o_conn, si ); + ldap_pvt_thread_mutex_lock( &entry2str_mutex ); + fprintf( fp, "%s", entry2str( rs->sr_entry, &len ) ); + ldap_pvt_thread_mutex_unlock( &entry2str_mutex ); + } + fprintf( fp, "\n" ); + fclose( fp ); + + return SLAP_CB_CONTINUE; +} + +static int +sock_over_setup() +{ + int rc; + + sockover.on_bi.bi_type = "sock"; + sockover.on_bi.bi_db_init = sock_over_db_init; + sockover.on_bi.bi_db_destroy = sock_over_db_destroy; + + sockover.on_bi.bi_op_bind = sock_over_op; + sockover.on_bi.bi_op_unbind = sock_over_op; + sockover.on_bi.bi_op_search = sock_over_op; + sockover.on_bi.bi_op_compare = sock_over_op; + sockover.on_bi.bi_op_modify = sock_over_op; + sockover.on_bi.bi_op_modrdn = sock_over_op; + sockover.on_bi.bi_op_add = sock_over_op; + sockover.on_bi.bi_op_delete = sock_over_op; + sockover.on_bi.bi_extended = sock_over_op; + sockover.on_response = sock_over_response; + + sockover.on_bi.bi_cf_ocs = osocs; + + rc = config_register_schema( bscfg, osocs ); + if ( rc ) return rc; + + return overlay_register( &sockover ); +} + +static int +sock_over_db_init( + Backend *be, + struct config_reply_s *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + void *private = be->be_private; + void *cf_ocs = be->be_cf_ocs; + int rc; + + be->be_private = NULL; + rc = sock_back_db_init( be, cr ); + on->on_bi.bi_private = be->be_private; + be->be_private = private; + be->be_cf_ocs = cf_ocs; + return rc; +} + +static int +sock_over_db_destroy( + Backend *be, + struct config_reply_s *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + void *private = be->be_private; + int rc; + + be->be_private = on->on_bi.bi_private; + rc = sock_back_db_destroy( be, cr ); + on->on_bi.bi_private = be->be_private; + be->be_private = private; + return rc; +} diff --git a/servers/slapd/back-sock/delete.c b/servers/slapd/back-sock/delete.c new file mode 100644 index 0000000..a657954 --- /dev/null +++ b/servers/slapd/back-sock/delete.c @@ -0,0 +1,75 @@ +/* delete.c - sock backend delete function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2007-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Brian Candler for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-sock.h" + +int +sock_back_delete( + Operation *op, + SlapReply *rs ) +{ + struct sockinfo *si = (struct sockinfo *) op->o_bd->be_private; + AttributeDescription *entry = slap_schema.si_ad_entry; + Entry e; + FILE *fp; + + e.e_id = NOID; + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + e.e_attrs = NULL; + e.e_ocflags = 0; + e.e_bv.bv_len = 0; + e.e_bv.bv_val = NULL; + e.e_private = NULL; + + if ( ! access_allowed( op, &e, + entry, NULL, ACL_WDEL, NULL ) ) + { + send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, NULL ); + return -1; + } + + if ( (fp = opensock( si->si_sockpath )) == NULL ) { + send_ldap_error( op, rs, LDAP_OTHER, + "could not open socket" ); + return( -1 ); + } + + /* write out the request to the delete process */ + fprintf( fp, "DELETE\n" ); + fprintf( fp, "msgid: %ld\n", (long) op->o_msgid ); + sock_print_conn( fp, op->o_conn, si ); + sock_print_suffixes( fp, op->o_bd ); + fprintf( fp, "dn: %s\n", op->o_req_dn.bv_val ); + fprintf( fp, "\n" ); + + /* read in the results and send them along */ + sock_read_and_send_results( op, rs, fp ); + fclose( fp ); + return( 0 ); +} diff --git a/servers/slapd/back-sock/extended.c b/servers/slapd/back-sock/extended.c new file mode 100644 index 0000000..779c249 --- /dev/null +++ b/servers/slapd/back-sock/extended.c @@ -0,0 +1,76 @@ +/* extended.c - sock backend extended routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-sock.h" + +#include "lutil.h" + +int +sock_back_extended( Operation *op, SlapReply *rs ) +{ + int rc; + struct sockinfo *si = (struct sockinfo *) op->o_bd->be_private; + FILE *fp; + struct berval b64; + + Debug( LDAP_DEBUG_ARGS, "==> sock_back_extended(%s)\n", + op->ore_reqoid.bv_val, op->o_req_dn.bv_val, 0 ); + + if ( (fp = opensock( si->si_sockpath )) == NULL ) { + send_ldap_error( op, rs, LDAP_OTHER, + "could not open socket" ); + return( -1 ); + } + + /* write out the request to the extended process */ + fprintf( fp, "EXTENDED\n" ); + fprintf( fp, "msgid: %ld\n", (long) op->o_msgid ); + sock_print_conn( fp, op->o_conn, si ); + sock_print_suffixes( fp, op->o_bd ); + fprintf( fp, "oid: %s\n", op->ore_reqoid.bv_val ); + + if (op->ore_reqdata) { + + b64.bv_len = LUTIL_BASE64_ENCODE_LEN( op->ore_reqdata->bv_len ) + 1; + b64.bv_val = op->o_tmpalloc( b64.bv_len + 1, op->o_tmpmemctx ); + + rc = lutil_b64_ntop( + (unsigned char *) op->ore_reqdata->bv_val, op->ore_reqdata->bv_len, + b64.bv_val, b64.bv_len ); + + b64.bv_len = rc; + assert( strlen(b64.bv_val) == b64.bv_len ); + + fprintf( fp, "value: %s\n", b64.bv_val ); + + op->o_tmpfree( b64.bv_val, op->o_tmpmemctx ); + + } + + fprintf( fp, "\n" ); + + /* read in the results and send them along */ + rc = sock_read_and_send_results( op, rs, fp ); + fclose( fp ); + + return( rc ); +} diff --git a/servers/slapd/back-sock/init.c b/servers/slapd/back-sock/init.c new file mode 100644 index 0000000..ef15270 --- /dev/null +++ b/servers/slapd/back-sock/init.c @@ -0,0 +1,97 @@ +/* init.c - initialize sock backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2007-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Brian Candler for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> + +#include "slap.h" +#include "back-sock.h" + +int +sock_back_initialize( + BackendInfo *bi +) +{ + bi->bi_open = 0; + bi->bi_config = 0; + bi->bi_close = 0; + bi->bi_destroy = 0; + + bi->bi_db_init = sock_back_db_init; + bi->bi_db_config = 0; + bi->bi_db_open = 0; + bi->bi_db_close = 0; + bi->bi_db_destroy = sock_back_db_destroy; + + bi->bi_op_bind = sock_back_bind; + bi->bi_op_unbind = sock_back_unbind; + bi->bi_op_search = sock_back_search; + bi->bi_op_compare = sock_back_compare; + bi->bi_op_modify = sock_back_modify; + bi->bi_op_modrdn = sock_back_modrdn; + bi->bi_op_add = sock_back_add; + bi->bi_op_delete = sock_back_delete; + bi->bi_op_abandon = 0; + + bi->bi_extended = sock_back_extended; + + bi->bi_chk_referrals = 0; + + bi->bi_connection_init = 0; + bi->bi_connection_destroy = 0; + + return sock_back_init_cf( bi ); +} + +int +sock_back_db_init( + Backend *be, + struct config_reply_s *cr +) +{ + struct sockinfo *si; + + si = (struct sockinfo *) ch_calloc( 1, sizeof(struct sockinfo) ); + + be->be_private = si; + be->be_cf_ocs = be->bd_info->bi_cf_ocs; + + return si == NULL; +} + +int +sock_back_db_destroy( + Backend *be, + struct config_reply_s *cr +) +{ + free( be->be_private ); + return 0; +} + +#if SLAPD_SOCK == SLAPD_MOD_DYNAMIC + +/* conditionally define the init_module() function */ +SLAP_BACKEND_INIT_MODULE( sock ) + +#endif /* SLAPD_SOCK == SLAPD_MOD_DYNAMIC */ diff --git a/servers/slapd/back-sock/modify.c b/servers/slapd/back-sock/modify.c new file mode 100644 index 0000000..dcf860d --- /dev/null +++ b/servers/slapd/back-sock/modify.c @@ -0,0 +1,117 @@ +/* modify.c - sock backend modify function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2007-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Brian Candler for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-sock.h" +#include "ldif.h" + +int +sock_back_modify( + Operation *op, + SlapReply *rs ) +{ + Modification *mod; + struct sockinfo *si = (struct sockinfo *) op->o_bd->be_private; + AttributeDescription *entry = slap_schema.si_ad_entry; + Modifications *ml = op->orm_modlist; + Entry e; + FILE *fp; + int i; + + e.e_id = NOID; + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + e.e_attrs = NULL; + e.e_ocflags = 0; + e.e_bv.bv_len = 0; + e.e_bv.bv_val = NULL; + e.e_private = NULL; + + if ( ! access_allowed( op, &e, + entry, NULL, ACL_WRITE, NULL ) ) + { + send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, NULL ); + return -1; + } + + if ( (fp = opensock( si->si_sockpath )) == NULL ) { + send_ldap_error( op, rs, LDAP_OTHER, + "could not open socket" ); + return( -1 ); + } + + /* write out the request to the modify process */ + fprintf( fp, "MODIFY\n" ); + fprintf( fp, "msgid: %ld\n", (long) op->o_msgid ); + sock_print_conn( fp, op->o_conn, si ); + sock_print_suffixes( fp, op->o_bd ); + fprintf( fp, "dn: %s\n", op->o_req_dn.bv_val ); + for ( ; ml != NULL; ml = ml->sml_next ) { + mod = &ml->sml_mod; + + switch ( mod->sm_op ) { + case LDAP_MOD_ADD: + fprintf( fp, "add: %s\n", mod->sm_desc->ad_cname.bv_val ); + break; + + case LDAP_MOD_DELETE: + fprintf( fp, "delete: %s\n", mod->sm_desc->ad_cname.bv_val ); + break; + + case LDAP_MOD_REPLACE: + fprintf( fp, "replace: %s\n", mod->sm_desc->ad_cname.bv_val ); + break; + + case LDAP_MOD_INCREMENT: + fprintf( fp, "increment: %s\n", mod->sm_desc->ad_cname.bv_val ); + break; + } + + if( mod->sm_values != NULL ) { + for ( i = 0; mod->sm_values[i].bv_val != NULL; i++ ) { + char *text = ldif_put_wrap( LDIF_PUT_VALUE, + mod->sm_desc->ad_cname.bv_val, + mod->sm_values[i].bv_val, + mod->sm_values[i].bv_len, LDIF_LINE_WIDTH_MAX ); + if ( text ) { + fprintf( fp, "%s", text ); + ber_memfree( text ); + } else { + break; + } + } + } + + fprintf( fp, "-\n" ); + } + fprintf( fp, "\n" ); + + /* read in the results and send them along */ + sock_read_and_send_results( op, rs, fp ); + fclose( fp ); + return( 0 ); +} diff --git a/servers/slapd/back-sock/modrdn.c b/servers/slapd/back-sock/modrdn.c new file mode 100644 index 0000000..7f1b7ca --- /dev/null +++ b/servers/slapd/back-sock/modrdn.c @@ -0,0 +1,81 @@ +/* modrdn.c - sock backend modrdn function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2007-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Brian Candler for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-sock.h" + +int +sock_back_modrdn( + Operation *op, + SlapReply *rs ) +{ + struct sockinfo *si = (struct sockinfo *) op->o_bd->be_private; + AttributeDescription *entry = slap_schema.si_ad_entry; + Entry e; + FILE *fp; + + e.e_id = NOID; + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + e.e_attrs = NULL; + e.e_ocflags = 0; + e.e_bv.bv_len = 0; + e.e_bv.bv_val = NULL; + e.e_private = NULL; + + if ( ! access_allowed( op, &e, entry, NULL, + op->oq_modrdn.rs_newSup ? ACL_WDEL : ACL_WRITE, + NULL ) ) + { + send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, NULL ); + return -1; + } + + if ( (fp = opensock( si->si_sockpath )) == NULL ) { + send_ldap_error( op, rs, LDAP_OTHER, + "could not open socket" ); + return( -1 ); + } + + /* write out the request to the modrdn process */ + fprintf( fp, "MODRDN\n" ); + fprintf( fp, "msgid: %ld\n", (long) op->o_msgid ); + sock_print_conn( fp, op->o_conn, si ); + sock_print_suffixes( fp, op->o_bd ); + fprintf( fp, "dn: %s\n", op->o_req_dn.bv_val ); + fprintf( fp, "newrdn: %s\n", op->oq_modrdn.rs_newrdn.bv_val ); + fprintf( fp, "deleteoldrdn: %d\n", op->oq_modrdn.rs_deleteoldrdn ? 1 : 0 ); + if ( op->oq_modrdn.rs_newSup != NULL ) { + fprintf( fp, "newSuperior: %s\n", op->oq_modrdn.rs_newSup->bv_val ); + } + fprintf( fp, "\n" ); + + /* read in the results and send them along */ + sock_read_and_send_results( op, rs, fp ); + fclose( fp ); + return( 0 ); +} diff --git a/servers/slapd/back-sock/opensock.c b/servers/slapd/back-sock/opensock.c new file mode 100644 index 0000000..cc93b1e --- /dev/null +++ b/servers/slapd/back-sock/opensock.c @@ -0,0 +1,71 @@ +/* opensock.c - open a unix domain socket */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2007-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Brian Candler for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/errno.h> +#include <ac/string.h> +#include <ac/socket.h> +#include <ac/unistd.h> + +#include "slap.h" +#include "back-sock.h" + +/* + * FIXME: count the number of concurrent open sockets (since each thread + * may open one). Perhaps block here if a soft limit is reached, and fail + * if a hard limit reached + */ + +FILE * +opensock( + const char *sockpath +) +{ + int fd; + FILE *fp; + struct sockaddr_un sockun; + + fd = socket(PF_UNIX, SOCK_STREAM, 0); + if ( fd < 0 ) { + Debug( LDAP_DEBUG_ANY, "socket create failed\n", 0, 0, 0 ); + return( NULL ); + } + + sockun.sun_family = AF_UNIX; + sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1), + sockpath); + if ( connect( fd, (struct sockaddr *)&sockun, sizeof(sockun) ) < 0 ) { + Debug( LDAP_DEBUG_ANY, "socket connect(%s) failed\n", + sockpath ? sockpath : "<null>", 0, 0 ); + close( fd ); + return( NULL ); + } + + if ( ( fp = fdopen( fd, "r+" ) ) == NULL ) { + Debug( LDAP_DEBUG_ANY, "fdopen failed\n", 0, 0, 0 ); + close( fd ); + return( NULL ); + } + + return( fp ); +} diff --git a/servers/slapd/back-sock/proto-sock.h b/servers/slapd/back-sock/proto-sock.h new file mode 100644 index 0000000..76410a7 --- /dev/null +++ b/servers/slapd/back-sock/proto-sock.h @@ -0,0 +1,49 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2007-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Brian Candler for inclusion + * in OpenLDAP Software. + */ + +#ifndef _PROTO_SOCK_H +#define _PROTO_SOCK_H + +LDAP_BEGIN_DECL + +extern BI_init sock_back_initialize; + +extern BI_open sock_back_open; +extern BI_close sock_back_close; +extern BI_destroy sock_back_destroy; + +extern BI_db_init sock_back_db_init; +extern BI_db_destroy sock_back_db_destroy; + +extern BI_op_bind sock_back_bind; +extern BI_op_unbind sock_back_unbind; +extern BI_op_search sock_back_search; +extern BI_op_compare sock_back_compare; +extern BI_op_modify sock_back_modify; +extern BI_op_modrdn sock_back_modrdn; +extern BI_op_add sock_back_add; +extern BI_op_delete sock_back_delete; + +extern BI_op_extended sock_back_extended; + +extern int sock_back_init_cf( BackendInfo *bi ); + +LDAP_END_DECL + +#endif /* _PROTO_SOCK_H */ diff --git a/servers/slapd/back-sock/result.c b/servers/slapd/back-sock/result.c new file mode 100644 index 0000000..f4c93d1 --- /dev/null +++ b/servers/slapd/back-sock/result.c @@ -0,0 +1,167 @@ +/* result.c - sock backend result reading function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2007-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Brian Candler for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/errno.h> +#include <ac/string.h> +#include <ac/socket.h> +#include <ac/unistd.h> + +#include "slap.h" +#include "back-sock.h" + +/* + * FIXME: make a RESULT section compulsory from the socket response. + * Otherwise, a partial/aborted response is treated as 'success'. + * This is a divergence from the back-shell protocol, but makes things + * more robust. + */ + +int +sock_read_and_send_results( + Operation *op, + SlapReply *rs, + FILE *fp ) +{ + int bsize, len; + char *buf, *bp; + char line[BUFSIZ]; + char ebuf[128]; + + (void) fflush(fp); + /* read in the result and send it along */ + buf = (char *) ch_malloc( BUFSIZ ); + buf[0] = '\0'; + bsize = BUFSIZ; + bp = buf; + while ( !feof(fp) ) { + errno = 0; + if ( fgets( line, sizeof(line), fp ) == NULL ) { + if ( errno == EINTR ) continue; + + Debug( LDAP_DEBUG_ANY, "sock: fgets failed: %s (%d)\n", + AC_STRERROR_R(errno, ebuf, sizeof ebuf), errno, 0 ); + break; + } + + Debug( LDAP_DEBUG_SHELL, "sock search reading line (%s)\n", + line, 0, 0 ); + + /* ignore lines beginning with # (LDIFv1 comments) */ + if ( *line == '#' ) { + continue; + } + + /* ignore lines beginning with DEBUG: */ + if ( strncasecmp( line, "DEBUG:", 6 ) == 0 ) { + continue; + } + + if ( strncasecmp( line, "CONTINUE", 8 ) == 0 ) { + struct sockinfo *si = (struct sockinfo *) op->o_bd->be_private; + /* Only valid when operating as an overlay! */ + assert( si->si_ops != 0 ); + rs->sr_err = SLAP_CB_CONTINUE; + goto skip; + } + + len = strlen( line ); + while ( bp + len + 1 - buf > bsize ) { + size_t offset = bp - buf; + bsize += BUFSIZ; + buf = (char *) ch_realloc( buf, bsize ); + bp = &buf[offset]; + } + strcpy( bp, line ); + bp += len; + + /* line marked the end of an entry or result */ + if ( *line == '\n' ) { + if ( strncasecmp( buf, "RESULT", 6 ) == 0 ) { + break; + } + + if ( (rs->sr_entry = str2entry( buf )) == NULL ) { + Debug( LDAP_DEBUG_ANY, "str2entry(%s) failed\n", + buf, 0, 0 ); + } else { + rs->sr_attrs = op->oq_search.rs_attrs; + rs->sr_flags = REP_ENTRY_MODIFIABLE; + send_search_entry( op, rs ); + entry_free( rs->sr_entry ); + rs->sr_attrs = NULL; + } + + bp = buf; + } + } + (void) str2result( buf, &rs->sr_err, (char **)&rs->sr_matched, (char **)&rs->sr_text ); + + /* otherwise, front end will send this result */ + if ( rs->sr_err != 0 || op->o_tag != LDAP_REQ_BIND ) { + send_ldap_result( op, rs ); + } + +skip: + ch_free( buf ); + + return( rs->sr_err ); +} + +void +sock_print_suffixes( + FILE *fp, + Backend *be +) +{ + int i; + + for ( i = 0; be->be_suffix[i].bv_val != NULL; i++ ) { + fprintf( fp, "suffix: %s\n", be->be_suffix[i].bv_val ); + } +} + +void +sock_print_conn( + FILE *fp, + Connection *conn, + struct sockinfo *si +) +{ + if ( conn == NULL ) return; + + if( si->si_extensions & SOCK_EXT_BINDDN ) { + fprintf( fp, "binddn: %s\n", + conn->c_dn.bv_len ? conn->c_dn.bv_val : "" ); + } + if( si->si_extensions & SOCK_EXT_PEERNAME ) { + fprintf( fp, "peername: %s\n", + conn->c_peer_name.bv_len ? conn->c_peer_name.bv_val : "" ); + } + if( si->si_extensions & SOCK_EXT_SSF ) { + fprintf( fp, "ssf: %d\n", conn->c_ssf ); + } + if( si->si_extensions & SOCK_EXT_CONNID ) { + fprintf( fp, "connid: %lu\n", conn->c_connid ); + } +} diff --git a/servers/slapd/back-sock/search.c b/servers/slapd/back-sock/search.c new file mode 100644 index 0000000..2a8279b --- /dev/null +++ b/servers/slapd/back-sock/search.c @@ -0,0 +1,74 @@ +/* search.c - sock backend search function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2007-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Brian Candler for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-sock.h" + +/* + * FIXME: add a filterSearchResults option like back-perl has + */ + +int +sock_back_search( + Operation *op, + SlapReply *rs ) +{ + struct sockinfo *si = (struct sockinfo *) op->o_bd->be_private; + FILE *fp; + AttributeName *an; + + if ( (fp = opensock( si->si_sockpath )) == NULL ) { + send_ldap_error( op, rs, LDAP_OTHER, + "could not open socket" ); + return( -1 ); + } + + /* write out the request to the search process */ + fprintf( fp, "SEARCH\n" ); + fprintf( fp, "msgid: %ld\n", (long) op->o_msgid ); + sock_print_conn( fp, op->o_conn, si ); + sock_print_suffixes( fp, op->o_bd ); + fprintf( fp, "base: %s\n", op->o_req_dn.bv_val ); + fprintf( fp, "scope: %d\n", op->oq_search.rs_scope ); + fprintf( fp, "deref: %d\n", op->oq_search.rs_deref ); + fprintf( fp, "sizelimit: %d\n", op->oq_search.rs_slimit ); + fprintf( fp, "timelimit: %d\n", op->oq_search.rs_tlimit ); + fprintf( fp, "filter: %s\n", op->oq_search.rs_filterstr.bv_val ); + fprintf( fp, "attrsonly: %d\n", op->oq_search.rs_attrsonly ? 1 : 0 ); + fprintf( fp, "attrs:%s", op->oq_search.rs_attrs == NULL ? " all" : "" ); + for ( an = op->oq_search.rs_attrs; an && an->an_name.bv_val; an++ ) { + fprintf( fp, " %s", an->an_name.bv_val ); + } + fprintf( fp, "\n\n" ); /* end of attr line plus blank line */ + + /* read in the results and send them along */ + rs->sr_attrs = op->oq_search.rs_attrs; + sock_read_and_send_results( op, rs, fp ); + + fclose( fp ); + return( 0 ); +} diff --git a/servers/slapd/back-sock/searchexample.conf b/servers/slapd/back-sock/searchexample.conf new file mode 100644 index 0000000..ac88c68 --- /dev/null +++ b/servers/slapd/back-sock/searchexample.conf @@ -0,0 +1,23 @@ +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 2007-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +## +## ACKNOWLEDGEMENTS: +## This work was initially developed by Brian Candler for inclusion +## in OpenLDAP Software. + +include /usr/local/etc/openldap/schema/core.schema + +database sock +suffix "dc=example,dc=com" +socketpath /tmp/example.sock diff --git a/servers/slapd/back-sock/searchexample.pl b/servers/slapd/back-sock/searchexample.pl new file mode 100644 index 0000000..a9b066c --- /dev/null +++ b/servers/slapd/back-sock/searchexample.pl @@ -0,0 +1,90 @@ +#!/usr/bin/perl -w -T +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 2007-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +## +## ACKNOWLEDGEMENTS: +## This work was initially developed by Brian Candler for inclusion +## in OpenLDAP Software. + +# See: http://search.cpan.org/dist/Net-Server/ + +package ExampleDB; + +use strict; +use vars qw(@ISA); +use Net::Server::PreFork; # any personality will do + +@ISA = qw(Net::Server::PreFork); + +ExampleDB->run( + port=>"/tmp/example.sock|unix" + #conf_file=>"/etc/example.conf" +); +exit; + +### over-ridden subs below +# The protocol is the same as back-shell + +sub process_request { + my $self = shift; + + eval { + + local $SIG{ALRM} = sub { die "Timed Out!\n" }; + my $timeout = 30; # give the user 30 seconds to type a line + alarm($timeout); + + my $request = <STDIN>; + + if ($request eq "SEARCH\n") { + my %req = (); + while (my $line = <STDIN>) { + chomp($line); + last if $line eq ""; + if ($line =~ /^([^:]+):\s*(.*)$/) { # FIXME: handle base64 encoded + $req{$1} = $2; + } + } + #sleep(2); # to test concurrency + print "dn: cn=test, dc=example, dc=com\n"; + print "cn: test\n"; + print "objectclass: cnobject\n"; + print "\n"; + print "RESULT\n"; + print "code: 0\n"; + print "info: answered by process $$\n"; + } + else { + print "RESULT\n"; + print "code: 53\n"; # unwillingToPerform + print "info: I don't implement $request"; + } + + }; + + return unless $@; + if( $@=~/timed out/i ){ + print "RESULT\n"; + print "code: 3\n"; # timeLimitExceeded + print "info: Timed out\n"; + } + else { + print "RESULT\n"; + print "code: 1\n"; # operationsError + print "info: $@\n"; # FIXME: remove CR/LF + } + +} + +1; diff --git a/servers/slapd/back-sock/unbind.c b/servers/slapd/back-sock/unbind.c new file mode 100644 index 0000000..5af29fc --- /dev/null +++ b/servers/slapd/back-sock/unbind.c @@ -0,0 +1,57 @@ +/* unbind.c - sock backend unbind function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2007-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Brian Candler for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-sock.h" + +int +sock_back_unbind( + Operation *op, + SlapReply *rs +) +{ + struct sockinfo *si = (struct sockinfo *) op->o_bd->be_private; + FILE *fp; + + if ( (fp = opensock( si->si_sockpath )) == NULL ) { + send_ldap_error( op, rs, LDAP_OTHER, + "could not open socket" ); + return( -1 ); + } + + /* write out the request to the unbind process */ + fprintf( fp, "UNBIND\n" ); + fprintf( fp, "msgid: %ld\n", (long) op->o_msgid ); + sock_print_conn( fp, op->o_conn, si ); + sock_print_suffixes( fp, op->o_bd ); + fprintf( fp, "\n" ); + + /* no response to unbind */ + fclose( fp ); + + return 0; +} diff --git a/servers/slapd/back-sql/Makefile.in b/servers/slapd/back-sql/Makefile.in new file mode 100644 index 0000000..2d5ae50 --- /dev/null +++ b/servers/slapd/back-sql/Makefile.in @@ -0,0 +1,45 @@ +# Makefile.in for back-sql +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +SRCS = init.c config.c search.c bind.c compare.c operational.c \ + entry-id.c schema-map.c sql-wrap.c modify.c util.c \ + add.c delete.c modrdn.c api.c +OBJS = init.lo config.lo search.lo bind.lo compare.lo operational.lo \ + entry-id.lo schema-map.lo sql-wrap.lo modify.lo util.lo \ + add.lo delete.lo modrdn.lo api.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-sql" +BUILD_MOD = @BUILD_SQL@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_SQL@_DEFS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_R_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) $(SLAPD_SQL_LIBS) + +LIBBASE = back_sql + +XINCPATH = -I.. -I$(srcdir)/.. $(SLAPD_SQL_INCLUDES) +XDEFS = $(MODULES_CPPFLAGS) + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ + diff --git a/servers/slapd/back-sql/add.c b/servers/slapd/back-sql/add.c new file mode 100644 index 0000000..1cfbd30 --- /dev/null +++ b/servers/slapd/back-sql/add.c @@ -0,0 +1,1544 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * Portions Copyright 2004 Mark Adamson. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati and Mark Adamson. + + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> +#include "ac/string.h" + +#include "slap.h" +#include "proto-sql.h" + +#ifdef BACKSQL_SYNCPROV +#include <lutil.h> +#endif /* BACKSQL_SYNCPROV */ + +/* + * Skip: + * - null values (e.g. delete modification) + * - single occurrence of objectClass, because it is already used + * to determine how to build the SQL entry + * - operational attributes + * - empty attributes + */ +#define backsql_opattr_skip(ad) \ + (is_at_operational( (ad)->ad_type ) && (ad) != slap_schema.si_ad_ref ) +#define backsql_attr_skip(ad, vals) \ + ( \ + ( (ad) == slap_schema.si_ad_objectClass \ + && (vals) && BER_BVISNULL( &((vals)[ 1 ]) ) ) \ + || backsql_opattr_skip( (ad) ) \ + || ( (vals) && BER_BVISNULL( &((vals)[ 0 ]) ) ) \ + ) + +int +backsql_modify_delete_all_values( + Operation *op, + SlapReply *rs, + SQLHDBC dbh, + backsql_entryID *e_id, + backsql_at_map_rec *at ) +{ + backsql_info *bi = (backsql_info *)op->o_bd->be_private; + RETCODE rc; + SQLHSTMT asth = SQL_NULL_HSTMT; + BACKSQL_ROW_NTS row; + + assert( at != NULL ); + if ( at->bam_delete_proc == NULL ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values(): " + "missing attribute value delete procedure " + "for attr \"%s\"\n", + at->bam_ad->ad_cname.bv_val, 0, 0 ); + if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) { + rs->sr_text = "SQL-backend error"; + return rs->sr_err = LDAP_OTHER; + } + + return LDAP_SUCCESS; + } + + rc = backsql_Prepare( dbh, &asth, at->bam_query, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values(): " + "error preparing attribute value select query " + "\"%s\"\n", + at->bam_query, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + asth, rc ); + + rs->sr_text = "SQL-backend error"; + return rs->sr_err = LDAP_OTHER; + } + + rc = backsql_BindParamID( asth, 1, SQL_PARAM_INPUT, &e_id->eid_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values(): " + "error binding key value parameter " + "to attribute value select query\n", + 0, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + asth, rc ); + SQLFreeStmt( asth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + return rs->sr_err = LDAP_OTHER; + } + + rc = SQLExecute( asth ); + if ( !BACKSQL_SUCCESS( rc ) ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values(): " + "error executing attribute value select query\n", + 0, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + asth, rc ); + SQLFreeStmt( asth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + return rs->sr_err = LDAP_OTHER; + } + + backsql_BindRowAsStrings_x( asth, &row, op->o_tmpmemctx ); + for ( rc = SQLFetch( asth ); + BACKSQL_SUCCESS( rc ); + rc = SQLFetch( asth ) ) + { + int i; + /* first parameter no, parameter order */ + SQLUSMALLINT pno = 0, + po = 0; + /* procedure return code */ + int prc = LDAP_SUCCESS; + + for ( i = 0; i < row.ncols; i++ ) { + SQLHSTMT sth = SQL_NULL_HSTMT; + ber_len_t col_len; + + rc = backsql_Prepare( dbh, &sth, at->bam_delete_proc, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values(): " + "error preparing attribute value " + "delete procedure " + "\"%s\"\n", + at->bam_delete_proc, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + goto done; + } + + if ( BACKSQL_IS_DEL( at->bam_expect_return ) ) { + pno = 1; + rc = backsql_BindParamInt( sth, 1, + SQL_PARAM_OUTPUT, &prc ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values(): " + "error binding output parameter for %s[%d]\n", + at->bam_ad->ad_cname.bv_val, i, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + goto done; + } + } + po = ( BACKSQL_IS_DEL( at->bam_param_order ) ) > 0; + rc = backsql_BindParamID( sth, pno + 1 + po, + SQL_PARAM_INPUT, &e_id->eid_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values(): " + "error binding keyval parameter for %s[%d]\n", + at->bam_ad->ad_cname.bv_val, i, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + goto done; + } + + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values() " + "arg(%d)=" BACKSQL_IDFMT "\n", + pno + 1 + po, + BACKSQL_IDARG(e_id->eid_keyval), 0 ); + + /* + * check for syntax needed here + * maybe need binary bind? + */ + col_len = strlen( row.cols[ i ] ); + rc = backsql_BindParamStr( sth, pno + 2 - po, + SQL_PARAM_INPUT, row.cols[ i ], col_len ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values(): " + "error binding value parameter for %s[%d]\n", + at->bam_ad->ad_cname.bv_val, i, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + goto done; + } + + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values(): " + "arg(%d)=%s; executing \"%s\"\n", + pno + 2 - po, row.cols[ i ], + at->bam_delete_proc ); + rc = SQLExecute( sth ); + if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS ) { + rs->sr_err = LDAP_SUCCESS; + + } else { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values(): " + "delete_proc " + "execution failed (rc=%d, prc=%d)\n", + rc, prc, 0 ); + if ( prc != LDAP_SUCCESS ) { + /* SQL procedure executed fine + * but returned an error */ + rs->sr_err = BACKSQL_SANITIZE_ERROR( prc ); + + } else { + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + rs->sr_err = LDAP_OTHER; + } + rs->sr_text = op->o_req_dn.bv_val; + SQLFreeStmt( sth, SQL_DROP ); + goto done; + } + SQLFreeStmt( sth, SQL_DROP ); + } + } + + rs->sr_err = LDAP_SUCCESS; + +done:; + backsql_FreeRow_x( &row, op->o_tmpmemctx ); + SQLFreeStmt( asth, SQL_DROP ); + + return rs->sr_err; +} + +int +backsql_modify_internal( + Operation *op, + SlapReply *rs, + SQLHDBC dbh, + backsql_oc_map_rec *oc, + backsql_entryID *e_id, + Modifications *modlist ) +{ + backsql_info *bi = (backsql_info *)op->o_bd->be_private; + RETCODE rc; + Modifications *ml; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_modify_internal(): " + "traversing modifications list\n", 0, 0, 0 ); + + for ( ml = modlist; ml != NULL; ml = ml->sml_next ) { + AttributeDescription *ad; + int sm_op; + static char *sm_ops[] = { "add", "delete", "replace", "increment", NULL }; + + BerVarray sm_values; +#if 0 + /* NOTE: some day we'll have to pass + * the normalized values as well */ + BerVarray sm_nvalues; +#endif + backsql_at_map_rec *at = NULL; + struct berval *at_val; + int i; + + ad = ml->sml_mod.sm_desc; + sm_op = ( ml->sml_mod.sm_op & LDAP_MOD_OP ); + sm_values = ml->sml_mod.sm_values; +#if 0 + sm_nvalues = ml->sml_mod.sm_nvalues; +#endif + + Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): " + "modifying attribute \"%s\" (%s) according to " + "mappings for objectClass \"%s\"\n", + ad->ad_cname.bv_val, sm_ops[ sm_op ], BACKSQL_OC_NAME( oc ) ); + + if ( backsql_attr_skip( ad, sm_values ) ) { + continue; + } + + at = backsql_ad2at( oc, ad ); + if ( at == NULL ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): " + "attribute \"%s\" is not registered " + "in objectClass \"%s\"\n", + ad->ad_cname.bv_val, BACKSQL_OC_NAME( oc ), 0 ); + + if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "operation not permitted " + "within namingContext"; + goto done; + } + + continue; + } + + switch ( sm_op ) { + case LDAP_MOD_REPLACE: { + Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): " + "replacing values for attribute \"%s\"\n", + at->bam_ad->ad_cname.bv_val, 0, 0 ); + + if ( at->bam_add_proc == NULL ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "add procedure is not defined " + "for attribute \"%s\" " + "- unable to perform replacements\n", + at->bam_ad->ad_cname.bv_val, 0, 0 ); + + if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "operation not permitted " + "within namingContext"; + goto done; + } + + break; + } + + if ( at->bam_delete_proc == NULL ) { + if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "delete procedure is not defined " + "for attribute \"%s\"\n", + at->bam_ad->ad_cname.bv_val, 0, 0 ); + + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "operation not permitted " + "within namingContext"; + goto done; + } + + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "delete procedure is not defined " + "for attribute \"%s\" " + "- adding only\n", + at->bam_ad->ad_cname.bv_val, 0, 0 ); + + goto add_only; + } + +del_all: + rs->sr_err = backsql_modify_delete_all_values( op, rs, dbh, e_id, at ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto done; + } + + /* LDAP_MOD_DELETE gets here if all values must be deleted */ + if ( sm_op == LDAP_MOD_DELETE ) { + break; + } + } + + /* + * PASSTHROUGH - to add new attributes -- do NOT add break + */ + case LDAP_MOD_ADD: + /* case SLAP_MOD_SOFTADD: */ + /* case SLAP_MOD_ADD_IF_NOT_PRESENT: */ +add_only:; + if ( at->bam_add_proc == NULL ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "add procedure is not defined " + "for attribute \"%s\"\n", + at->bam_ad->ad_cname.bv_val, 0, 0 ); + + if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "operation not permitted " + "within namingContext"; + goto done; + } + + break; + } + + Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): " + "adding new values for attribute \"%s\"\n", + at->bam_ad->ad_cname.bv_val, 0, 0 ); + + /* can't add a NULL val array */ + assert( sm_values != NULL ); + + for ( i = 0, at_val = sm_values; + !BER_BVISNULL( at_val ); + i++, at_val++ ) + { + SQLHSTMT sth = SQL_NULL_HSTMT; + /* first parameter position, parameter order */ + SQLUSMALLINT pno = 0, + po; + /* procedure return code */ + int prc = LDAP_SUCCESS; + + rc = backsql_Prepare( dbh, &sth, at->bam_add_proc, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "error preparing add query\n", + 0, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + goto done; + } + + if ( BACKSQL_IS_ADD( at->bam_expect_return ) ) { + pno = 1; + rc = backsql_BindParamInt( sth, 1, + SQL_PARAM_OUTPUT, &prc ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "error binding output parameter for %s[%d]\n", + at->bam_ad->ad_cname.bv_val, i, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + goto done; + } + } + po = ( BACKSQL_IS_ADD( at->bam_param_order ) ) > 0; + rc = backsql_BindParamID( sth, pno + 1 + po, + SQL_PARAM_INPUT, &e_id->eid_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "error binding keyval parameter for %s[%d]\n", + at->bam_ad->ad_cname.bv_val, i, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + goto done; + } + + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "arg(%d)=" BACKSQL_IDFMT "\n", + pno + 1 + po, + BACKSQL_IDARG(e_id->eid_keyval), 0 ); + + /* + * check for syntax needed here + * maybe need binary bind? + */ + rc = backsql_BindParamBerVal( sth, pno + 2 - po, + SQL_PARAM_INPUT, at_val ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "error binding value parameter for %s[%d]\n", + at->bam_ad->ad_cname.bv_val, i, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + goto done; + } + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "arg(%d)=\"%s\"; executing \"%s\"\n", + pno + 2 - po, at_val->bv_val, + at->bam_add_proc ); + + rc = SQLExecute( sth ); + if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS ) { + rs->sr_err = LDAP_SUCCESS; + + } else { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "add_proc execution failed " + "(rc=%d, prc=%d)\n", + rc, prc, 0 ); + if ( prc != LDAP_SUCCESS ) { + /* SQL procedure executed fine + * but returned an error */ + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_err = BACKSQL_SANITIZE_ERROR( prc ); + rs->sr_text = at->bam_ad->ad_cname.bv_val; + return rs->sr_err; + + } else { + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) + { + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + goto done; + } + } + } + SQLFreeStmt( sth, SQL_DROP ); + } + break; + + case LDAP_MOD_DELETE: + /* case SLAP_MOD_SOFTDEL: */ + if ( at->bam_delete_proc == NULL ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "delete procedure is not defined " + "for attribute \"%s\"\n", + at->bam_ad->ad_cname.bv_val, 0, 0 ); + + if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "operation not permitted " + "within namingContext"; + goto done; + } + + break; + } + + if ( sm_values == NULL ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "no values given to delete " + "for attribute \"%s\" " + "-- deleting all values\n", + at->bam_ad->ad_cname.bv_val, 0, 0 ); + goto del_all; + } + + Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): " + "deleting values for attribute \"%s\"\n", + at->bam_ad->ad_cname.bv_val, 0, 0 ); + + for ( i = 0, at_val = sm_values; + !BER_BVISNULL( at_val ); + i++, at_val++ ) + { + SQLHSTMT sth = SQL_NULL_HSTMT; + /* first parameter position, parameter order */ + SQLUSMALLINT pno = 0, + po; + /* procedure return code */ + int prc = LDAP_SUCCESS; + + rc = backsql_Prepare( dbh, &sth, at->bam_delete_proc, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "error preparing delete query\n", + 0, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + goto done; + } + + if ( BACKSQL_IS_DEL( at->bam_expect_return ) ) { + pno = 1; + rc = backsql_BindParamInt( sth, 1, + SQL_PARAM_OUTPUT, &prc ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "error binding output parameter for %s[%d]\n", + at->bam_ad->ad_cname.bv_val, i, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + goto done; + } + } + po = ( BACKSQL_IS_DEL( at->bam_param_order ) ) > 0; + rc = backsql_BindParamID( sth, pno + 1 + po, + SQL_PARAM_INPUT, &e_id->eid_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "error binding keyval parameter for %s[%d]\n", + at->bam_ad->ad_cname.bv_val, i, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + goto done; + } + + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "arg(%d)=" BACKSQL_IDFMT "\n", + pno + 1 + po, + BACKSQL_IDARG(e_id->eid_keyval), 0 ); + + /* + * check for syntax needed here + * maybe need binary bind? + */ + rc = backsql_BindParamBerVal( sth, pno + 2 - po, + SQL_PARAM_INPUT, at_val ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "error binding value parameter for %s[%d]\n", + at->bam_ad->ad_cname.bv_val, i, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + goto done; + } + + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "executing \"%s\"\n", + at->bam_delete_proc, 0, 0 ); + rc = SQLExecute( sth ); + if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS ) + { + rs->sr_err = LDAP_SUCCESS; + + } else { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "delete_proc execution " + "failed (rc=%d, prc=%d)\n", + rc, prc, 0 ); + + if ( prc != LDAP_SUCCESS ) { + /* SQL procedure executed fine + * but returned an error */ + rs->sr_err = BACKSQL_SANITIZE_ERROR( prc ); + rs->sr_text = at->bam_ad->ad_cname.bv_val; + goto done; + + } else { + backsql_PrintErrors( bi->sql_db_env, + dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = at->bam_ad->ad_cname.bv_val; + goto done; + } + } + SQLFreeStmt( sth, SQL_DROP ); + } + break; + + case LDAP_MOD_INCREMENT: + Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): " + "increment not supported yet\n", 0, 0, 0 ); + if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + goto done; + } + break; + } + } + +done:; + Debug( LDAP_DEBUG_TRACE, "<==backsql_modify_internal(): %d%s%s\n", + rs->sr_err, + rs->sr_text ? ": " : "", + rs->sr_text ? rs->sr_text : "" ); + + /* + * FIXME: should fail in case one change fails? + */ + return rs->sr_err; +} + +static int +backsql_add_attr( + Operation *op, + SlapReply *rs, + SQLHDBC dbh, + backsql_oc_map_rec *oc, + Attribute *at, + backsql_key_t new_keyval ) +{ + backsql_info *bi = (backsql_info*)op->o_bd->be_private; + backsql_at_map_rec *at_rec = NULL; + struct berval *at_val; + unsigned long i; + RETCODE rc; + SQLUSMALLINT currpos; + SQLHSTMT sth = SQL_NULL_HSTMT; + + at_rec = backsql_ad2at( oc, at->a_desc ); + + if ( at_rec == NULL ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add_attr(\"%s\"): " + "attribute \"%s\" is not registered " + "in objectclass \"%s\"\n", + op->ora_e->e_name.bv_val, + at->a_desc->ad_cname.bv_val, + BACKSQL_OC_NAME( oc ) ); + + if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) { + rs->sr_text = "operation not permitted " + "within namingContext"; + return rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + } + + return LDAP_SUCCESS; + } + + if ( at_rec->bam_add_proc == NULL ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add_attr(\"%s\"): " + "add procedure is not defined " + "for attribute \"%s\" " + "of structuralObjectClass \"%s\"\n", + op->ora_e->e_name.bv_val, + at->a_desc->ad_cname.bv_val, + BACKSQL_OC_NAME( oc ) ); + + if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) { + rs->sr_text = "operation not permitted " + "within namingContext"; + return rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + } + + return LDAP_SUCCESS; + } + + for ( i = 0, at_val = &at->a_vals[ i ]; + !BER_BVISNULL( at_val ); + i++, at_val = &at->a_vals[ i ] ) + { + /* procedure return code */ + int prc = LDAP_SUCCESS; + /* first parameter #, parameter order */ + SQLUSMALLINT pno, po; + char logbuf[ STRLENOF("val[], id=") + 2*LDAP_PVT_INTTYPE_CHARS(unsigned long)]; + + /* + * Do not deal with the objectClass that is used + * to build the entry + */ + if ( at->a_desc == slap_schema.si_ad_objectClass ) { + if ( dn_match( at_val, &oc->bom_oc->soc_cname ) ) + { + continue; + } + } + + rc = backsql_Prepare( dbh, &sth, at_rec->bam_add_proc, 0 ); + if ( rc != SQL_SUCCESS ) { + rs->sr_text = "SQL-backend error"; + return rs->sr_err = LDAP_OTHER; + } + + if ( BACKSQL_IS_ADD( at_rec->bam_expect_return ) ) { + pno = 1; + rc = backsql_BindParamInt( sth, 1, SQL_PARAM_OUTPUT, &prc ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_add_attr(): " + "error binding output parameter for %s[%lu]\n", + at_rec->bam_ad->ad_cname.bv_val, i, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + return rs->sr_err = LDAP_OTHER; + } + + } else { + pno = 0; + } + + po = ( BACKSQL_IS_ADD( at_rec->bam_param_order ) ) > 0; + currpos = pno + 1 + po; + rc = backsql_BindParamNumID( sth, currpos, + SQL_PARAM_INPUT, &new_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_add_attr(): " + "error binding keyval parameter for %s[%lu]\n", + at_rec->bam_ad->ad_cname.bv_val, i, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + return rs->sr_err = LDAP_OTHER; + } + + currpos = pno + 2 - po; + + /* + * check for syntax needed here + * maybe need binary bind? + */ + + rc = backsql_BindParamBerVal( sth, currpos, SQL_PARAM_INPUT, at_val ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_add_attr(): " + "error binding value parameter for %s[%lu]\n", + at_rec->bam_ad->ad_cname.bv_val, i, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + return rs->sr_err = LDAP_OTHER; + } + +#ifdef LDAP_DEBUG + if ( LogTest( LDAP_DEBUG_TRACE ) ) { + snprintf( logbuf, sizeof( logbuf ), "val[%lu], id=" BACKSQL_IDNUMFMT, + i, new_keyval ); + Debug( LDAP_DEBUG_TRACE, " backsql_add_attr(\"%s\"): " + "executing \"%s\" %s\n", + op->ora_e->e_name.bv_val, + at_rec->bam_add_proc, logbuf ); + } +#endif + rc = SQLExecute( sth ); + if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS ) { + rs->sr_err = LDAP_SUCCESS; + + } else { + Debug( LDAP_DEBUG_TRACE, + " backsql_add_attr(\"%s\"): " + "add_proc execution failed (rc=%d, prc=%d)\n", + op->ora_e->e_name.bv_val, rc, prc ); + if ( prc != LDAP_SUCCESS ) { + /* SQL procedure executed fine + * but returned an error */ + rs->sr_err = BACKSQL_SANITIZE_ERROR( prc ); + rs->sr_text = op->ora_e->e_name.bv_val; + SQLFreeStmt( sth, SQL_DROP ); + return rs->sr_err; + + } else { + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = op->ora_e->e_name.bv_val; + SQLFreeStmt( sth, SQL_DROP ); + return rs->sr_err; + } + } + SQLFreeStmt( sth, SQL_DROP ); + } + + return LDAP_SUCCESS; +} + +int +backsql_add( Operation *op, SlapReply *rs ) +{ + backsql_info *bi = (backsql_info*)op->o_bd->be_private; + SQLHDBC dbh = SQL_NULL_HDBC; + SQLHSTMT sth = SQL_NULL_HSTMT; + backsql_key_t new_keyval = 0; + RETCODE rc; + backsql_oc_map_rec *oc = NULL; + backsql_srch_info bsi = { 0 }; + Entry p = { 0 }, *e = NULL; + Attribute *at, + *at_objectClass = NULL; + ObjectClass *soc = NULL; + struct berval scname = BER_BVNULL; + struct berval pdn; + struct berval realdn = BER_BVNULL; + int colnum; + slap_mask_t mask; + + char textbuf[ SLAP_TEXT_BUFLEN ]; + size_t textlen = sizeof( textbuf ); + +#ifdef BACKSQL_SYNCPROV + /* + * NOTE: fake successful result to force contextCSN to be bumped up + */ + if ( op->o_sync ) { + char buf[ LDAP_PVT_CSNSTR_BUFSIZE ]; + struct berval csn; + + csn.bv_val = buf; + csn.bv_len = sizeof( buf ); + slap_get_csn( op, &csn, 1 ); + + rs->sr_err = LDAP_SUCCESS; + send_ldap_result( op, rs ); + + slap_graduate_commit_csn( op ); + + return 0; + } +#endif /* BACKSQL_SYNCPROV */ + + Debug( LDAP_DEBUG_TRACE, "==>backsql_add(\"%s\")\n", + op->ora_e->e_name.bv_val, 0, 0 ); + + /* check schema */ + if ( BACKSQL_CHECK_SCHEMA( bi ) ) { + char textbuf[ SLAP_TEXT_BUFLEN ] = { '\0' }; + + rs->sr_err = entry_schema_check( op, op->ora_e, NULL, 0, 1, NULL, + &rs->sr_text, textbuf, sizeof( textbuf ) ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "entry failed schema check -- aborting\n", + op->ora_e->e_name.bv_val, 0, 0 ); + e = NULL; + goto done; + } + } + + slap_add_opattrs( op, &rs->sr_text, textbuf, textlen, 1 ); + + if ( get_assert( op ) && + ( test_filter( op, op->ora_e, get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "assertion control failed -- aborting\n", + op->ora_e->e_name.bv_val, 0, 0 ); + e = NULL; + rs->sr_err = LDAP_ASSERTION_FAILED; + goto done; + } + + /* search structuralObjectClass */ + for ( at = op->ora_e->e_attrs; at != NULL; at = at->a_next ) { + if ( at->a_desc == slap_schema.si_ad_structuralObjectClass ) { + break; + } + } + + /* there must exist */ + if ( at == NULL ) { + char buf[ SLAP_TEXT_BUFLEN ]; + const char *text; + + /* search structuralObjectClass */ + for ( at = op->ora_e->e_attrs; at != NULL; at = at->a_next ) { + if ( at->a_desc == slap_schema.si_ad_objectClass ) { + break; + } + } + + if ( at == NULL ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "no objectClass\n", + op->ora_e->e_name.bv_val, 0, 0 ); + rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; + e = NULL; + goto done; + } + + rs->sr_err = structural_class( at->a_vals, &soc, NULL, + &text, buf, sizeof( buf ), op->o_tmpmemctx ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "%s (%d)\n", + op->ora_e->e_name.bv_val, text, rs->sr_err ); + e = NULL; + goto done; + } + scname = soc->soc_cname; + + } else { + scname = at->a_vals[0]; + } + + /* I guess we should play with sub/supertypes to find a suitable oc */ + oc = backsql_name2oc( bi, &scname ); + + if ( oc == NULL ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "cannot map structuralObjectClass \"%s\" -- aborting\n", + op->ora_e->e_name.bv_val, + scname.bv_val, 0 ); + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "operation not permitted within namingContext"; + e = NULL; + goto done; + } + + if ( oc->bom_create_proc == NULL ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "create procedure is not defined " + "for structuralObjectClass \"%s\" - aborting\n", + op->ora_e->e_name.bv_val, + scname.bv_val, 0 ); + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "operation not permitted within namingContext"; + e = NULL; + goto done; + + } else if ( BACKSQL_CREATE_NEEDS_SELECT( bi ) + && oc->bom_create_keyval == NULL ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "create procedure needs select procedure, " + "but none is defined for structuralObjectClass \"%s\" " + "- aborting\n", + op->ora_e->e_name.bv_val, + scname.bv_val, 0 ); + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "operation not permitted within namingContext"; + e = NULL; + goto done; + } + + /* check write access */ + if ( !access_allowed_mask( op, op->ora_e, + slap_schema.si_ad_entry, + NULL, ACL_WADD, NULL, &mask ) ) + { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + e = op->ora_e; + goto done; + } + + rs->sr_err = backsql_get_db_conn( op, &dbh ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "could not get connection handle - exiting\n", + op->ora_e->e_name.bv_val, 0, 0 ); + rs->sr_text = ( rs->sr_err == LDAP_OTHER ) + ? "SQL-backend error" : NULL; + e = NULL; + goto done; + } + + /* + * Check if entry exists + * + * NOTE: backsql_api_dn2odbc() is called explicitly because + * we need the mucked DN to pass it to the create procedure. + */ + realdn = op->ora_e->e_name; + if ( backsql_api_dn2odbc( op, rs, &realdn ) ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "backsql_api_dn2odbc(\"%s\") failed\n", + op->ora_e->e_name.bv_val, realdn.bv_val, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + } + + rs->sr_err = backsql_dn2id( op, rs, dbh, &realdn, NULL, 0, 0 ); + if ( rs->sr_err == LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "entry exists\n", + op->ora_e->e_name.bv_val, 0, 0 ); + rs->sr_err = LDAP_ALREADY_EXISTS; + e = op->ora_e; + goto done; + } + + /* + * Get the parent dn and see if the corresponding entry exists. + */ + if ( be_issuffix( op->o_bd, &op->ora_e->e_nname ) ) { + pdn = slap_empty_bv; + + } else { + dnParent( &op->ora_e->e_nname, &pdn ); + + /* + * Get the parent + */ + bsi.bsi_e = &p; + rs->sr_err = backsql_init_search( &bsi, &pdn, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, slap_anlist_no_attrs, + ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY ) ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_add(): " + "could not retrieve addDN parent " + "\"%s\" ID - %s matched=\"%s\"\n", + pdn.bv_val, + rs->sr_err == LDAP_REFERRAL ? "referral" : "no such entry", + rs->sr_matched ? rs->sr_matched : "(null)" ); + e = &p; + goto done; + } + + /* check "children" pseudo-attribute access to parent */ + if ( !access_allowed( op, &p, slap_schema.si_ad_children, + NULL, ACL_WADD, NULL ) ) + { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + e = &p; + goto done; + } + } + + /* + * create_proc is executed; if expect_return is set, then + * an output parameter is bound, which should contain + * the id of the added row; otherwise the procedure + * is expected to return the id as the first column of a select + */ + rc = backsql_Prepare( dbh, &sth, oc->bom_create_proc, 0 ); + if ( rc != SQL_SUCCESS ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + } + + colnum = 1; + if ( BACKSQL_IS_ADD( oc->bom_expect_return ) ) { + rc = backsql_BindParamNumID( sth, 1, SQL_PARAM_OUTPUT, &new_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "error binding keyval parameter " + "for objectClass %s\n", + op->ora_e->e_name.bv_val, + oc->bom_oc->soc_cname.bv_val, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + colnum++; + } + + if ( oc->bom_create_hint ) { + at = attr_find( op->ora_e->e_attrs, oc->bom_create_hint ); + if ( at && at->a_vals ) { + backsql_BindParamStr( sth, colnum, SQL_PARAM_INPUT, + at->a_vals[0].bv_val, + at->a_vals[0].bv_len ); + Debug( LDAP_DEBUG_TRACE, "backsql_add(): " + "create_proc hint: param = '%s'\n", + at->a_vals[0].bv_val, 0, 0 ); + + } else { + backsql_BindParamStr( sth, colnum, SQL_PARAM_INPUT, + "", 0 ); + Debug( LDAP_DEBUG_TRACE, "backsql_add(): " + "create_proc hint (%s) not avalable\n", + oc->bom_create_hint->ad_cname.bv_val, + 0, 0 ); + } + colnum++; + } + + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): executing \"%s\"\n", + op->ora_e->e_name.bv_val, oc->bom_create_proc, 0 ); + rc = SQLExecute( sth ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "create_proc execution failed\n", + op->ora_e->e_name.bv_val, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc); + SQLFreeStmt( sth, SQL_DROP ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + } + + /* FIXME: after SQLExecute(), the row is already inserted + * (at least with PostgreSQL and unixODBC); needs investigation */ + + if ( !BACKSQL_IS_ADD( oc->bom_expect_return ) ) { + SWORD ncols; + SQLLEN value_len; + + if ( BACKSQL_CREATE_NEEDS_SELECT( bi ) ) { + SQLFreeStmt( sth, SQL_DROP ); + + rc = backsql_Prepare( dbh, &sth, oc->bom_create_keyval, 0 ); + if ( rc != SQL_SUCCESS ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + } + + rc = SQLExecute( sth ); + if ( rc != SQL_SUCCESS ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + } + } + + /* + * the query to know the id of the inserted entry + * must be embedded in the create procedure + */ + rc = SQLNumResultCols( sth, &ncols ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "create_proc result evaluation failed\n", + op->ora_e->e_name.bv_val, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc); + SQLFreeStmt( sth, SQL_DROP ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + + } else if ( ncols != 1 ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "create_proc result is bogus (ncols=%d)\n", + op->ora_e->e_name.bv_val, ncols, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc); + SQLFreeStmt( sth, SQL_DROP ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + } + +#if 0 + { + SQLCHAR colname[ 64 ]; + SQLSMALLINT name_len, col_type, col_scale, col_null; + UDWORD col_prec; + + /* + * FIXME: check whether col_type is compatible, + * if it can be null and so on ... + */ + rc = SQLDescribeCol( sth, (SQLUSMALLINT)1, + &colname[ 0 ], + (SQLUINTEGER)( sizeof( colname ) - 1 ), + &name_len, &col_type, + &col_prec, &col_scale, &col_null ); + } +#endif + + rc = SQLBindCol( sth, (SQLUSMALLINT)1, SQL_C_ULONG, + (SQLPOINTER)&new_keyval, + (SQLINTEGER)sizeof( new_keyval ), + &value_len ); + + rc = SQLFetch( sth ); + + if ( value_len <= 0 ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "create_proc result is empty?\n", + op->ora_e->e_name.bv_val, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc); + SQLFreeStmt( sth, SQL_DROP ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + } + } + + SQLFreeStmt( sth, SQL_DROP ); + + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "create_proc returned keyval=" BACKSQL_IDNUMFMT "\n", + op->ora_e->e_name.bv_val, new_keyval, 0 ); + + rc = backsql_Prepare( dbh, &sth, bi->sql_insentry_stmt, 0 ); + if ( rc != SQL_SUCCESS ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + } + + rc = backsql_BindParamBerVal( sth, 1, SQL_PARAM_INPUT, &realdn ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "error binding DN parameter for objectClass %s\n", + op->ora_e->e_name.bv_val, + oc->bom_oc->soc_cname.bv_val, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + + rc = backsql_BindParamNumID( sth, 2, SQL_PARAM_INPUT, &oc->bom_id ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "error binding objectClass ID parameter " + "for objectClass %s\n", + op->ora_e->e_name.bv_val, + oc->bom_oc->soc_cname.bv_val, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + + rc = backsql_BindParamID( sth, 3, SQL_PARAM_INPUT, &bsi.bsi_base_id.eid_id ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "error binding parent ID parameter " + "for objectClass %s\n", + op->ora_e->e_name.bv_val, + oc->bom_oc->soc_cname.bv_val, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + + rc = backsql_BindParamNumID( sth, 4, SQL_PARAM_INPUT, &new_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "error binding entry ID parameter " + "for objectClass %s\n", + op->ora_e->e_name.bv_val, + oc->bom_oc->soc_cname.bv_val, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + + if ( LogTest( LDAP_DEBUG_TRACE ) ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof(buf), + "executing \"%s\" for dn=\"%s\" oc_map_id=" BACKSQL_IDNUMFMT " p_id=" BACKSQL_IDFMT " keyval=" BACKSQL_IDNUMFMT, + bi->sql_insentry_stmt, op->ora_e->e_name.bv_val, + oc->bom_id, BACKSQL_IDARG(bsi.bsi_base_id.eid_id), + new_keyval ); + Debug( LDAP_DEBUG_TRACE, " backsql_add(): %s\n", buf, 0, 0 ); + } + + rc = SQLExecute( sth ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "could not insert ldap_entries record\n", + op->ora_e->e_name.bv_val, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + + /* + * execute delete_proc to delete data added !!! + */ + SQLFreeStmt( sth, SQL_DROP ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + } + + SQLFreeStmt( sth, SQL_DROP ); + + for ( at = op->ora_e->e_attrs; at != NULL; at = at->a_next ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(): " + "adding attribute \"%s\"\n", + at->a_desc->ad_cname.bv_val, 0, 0 ); + + /* + * Skip: + * - the first occurrence of objectClass, which is used + * to determine how to build the SQL entry (FIXME ?!?) + * - operational attributes + * - empty attributes (FIXME ?!?) + */ + if ( backsql_attr_skip( at->a_desc, at->a_vals ) ) { + continue; + } + + if ( at->a_desc == slap_schema.si_ad_objectClass ) { + at_objectClass = at; + continue; + } + + rs->sr_err = backsql_add_attr( op, rs, dbh, oc, at, new_keyval ); + if ( rs->sr_err != LDAP_SUCCESS ) { + e = op->ora_e; + goto done; + } + } + + if ( at_objectClass ) { + rs->sr_err = backsql_add_attr( op, rs, dbh, oc, + at_objectClass, new_keyval ); + if ( rs->sr_err != LDAP_SUCCESS ) { + e = op->ora_e; + goto done; + } + } + +done:; + /* + * Commit only if all operations succeed + */ + if ( sth != SQL_NULL_HSTMT ) { + SQLUSMALLINT CompletionType = SQL_ROLLBACK; + + if ( rs->sr_err == LDAP_SUCCESS && !op->o_noop ) { + assert( e == NULL ); + CompletionType = SQL_COMMIT; + } + + SQLTransact( SQL_NULL_HENV, dbh, CompletionType ); + } + + /* + * FIXME: NOOP does not work for add -- it works for all + * the other operations, and I don't get the reason :( + * + * hint: there might be some autocommit in Postgres + * so that when the unique id of the key table is + * automatically increased, there's no rollback. + * We might implement a "rollback" procedure consisting + * in deleting that row. + */ + + if ( e != NULL ) { + int disclose = 1; + + if ( e == op->ora_e && !ACL_GRANT( mask, ACL_DISCLOSE ) ) { + /* mask already collected */ + disclose = 0; + + } else if ( e == &p && !access_allowed( op, &p, + slap_schema.si_ad_entry, NULL, + ACL_DISCLOSE, NULL ) ) + { + disclose = 0; + } + + if ( disclose == 0 ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + } + } + + if ( op->o_noop && rs->sr_err == LDAP_SUCCESS ) { + rs->sr_err = LDAP_X_NO_OPERATION; + } + + send_ldap_result( op, rs ); + slap_graduate_commit_csn( op ); + + if ( !BER_BVISNULL( &realdn ) + && realdn.bv_val != op->ora_e->e_name.bv_val ) + { + ch_free( realdn.bv_val ); + } + + if ( !BER_BVISNULL( &bsi.bsi_base_id.eid_ndn ) ) { + (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &p.e_nname ) ) { + backsql_entry_clean( op, &p ); + } + + Debug( LDAP_DEBUG_TRACE, "<==backsql_add(\"%s\"): %d \"%s\"\n", + op->ora_e->e_name.bv_val, + rs->sr_err, + rs->sr_text ? rs->sr_text : "" ); + + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + + return rs->sr_err; +} + diff --git a/servers/slapd/back-sql/api.c b/servers/slapd/back-sql/api.c new file mode 100644 index 0000000..bbce296 --- /dev/null +++ b/servers/slapd/back-sql/api.c @@ -0,0 +1,211 @@ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2004 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> +#include "ac/string.h" + +#include "slap.h" +#include "proto-sql.h" + +static backsql_api *backsqlapi; + +int +backsql_api_config( backsql_info *bi, const char *name, int argc, char *argv[] ) +{ + backsql_api *ba; + + assert( bi != NULL ); + assert( name != NULL ); + + for ( ba = backsqlapi; ba; ba = ba->ba_next ) { + if ( strcasecmp( name, ba->ba_name ) == 0 ) { + backsql_api *ba2; + + ba2 = ch_malloc( sizeof( backsql_api ) ); + *ba2 = *ba; + + if ( ba2->ba_config ) { + if ( ( *ba2->ba_config )( ba2, argc, argv ) ) { + ch_free( ba2 ); + return 1; + } + ba2->ba_argc = argc; + if ( argc ) { + int i; + ba2->ba_argv = ch_malloc( argc * sizeof(char *)); + for ( i=0; i<argc; i++ ) + ba2->ba_argv[i] = ch_strdup( argv[i] ); + } + } + + ba2->ba_next = bi->sql_api; + bi->sql_api = ba2; + return 0; + } + } + + return 1; +} + +int +backsql_api_destroy( backsql_info *bi ) +{ + backsql_api *ba; + + assert( bi != NULL ); + + ba = bi->sql_api; + + if ( ba == NULL ) { + return 0; + } + + for ( ; ba; ba = ba->ba_next ) { + if ( ba->ba_destroy ) { + (void)( *ba->ba_destroy )( ba ); + } + } + + return 0; +} + +int +backsql_api_register( backsql_api *ba ) +{ + backsql_api *ba2; + + assert( ba != NULL ); + assert( ba->ba_private == NULL ); + + if ( ba->ba_name == NULL ) { + fprintf( stderr, "API module has no name\n" ); + exit(EXIT_FAILURE); + } + + for ( ba2 = backsqlapi; ba2; ba2 = ba2->ba_next ) { + if ( strcasecmp( ba->ba_name, ba2->ba_name ) == 0 ) { + fprintf( stderr, "API module \"%s\" already defined\n", ba->ba_name ); + exit( EXIT_FAILURE ); + } + } + + ba->ba_next = backsqlapi; + backsqlapi = ba; + + return 0; +} + +int +backsql_api_dn2odbc( Operation *op, SlapReply *rs, struct berval *dn ) +{ + backsql_info *bi = (backsql_info *)op->o_bd->be_private; + backsql_api *ba; + int rc; + struct berval bv; + + ba = bi->sql_api; + + if ( ba == NULL ) { + return 0; + } + + ber_dupbv( &bv, dn ); + + for ( ; ba; ba = ba->ba_next ) { + if ( ba->ba_dn2odbc ) { + /* + * The dn2odbc() helper is supposed to rewrite + * the contents of bv, freeing the original value + * with ch_free() if required and replacing it + * with a newly allocated one using ch_malloc() + * or companion functions. + * + * NOTE: it is supposed to __always__ free + * the value of bv in case of error, and reset + * it with BER_BVZERO() . + */ + rc = ( *ba->ba_dn2odbc )( op, rs, &bv ); + + if ( rc ) { + /* in case of error, dn2odbc() must cleanup */ + assert( BER_BVISNULL( &bv ) ); + + return rc; + } + } + } + + assert( !BER_BVISNULL( &bv ) ); + + *dn = bv; + + return 0; +} + +int +backsql_api_odbc2dn( Operation *op, SlapReply *rs, struct berval *dn ) +{ + backsql_info *bi = (backsql_info *)op->o_bd->be_private; + backsql_api *ba; + int rc; + struct berval bv; + + ba = bi->sql_api; + + if ( ba == NULL ) { + return 0; + } + + ber_dupbv( &bv, dn ); + + for ( ; ba; ba = ba->ba_next ) { + if ( ba->ba_dn2odbc ) { + rc = ( *ba->ba_odbc2dn )( op, rs, &bv ); + /* + * The odbc2dn() helper is supposed to rewrite + * the contents of bv, freeing the original value + * with ch_free() if required and replacing it + * with a newly allocated one using ch_malloc() + * or companion functions. + * + * NOTE: it is supposed to __always__ free + * the value of bv in case of error, and reset + * it with BER_BVZERO() . + */ + if ( rc ) { + /* in case of error, odbc2dn() must cleanup */ + assert( BER_BVISNULL( &bv ) ); + + return rc; + } + } + } + + assert( !BER_BVISNULL( &bv ) ); + + *dn = bv; + + return 0; +} + diff --git a/servers/slapd/back-sql/back-sql.h b/servers/slapd/back-sql/back-sql.h new file mode 100644 index 0000000..b7f53ab --- /dev/null +++ b/servers/slapd/back-sql/back-sql.h @@ -0,0 +1,631 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Mararati. + * Portions Copyright 2004 Mark Adamson. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati and Mark Adamson. + */ +/* + * The following changes have been addressed: + * + * Enhancements: + * - re-styled code for better readability + * - upgraded backend API to reflect recent changes + * - LDAP schema is checked when loading SQL/LDAP mapping + * - AttributeDescription/ObjectClass pointers used for more efficient + * mapping lookup + * - bervals used where string length is required often + * - atomized write operations by committing at the end of each operation + * and defaulting connection closure to rollback + * - added LDAP access control to write operations + * - fully implemented modrdn (with rdn attrs change, deleteoldrdn, + * access check, parent/children check and more) + * - added parent access control, children control to delete operation + * - added structuralObjectClass operational attribute check and + * value return on search + * - added hasSubordinate operational attribute on demand + * - search limits are appropriately enforced + * - function backsql_strcat() has been made more efficient + * - concat function has been made configurable by means of a pattern + * - added config switches: + * - fail_if_no_mapping write operations fail if there is no mapping + * - has_ldapinfo_dn_ru overrides autodetect + * - concat_pattern a string containing two '?' is used + * (note that "?||?" should be more portable + * than builtin function "CONCAT(?,?)") + * - strcast_func cast of string constants in "SELECT DISTINCT + * statements (needed by PostgreSQL) + * - upper_needs_cast cast the argument of upper when required + * (basically when building dn substring queries) + * - added noop control + * - added values return filter control + * - hasSubordinate can be used in search filters (with limitations) + * - eliminated oc->name; use oc->oc->soc_cname instead + * + * Todo: + * - add security checks for SQL statements that can be injected (?) + * - re-test with previously supported RDBMs + * - replace dn_ru and so with normalized dn (no need for upper() and so + * in dn match) + * - implement a backsql_normalize() function to replace the upper() + * conversion routines + * - note that subtree deletion, subtree renaming and so could be easily + * implemented (rollback and consistency checks are available :) + * - implement "lastmod" and other operational stuff (ldap_entries table ?) + * - check how to allow multiple operations with one statement, to remove + * BACKSQL_REALLOC_STMT from modify.c (a more recent unixODBC lib?) + */ +/* + * Improvements submitted by (ITS#3432) + * + * 1. id_query.patch applied (with changes) + * 2. shortcut.patch applied (reworked) + * 3. create_hint.patch applied + * 4. count_query.patch applied (reworked) + * 5. returncodes.patch applied (with sanity checks) + * 6. connpool.patch under evaluation + * 7. modoc.patch under evaluation (requires + * manageDSAit and "manage" + * access privileges) + * 8. miscfixes.patch applied (reworked; other + * operations need to load the + * entire entry for ACL purposes; + * see ITS#3480, now fixed) + * + * original description: + + Changes that were made to the SQL backend. + +The patches were made against 2.2.18 and can be applied individually, +but would best be applied in the numerical order of the file names. +A synopsis of each patch is given here: + + +1. Added an option to set SQL query for the "id_query" operation. + +2. Added an option to the SQL backend called "use_subtree_shortcut". +When a search is performed, the SQL query includes a WHERE clause +which says the DN must be "LIKE %<searchbase>". The LIKE operation +can be slow in an RDBM. This shortcut option says that if the +searchbase of the LDAP search is the root DN of the SQL backend, +and thus all objects will match the LIKE operator, do not include +the "LIKE %<searchbase>" clause in the SQL query (it is replaced +instead by the always true "1=1" clause to keep the "AND"'s +working correctly). This option is off by default, and should be +turned on only if all objects to be found in the RDBM are under the +same root DN. Multiple backends working within the same RDBM table +space would encounter problems. LDAP searches whose searchbase are +not at the root DN will bypass this shortcut and employ the LIKE +clause. + +3. Added a "create_hint" column to ldap_oc_mappings table. Allows +taking the value of an attr named in "create_hint" and passing it to +the create_proc procedure. This is necessary for when an objectClass's +table is partition indexed by some indexing column and thus the value +in that indexing column cannot change after the row is created. The +value for the indexed column is passed into the create_proc, which +uses it to fill in the indexed column as the new row is created. + +4. When loading the values of an attribute, the count(*) of the number +of values is fetched first and memory is allocated for the array of +values and normalized values. The old system of loading the values one +by one and running realloc() on the array of values and normalized +values each time was badly fragmenting memory. The array of values and +normalized values would be side by side in memory, and realloc()'ing +them over and over would force them to leapfrog each other through all +of available memory. Attrs with a large number of values could not be +loaded without crashing the slapd daemon. + +5. Added code to interpret the value returned by stored procedures +which have expect_return set. Returned value is interpreted as an LDAP +return code. This allows the distinction between the SQL failing to +execute and the SQL running to completion and returning an error code +which can indicate a policy violation. + +6. Added RDBM connection pooling. Once an operation is finished the +connection to the RDBM is returned to a pool rather than closing. +Allows the next operation to skip the initialization and authentication +phases of contacting the RDBM. Also, if licensing with ODBC places +a limit on the number of connections, an LDAP thread can block waiting +for another thread to finish, so that no LDAP errors are returned +for having more LDAP connections than allowed RDBM connections. An +RDBM connection which receives an SQL error is marked as "tainted" +so that it will be closed rather than returned to the pool. + Also, RDBM connections must be bound to a given LDAP connection AND +operation number, and NOT just the connection number. Asynchronous +LDAP clients can have multiple simultaneous LDAP operations which +should not share the same RDBM connection. A given LDAP operation can +even make multiple SQL operations (e.g. a BIND operation which +requires SASL to perform an LDAP search to convert the SASL ID to an +LDAP DN), so each RDBM connection now has a refcount that must reach +zero before the connection is returned to the free pool. + +7. Added ability to change the objectClass of an object. Required +considerable work to copy all attributes out of old object and into +new object. Does a schema check before proceeding. Creates a new +object, fills it in, deletes the old object, then changes the +oc_map_id and keyval of the entry in the "ldap_entries" table. + +8. Generic fixes. Includes initializing pointers before they +get used in error branch cases, pointer checks before dereferencing, +resetting a return code to success after a COMPARE op, sealing +memory leaks, and in search.c, changing some of the "1=1" tests to +"2=2", "3=3", etc so that when reading slapd trace output, the +location in the source code where the x=x test was added to the SQL +can be easily distinguished. + */ + +#ifndef __BACKSQL_H__ +#define __BACKSQL_H__ + +/* former sql-types.h */ +#include <sql.h> +#include <sqlext.h> + +typedef struct { + SWORD ncols; + BerVarray col_names; + UDWORD *col_prec; + SQLSMALLINT *col_type; + char **cols; + SQLLEN *value_len; +} BACKSQL_ROW_NTS; + +/* + * Better use the standard length of 8192 (as of slap.h)? + * + * NOTE: must be consistent with definition in ldap_entries table + */ +/* #define BACKSQL_MAX_DN_LEN SLAP_LDAPDN_MAXLEN */ +#define BACKSQL_MAX_DN_LEN 255 + +/* + * define to enable very extensive trace logging (debug only) + */ +#undef BACKSQL_TRACE + +/* + * define if using MS SQL and workaround needed (see sql-wrap.c) + */ +#undef BACKSQL_MSSQL_WORKAROUND + +/* + * define to enable values counting for attributes + */ +#define BACKSQL_COUNTQUERY + +/* + * define to enable prettification/validation of values + */ +#define BACKSQL_PRETTY_VALIDATE + +/* + * define to enable varchars as unique keys in user tables + * + * by default integers are used (and recommended) + * for performances. Integers are used anyway in back-sql + * related tables. + */ +#undef BACKSQL_ARBITRARY_KEY + +/* + * type used for keys + */ +#if defined(HAVE_LONG_LONG) && defined(SQL_C_UBIGINT) && \ + ( defined(HAVE_STRTOULL) || defined(HAVE_STRTOUQ) ) +typedef unsigned long long backsql_key_t; +#define BACKSQL_C_NUMID SQL_C_UBIGINT +#define BACKSQL_IDNUMFMT "%llu" +#define BACKSQL_STR2ID lutil_atoullx +#else /* ! HAVE_LONG_LONG || ! SQL_C_UBIGINT */ +typedef unsigned long backsql_key_t; +#define BACKSQL_C_NUMID SQL_C_ULONG +#define BACKSQL_IDNUMFMT "%lu" +#define BACKSQL_STR2ID lutil_atoulx +#endif /* ! HAVE_LONG_LONG */ + +/* + * define to enable support for syncprov overlay + */ +#define BACKSQL_SYNCPROV + +/* + * define to the appropriate aliasing string + * + * some RDBMSes tolerate (or require) that " AS " is not used + * when aliasing tables/columns + */ +#define BACKSQL_ALIASING "AS " +/* #define BACKSQL_ALIASING "" */ + +/* + * define to the appropriate quoting char + * + * some RDBMSes tolerate/require that the aliases be enclosed + * in quotes. This is especially true for those that do not + * allow keywords used as aliases. + */ +#define BACKSQL_ALIASING_QUOTE "" +/* #define BACKSQL_ALIASING_QUOTE "\"" */ +/* #define BACKSQL_ALIASING_QUOTE "'" */ + +/* + * API + * + * a simple mechanism to allow DN mucking between the LDAP + * and the stored string representation. + */ +typedef struct backsql_api { + char *ba_name; + int (*ba_config)( struct backsql_api *self, int argc, char *argv[] ); + int (*ba_destroy)( struct backsql_api *self ); + + int (*ba_dn2odbc)( Operation *op, SlapReply *rs, struct berval *dn ); + int (*ba_odbc2dn)( Operation *op, SlapReply *rs, struct berval *dn ); + + void *ba_private; + struct backsql_api *ba_next; + char **ba_argv; + int ba_argc; +} backsql_api; + +/* + * "structural" objectClass mapping structure + */ +typedef struct backsql_oc_map_rec { + /* + * Structure of corresponding LDAP objectClass definition + */ + ObjectClass *bom_oc; +#define BACKSQL_OC_NAME(ocmap) ((ocmap)->bom_oc->soc_cname.bv_val) + + struct berval bom_keytbl; + struct berval bom_keycol; + /* expected to return keyval of newly created entry */ + char *bom_create_proc; + /* in case create_proc does not return the keyval of the newly + * created row */ + char *bom_create_keyval; + /* supposed to expect keyval as parameter and delete + * all the attributes as well */ + char *bom_delete_proc; + /* flags whether delete_proc is a function (whether back-sql + * should bind first parameter as output for return code) */ + int bom_expect_return; + backsql_key_t bom_id; + Avlnode *bom_attrs; + AttributeDescription *bom_create_hint; +} backsql_oc_map_rec; + +/* + * attributeType mapping structure + */ +typedef struct backsql_at_map_rec { + /* Description of corresponding LDAP attribute type */ + AttributeDescription *bam_ad; + AttributeDescription *bam_true_ad; + /* ObjectClass if bam_ad is objectClass */ + ObjectClass *bam_oc; + + struct berval bam_from_tbls; + struct berval bam_join_where; + struct berval bam_sel_expr; + + /* TimesTen, or, if a uppercase function is defined, + * an uppercased version of bam_sel_expr */ + struct berval bam_sel_expr_u; + + /* supposed to expect 2 binded values: entry keyval + * and attr. value to add, like "add_name(?,?,?)" */ + char *bam_add_proc; + /* supposed to expect 2 binded values: entry keyval + * and attr. value to delete */ + char *bam_delete_proc; + /* for optimization purposes attribute load query + * is preconstructed from parts on schemamap load time */ + char *bam_query; +#ifdef BACKSQL_COUNTQUERY + char *bam_countquery; +#endif /* BACKSQL_COUNTQUERY */ + /* following flags are bitmasks (first bit used for add_proc, + * second - for delete_proc) */ + /* order of parameters for procedures above; + * 1 means "data then keyval", 0 means "keyval then data" */ + int bam_param_order; + /* flags whether one or more of procedures is a function + * (whether back-sql should bind first parameter as output + * for return code) */ + int bam_expect_return; + + /* next mapping for attribute */ + struct backsql_at_map_rec *bam_next; +} backsql_at_map_rec; + +#define BACKSQL_AT_MAP_REC_INIT { NULL, NULL, BER_BVC(""), BER_BVC(""), BER_BVNULL, BER_BVNULL, NULL, NULL, NULL, 0, 0, NULL } + +/* define to uppercase filters only if the matching rule requires it + * (currently broken) */ +/* #define BACKSQL_UPPERCASE_FILTER */ + +#define BACKSQL_AT_CANUPPERCASE(at) ( !BER_BVISNULL( &(at)->bam_sel_expr_u ) ) + +/* defines to support bitmasks above */ +#define BACKSQL_ADD 0x1 +#define BACKSQL_DEL 0x2 + +#define BACKSQL_IS_ADD(x) ( ( BACKSQL_ADD & (x) ) == BACKSQL_ADD ) +#define BACKSQL_IS_DEL(x) ( ( BACKSQL_DEL & (x) ) == BACKSQL_DEL ) + +#define BACKSQL_NCMP(v1,v2) ber_bvcmp((v1),(v2)) + +#define BACKSQL_CONCAT +/* + * berbuf structure: a berval with a buffer size associated + */ +typedef struct berbuf { + struct berval bb_val; + ber_len_t bb_len; +} BerBuffer; + +#define BB_NULL { BER_BVNULL, 0 } + +/* + * Entry ID structure + */ +typedef struct backsql_entryID { + /* #define BACKSQL_ARBITRARY_KEY to allow a non-numeric key. + * It is required by some special applications that use + * strings as keys for the main table. + * In this case, #define BACKSQL_MAX_KEY_LEN consistently + * with the key size definition */ +#ifdef BACKSQL_ARBITRARY_KEY + struct berval eid_id; + struct berval eid_keyval; +#define BACKSQL_MAX_KEY_LEN 64 +#else /* ! BACKSQL_ARBITRARY_KEY */ + /* The original numeric key is maintained as default. */ + backsql_key_t eid_id; + backsql_key_t eid_keyval; +#endif /* ! BACKSQL_ARBITRARY_KEY */ + + backsql_key_t eid_oc_id; + backsql_oc_map_rec *eid_oc; + struct berval eid_dn; + struct berval eid_ndn; + struct backsql_entryID *eid_next; +} backsql_entryID; + +#ifdef BACKSQL_ARBITRARY_KEY +#define BACKSQL_ENTRYID_INIT { BER_BVNULL, BER_BVNULL, 0, NULL, BER_BVNULL, BER_BVNULL, NULL } +#else /* ! BACKSQL_ARBITRARY_KEY */ +#define BACKSQL_ENTRYID_INIT { 0, 0, 0, NULL, BER_BVNULL, BER_BVNULL, NULL } +#endif /* BACKSQL_ARBITRARY_KEY */ + +/* the function must collect the entry associated to nbase */ +#define BACKSQL_ISF_GET_ID 0x1U +#define BACKSQL_ISF_GET_ENTRY ( 0x2U | BACKSQL_ISF_GET_ID ) +#define BACKSQL_ISF_GET_OC ( 0x4U | BACKSQL_ISF_GET_ID ) +#define BACKSQL_ISF_MATCHED 0x8U +#define BACKSQL_IS_GET_ID(f) \ + ( ( (f) & BACKSQL_ISF_GET_ID ) == BACKSQL_ISF_GET_ID ) +#define BACKSQL_IS_GET_ENTRY(f) \ + ( ( (f) & BACKSQL_ISF_GET_ENTRY ) == BACKSQL_ISF_GET_ENTRY ) +#define BACKSQL_IS_GET_OC(f) \ + ( ( (f) & BACKSQL_ISF_GET_OC ) == BACKSQL_ISF_GET_OC ) +#define BACKSQL_IS_MATCHED(f) \ + ( ( (f) & BACKSQL_ISF_MATCHED ) == BACKSQL_ISF_MATCHED ) +typedef struct backsql_srch_info { + Operation *bsi_op; + SlapReply *bsi_rs; + + unsigned bsi_flags; +#define BSQL_SF_NONE 0x0000U +#define BSQL_SF_ALL_USER 0x0001U +#define BSQL_SF_ALL_OPER 0x0002U +#define BSQL_SF_ALL_ATTRS (BSQL_SF_ALL_USER|BSQL_SF_ALL_OPER) +#define BSQL_SF_FILTER_HASSUBORDINATE 0x0010U +#define BSQL_SF_FILTER_ENTRYUUID 0x0020U +#define BSQL_SF_FILTER_ENTRYCSN 0x0040U +#define BSQL_SF_RETURN_ENTRYUUID (BSQL_SF_FILTER_ENTRYUUID << 8) +#define BSQL_ISF(bsi, f) ( ( (bsi)->bsi_flags & f ) == f ) +#define BSQL_ISF_ALL_USER(bsi) BSQL_ISF(bsi, BSQL_SF_ALL_USER) +#define BSQL_ISF_ALL_OPER(bsi) BSQL_ISF(bsi, BSQL_SF_ALL_OPER) +#define BSQL_ISF_ALL_ATTRS(bsi) BSQL_ISF(bsi, BSQL_SF_ALL_ATTRS) + + struct berval *bsi_base_ndn; + int bsi_use_subtree_shortcut; + backsql_entryID bsi_base_id; + int bsi_scope; +/* BACKSQL_SCOPE_BASE_LIKE can be set by API in ors_scope + * whenever the search base DN contains chars that cannot + * be mapped into the charset used in the RDBMS; so they're + * turned into '%' and an approximate ('LIKE') condition + * is used */ +#define BACKSQL_SCOPE_BASE_LIKE ( LDAP_SCOPE_BASE | 0x1000 ) + Filter *bsi_filter; + time_t bsi_stoptime; + + backsql_entryID *bsi_id_list, + **bsi_id_listtail, + *bsi_c_eid; + int bsi_n_candidates; + int bsi_status; + + backsql_oc_map_rec *bsi_oc; + struct berbuf bsi_sel, + bsi_from, + bsi_join_where, + bsi_flt_where; + ObjectClass *bsi_filter_oc; + SQLHDBC bsi_dbh; + AttributeName *bsi_attrs; + + Entry *bsi_e; +} backsql_srch_info; + +/* + * Backend private data structure + */ +typedef struct backsql_info { + char *sql_dbhost; + int sql_dbport; + char *sql_dbuser; + char *sql_dbpasswd; + char *sql_dbname; + + /* + * SQL condition for subtree searches differs in syntax: + * "LIKE CONCAT('%',?)" or "LIKE '%'+?" or "LIKE '%'||?" + * or smtg else + */ + struct berval sql_subtree_cond; + struct berval sql_children_cond; + struct berval sql_dn_match_cond; + char *sql_oc_query; + char *sql_at_query; + char *sql_insentry_stmt; + char *sql_delentry_stmt; + char *sql_renentry_stmt; + char *sql_delobjclasses_stmt; + char *sql_id_query; + char *sql_has_children_query; + char *sql_list_children_query; + + MatchingRule *sql_caseIgnoreMatch; + MatchingRule *sql_telephoneNumberMatch; + + struct berval sql_upper_func; + struct berval sql_upper_func_open; + struct berval sql_upper_func_close; + struct berval sql_strcast_func; + BerVarray sql_concat_func; + char *sql_concat_patt; + + struct berval sql_aliasing; + struct berval sql_aliasing_quote; + struct berval sql_dn_oc_aliasing; + + AttributeName *sql_anlist; + + unsigned int sql_flags; +#define BSQLF_SCHEMA_LOADED 0x0001 +#define BSQLF_UPPER_NEEDS_CAST 0x0002 +#define BSQLF_CREATE_NEEDS_SELECT 0x0004 +#define BSQLF_FAIL_IF_NO_MAPPING 0x0008 +#define BSQLF_HAS_LDAPINFO_DN_RU 0x0010 +#define BSQLF_DONTCHECK_LDAPINFO_DN_RU 0x0020 +#define BSQLF_USE_REVERSE_DN 0x0040 +#define BSQLF_ALLOW_ORPHANS 0x0080 +#define BSQLF_USE_SUBTREE_SHORTCUT 0x0100 +#define BSQLF_FETCH_ALL_USERATTRS 0x0200 +#define BSQLF_FETCH_ALL_OPATTRS 0x0400 +#define BSQLF_FETCH_ALL_ATTRS (BSQLF_FETCH_ALL_USERATTRS|BSQLF_FETCH_ALL_OPATTRS) +#define BSQLF_CHECK_SCHEMA 0x0800 +#define BSQLF_AUTOCOMMIT_ON 0x1000 + +#define BACKSQL_ISF(si, f) \ + (((si)->sql_flags & f) == f) + +#define BACKSQL_SCHEMA_LOADED(si) \ + BACKSQL_ISF(si, BSQLF_SCHEMA_LOADED) +#define BACKSQL_UPPER_NEEDS_CAST(si) \ + BACKSQL_ISF(si, BSQLF_UPPER_NEEDS_CAST) +#define BACKSQL_CREATE_NEEDS_SELECT(si) \ + BACKSQL_ISF(si, BSQLF_CREATE_NEEDS_SELECT) +#define BACKSQL_FAIL_IF_NO_MAPPING(si) \ + BACKSQL_ISF(si, BSQLF_FAIL_IF_NO_MAPPING) +#define BACKSQL_HAS_LDAPINFO_DN_RU(si) \ + BACKSQL_ISF(si, BSQLF_HAS_LDAPINFO_DN_RU) +#define BACKSQL_DONTCHECK_LDAPINFO_DN_RU(si) \ + BACKSQL_ISF(si, BSQLF_DONTCHECK_LDAPINFO_DN_RU) +#define BACKSQL_USE_REVERSE_DN(si) \ + BACKSQL_ISF(si, BSQLF_USE_REVERSE_DN) +#define BACKSQL_CANUPPERCASE(si) \ + (!BER_BVISNULL( &(si)->sql_upper_func )) +#define BACKSQL_ALLOW_ORPHANS(si) \ + BACKSQL_ISF(si, BSQLF_ALLOW_ORPHANS) +#define BACKSQL_USE_SUBTREE_SHORTCUT(si) \ + BACKSQL_ISF(si, BSQLF_USE_SUBTREE_SHORTCUT) +#define BACKSQL_FETCH_ALL_USERATTRS(si) \ + BACKSQL_ISF(si, BSQLF_FETCH_ALL_USERATTRS) +#define BACKSQL_FETCH_ALL_OPATTRS(si) \ + BACKSQL_ISF(si, BSQLF_FETCH_ALL_OPATTRS) +#define BACKSQL_FETCH_ALL_ATTRS(si) \ + BACKSQL_ISF(si, BSQLF_FETCH_ALL_ATTRS) +#define BACKSQL_CHECK_SCHEMA(si) \ + BACKSQL_ISF(si, BSQLF_CHECK_SCHEMA) +#define BACKSQL_AUTOCOMMIT_ON(si) \ + BACKSQL_ISF(si, BSQLF_AUTOCOMMIT_ON) + + Entry *sql_baseObject; + char *sql_base_ob_file; +#ifdef BACKSQL_ARBITRARY_KEY +#define BACKSQL_BASEOBJECT_IDSTR "baseObject" +#define BACKSQL_BASEOBJECT_KEYVAL BACKSQL_BASEOBJECT_IDSTR +#define BACKSQL_IS_BASEOBJECT_ID(id) (bvmatch((id), &backsql_baseObject_bv)) +#else /* ! BACKSQL_ARBITRARY_KEY */ +#define BACKSQL_BASEOBJECT_ID 0 +#define BACKSQL_BASEOBJECT_IDSTR LDAP_XSTRING(BACKSQL_BASEOBJECT_ID) +#define BACKSQL_BASEOBJECT_KEYVAL 0 +#define BACKSQL_IS_BASEOBJECT_ID(id) (*(id) == BACKSQL_BASEOBJECT_ID) +#endif /* ! BACKSQL_ARBITRARY_KEY */ +#define BACKSQL_BASEOBJECT_OC 0 + + Avlnode *sql_db_conns; + SQLHDBC sql_dbh; + ldap_pvt_thread_mutex_t sql_dbconn_mutex; + Avlnode *sql_oc_by_oc; + Avlnode *sql_oc_by_id; + ldap_pvt_thread_mutex_t sql_schema_mutex; + SQLHENV sql_db_env; + + backsql_api *sql_api; +} backsql_info; + +#define BACKSQL_SUCCESS( rc ) \ + ( (rc) == SQL_SUCCESS || (rc) == SQL_SUCCESS_WITH_INFO ) + +#define BACKSQL_AVL_STOP 0 +#define BACKSQL_AVL_CONTINUE 1 + +/* see ldap.h for the meaning of the macros and of the values */ +#define BACKSQL_LEGAL_ERROR( rc ) \ + ( LDAP_RANGE( (rc), 0x00, 0x0e ) \ + || LDAP_ATTR_ERROR( (rc) ) \ + || LDAP_NAME_ERROR( (rc) ) \ + || LDAP_SECURITY_ERROR( (rc) ) \ + || LDAP_SERVICE_ERROR( (rc) ) \ + || LDAP_UPDATE_ERROR( (rc) ) ) +#define BACKSQL_SANITIZE_ERROR( rc ) \ + ( BACKSQL_LEGAL_ERROR( (rc) ) ? (rc) : LDAP_OTHER ) + +#define BACKSQL_IS_BINARY(ct) \ + ( (ct) == SQL_BINARY \ + || (ct) == SQL_VARBINARY \ + || (ct) == SQL_LONGVARBINARY) + +#ifdef BACKSQL_ARBITRARY_KEY +#define BACKSQL_IDFMT "%s" +#define BACKSQL_IDARG(arg) ((arg).bv_val) +#else /* ! BACKSQL_ARBITRARY_KEY */ +#define BACKSQL_IDFMT BACKSQL_IDNUMFMT +#define BACKSQL_IDARG(arg) (arg) +#endif /* ! BACKSQL_ARBITRARY_KEY */ + +#endif /* __BACKSQL_H__ */ + diff --git a/servers/slapd/back-sql/bind.c b/servers/slapd/back-sql/bind.c new file mode 100644 index 0000000..44d0b10 --- /dev/null +++ b/servers/slapd/back-sql/bind.c @@ -0,0 +1,116 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> + +#include "slap.h" +#include "proto-sql.h" + +int +backsql_bind( Operation *op, SlapReply *rs ) +{ + SQLHDBC dbh = SQL_NULL_HDBC; + Entry e = { 0 }; + Attribute *a; + backsql_srch_info bsi = { 0 }; + AttributeName anlist[2]; + int rc; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_bind()\n", 0, 0, 0 ); + + switch ( be_rootdn_bind( op, rs ) ) { + case SLAP_CB_CONTINUE: + break; + + default: + /* in case of success, front end will send result; + * otherwise, be_rootdn_bind() did */ + Debug( LDAP_DEBUG_TRACE, "<==backsql_bind(%d)\n", + rs->sr_err, 0, 0 ); + return rs->sr_err; + } + + rs->sr_err = backsql_get_db_conn( op, &dbh ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_bind(): " + "could not get connection handle - exiting\n", + 0, 0, 0 ); + + rs->sr_text = ( rs->sr_err == LDAP_OTHER ) + ? "SQL-backend error" : NULL; + goto error_return; + } + + anlist[0].an_name = slap_schema.si_ad_userPassword->ad_cname; + anlist[0].an_desc = slap_schema.si_ad_userPassword; + anlist[1].an_name.bv_val = NULL; + + bsi.bsi_e = &e; + rc = backsql_init_search( &bsi, &op->o_req_ndn, LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, anlist, + BACKSQL_ISF_GET_ENTRY ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_bind(): " + "could not retrieve bindDN ID - no such entry\n", + 0, 0, 0 ); + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto error_return; + } + + a = attr_find( e.e_attrs, slap_schema.si_ad_userPassword ); + if ( a == NULL ) { + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto error_return; + } + + if ( slap_passwd_check( op, &e, a, &op->oq_bind.rb_cred, + &rs->sr_text ) != 0 ) + { + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto error_return; + } + +error_return:; + if ( !BER_BVISNULL( &bsi.bsi_base_id.eid_ndn ) ) { + (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &e.e_nname ) ) { + backsql_entry_clean( op, &e ); + } + + if ( bsi.bsi_attrs != NULL ) { + op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx ); + } + + if ( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + } + + Debug( LDAP_DEBUG_TRACE,"<==backsql_bind()\n", 0, 0, 0 ); + + return rs->sr_err; +} + diff --git a/servers/slapd/back-sql/compare.c b/servers/slapd/back-sql/compare.c new file mode 100644 index 0000000..696bcc4 --- /dev/null +++ b/servers/slapd/back-sql/compare.c @@ -0,0 +1,196 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> + +#include "slap.h" +#include "proto-sql.h" + +int +backsql_compare( Operation *op, SlapReply *rs ) +{ + SQLHDBC dbh = SQL_NULL_HDBC; + Entry e = { 0 }; + Attribute *a = NULL; + backsql_srch_info bsi = { 0 }; + int rc; + int manageDSAit = get_manageDSAit( op ); + AttributeName anlist[2]; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_compare()\n", 0, 0, 0 ); + + rs->sr_err = backsql_get_db_conn( op, &dbh ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_compare(): " + "could not get connection handle - exiting\n", + 0, 0, 0 ); + + rs->sr_text = ( rs->sr_err == LDAP_OTHER ) + ? "SQL-backend error" : NULL; + goto return_results; + } + + anlist[ 0 ].an_name = op->oq_compare.rs_ava->aa_desc->ad_cname; + anlist[ 0 ].an_desc = op->oq_compare.rs_ava->aa_desc; + BER_BVZERO( &anlist[ 1 ].an_name ); + + /* + * Get the entry + */ + bsi.bsi_e = &e; + rc = backsql_init_search( &bsi, &op->o_req_ndn, LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, anlist, + ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY ) ); + switch ( rc ) { + case LDAP_SUCCESS: + break; + + case LDAP_REFERRAL: + if ( manageDSAit && !BER_BVISNULL( &bsi.bsi_e->e_nname ) && + dn_match( &op->o_req_ndn, &bsi.bsi_e->e_nname ) ) + { + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + break; + } + /* fallthru */ + + default: + Debug( LDAP_DEBUG_TRACE, "backsql_compare(): " + "could not retrieve compareDN ID - no such entry\n", + 0, 0, 0 ); + goto return_results; + } + + if ( get_assert( op ) && + ( test_filter( op, &e, get_assertion( op ) ) + != LDAP_COMPARE_TRUE ) ) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto return_results; + } + + if ( is_at_operational( op->oq_compare.rs_ava->aa_desc->ad_type ) ) { + SlapReply nrs = { REP_SEARCH }; + Attribute **ap; + + for ( ap = &e.e_attrs; *ap; ap = &(*ap)->a_next ) + ; + + nrs.sr_attrs = anlist; + nrs.sr_entry = &e; + nrs.sr_attr_flags = SLAP_OPATTRS_NO; + nrs.sr_operational_attrs = NULL; + + rs->sr_err = backsql_operational( op, &nrs ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto return_results; + } + + *ap = nrs.sr_operational_attrs; + } + + if ( ! access_allowed( op, &e, op->oq_compare.rs_ava->aa_desc, + &op->oq_compare.rs_ava->aa_value, + ACL_COMPARE, NULL ) ) + { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto return_results; + } + + rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE; + for ( a = attrs_find( e.e_attrs, op->oq_compare.rs_ava->aa_desc ); + a != NULL; + a = attrs_find( a->a_next, op->oq_compare.rs_ava->aa_desc ) ) + { + rs->sr_err = LDAP_COMPARE_FALSE; + if ( attr_valfind( a, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + &op->oq_compare.rs_ava->aa_value, NULL, + op->o_tmpmemctx ) == 0 ) + { + rs->sr_err = LDAP_COMPARE_TRUE; + break; + } + } + +return_results:; + switch ( rs->sr_err ) { + case LDAP_COMPARE_TRUE: + case LDAP_COMPARE_FALSE: + break; + + default: + if ( !BER_BVISNULL( &e.e_nname ) && + ! access_allowed( op, &e, + slap_schema.si_ad_entry, NULL, + ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = NULL; + } + break; + } + + send_ldap_result( op, rs ); + + if ( rs->sr_matched ) { + rs->sr_matched = NULL; + } + + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + + if ( !BER_BVISNULL( &bsi.bsi_base_id.eid_ndn ) ) { + (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &e.e_nname ) ) { + backsql_entry_clean( op, &e ); + } + + if ( bsi.bsi_attrs != NULL ) { + op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx ); + } + + Debug(LDAP_DEBUG_TRACE,"<==backsql_compare()\n",0,0,0); + switch ( rs->sr_err ) { + case LDAP_COMPARE_TRUE: + case LDAP_COMPARE_FALSE: + return LDAP_SUCCESS; + + default: + return rs->sr_err; + } +} + diff --git a/servers/slapd/back-sql/config.c b/servers/slapd/back-sql/config.c new file mode 100644 index 0000000..e421bcd --- /dev/null +++ b/servers/slapd/back-sql/config.c @@ -0,0 +1,761 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * Portions Copyright 2004 Mark Adamson. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include "ac/string.h" +#include <sys/types.h> + +#include "slap.h" +#include "config.h" +#include "ldif.h" +#include "lutil.h" +#include "proto-sql.h" + +static int +create_baseObject( + BackendDB *be, + const char *fname, + int lineno ); + +static int +read_baseObject( + BackendDB *be, + const char *fname ); + +static ConfigDriver sql_cf_gen; + +enum { + BSQL_CONCAT_PATT = 1, + BSQL_CREATE_NEEDS_SEL, + BSQL_UPPER_NEEDS_CAST, + BSQL_HAS_LDAPINFO_DN_RU, + BSQL_FAIL_IF_NO_MAPPING, + BSQL_ALLOW_ORPHANS, + BSQL_BASE_OBJECT, + BSQL_LAYER, + BSQL_SUBTREE_SHORTCUT, + BSQL_FETCH_ALL_ATTRS, + BSQL_FETCH_ATTRS, + BSQL_CHECK_SCHEMA, + BSQL_ALIASING_KEYWORD, + BSQL_AUTOCOMMIT +}; + +static ConfigTable sqlcfg[] = { + { "dbhost", "hostname", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_dbhost), + "( OLcfgDbAt:6.1 NAME 'olcDbHost' " + "DESC 'Hostname of SQL server' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "dbname", "name", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_dbname), + "( OLcfgDbAt:6.2 NAME 'olcDbName' " + "DESC 'Name of SQL database' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "dbuser", "username", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_dbuser), + "( OLcfgDbAt:6.3 NAME 'olcDbUser' " + "DESC 'Username for SQL session' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "dbpasswd", "password", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_dbpasswd), + "( OLcfgDbAt:6.4 NAME 'olcDbPass' " + "DESC 'Password for SQL session' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "concat_pattern", "pattern", 2, 2, 0, + ARG_STRING|ARG_MAGIC|BSQL_CONCAT_PATT, (void *)sql_cf_gen, + "( OLcfgDbAt:6.20 NAME 'olcSqlConcatPattern' " + "DESC 'Pattern used to concatenate strings' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "subtree_cond", "SQL expression", 2, 0, 0, ARG_BERVAL|ARG_QUOTE|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_subtree_cond), + "( OLcfgDbAt:6.21 NAME 'olcSqlSubtreeCond' " + "DESC 'Where-clause template for a subtree search condition' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "children_cond", "SQL expression", 2, 0, 0, ARG_BERVAL|ARG_QUOTE|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_children_cond), + "( OLcfgDbAt:6.22 NAME 'olcSqlChildrenCond' " + "DESC 'Where-clause template for a children search condition' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "dn_match_cond", "SQL expression", 2, 0, 0, ARG_BERVAL|ARG_QUOTE|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_dn_match_cond), + "( OLcfgDbAt:6.23 NAME 'olcSqlDnMatchCond' " + "DESC 'Where-clause template for a DN match search condition' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "oc_query", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_oc_query), + "( OLcfgDbAt:6.24 NAME 'olcSqlOcQuery' " + "DESC 'Query used to collect objectClass mapping data' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "at_query", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_at_query), + "( OLcfgDbAt:6.25 NAME 'olcSqlAtQuery' " + "DESC 'Query used to collect attributeType mapping data' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "insentry_stmt", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_insentry_stmt), + "( OLcfgDbAt:6.26 NAME 'olcSqlInsEntryStmt' " + "DESC 'Statement used to insert a new entry' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "create_needs_select", "yes|no", 2, 2, 0, + ARG_ON_OFF|ARG_MAGIC|BSQL_CREATE_NEEDS_SEL, (void *)sql_cf_gen, + "( OLcfgDbAt:6.27 NAME 'olcSqlCreateNeedsSelect' " + "DESC 'Whether entry creation needs a subsequent select' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "upper_func", "SQL function name", 2, 2, 0, ARG_BERVAL|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_upper_func), + "( OLcfgDbAt:6.28 NAME 'olcSqlUpperFunc' " + "DESC 'Function that converts a value to uppercase' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "upper_needs_cast", "yes|no", 2, 2, 0, + ARG_ON_OFF|ARG_MAGIC|BSQL_UPPER_NEEDS_CAST, (void *)sql_cf_gen, + "( OLcfgDbAt:6.29 NAME 'olcSqlUpperNeedsCast' " + "DESC 'Whether olcSqlUpperFunc needs an explicit cast' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "strcast_func", "SQL function name", 2, 2, 0, ARG_BERVAL|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_strcast_func), + "( OLcfgDbAt:6.30 NAME 'olcSqlStrcastFunc' " + "DESC 'Function that converts a value to a string' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "delentry_stmt", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_delentry_stmt), + "( OLcfgDbAt:6.31 NAME 'olcSqlDelEntryStmt' " + "DESC 'Statement used to delete an existing entry' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "renentry_stmt", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_renentry_stmt), + "( OLcfgDbAt:6.32 NAME 'olcSqlRenEntryStmt' " + "DESC 'Statement used to rename an entry' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "delobjclasses_stmt", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_delobjclasses_stmt), + "( OLcfgDbAt:6.33 NAME 'olcSqlDelObjclassesStmt' " + "DESC 'Statement used to delete the ID of an entry' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "has_ldapinfo_dn_ru", "yes|no", 2, 2, 0, + ARG_ON_OFF|ARG_MAGIC|BSQL_HAS_LDAPINFO_DN_RU, (void *)sql_cf_gen, + "( OLcfgDbAt:6.34 NAME 'olcSqlHasLDAPinfoDnRu' " + "DESC 'Whether the dn_ru column is present' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "fail_if_no_mapping", "yes|no", 2, 2, 0, + ARG_ON_OFF|ARG_MAGIC|BSQL_FAIL_IF_NO_MAPPING, (void *)sql_cf_gen, + "( OLcfgDbAt:6.35 NAME 'olcSqlFailIfNoMapping' " + "DESC 'Whether to fail on unknown attribute mappings' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "allow_orphans", "yes|no", 2, 2, 0, + ARG_ON_OFF|ARG_MAGIC|BSQL_ALLOW_ORPHANS, (void *)sql_cf_gen, + "( OLcfgDbAt:6.36 NAME 'olcSqlAllowOrphans' " + "DESC 'Whether to allow adding entries with no parent' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "baseobject", "[file]", 1, 2, 0, + ARG_STRING|ARG_MAGIC|BSQL_BASE_OBJECT, (void *)sql_cf_gen, + "( OLcfgDbAt:6.37 NAME 'olcSqlBaseObject' " + "DESC 'Manage an in-memory baseObject entry' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "sqllayer", "name", 2, 0, 0, + ARG_MAGIC|BSQL_LAYER, (void *)sql_cf_gen, + "( OLcfgDbAt:6.38 NAME 'olcSqlLayer' " + "DESC 'Helper used to map DNs between LDAP and SQL' " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "use_subtree_shortcut", "yes|no", 2, 2, 0, + ARG_ON_OFF|ARG_MAGIC|BSQL_SUBTREE_SHORTCUT, (void *)sql_cf_gen, + "( OLcfgDbAt:6.39 NAME 'olcSqlUseSubtreeShortcut' " + "DESC 'Collect all entries when searchBase is DB suffix' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "fetch_all_attrs", "yes|no", 2, 2, 0, + ARG_ON_OFF|ARG_MAGIC|BSQL_FETCH_ALL_ATTRS, (void *)sql_cf_gen, + "( OLcfgDbAt:6.40 NAME 'olcSqlFetchAllAttrs' " + "DESC 'Require all attributes to always be loaded' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "fetch_attrs", "attrlist", 2, 0, 0, + ARG_MAGIC|BSQL_FETCH_ATTRS, (void *)sql_cf_gen, + "( OLcfgDbAt:6.41 NAME 'olcSqlFetchAttrs' " + "DESC 'Set of attributes to always fetch' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "check_schema", "yes|no", 2, 2, 0, + ARG_ON_OFF|ARG_MAGIC|BSQL_CHECK_SCHEMA, (void *)sql_cf_gen, + "( OLcfgDbAt:6.42 NAME 'olcSqlCheckSchema' " + "DESC 'Check schema after modifications' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "aliasing_keyword", "string", 2, 2, 0, + ARG_STRING|ARG_MAGIC|BSQL_ALIASING_KEYWORD, (void *)sql_cf_gen, + "( OLcfgDbAt:6.43 NAME 'olcSqlAliasingKeyword' " + "DESC 'The aliasing keyword' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "aliasing_quote", "string", 2, 2, 0, ARG_BERVAL|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_aliasing_quote), + "( OLcfgDbAt:6.44 NAME 'olcSqlAliasingQuote' " + "DESC 'Quoting char of the aliasing keyword' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "autocommit", "yes|no", 2, 2, 0, + ARG_ON_OFF|ARG_MAGIC|BSQL_AUTOCOMMIT, (void *)sql_cf_gen, + "( OLcfgDbAt:6.45 NAME 'olcSqlAutocommit' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "id_query", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_id_query), + "( OLcfgDbAt:6.46 NAME 'olcSqlIdQuery' " + "DESC 'Query used to collect entryID mapping data' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED, + NULL, NULL, NULL, NULL } +}; + +static ConfigOCs sqlocs[] = { + { + "( OLcfgDbOc:6.1 " + "NAME 'olcSqlConfig' " + "DESC 'SQL backend configuration' " + "SUP olcDatabaseConfig " + "MUST olcDbName " + "MAY ( olcDbHost $ olcDbUser $ olcDbPass $ olcSqlConcatPattern $ " + "olcSqlSubtreeCond $ olcsqlChildrenCond $ olcSqlDnMatchCond $ " + "olcSqlOcQuery $ olcSqlAtQuery $ olcSqlInsEntryStmt $ " + "olcSqlCreateNeedsSelect $ olcSqlUpperFunc $ olcSqlUpperNeedsCast $ " + "olcSqlStrCastFunc $ olcSqlDelEntryStmt $ olcSqlRenEntryStmt $ " + "olcSqlDelObjClassesStmt $ olcSqlHasLDAPInfoDnRu $ " + "olcSqlFailIfNoMapping $ olcSqlAllowOrphans $ olcSqlBaseObject $ " + "olcSqlLayer $ olcSqlUseSubtreeShortcut $ olcSqlFetchAllAttrs $ " + "olcSqlFetchAttrs $ olcSqlCheckSchema $ olcSqlAliasingKeyword $ " + "olcSqlAliasingQuote $ olcSqlAutocommit $ olcSqlIdQuery ) )", + Cft_Database, sqlcfg }, + { NULL, Cft_Abstract, NULL } +}; + +static int +sql_cf_gen( ConfigArgs *c ) +{ + backsql_info *bi = (backsql_info *)c->be->be_private; + int rc = 0; + + if ( c->op == SLAP_CONFIG_EMIT ) { + switch( c->type ) { + case BSQL_CONCAT_PATT: + if ( bi->sql_concat_patt ) { + c->value_string = ch_strdup( bi->sql_concat_patt ); + } else { + rc = 1; + } + break; + case BSQL_CREATE_NEEDS_SEL: + if ( bi->sql_flags & BSQLF_CREATE_NEEDS_SELECT ) + c->value_int = 1; + break; + case BSQL_UPPER_NEEDS_CAST: + if ( bi->sql_flags & BSQLF_UPPER_NEEDS_CAST ) + c->value_int = 1; + break; + case BSQL_HAS_LDAPINFO_DN_RU: + if ( !(bi->sql_flags & BSQLF_DONTCHECK_LDAPINFO_DN_RU) ) + return 1; + if ( bi->sql_flags & BSQLF_HAS_LDAPINFO_DN_RU ) + c->value_int = 1; + break; + case BSQL_FAIL_IF_NO_MAPPING: + if ( bi->sql_flags & BSQLF_FAIL_IF_NO_MAPPING ) + c->value_int = 1; + break; + case BSQL_ALLOW_ORPHANS: + if ( bi->sql_flags & BSQLF_ALLOW_ORPHANS ) + c->value_int = 1; + break; + case BSQL_SUBTREE_SHORTCUT: + if ( bi->sql_flags & BSQLF_USE_SUBTREE_SHORTCUT ) + c->value_int = 1; + break; + case BSQL_FETCH_ALL_ATTRS: + if ( bi->sql_flags & BSQLF_FETCH_ALL_ATTRS ) + c->value_int = 1; + break; + case BSQL_CHECK_SCHEMA: + if ( bi->sql_flags & BSQLF_CHECK_SCHEMA ) + c->value_int = 1; + break; + case BSQL_AUTOCOMMIT: + if ( bi->sql_flags & BSQLF_AUTOCOMMIT_ON ) + c->value_int = 1; + break; + case BSQL_BASE_OBJECT: + if ( bi->sql_base_ob_file ) { + c->value_string = ch_strdup( bi->sql_base_ob_file ); + } else if ( bi->sql_baseObject ) { + c->value_string = ch_strdup( "TRUE" ); + } else { + rc = 1; + } + break; + case BSQL_LAYER: + if ( bi->sql_api ) { + backsql_api *ba; + struct berval bv; + char *ptr; + int i; + for ( ba = bi->sql_api; ba; ba = ba->ba_next ) { + bv.bv_len = strlen( ba->ba_name ); + if ( ba->ba_argc ) { + for ( i = 0; i<ba->ba_argc; i++ ) + bv.bv_len += strlen( ba->ba_argv[i] ) + 3; + } + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + ptr = lutil_strcopy( bv.bv_val, ba->ba_name ); + if ( ba->ba_argc ) { + for ( i = 0; i<ba->ba_argc; i++ ) { + *ptr++ = ' '; + *ptr++ = '"'; + ptr = lutil_strcopy( ptr, ba->ba_argv[i] ); + *ptr++ = '"'; + } + } + ber_bvarray_add( &c->rvalue_vals, &bv ); + } + } else { + rc = 1; + } + break; + case BSQL_ALIASING_KEYWORD: + if ( !BER_BVISNULL( &bi->sql_aliasing )) { + struct berval bv; + bv = bi->sql_aliasing; + bv.bv_len--; + value_add_one( &c->rvalue_vals, &bv ); + } else { + rc = 1; + } + break; + case BSQL_FETCH_ATTRS: + if ( bi->sql_anlist || + ( bi->sql_flags & (BSQLF_FETCH_ALL_USERATTRS| + BSQLF_FETCH_ALL_OPATTRS))) + { + char buf[BUFSIZ*2], *ptr; + struct berval bv; +# define WHATSLEFT ((ber_len_t) (&buf[sizeof( buf )] - ptr)) + ptr = buf; + if ( bi->sql_anlist ) { + ptr = anlist_unparse( bi->sql_anlist, ptr, WHATSLEFT ); + if ( ptr == NULL ) + return 1; + } + if ( bi->sql_flags & BSQLF_FETCH_ALL_USERATTRS ) { + if ( WHATSLEFT <= STRLENOF( ",*" )) return 1; + if ( ptr != buf ) *ptr++ = ','; + *ptr++ = '*'; + } + if ( bi->sql_flags & BSQLF_FETCH_ALL_OPATTRS ) { + if ( WHATSLEFT <= STRLENOF( ",+" )) return 1; + if ( ptr != buf ) *ptr++ = ','; + *ptr++ = '+'; + } + bv.bv_val = buf; + bv.bv_len = ptr - buf; + value_add_one( &c->rvalue_vals, &bv ); + } + break; + } + return rc; + } else if ( c->op == LDAP_MOD_DELETE ) { /* FIXME */ + return -1; + } + + switch( c->type ) { + case BSQL_CONCAT_PATT: + if ( backsql_split_pattern( c->argv[ 1 ], &bi->sql_concat_func, 2 ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s: unable to parse pattern \"%s\"", + c->log, c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + return -1; + } + bi->sql_concat_patt = c->value_string; + break; + case BSQL_CREATE_NEEDS_SEL: + if ( c->value_int ) + bi->sql_flags |= BSQLF_CREATE_NEEDS_SELECT; + else + bi->sql_flags &= ~BSQLF_CREATE_NEEDS_SELECT; + break; + case BSQL_UPPER_NEEDS_CAST: + if ( c->value_int ) + bi->sql_flags |= BSQLF_UPPER_NEEDS_CAST; + else + bi->sql_flags &= ~BSQLF_UPPER_NEEDS_CAST; + break; + case BSQL_HAS_LDAPINFO_DN_RU: + bi->sql_flags |= BSQLF_DONTCHECK_LDAPINFO_DN_RU; + if ( c->value_int ) + bi->sql_flags |= BSQLF_HAS_LDAPINFO_DN_RU; + else + bi->sql_flags &= ~BSQLF_HAS_LDAPINFO_DN_RU; + break; + case BSQL_FAIL_IF_NO_MAPPING: + if ( c->value_int ) + bi->sql_flags |= BSQLF_FAIL_IF_NO_MAPPING; + else + bi->sql_flags &= ~BSQLF_FAIL_IF_NO_MAPPING; + break; + case BSQL_ALLOW_ORPHANS: + if ( c->value_int ) + bi->sql_flags |= BSQLF_ALLOW_ORPHANS; + else + bi->sql_flags &= ~BSQLF_ALLOW_ORPHANS; + break; + case BSQL_SUBTREE_SHORTCUT: + if ( c->value_int ) + bi->sql_flags |= BSQLF_USE_SUBTREE_SHORTCUT; + else + bi->sql_flags &= ~BSQLF_USE_SUBTREE_SHORTCUT; + break; + case BSQL_FETCH_ALL_ATTRS: + if ( c->value_int ) + bi->sql_flags |= BSQLF_FETCH_ALL_ATTRS; + else + bi->sql_flags &= ~BSQLF_FETCH_ALL_ATTRS; + break; + case BSQL_CHECK_SCHEMA: + if ( c->value_int ) + bi->sql_flags |= BSQLF_CHECK_SCHEMA; + else + bi->sql_flags &= ~BSQLF_CHECK_SCHEMA; + break; + case BSQL_AUTOCOMMIT: + if ( c->value_int ) + bi->sql_flags |= BSQLF_AUTOCOMMIT_ON; + else + bi->sql_flags &= ~BSQLF_AUTOCOMMIT_ON; + break; + case BSQL_BASE_OBJECT: + if ( c->be->be_nsuffix == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s: suffix must be set", c->log ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + rc = ARG_BAD_CONF; + break; + } + if ( bi->sql_baseObject ) { + Debug( LDAP_DEBUG_CONFIG, + "%s: " + "\"baseObject\" already provided (will be overwritten)\n", + c->log, 0, 0 ); + entry_free( bi->sql_baseObject ); + } + if ( c->argc == 2 && !strcmp( c->argv[1], "TRUE" )) + c->argc = 1; + switch( c->argc ) { + case 1: + return create_baseObject( c->be, c->fname, c->lineno ); + + case 2: + rc = read_baseObject( c->be, c->argv[ 1 ] ); + if ( rc == 0 ) { + ch_free( bi->sql_base_ob_file ); + bi->sql_base_ob_file = c->value_string; + } + return rc; + + default: + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s: trailing values in directive", c->log ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + return 1; + } + break; + case BSQL_LAYER: + if ( backsql_api_config( bi, c->argv[ 1 ], c->argc - 2, &c->argv[ 2 ] ) ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s: unable to load sql layer", c->log ); + Debug( LDAP_DEBUG_ANY, "%s \"%s\"\n", + c->cr_msg, c->argv[1], 0 ); + return 1; + } + break; + case BSQL_ALIASING_KEYWORD: + if ( ! BER_BVISNULL( &bi->sql_aliasing ) ) { + ch_free( bi->sql_aliasing.bv_val ); + } + + ber_str2bv( c->argv[ 1 ], strlen( c->argv[ 1 ] ) + 1, 1, + &bi->sql_aliasing ); + /* add a trailing space... */ + bi->sql_aliasing.bv_val[ bi->sql_aliasing.bv_len - 1] = ' '; + break; + case BSQL_FETCH_ATTRS: { + char *str, *s, *next; + const char *delimstr = ","; + + str = ch_strdup( c->argv[ 1 ] ); + for ( s = ldap_pvt_strtok( str, delimstr, &next ); + s != NULL; + s = ldap_pvt_strtok( NULL, delimstr, &next ) ) + { + if ( strlen( s ) == 1 ) { + if ( *s == '*' ) { + bi->sql_flags |= BSQLF_FETCH_ALL_USERATTRS; + c->argv[ 1 ][ s - str ] = ','; + + } else if ( *s == '+' ) { + bi->sql_flags |= BSQLF_FETCH_ALL_OPATTRS; + c->argv[ 1 ][ s - str ] = ','; + } + } + } + ch_free( str ); + bi->sql_anlist = str2anlist( bi->sql_anlist, c->argv[ 1 ], delimstr ); + if ( bi->sql_anlist == NULL ) { + return -1; + } + } + break; + } + return rc; +} + +/* + * Read the entries specified in fname and merge the attributes + * to the user defined baseObject entry. Note that if we find any errors + * what so ever, we will discard the entire entries, print an + * error message and return. + */ +static int +read_baseObject( + BackendDB *be, + const char *fname ) +{ + backsql_info *bi = (backsql_info *)be->be_private; + LDIFFP *fp; + int rc = 0, lmax = 0, ldifrc; + unsigned long lineno = 0; + char *buf = NULL; + + assert( fname != NULL ); + + fp = ldif_open( fname, "r" ); + if ( fp == NULL ) { + Debug( LDAP_DEBUG_ANY, + "could not open back-sql baseObject " + "attr file \"%s\" - absolute path?\n", + fname, 0, 0 ); + perror( fname ); + return LDAP_OTHER; + } + + bi->sql_baseObject = entry_alloc(); + if ( bi->sql_baseObject == NULL ) { + Debug( LDAP_DEBUG_ANY, + "read_baseObject_file: entry_alloc failed", 0, 0, 0 ); + ldif_close( fp ); + return LDAP_NO_MEMORY; + } + bi->sql_baseObject->e_name = be->be_suffix[0]; + bi->sql_baseObject->e_nname = be->be_nsuffix[0]; + bi->sql_baseObject->e_attrs = NULL; + + while (( ldifrc = ldif_read_record( fp, &lineno, &buf, &lmax )) > 0 ) { + Entry *e = str2entry( buf ); + Attribute *a; + + if( e == NULL ) { + fprintf( stderr, "back-sql baseObject: " + "could not parse entry (line=%lu)\n", + lineno ); + rc = LDAP_OTHER; + break; + } + + /* make sure the DN is the database's suffix */ + if ( !be_issuffix( be, &e->e_nname ) ) { + fprintf( stderr, + "back-sql: invalid baseObject - " + "dn=\"%s\" (line=%lu)\n", + e->e_name.bv_val, lineno ); + entry_free( e ); + rc = LDAP_OTHER; + break; + } + + /* + * we found a valid entry, so walk thru all the attributes in the + * entry, and add each attribute type and description to baseObject + */ + for ( a = e->e_attrs; a != NULL; a = a->a_next ) { + if ( attr_merge( bi->sql_baseObject, a->a_desc, + a->a_vals, + ( a->a_nvals == a->a_vals ) ? + NULL : a->a_nvals ) ) + { + rc = LDAP_OTHER; + break; + } + } + + entry_free( e ); + if ( rc ) { + break; + } + } + + if ( ldifrc < 0 ) + rc = LDAP_OTHER; + + if ( rc ) { + entry_free( bi->sql_baseObject ); + bi->sql_baseObject = NULL; + } + + ch_free( buf ); + + ldif_close( fp ); + + Debug( LDAP_DEBUG_CONFIG, "back-sql baseObject file \"%s\" read.\n", + fname, 0, 0 ); + + return rc; +} + +static int +create_baseObject( + BackendDB *be, + const char *fname, + int lineno ) +{ + backsql_info *bi = (backsql_info *)be->be_private; + LDAPRDN rdn; + char *p; + int rc, iAVA; + char buf[1024]; + + snprintf( buf, sizeof(buf), + "dn: %s\n" + "objectClass: extensibleObject\n" + "description: builtin baseObject for back-sql\n" + "description: all entries mapped " + "in table \"ldap_entries\" " + "must have " + "\"" BACKSQL_BASEOBJECT_IDSTR "\" " + "in the \"parent\" column", + be->be_suffix[0].bv_val ); + + bi->sql_baseObject = str2entry( buf ); + if ( bi->sql_baseObject == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "<==backsql_db_config (%s line %d): " + "unable to parse baseObject entry\n", + fname, lineno, 0 ); + return 1; + } + + if ( BER_BVISEMPTY( &be->be_suffix[ 0 ] ) ) { + return 0; + } + + rc = ldap_bv2rdn( &be->be_suffix[ 0 ], &rdn, (char **)&p, + LDAP_DN_FORMAT_LDAP ); + if ( rc != LDAP_SUCCESS ) { + snprintf( buf, sizeof(buf), + "unable to extract RDN " + "from baseObject DN \"%s\" (%d: %s)", + be->be_suffix[ 0 ].bv_val, + rc, ldap_err2string( rc ) ); + Debug( LDAP_DEBUG_TRACE, + "<==backsql_db_config (%s line %d): %s\n", + fname, lineno, buf ); + return 1; + } + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ]; + AttributeDescription *ad = NULL; + slap_syntax_transform_func *transf = NULL; + struct berval bv = BER_BVNULL; + const char *text = NULL; + + assert( ava != NULL ); + + rc = slap_bv2ad( &ava->la_attr, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( buf, sizeof(buf), + "AttributeDescription of naming " + "attribute #%d from baseObject " + "DN \"%s\": %d: %s", + iAVA, be->be_suffix[ 0 ].bv_val, + rc, ldap_err2string( rc ) ); + Debug( LDAP_DEBUG_TRACE, + "<==backsql_db_config (%s line %d): %s\n", + fname, lineno, buf ); + return 1; + } + + transf = ad->ad_type->sat_syntax->ssyn_pretty; + if ( transf ) { + /* + * transform value by pretty function + * if value is empty, use empty_bv + */ + rc = ( *transf )( ad->ad_type->sat_syntax, + ava->la_value.bv_len + ? &ava->la_value + : (struct berval *) &slap_empty_bv, + &bv, NULL ); + + if ( rc != LDAP_SUCCESS ) { + snprintf( buf, sizeof(buf), + "prettying of attribute #%d " + "from baseObject " + "DN \"%s\" failed: %d: %s", + iAVA, be->be_suffix[ 0 ].bv_val, + rc, ldap_err2string( rc ) ); + Debug( LDAP_DEBUG_TRACE, + "<==backsql_db_config (%s line %d): " + "%s\n", + fname, lineno, buf ); + return 1; + } + } + + if ( !BER_BVISNULL( &bv ) ) { + if ( ava->la_flags & LDAP_AVA_FREE_VALUE ) { + ber_memfree( ava->la_value.bv_val ); + } + ava->la_value = bv; + ava->la_flags |= LDAP_AVA_FREE_VALUE; + } + + attr_merge_normalize_one( bi->sql_baseObject, + ad, &ava->la_value, NULL ); + } + + ldap_rdnfree( rdn ); + + return 0; +} + +int backsql_init_cf( BackendInfo *bi ) +{ + int rc; + + bi->bi_cf_ocs = sqlocs; + rc = config_register_schema( sqlcfg, sqlocs ); + if ( rc ) return rc; + return 0; +} diff --git a/servers/slapd/back-sql/delete.c b/servers/slapd/back-sql/delete.c new file mode 100644 index 0000000..78c632d --- /dev/null +++ b/servers/slapd/back-sql/delete.c @@ -0,0 +1,637 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> +#include "ac/string.h" + +#include "slap.h" +#include "proto-sql.h" + +typedef struct backsql_delete_attr_t { + Operation *op; + SlapReply *rs; + SQLHDBC dbh; + backsql_entryID *e_id; +} backsql_delete_attr_t; + +static int +backsql_delete_attr_f( void *v_at, void *v_bda ) +{ + backsql_at_map_rec *at = (backsql_at_map_rec *)v_at; + backsql_delete_attr_t *bda = (backsql_delete_attr_t *)v_bda; + int rc; + + rc = backsql_modify_delete_all_values( bda->op, + bda->rs, bda->dbh, bda->e_id, at ); + + if ( rc != LDAP_SUCCESS ) { + return BACKSQL_AVL_STOP; + } + + return BACKSQL_AVL_CONTINUE; +} + +static int +backsql_delete_all_attrs( + Operation *op, + SlapReply *rs, + SQLHDBC dbh, + backsql_entryID *eid ) +{ + backsql_delete_attr_t bda; + int rc; + + bda.op = op; + bda.rs = rs; + bda.dbh = dbh; + bda.e_id = eid; + + rc = avl_apply( eid->eid_oc->bom_attrs, backsql_delete_attr_f, &bda, + BACKSQL_AVL_STOP, AVL_INORDER ); + if ( rc == BACKSQL_AVL_STOP ) { + return rs->sr_err; + } + + return LDAP_SUCCESS; +} + +static int +backsql_delete_int( + Operation *op, + SlapReply *rs, + SQLHDBC dbh, + SQLHSTMT *sthp, + backsql_entryID *eid, + Entry **ep ) +{ + backsql_info *bi = (backsql_info*)op->o_bd->be_private; + SQLHSTMT sth = SQL_NULL_HSTMT; + RETCODE rc; + int prc = LDAP_SUCCESS; + /* first parameter no */ + SQLUSMALLINT pno = 0; + + sth = *sthp; + + /* avl_apply ... */ + rs->sr_err = backsql_delete_all_attrs( op, rs, dbh, eid ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto done; + } + + rc = backsql_Prepare( dbh, &sth, eid->eid_oc->bom_delete_proc, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_delete(): " + "error preparing delete query\n", + 0, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + *ep = NULL; + goto done; + } + + if ( BACKSQL_IS_DEL( eid->eid_oc->bom_expect_return ) ) { + pno = 1; + rc = backsql_BindParamInt( sth, 1, SQL_PARAM_OUTPUT, &prc ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_delete(): " + "error binding output parameter for objectClass %s\n", + eid->eid_oc->bom_oc->soc_cname.bv_val, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + *ep = NULL; + goto done; + } + } + + rc = backsql_BindParamID( sth, pno + 1, SQL_PARAM_INPUT, &eid->eid_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_delete(): " + "error binding keyval parameter for objectClass %s\n", + eid->eid_oc->bom_oc->soc_cname.bv_val, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + *ep = NULL; + goto done; + } + + rc = SQLExecute( sth ); + if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS ) { + rs->sr_err = LDAP_SUCCESS; + + } else { + Debug( LDAP_DEBUG_TRACE, " backsql_delete(): " + "delete_proc execution failed (rc=%d, prc=%d)\n", + rc, prc, 0 ); + + + if ( prc != LDAP_SUCCESS ) { + /* SQL procedure executed fine + * but returned an error */ + rs->sr_err = BACKSQL_SANITIZE_ERROR( prc ); + + } else { + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + rs->sr_err = LDAP_OTHER; + } + SQLFreeStmt( sth, SQL_DROP ); + goto done; + } + SQLFreeStmt( sth, SQL_DROP ); + + /* delete "auxiliary" objectClasses, if any... */ + rc = backsql_Prepare( dbh, &sth, bi->sql_delobjclasses_stmt, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_delete(): " + "error preparing ldap_entry_objclasses delete query\n", + 0, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + *ep = NULL; + goto done; + } + + rc = backsql_BindParamID( sth, 1, SQL_PARAM_INPUT, &eid->eid_id ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_delete(): " + "error binding auxiliary objectClasses " + "entry ID parameter for objectClass %s\n", + eid->eid_oc->bom_oc->soc_cname.bv_val, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + *ep = NULL; + goto done; + } + + rc = SQLExecute( sth ); + switch ( rc ) { + case SQL_NO_DATA: + /* apparently there were no "auxiliary" objectClasses + * for this entry... */ + case SQL_SUCCESS: + break; + + default: + Debug( LDAP_DEBUG_TRACE, " backsql_delete(): " + "failed to delete record from ldap_entry_objclasses\n", + 0, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + *ep = NULL; + goto done; + } + SQLFreeStmt( sth, SQL_DROP ); + + /* delete entry... */ + rc = backsql_Prepare( dbh, &sth, bi->sql_delentry_stmt, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_delete(): " + "error preparing ldap_entries delete query\n", + 0, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + *ep = NULL; + goto done; + } + + rc = backsql_BindParamID( sth, 1, SQL_PARAM_INPUT, &eid->eid_id ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_delete(): " + "error binding entry ID parameter " + "for objectClass %s\n", + eid->eid_oc->bom_oc->soc_cname.bv_val, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + *ep = NULL; + goto done; + } + + rc = SQLExecute( sth ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_delete(): " + "failed to delete record from ldap_entries\n", + 0, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + *ep = NULL; + goto done; + } + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_err = LDAP_SUCCESS; + *ep = NULL; + +done:; + *sthp = sth; + + return rs->sr_err; +} + +typedef struct backsql_tree_delete_t { + Operation *btd_op; + int btd_rc; + backsql_entryID *btd_eid; +} backsql_tree_delete_t; + +static int +backsql_tree_delete_search_cb( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH ) { + backsql_tree_delete_t *btd; + backsql_entryID *eid; + + btd = (backsql_tree_delete_t *)op->o_callback->sc_private; + + if ( !access_allowed( btd->btd_op, rs->sr_entry, + slap_schema.si_ad_entry, NULL, ACL_WDEL, NULL ) + || !access_allowed( btd->btd_op, rs->sr_entry, + slap_schema.si_ad_children, NULL, ACL_WDEL, NULL ) ) + { + btd->btd_rc = LDAP_INSUFFICIENT_ACCESS; + return rs->sr_err = LDAP_UNAVAILABLE; + } + + assert( rs->sr_entry != NULL ); + assert( rs->sr_entry->e_private != NULL ); + + eid = (backsql_entryID *)rs->sr_entry->e_private; + assert( eid->eid_oc != NULL ); + if ( eid->eid_oc == NULL || eid->eid_oc->bom_delete_proc == NULL ) { + btd->btd_rc = LDAP_UNWILLING_TO_PERFORM; + return rs->sr_err = LDAP_UNAVAILABLE; + } + + eid = backsql_entryID_dup( eid, op->o_tmpmemctx ); + eid->eid_next = btd->btd_eid; + btd->btd_eid = eid; + } + + return 0; +} + +static int +backsql_tree_delete( + Operation *op, + SlapReply *rs, + SQLHDBC dbh, + SQLHSTMT *sthp ) +{ + Operation op2 = *op; + slap_callback sc = { 0 }; + SlapReply rs2 = { REP_RESULT }; + backsql_tree_delete_t btd = { 0 }; + + int rc; + + /* + * - perform an internal subtree search as the rootdn + * - for each entry + * - check access + * - check objectClass and delete method(s) + * - for each entry + * - delete + * - if successful, commit + */ + + op2.o_tag = LDAP_REQ_SEARCH; + op2.o_protocol = LDAP_VERSION3; + + btd.btd_op = op; + sc.sc_private = &btd; + sc.sc_response = backsql_tree_delete_search_cb; + op2.o_callback = ≻ + + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + + op2.o_managedsait = SLAP_CONTROL_CRITICAL; + + op2.ors_scope = LDAP_SCOPE_SUBTREE; + op2.ors_deref = LDAP_DEREF_NEVER; + op2.ors_slimit = SLAP_NO_LIMIT; + op2.ors_tlimit = SLAP_NO_LIMIT; + op2.ors_filter = (Filter *)slap_filter_objectClass_pres; + op2.ors_filterstr = *slap_filterstr_objectClass_pres; + op2.ors_attrs = slap_anlist_all_attributes; + op2.ors_attrsonly = 0; + + rc = op->o_bd->be_search( &op2, &rs2 ); + if ( rc != LDAP_SUCCESS ) { + rc = rs->sr_err = btd.btd_rc; + rs->sr_text = "subtree delete not possible"; + send_ldap_result( op, rs ); + goto clean; + } + + for ( ; btd.btd_eid != NULL; + btd.btd_eid = backsql_free_entryID( btd.btd_eid, + 1, op->o_tmpmemctx ) ) + { + Entry *e = (void *)0xbad; + rc = backsql_delete_int( op, rs, dbh, sthp, btd.btd_eid, &e ); + if ( rc != LDAP_SUCCESS ) { + break; + } + } + +clean:; + for ( ; btd.btd_eid != NULL; + btd.btd_eid = backsql_free_entryID( btd.btd_eid, + 1, op->o_tmpmemctx ) ) + ; + + return rc; +} + +int +backsql_delete( Operation *op, SlapReply *rs ) +{ + SQLHDBC dbh = SQL_NULL_HDBC; + SQLHSTMT sth = SQL_NULL_HSTMT; + backsql_oc_map_rec *oc = NULL; + backsql_srch_info bsi = { 0 }; + backsql_entryID e_id = { 0 }; + Entry d = { 0 }, p = { 0 }, *e = NULL; + struct berval pdn = BER_BVNULL; + int manageDSAit = get_manageDSAit( op ); + + Debug( LDAP_DEBUG_TRACE, "==>backsql_delete(): deleting entry \"%s\"\n", + op->o_req_ndn.bv_val, 0, 0 ); + + rs->sr_err = backsql_get_db_conn( op, &dbh ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_delete(): " + "could not get connection handle - exiting\n", + 0, 0, 0 ); + rs->sr_text = ( rs->sr_err == LDAP_OTHER ) + ? "SQL-backend error" : NULL; + e = NULL; + goto done; + } + + /* + * Get the entry + */ + bsi.bsi_e = &d; + rs->sr_err = backsql_init_search( &bsi, &op->o_req_ndn, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, slap_anlist_no_attrs, + ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY | BACKSQL_ISF_GET_OC ) ); + switch ( rs->sr_err ) { + case LDAP_SUCCESS: + break; + + case LDAP_REFERRAL: + if ( manageDSAit && !BER_BVISNULL( &bsi.bsi_e->e_nname ) && + dn_match( &op->o_req_ndn, &bsi.bsi_e->e_nname ) ) + { + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + break; + } + e = &d; + /* fallthru */ + + default: + Debug( LDAP_DEBUG_TRACE, "backsql_delete(): " + "could not retrieve deleteDN ID - no such entry\n", + 0, 0, 0 ); + if ( !BER_BVISNULL( &d.e_nname ) ) { + /* FIXME: should always be true! */ + e = &d; + + } else { + e = NULL; + } + goto done; + } + + if ( get_assert( op ) && + ( test_filter( op, &d, get_assertion( op ) ) + != LDAP_COMPARE_TRUE ) ) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + e = &d; + goto done; + } + + if ( !access_allowed( op, &d, slap_schema.si_ad_entry, + NULL, ACL_WDEL, NULL ) ) + { + Debug( LDAP_DEBUG_TRACE, " backsql_delete(): " + "no write access to entry\n", + 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + e = &d; + goto done; + } + + rs->sr_err = backsql_has_children( op, dbh, &op->o_req_ndn ); + switch ( rs->sr_err ) { + case LDAP_COMPARE_FALSE: + rs->sr_err = LDAP_SUCCESS; + break; + + case LDAP_COMPARE_TRUE: +#ifdef SLAP_CONTROL_X_TREE_DELETE + if ( get_treeDelete( op ) ) { + rs->sr_err = LDAP_SUCCESS; + break; + } +#endif /* SLAP_CONTROL_X_TREE_DELETE */ + + Debug( LDAP_DEBUG_TRACE, " backsql_delete(): " + "entry \"%s\" has children\n", + op->o_req_dn.bv_val, 0, 0 ); + rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF; + rs->sr_text = "subordinate objects must be deleted first"; + /* fallthru */ + + default: + e = &d; + goto done; + } + + assert( bsi.bsi_base_id.eid_oc != NULL ); + oc = bsi.bsi_base_id.eid_oc; + if ( oc->bom_delete_proc == NULL ) { + Debug( LDAP_DEBUG_TRACE, " backsql_delete(): " + "delete procedure is not defined " + "for this objectclass - aborting\n", 0, 0, 0 ); + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "operation not permitted within namingContext"; + e = NULL; + goto done; + } + + /* + * Get the parent + */ + e_id = bsi.bsi_base_id; + memset( &bsi.bsi_base_id, 0, sizeof( bsi.bsi_base_id ) ); + if ( !be_issuffix( op->o_bd, &op->o_req_ndn ) ) { + dnParent( &op->o_req_ndn, &pdn ); + bsi.bsi_e = &p; + rs->sr_err = backsql_init_search( &bsi, &pdn, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, + slap_anlist_no_attrs, + BACKSQL_ISF_GET_ENTRY ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_delete(): " + "could not retrieve deleteDN ID " + "- no such entry\n", + 0, 0, 0 ); + e = &p; + goto done; + } + + (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx ); + + /* check parent for "children" acl */ + if ( !access_allowed( op, &p, slap_schema.si_ad_children, + NULL, ACL_WDEL, NULL ) ) + { + Debug( LDAP_DEBUG_TRACE, " backsql_delete(): " + "no write access to parent\n", + 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + e = &p; + goto done; + + } + } + + e = &d; +#ifdef SLAP_CONTROL_X_TREE_DELETE + if ( get_treeDelete( op ) ) { + backsql_tree_delete( op, rs, dbh, &sth ); + if ( rs->sr_err == LDAP_OTHER || rs->sr_err == LDAP_SUCCESS ) + { + e = NULL; + } + + } else +#endif /* SLAP_CONTROL_X_TREE_DELETE */ + { + backsql_delete_int( op, rs, dbh, &sth, &e_id, &e ); + } + + /* + * Commit only if all operations succeed + */ + if ( sth != SQL_NULL_HSTMT ) { + SQLUSMALLINT CompletionType = SQL_ROLLBACK; + + if ( rs->sr_err == LDAP_SUCCESS && !op->o_noop ) { + assert( e == NULL ); + CompletionType = SQL_COMMIT; + } + + SQLTransact( SQL_NULL_HENV, dbh, CompletionType ); + } + +done:; + if ( e != NULL ) { + if ( !access_allowed( op, e, slap_schema.si_ad_entry, NULL, + ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + } + } + + if ( op->o_noop && rs->sr_err == LDAP_SUCCESS ) { + rs->sr_err = LDAP_X_NO_OPERATION; + } + + send_ldap_result( op, rs ); + + Debug( LDAP_DEBUG_TRACE, "<==backsql_delete()\n", 0, 0, 0 ); + + if ( !BER_BVISNULL( &e_id.eid_ndn ) ) { + (void)backsql_free_entryID( &e_id, 0, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &d.e_nname ) ) { + backsql_entry_clean( op, &d ); + } + + if ( !BER_BVISNULL( &p.e_nname ) ) { + backsql_entry_clean( op, &p ); + } + + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + + return rs->sr_err; +} + diff --git a/servers/slapd/back-sql/docs/bugs b/servers/slapd/back-sql/docs/bugs new file mode 100644 index 0000000..ae47e67 --- /dev/null +++ b/servers/slapd/back-sql/docs/bugs @@ -0,0 +1,16 @@ +1) driver name comparison for MS SQL Server workaround is realy kinda dirty + hack, but for now i don't know how to code it more carefully +2) another dirty hack: length of LONGVARCHAR and LONGVARBINARY fields is + currently set to MAX_ATTR_LEN. Maybe such fields must be handled with + SQLGetData() instead of SQLBindCol(), but it is said in documentation, + that it is guaranteed to work only when such column goes after last bound + column. Or should we get ALL columns with SQLGetData (then something like + _SQLFetchAsStrings() wrapper would do SQLGetData() for all columns)... +3) in some cases (particularly, when using OpenLink Generic ODBC driver with + MS SQL Server), it returns "Function sequence error" after all records are + fetched. I really don't know what it means, and after all + - it works with any other driver I tried +4) ldapsearch sometimes refuses to show some attributes ("NOT PRINTABLE" diags) + on Win32 (on linux everything's fine) +5) back-sql crashes on invalid filters (to be fixed ASAP) +
\ No newline at end of file diff --git a/servers/slapd/back-sql/docs/concept b/servers/slapd/back-sql/docs/concept new file mode 100644 index 0000000..ed29047 --- /dev/null +++ b/servers/slapd/back-sql/docs/concept @@ -0,0 +1 @@ +The SQL backend is described in the slapd-sql(5) manual page. diff --git a/servers/slapd/back-sql/docs/install b/servers/slapd/back-sql/docs/install new file mode 100644 index 0000000..230bf0a --- /dev/null +++ b/servers/slapd/back-sql/docs/install @@ -0,0 +1,86 @@ +PLEASE READ THIS WHOLE FILE AND CONCEPT, BECAUSE THEY COVER SEVERAL STICKY +ISSUES THAT YOU WILL PROBABLY STUMBLE ACROSS ANYWAY + +1. Build +To build slapd with back-sql under Unix you need to build and install +iODBC 2.50.3 (later versions should probably work, but not earlier), +or unixODBC (you will have to change -liodbc to -lodbc then). +Then, at top of OpenLDAP source tree, run +"configure <other options you need> --enable-sql", then "make" - +this should build back-sql-enabled slapd, provided that you have iODBC/unixODBC +libraries and include files in include/library paths, "make install"... +In other words, follow installation procedure described in OpenLDAP +Administrators Guide, adding --enable-sql option to configure, and +having iODBC/unixODBC libraries installed an accessible by compiler. + +Under Win32/MSVC++, I modified the workspace so that back-sql is built into +slapd automatically, since MS ODBC manager, odbc32.dll, is included in +standard library pack, and it does no bad even if you don't plan to use it. +I also could provide precompiled executables for those who don't have MSVC. +Note that Win32 port of OpenLDAP itself is experimental, and thus doesn't +provide very convenient build environment (yet). + +2. Tune datasources and slapd.conf +Next, you need to define ODBC datasource with data you want to publish +with help of back-sql. Assuming that you have your data in some SQL-compliant +RDBMS, and have installed proper ODBC driver for this RDBMS, this is as simple +as adding a record into odbc.ini (for iODBC/unixODBC), or using ODBC wizard in +Control Panel (for odbc32). +Next, you need to add appropriate "database" record to your slapd.conf. +See samples provided in "back-sql/RDBMS_DEPENDENT/" subdirectory. + +Several things worth noting about ODBC: +- "dbname" directive stands for ODBC datasource name (DSN), + not the name of your database in RDBMS context +- ODBC under Unix is not so common as under Windows, so you could have + problems with Unix drivers for your RDBMS. Visit http://www.openlinksw.com, + they provide a multitier solution which allows connecting to DBMSes on + different platforms, proxying and other connectivity and integration issues. + They also support iODBC, and have good free customer service through + newsserver (at news.openlinksw.com). + Also worth noting are: ODBC-ODBC bridge by EasySoft (which was claimed + by several people to be far more effective and stable than OpenLink), + OpenRDA package etc. +- be careful defining RDBMS connection parameters, you'll probably need only + "dbname" directive - all the rest can be defined in datasource. Every other + directive is used to override value stored in datasource definition. + Maybe you will want to use dbuser/dbpasswd to override credentials defined in datasource +- full list of configuration directives supported is available in file "guide", + you may also analyze output of 'slapd -d 5' to find out some useful + directives for redefining default queries + +3. Creating and using back-sql metatables +Read the file "concept" to understand, what metainformation you need to add, +and what for... ;) +See SQL scripts and slapd.conf files in samples directory. +Find subdirectory in "rdbms_depend/" corresponding to your RDBMS (Oracle, +MS SQL Server and mySQL are listed there currently), or copy and edit +any of these to conform to SQL dialect of your RDBMS (please be sure to send +me scripts and notes for new RDBMSes ;). + +Execute "backsql_create.sql" from that subdirectory (or edited one), +so that the tables it creates appear in the same +context with the data you want to export through LDAP (under same DB/user, +or whatever is needed in RDBMS you use). You can use something like +"mysql < xxx.sql" for mySQL, Query Analyzer+Open query file for MS SQL, +sqlplus and "@xxx.sql" for Oracle. + +You may well want to try it with test data first, and see how metatables +are used. Create test data and metadata by running testdb_create.sql, +testdb_data.sql, and testdb_metadata.sql scripts (again, adopted for your +RDBMS, and in the same context as metatables you created before), and +tune slapd.conf to use your test DB. + +4. Testing +To diagnose back-sql, run slapd with debug level TRACE ("slapd -d 5" will go). +Then, use some LDAP client to query corresponding subtree (for test database, +you could for instance search one level from "o=sql,c=RU"). I personally used +saucer, which is included in OpenLDAP package (it builds automatically under +Unix/GNU configure and for MSVC I added appropriate project to workspace). +And also Java LDAP browser-editor (see link somewhere on OpenLDAP site) to +test ADD/DELETE/MODIFY operations on Oracle and MS SQL. + +See file "platforms" if you encounter connection problems - you may find +a hint for your RDBMS or OS there. If you are stuck - please contact me at +mit@openldap.org, or (better) post an issue through OpenLDAP's Issue Tracking +System (see http:/www.openldap.org/its). diff --git a/servers/slapd/back-sql/docs/platforms b/servers/slapd/back-sql/docs/platforms new file mode 100644 index 0000000..65e326a --- /dev/null +++ b/servers/slapd/back-sql/docs/platforms @@ -0,0 +1,8 @@ +Platforms and configurations it has been tested on: + +General: + - ODBC managers: iODBC,unixODBC under unixes, odbc32.dll under Win32 family + - OSes: Linux/glibc, FreeBSD, OpenBSD, Solaris 2.6, Win98, WinNT, Win2000 server + - RDBMSes: Oracle 7/8/8i, MS SQL Server 6.5/7, mySQL + - access suites: OpenLink DAS, EasySoft OOB, various win32 drivers + diff --git a/servers/slapd/back-sql/docs/todo b/servers/slapd/back-sql/docs/todo new file mode 100644 index 0000000..9d8736f --- /dev/null +++ b/servers/slapd/back-sql/docs/todo @@ -0,0 +1,12 @@ +1) must add alias handling +2) [sizelimit moved to frontend] + must set time limit when preparing all queries, and check size limit +3) there was expressed a need to have access to IP in while constructing + queries, to have response alter in accordance to client IP. Will add + preprocessor for values in metatables, which would substitute things + like "$IP$". +4) must handle NOT filters (see ITS#2652) +5) must map attribute types and syntaxes between LDAP and SQL types (e.g. + use BLOBs for octet streams) +6) must define another mech to add auxiliary objectClass to all entries + according to ldap_at_mappings (ldap_entry_objclasses has limitations) diff --git a/servers/slapd/back-sql/entry-id.c b/servers/slapd/back-sql/entry-id.c new file mode 100644 index 0000000..de2513f --- /dev/null +++ b/servers/slapd/back-sql/entry-id.c @@ -0,0 +1,1107 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * Portions Copyright 2004 Mark Adamson. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati and Mark Adamson. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> +#include "ac/string.h" + +#include "lutil.h" +#include "slap.h" +#include "proto-sql.h" + +#ifdef BACKSQL_ARBITRARY_KEY +struct berval backsql_baseObject_bv = BER_BVC( BACKSQL_BASEOBJECT_IDSTR ); +#endif /* BACKSQL_ARBITRARY_KEY */ + +backsql_entryID * +backsql_entryID_dup( backsql_entryID *src, void *ctx ) +{ + backsql_entryID *dst; + + if ( src == NULL ) return NULL; + + dst = slap_sl_calloc( 1, sizeof( backsql_entryID ), ctx ); + ber_dupbv_x( &dst->eid_ndn, &src->eid_ndn, ctx ); + if ( src->eid_dn.bv_val == src->eid_ndn.bv_val ) { + dst->eid_dn = dst->eid_ndn; + } else { + ber_dupbv_x( &dst->eid_dn, &src->eid_dn, ctx ); + } + +#ifdef BACKSQL_ARBITRARY_KEY + ber_dupbv_x( &dst->eid_id, &src->eid_id, ctx ); + ber_dupbv_x( &dst->eid_keyval, &src->eid_keyval, ctx ); +#else /* ! BACKSQL_ARBITRARY_KEY */ + dst->eid_id = src->eid_id; + dst->eid_keyval = src->eid_keyval; +#endif /* ! BACKSQL_ARBITRARY_KEY */ + + dst->eid_oc = src->eid_oc; + dst->eid_oc_id = src->eid_oc_id; + + return dst; +} + +backsql_entryID * +backsql_free_entryID( backsql_entryID *id, int freeit, void *ctx ) +{ + backsql_entryID *next; + + assert( id != NULL ); + + next = id->eid_next; + + if ( !BER_BVISNULL( &id->eid_ndn ) ) { + if ( !BER_BVISNULL( &id->eid_dn ) + && id->eid_dn.bv_val != id->eid_ndn.bv_val ) + { + slap_sl_free( id->eid_dn.bv_val, ctx ); + BER_BVZERO( &id->eid_dn ); + } + + slap_sl_free( id->eid_ndn.bv_val, ctx ); + BER_BVZERO( &id->eid_ndn ); + } + +#ifdef BACKSQL_ARBITRARY_KEY + if ( !BER_BVISNULL( &id->eid_id ) ) { + slap_sl_free( id->eid_id.bv_val, ctx ); + BER_BVZERO( &id->eid_id ); + } + + if ( !BER_BVISNULL( &id->eid_keyval ) ) { + slap_sl_free( id->eid_keyval.bv_val, ctx ); + BER_BVZERO( &id->eid_keyval ); + } +#endif /* BACKSQL_ARBITRARY_KEY */ + + if ( freeit ) { + slap_sl_free( id, ctx ); + } + + return next; +} + +/* + * NOTE: the dn must be normalized + */ +int +backsql_dn2id( + Operation *op, + SlapReply *rs, + SQLHDBC dbh, + struct berval *ndn, + backsql_entryID *id, + int matched, + int muck ) +{ + backsql_info *bi = op->o_bd->be_private; + SQLHSTMT sth = SQL_NULL_HSTMT; + BACKSQL_ROW_NTS row = { 0 }; + RETCODE rc; + int res; + struct berval realndn = BER_BVNULL; + + /* TimesTen */ + char upperdn[ BACKSQL_MAX_DN_LEN + 1 ]; + struct berval tbbDN; + int i, j; + + /* + * NOTE: id can be NULL; in this case, the function + * simply checks whether the DN can be successfully + * turned into an ID, returning LDAP_SUCCESS for + * positive cases, or the most appropriate error + */ + + Debug( LDAP_DEBUG_TRACE, "==>backsql_dn2id(\"%s\")%s%s\n", + ndn->bv_val, id == NULL ? " (no ID expected)" : "", + matched ? " matched expected" : "" ); + + if ( id ) { + /* NOTE: trap inconsistencies */ + assert( BER_BVISNULL( &id->eid_ndn ) ); + } + + if ( ndn->bv_len > BACKSQL_MAX_DN_LEN ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_dn2id(\"%s\"): DN length=%ld " + "exceeds max DN length %d:\n", + ndn->bv_val, ndn->bv_len, BACKSQL_MAX_DN_LEN ); + return LDAP_OTHER; + } + + /* return baseObject if available and matches */ + /* FIXME: if ndn is already mucked, we cannot check this */ + if ( bi->sql_baseObject != NULL && + dn_match( ndn, &bi->sql_baseObject->e_nname ) ) + { + if ( id != NULL ) { +#ifdef BACKSQL_ARBITRARY_KEY + ber_dupbv_x( &id->eid_id, &backsql_baseObject_bv, + op->o_tmpmemctx ); + ber_dupbv_x( &id->eid_keyval, &backsql_baseObject_bv, + op->o_tmpmemctx ); +#else /* ! BACKSQL_ARBITRARY_KEY */ + id->eid_id = BACKSQL_BASEOBJECT_ID; + id->eid_keyval = BACKSQL_BASEOBJECT_KEYVAL; +#endif /* ! BACKSQL_ARBITRARY_KEY */ + id->eid_oc_id = BACKSQL_BASEOBJECT_OC; + + ber_dupbv_x( &id->eid_ndn, &bi->sql_baseObject->e_nname, + op->o_tmpmemctx ); + ber_dupbv_x( &id->eid_dn, &bi->sql_baseObject->e_name, + op->o_tmpmemctx ); + + id->eid_next = NULL; + } + + return LDAP_SUCCESS; + } + + /* begin TimesTen */ + assert( bi->sql_id_query != NULL ); + Debug( LDAP_DEBUG_TRACE, " backsql_dn2id(\"%s\"): id_query \"%s\"\n", + ndn->bv_val, bi->sql_id_query, 0 ); + rc = backsql_Prepare( dbh, &sth, bi->sql_id_query, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_dn2id(\"%s\"): " + "error preparing SQL:\n %s", + ndn->bv_val, bi->sql_id_query, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + res = LDAP_OTHER; + goto done; + } + + realndn = *ndn; + if ( muck ) { + if ( backsql_api_dn2odbc( op, rs, &realndn ) ) { + Debug( LDAP_DEBUG_TRACE, " backsql_dn2id(\"%s\"): " + "backsql_api_dn2odbc(\"%s\") failed\n", + ndn->bv_val, realndn.bv_val, 0 ); + res = LDAP_OTHER; + goto done; + } + } + + if ( BACKSQL_HAS_LDAPINFO_DN_RU( bi ) ) { + /* + * Prepare an upper cased, byte reversed version + * that can be searched using indexes + */ + + for ( i = 0, j = realndn.bv_len - 1; realndn.bv_val[ i ]; i++, j--) + { + upperdn[ i ] = realndn.bv_val[ j ]; + } + upperdn[ i ] = '\0'; + ldap_pvt_str2upper( upperdn ); + + Debug( LDAP_DEBUG_TRACE, " backsql_dn2id(\"%s\"): " + "upperdn=\"%s\"\n", + ndn->bv_val, upperdn, 0 ); + ber_str2bv( upperdn, 0, 0, &tbbDN ); + + } else { + if ( BACKSQL_USE_REVERSE_DN( bi ) ) { + AC_MEMCPY( upperdn, realndn.bv_val, realndn.bv_len + 1 ); + ldap_pvt_str2upper( upperdn ); + Debug( LDAP_DEBUG_TRACE, + " backsql_dn2id(\"%s\"): " + "upperdn=\"%s\"\n", + ndn->bv_val, upperdn, 0 ); + ber_str2bv( upperdn, 0, 0, &tbbDN ); + + } else { + tbbDN = realndn; + } + } + + rc = backsql_BindParamBerVal( sth, 1, SQL_PARAM_INPUT, &tbbDN ); + if ( rc != SQL_SUCCESS) { + /* end TimesTen */ + Debug( LDAP_DEBUG_TRACE, " backsql_dn2id(\"%s\"): " + "error binding dn=\"%s\" parameter:\n", + ndn->bv_val, tbbDN.bv_val, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + res = LDAP_OTHER; + goto done; + } + + rc = SQLExecute( sth ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_dn2id(\"%s\"): " + "error executing query (\"%s\", \"%s\"):\n", + ndn->bv_val, bi->sql_id_query, tbbDN.bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + res = LDAP_OTHER; + goto done; + } + + backsql_BindRowAsStrings_x( sth, &row, op->o_tmpmemctx ); + rc = SQLFetch( sth ); + if ( BACKSQL_SUCCESS( rc ) ) { + char buf[ SLAP_TEXT_BUFLEN ]; + +#ifdef LDAP_DEBUG + snprintf( buf, sizeof(buf), + "id=%s keyval=%s oc_id=%s dn=%s", + row.cols[ 0 ], row.cols[ 1 ], + row.cols[ 2 ], row.cols[ 3 ] ); + Debug( LDAP_DEBUG_TRACE, + " backsql_dn2id(\"%s\"): %s\n", + ndn->bv_val, buf, 0 ); +#endif /* LDAP_DEBUG */ + + res = LDAP_SUCCESS; + if ( id != NULL ) { + struct berval dn; + + id->eid_next = NULL; + +#ifdef BACKSQL_ARBITRARY_KEY + ber_str2bv_x( row.cols[ 0 ], 0, 1, &id->eid_id, + op->o_tmpmemctx ); + ber_str2bv_x( row.cols[ 1 ], 0, 1, &id->eid_keyval, + op->o_tmpmemctx ); +#else /* ! BACKSQL_ARBITRARY_KEY */ + if ( BACKSQL_STR2ID( &id->eid_id, row.cols[ 0 ], 0 ) != 0 ) { + res = LDAP_OTHER; + goto done; + } + if ( BACKSQL_STR2ID( &id->eid_keyval, row.cols[ 1 ], 0 ) != 0 ) { + res = LDAP_OTHER; + goto done; + } +#endif /* ! BACKSQL_ARBITRARY_KEY */ + if ( BACKSQL_STR2ID( &id->eid_oc_id, row.cols[ 2 ], 0 ) != 0 ) { + res = LDAP_OTHER; + goto done; + } + + ber_str2bv( row.cols[ 3 ], 0, 0, &dn ); + + if ( backsql_api_odbc2dn( op, rs, &dn ) ) { + res = LDAP_OTHER; + goto done; + } + + res = dnPrettyNormal( NULL, &dn, + &id->eid_dn, &id->eid_ndn, + op->o_tmpmemctx ); + if ( res != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_dn2id(\"%s\"): " + "dnPrettyNormal failed (%d: %s)\n", + realndn.bv_val, res, + ldap_err2string( res ) ); + + /* cleanup... */ + (void)backsql_free_entryID( id, 0, op->o_tmpmemctx ); + } + + if ( dn.bv_val != row.cols[ 3 ] ) { + free( dn.bv_val ); + } + } + + } else { + res = LDAP_NO_SUCH_OBJECT; + if ( matched ) { + struct berval pdn = *ndn; + + /* + * Look for matched + */ + rs->sr_matched = NULL; + while ( !be_issuffix( op->o_bd, &pdn ) ) { + char *matchedDN = NULL; + + dnParent( &pdn, &pdn ); + + /* + * Empty DN ("") defaults to LDAP_SUCCESS + */ + rs->sr_err = backsql_dn2id( op, rs, dbh, &pdn, id, 0, 1 ); + switch ( rs->sr_err ) { + case LDAP_NO_SUCH_OBJECT: + /* try another one */ + break; + + case LDAP_SUCCESS: + matchedDN = pdn.bv_val; + /* fail over to next case */ + + default: + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_matched = matchedDN; + goto done; + } + } + } + } + +done:; + backsql_FreeRow_x( &row, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, + "<==backsql_dn2id(\"%s\"): err=%d\n", + ndn->bv_val, res, 0 ); + if ( sth != SQL_NULL_HSTMT ) { + SQLFreeStmt( sth, SQL_DROP ); + } + + if ( !BER_BVISNULL( &realndn ) && realndn.bv_val != ndn->bv_val ) { + ch_free( realndn.bv_val ); + } + + return res; +} + +int +backsql_count_children( + Operation *op, + SQLHDBC dbh, + struct berval *dn, + unsigned long *nchildren ) +{ + backsql_info *bi = (backsql_info *)op->o_bd->be_private; + SQLHSTMT sth = SQL_NULL_HSTMT; + BACKSQL_ROW_NTS row; + RETCODE rc; + int res = LDAP_SUCCESS; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_count_children(): dn=\"%s\"\n", + dn->bv_val, 0, 0 ); + + if ( dn->bv_len > BACKSQL_MAX_DN_LEN ) { + Debug( LDAP_DEBUG_TRACE, + "backsql_count_children(): DN \"%s\" (%ld bytes) " + "exceeds max DN length (%d):\n", + dn->bv_val, dn->bv_len, BACKSQL_MAX_DN_LEN ); + return LDAP_OTHER; + } + + /* begin TimesTen */ + assert( bi->sql_has_children_query != NULL ); + Debug(LDAP_DEBUG_TRACE, "children id query \"%s\"\n", + bi->sql_has_children_query, 0, 0); + rc = backsql_Prepare( dbh, &sth, bi->sql_has_children_query, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "backsql_count_children(): error preparing SQL:\n%s", + bi->sql_has_children_query, 0, 0); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + return LDAP_OTHER; + } + + rc = backsql_BindParamBerVal( sth, 1, SQL_PARAM_INPUT, dn ); + if ( rc != SQL_SUCCESS) { + /* end TimesTen */ + Debug( LDAP_DEBUG_TRACE, "backsql_count_children(): " + "error binding dn=\"%s\" parameter:\n", + dn->bv_val, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + return LDAP_OTHER; + } + + rc = SQLExecute( sth ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_count_children(): " + "error executing query (\"%s\", \"%s\"):\n", + bi->sql_has_children_query, dn->bv_val, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + return LDAP_OTHER; + } + + backsql_BindRowAsStrings_x( sth, &row, op->o_tmpmemctx ); + + rc = SQLFetch( sth ); + if ( BACKSQL_SUCCESS( rc ) ) { + char *end; + + *nchildren = strtol( row.cols[ 0 ], &end, 0 ); + if ( end == row.cols[ 0 ] ) { + res = LDAP_OTHER; + + } else { + switch ( end[ 0 ] ) { + case '\0': + break; + + case '.': { + unsigned long ul; + + /* FIXME: braindead RDBMSes return + * a fractional number from COUNT! + */ + if ( lutil_atoul( &ul, end + 1 ) != 0 || ul != 0 ) { + res = LDAP_OTHER; + } + } break; + + default: + res = LDAP_OTHER; + } + } + + } else { + res = LDAP_OTHER; + } + backsql_FreeRow_x( &row, op->o_tmpmemctx ); + + SQLFreeStmt( sth, SQL_DROP ); + + Debug( LDAP_DEBUG_TRACE, "<==backsql_count_children(): %lu\n", + *nchildren, 0, 0 ); + + return res; +} + +int +backsql_has_children( + Operation *op, + SQLHDBC dbh, + struct berval *dn ) +{ + unsigned long nchildren; + int rc; + + rc = backsql_count_children( op, dbh, dn, &nchildren ); + + if ( rc == LDAP_SUCCESS ) { + return nchildren > 0 ? LDAP_COMPARE_TRUE : LDAP_COMPARE_FALSE; + } + + return rc; +} + +static int +backsql_get_attr_vals( void *v_at, void *v_bsi ) +{ + backsql_at_map_rec *at = v_at; + backsql_srch_info *bsi = v_bsi; + backsql_info *bi; + RETCODE rc; + SQLHSTMT sth = SQL_NULL_HSTMT; + BACKSQL_ROW_NTS row; + unsigned long i, + k = 0, + oldcount = 0, + res = 0; +#ifdef BACKSQL_COUNTQUERY + unsigned count, + j, + append = 0; + SQLLEN countsize = sizeof( count ); + Attribute *attr = NULL; + + slap_mr_normalize_func *normfunc = NULL; +#endif /* BACKSQL_COUNTQUERY */ +#ifdef BACKSQL_PRETTY_VALIDATE + slap_syntax_validate_func *validate = NULL; + slap_syntax_transform_func *pretty = NULL; +#endif /* BACKSQL_PRETTY_VALIDATE */ + + assert( at != NULL ); + assert( bsi != NULL ); + Debug( LDAP_DEBUG_TRACE, "==>backsql_get_attr_vals(): " + "oc=\"%s\" attr=\"%s\" keyval=" BACKSQL_IDFMT "\n", + BACKSQL_OC_NAME( bsi->bsi_oc ), at->bam_ad->ad_cname.bv_val, + BACKSQL_IDARG(bsi->bsi_c_eid->eid_keyval) ); + + bi = (backsql_info *)bsi->bsi_op->o_bd->be_private; + +#ifdef BACKSQL_PRETTY_VALIDATE + validate = at->bam_true_ad->ad_type->sat_syntax->ssyn_validate; + pretty = at->bam_true_ad->ad_type->sat_syntax->ssyn_pretty; + + if ( validate == NULL && pretty == NULL ) { + return 1; + } +#endif /* BACKSQL_PRETTY_VALIDATE */ + +#ifdef BACKSQL_COUNTQUERY + if ( at->bam_true_ad->ad_type->sat_equality ) { + normfunc = at->bam_true_ad->ad_type->sat_equality->smr_normalize; + } + + /* Count how many rows will be returned. This avoids memory + * fragmentation that can result from loading the values in + * one by one and using realloc() + */ + rc = backsql_Prepare( bsi->bsi_dbh, &sth, at->bam_countquery, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): " + "error preparing count query: %s\n", + at->bam_countquery, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc ); + return 1; + } + + rc = backsql_BindParamID( sth, 1, SQL_PARAM_INPUT, + &bsi->bsi_c_eid->eid_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): " + "error binding key value parameter\n", 0, 0, 0 ); + SQLFreeStmt( sth, SQL_DROP ); + return 1; + } + + rc = SQLExecute( sth ); + if ( ! BACKSQL_SUCCESS( rc ) ) { + Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): " + "error executing attribute count query '%s'\n", + at->bam_countquery, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + return 1; + } + + SQLBindCol( sth, (SQLUSMALLINT)1, SQL_C_LONG, + (SQLPOINTER)&count, + (SQLINTEGER)sizeof( count ), + &countsize ); + + rc = SQLFetch( sth ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): " + "error fetch results of count query: %s\n", + at->bam_countquery, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + return 1; + } + + Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): " + "number of values in query: %u\n", count, 0, 0 ); + SQLFreeStmt( sth, SQL_DROP ); + if ( count == 0 ) { + return 1; + } + + attr = attr_find( bsi->bsi_e->e_attrs, at->bam_true_ad ); + if ( attr != NULL ) { + BerVarray tmp; + + if ( attr->a_vals != NULL ) { + oldcount = attr->a_numvals; + } + + tmp = ch_realloc( attr->a_vals, ( oldcount + count + 1 ) * sizeof( struct berval ) ); + if ( tmp == NULL ) { + return 1; + } + attr->a_vals = tmp; + memset( &attr->a_vals[ oldcount ], 0, ( count + 1 ) * sizeof( struct berval ) ); + + if ( normfunc ) { + tmp = ch_realloc( attr->a_nvals, ( oldcount + count + 1 ) * sizeof( struct berval ) ); + if ( tmp == NULL ) { + return 1; + } + attr->a_nvals = tmp; + memset( &attr->a_nvals[ oldcount ], 0, ( count + 1 ) * sizeof( struct berval ) ); + + } else { + attr->a_nvals = attr->a_vals; + } + attr->a_numvals += count; + + } else { + append = 1; + + /* Make space for the array of values */ + attr = attr_alloc( at->bam_true_ad ); + attr->a_numvals = count; + attr->a_vals = ch_calloc( count + 1, sizeof( struct berval ) ); + if ( attr->a_vals == NULL ) { + Debug( LDAP_DEBUG_TRACE, "Out of memory!\n", 0,0,0 ); + ch_free( attr ); + return 1; + } + if ( normfunc ) { + attr->a_nvals = ch_calloc( count + 1, sizeof( struct berval ) ); + if ( attr->a_nvals == NULL ) { + ch_free( attr->a_vals ); + ch_free( attr ); + return 1; + + } + + } else { + attr->a_nvals = attr->a_vals; + } + } +#endif /* BACKSQL_COUNTQUERY */ + + rc = backsql_Prepare( bsi->bsi_dbh, &sth, at->bam_query, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): " + "error preparing query: %s\n", at->bam_query, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc ); +#ifdef BACKSQL_COUNTQUERY + if ( append ) { + attr_free( attr ); + } +#endif /* BACKSQL_COUNTQUERY */ + return 1; + } + + rc = backsql_BindParamID( sth, 1, SQL_PARAM_INPUT, + &bsi->bsi_c_eid->eid_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): " + "error binding key value parameter\n", 0, 0, 0 ); +#ifdef BACKSQL_COUNTQUERY + if ( append ) { + attr_free( attr ); + } +#endif /* BACKSQL_COUNTQUERY */ + return 1; + } + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): " + "query=\"%s\" keyval=" BACKSQL_IDFMT "\n", at->bam_query, + BACKSQL_IDARG(bsi->bsi_c_eid->eid_keyval), 0 ); +#endif /* BACKSQL_TRACE */ + + rc = SQLExecute( sth ); + if ( ! BACKSQL_SUCCESS( rc ) ) { + Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): " + "error executing attribute query \"%s\"\n", + at->bam_query, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); +#ifdef BACKSQL_COUNTQUERY + if ( append ) { + attr_free( attr ); + } +#endif /* BACKSQL_COUNTQUERY */ + return 1; + } + + backsql_BindRowAsStrings_x( sth, &row, bsi->bsi_op->o_tmpmemctx ); +#ifdef BACKSQL_COUNTQUERY + j = oldcount; +#endif /* BACKSQL_COUNTQUERY */ + for ( rc = SQLFetch( sth ), k = 0; + BACKSQL_SUCCESS( rc ); + rc = SQLFetch( sth ), k++ ) + { + for ( i = 0; i < (unsigned long)row.ncols; i++ ) { + + if ( row.value_len[ i ] > 0 ) { + struct berval bv; + int retval; +#ifdef BACKSQL_TRACE + AttributeDescription *ad = NULL; + const char *text; + + retval = slap_bv2ad( &row.col_names[ i ], &ad, &text ); + if ( retval != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "==>backsql_get_attr_vals(\"%s\"): " + "unable to find AttributeDescription %s " + "in schema (%d)\n", + bsi->bsi_e->e_name.bv_val, + row.col_names[ i ].bv_val, retval ); + res = 1; + goto done; + } + + if ( ad != at->bam_ad ) { + Debug( LDAP_DEBUG_ANY, + "==>backsql_get_attr_vals(\"%s\"): " + "column name %s differs from " + "AttributeDescription %s\n", + bsi->bsi_e->e_name.bv_val, + ad->ad_cname.bv_val, + at->bam_ad->ad_cname.bv_val ); + res = 1; + goto done; + } +#endif /* BACKSQL_TRACE */ + + /* ITS#3386, ITS#3113 - 20070308 + * If a binary is fetched? + * must use the actual size read + * from the database. + */ + if ( BACKSQL_IS_BINARY( row.col_type[ i ] ) ) { +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_ANY, + "==>backsql_get_attr_vals(\"%s\"): " + "column name %s: data is binary; " + "using database size %ld\n", + bsi->bsi_e->e_name.bv_val, + ad->ad_cname.bv_val, + row.value_len[ i ] ); +#endif /* BACKSQL_TRACE */ + bv.bv_val = row.cols[ i ]; + bv.bv_len = row.value_len[ i ]; + + } else { + ber_str2bv( row.cols[ i ], 0, 0, &bv ); + } + +#ifdef BACKSQL_PRETTY_VALIDATE + if ( pretty ) { + struct berval pbv; + + retval = pretty( at->bam_true_ad->ad_type->sat_syntax, + &bv, &pbv, bsi->bsi_op->o_tmpmemctx ); + bv = pbv; + + } else { + retval = validate( at->bam_true_ad->ad_type->sat_syntax, + &bv ); + } + + if ( retval != LDAP_SUCCESS ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + /* FIXME: we're ignoring invalid values, + * but we're accepting the attributes; + * should we fail at all? */ + snprintf( buf, sizeof( buf ), + "unable to %s value #%lu " + "of AttributeDescription %s", + pretty ? "prettify" : "validate", + k - oldcount, + at->bam_ad->ad_cname.bv_val ); + Debug( LDAP_DEBUG_TRACE, + "==>backsql_get_attr_vals(\"%s\"): " + "%s (%d)\n", + bsi->bsi_e->e_name.bv_val, buf, retval ); + continue; + } +#endif /* BACKSQL_PRETTY_VALIDATE */ + +#ifndef BACKSQL_COUNTQUERY + (void)backsql_entry_addattr( bsi->bsi_e, + at->bam_true_ad, &bv, + bsi->bsi_op->o_tmpmemctx ); + +#else /* BACKSQL_COUNTQUERY */ + if ( normfunc ) { + struct berval nbv; + + retval = (*normfunc)( SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + at->bam_true_ad->ad_type->sat_syntax, + at->bam_true_ad->ad_type->sat_equality, + &bv, &nbv, + bsi->bsi_op->o_tmpmemctx ); + + if ( retval != LDAP_SUCCESS ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + /* FIXME: we're ignoring invalid values, + * but we're accepting the attributes; + * should we fail at all? */ + snprintf( buf, sizeof( buf ), + "unable to normalize value #%lu " + "of AttributeDescription %s", + k - oldcount, + at->bam_ad->ad_cname.bv_val ); + Debug( LDAP_DEBUG_TRACE, + "==>backsql_get_attr_vals(\"%s\"): " + "%s (%d)\n", + bsi->bsi_e->e_name.bv_val, buf, retval ); + +#ifdef BACKSQL_PRETTY_VALIDATE + if ( pretty ) { + bsi->bsi_op->o_tmpfree( bv.bv_val, + bsi->bsi_op->o_tmpmemctx ); + } +#endif /* BACKSQL_PRETTY_VALIDATE */ + + continue; + } + ber_dupbv( &attr->a_nvals[ j ], &nbv ); + bsi->bsi_op->o_tmpfree( nbv.bv_val, + bsi->bsi_op->o_tmpmemctx ); + } + + ber_dupbv( &attr->a_vals[ j ], &bv ); + + assert( j < oldcount + count ); + j++; +#endif /* BACKSQL_COUNTQUERY */ + +#ifdef BACKSQL_PRETTY_VALIDATE + if ( pretty ) { + bsi->bsi_op->o_tmpfree( bv.bv_val, + bsi->bsi_op->o_tmpmemctx ); + } +#endif /* BACKSQL_PRETTY_VALIDATE */ + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "prec=%d\n", + (int)row.col_prec[ i ], 0, 0 ); + + } else { + Debug( LDAP_DEBUG_TRACE, "NULL value " + "in this row for attribute \"%s\"\n", + row.col_names[ i ].bv_val, 0, 0 ); +#endif /* BACKSQL_TRACE */ + } + } + } + +#ifdef BACKSQL_COUNTQUERY + if ( BER_BVISNULL( &attr->a_vals[ 0 ] ) ) { + /* don't leave around attributes with no values */ + attr_free( attr ); + + } else if ( append ) { + Attribute **ap; + + for ( ap = &bsi->bsi_e->e_attrs; (*ap) != NULL; ap = &(*ap)->a_next ) + /* goto last */ ; + *ap = attr; + } +#endif /* BACKSQL_COUNTQUERY */ + + SQLFreeStmt( sth, SQL_DROP ); + Debug( LDAP_DEBUG_TRACE, "<==backsql_get_attr_vals()\n", 0, 0, 0 ); + + if ( at->bam_next ) { + res = backsql_get_attr_vals( at->bam_next, v_bsi ); + } else { + res = 1; + } + +#ifdef BACKSQL_TRACE +done:; +#endif /* BACKSQL_TRACE */ + backsql_FreeRow_x( &row, bsi->bsi_op->o_tmpmemctx ); + + return res; +} + +int +backsql_id2entry( backsql_srch_info *bsi, backsql_entryID *eid ) +{ + Operation *op = bsi->bsi_op; + backsql_info *bi = (backsql_info *)op->o_bd->be_private; + int i; + int rc; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_id2entry()\n", 0, 0, 0 ); + + assert( bsi->bsi_e != NULL ); + + memset( bsi->bsi_e, 0, sizeof( Entry ) ); + + if ( bi->sql_baseObject && BACKSQL_IS_BASEOBJECT_ID( &eid->eid_id ) ) { + (void)entry_dup2( bsi->bsi_e, bi->sql_baseObject ); + goto done; + } + + bsi->bsi_e->e_attrs = NULL; + bsi->bsi_e->e_private = NULL; + + if ( eid->eid_oc == NULL ) { + eid->eid_oc = backsql_id2oc( bsi->bsi_op->o_bd->be_private, + eid->eid_oc_id ); + if ( eid->eid_oc == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "backsql_id2entry(): unable to fetch objectClass with id=" BACKSQL_IDNUMFMT " for entry id=" BACKSQL_IDFMT " dn=\"%s\"\n", + eid->eid_oc_id, BACKSQL_IDARG(eid->eid_id), + eid->eid_dn.bv_val ); + return LDAP_OTHER; + } + } + bsi->bsi_oc = eid->eid_oc; + bsi->bsi_c_eid = eid; + + ber_dupbv_x( &bsi->bsi_e->e_name, &eid->eid_dn, op->o_tmpmemctx ); + ber_dupbv_x( &bsi->bsi_e->e_nname, &eid->eid_ndn, op->o_tmpmemctx ); + +#ifndef BACKSQL_ARBITRARY_KEY + /* FIXME: unused */ + bsi->bsi_e->e_id = eid->eid_id; +#endif /* ! BACKSQL_ARBITRARY_KEY */ + + rc = attr_merge_normalize_one( bsi->bsi_e, + slap_schema.si_ad_objectClass, + &bsi->bsi_oc->bom_oc->soc_cname, + bsi->bsi_op->o_tmpmemctx ); + if ( rc != LDAP_SUCCESS ) { + backsql_entry_clean( op, bsi->bsi_e ); + return rc; + } + + if ( bsi->bsi_attrs == NULL || ( bsi->bsi_flags & BSQL_SF_ALL_USER ) ) + { + Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(): " + "retrieving all attributes\n", 0, 0, 0 ); + avl_apply( bsi->bsi_oc->bom_attrs, backsql_get_attr_vals, + bsi, 0, AVL_INORDER ); + + } else { + Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(): " + "custom attribute list\n", 0, 0, 0 ); + for ( i = 0; !BER_BVISNULL( &bsi->bsi_attrs[ i ].an_name ); i++ ) { + backsql_at_map_rec **vat; + AttributeName *an = &bsi->bsi_attrs[ i ]; + int j; + + /* if one of the attributes listed here is + * a subtype of another, it must be ignored, + * because subtypes are already dealt with + * by backsql_supad2at() + */ + for ( j = 0; !BER_BVISNULL( &bsi->bsi_attrs[ j ].an_name ); j++ ) { + /* skip self */ + if ( j == i ) { + continue; + } + + /* skip subtypes */ + if ( is_at_subtype( an->an_desc->ad_type, + bsi->bsi_attrs[ j ].an_desc->ad_type ) ) + { + goto next; + } + } + + rc = backsql_supad2at( bsi->bsi_oc, an->an_desc, &vat ); + if ( rc != 0 || vat == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(): " + "attribute \"%s\" is not defined " + "for objectlass \"%s\"\n", + an->an_name.bv_val, + BACKSQL_OC_NAME( bsi->bsi_oc ), 0 ); + continue; + } + + for ( j = 0; vat[j]; j++ ) { + backsql_get_attr_vals( vat[j], bsi ); + } + + ch_free( vat ); + +next:; + } + } + + if ( bsi->bsi_flags & BSQL_SF_RETURN_ENTRYUUID ) { + Attribute *a_entryUUID, + **ap; + + a_entryUUID = backsql_operational_entryUUID( bi, eid ); + if ( a_entryUUID != NULL ) { + for ( ap = &bsi->bsi_e->e_attrs; + *ap; + ap = &(*ap)->a_next ); + + *ap = a_entryUUID; + } + } + + if ( ( bsi->bsi_flags & BSQL_SF_ALL_OPER ) + || an_find( bsi->bsi_attrs, slap_bv_all_operational_attrs ) + || an_find( bsi->bsi_attrs, &slap_schema.si_ad_structuralObjectClass->ad_cname ) ) + { + ObjectClass *soc = NULL; + + if ( BACKSQL_CHECK_SCHEMA( bi ) ) { + Attribute *a; + const char *text = NULL; + char textbuf[ 1024 ]; + size_t textlen = sizeof( textbuf ); + struct berval bv[ 2 ], + *nvals; + int rc = LDAP_SUCCESS; + + a = attr_find( bsi->bsi_e->e_attrs, + slap_schema.si_ad_objectClass ); + if ( a != NULL ) { + nvals = a->a_nvals; + + } else { + bv[ 0 ] = bsi->bsi_oc->bom_oc->soc_cname; + BER_BVZERO( &bv[ 1 ] ); + nvals = bv; + } + + rc = structural_class( nvals, &soc, NULL, + &text, textbuf, textlen, op->o_tmpmemctx ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(%s): " + "structural_class() failed %d (%s)\n", + bsi->bsi_e->e_name.bv_val, + rc, text ? text : "" ); + backsql_entry_clean( op, bsi->bsi_e ); + return rc; + } + + if ( !bvmatch( &soc->soc_cname, &bsi->bsi_oc->bom_oc->soc_cname ) ) { + if ( !is_object_subclass( bsi->bsi_oc->bom_oc, soc ) ) { + Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(%s): " + "computed structuralObjectClass %s " + "does not match objectClass %s associated " + "to entry\n", + bsi->bsi_e->e_name.bv_val, soc->soc_cname.bv_val, + bsi->bsi_oc->bom_oc->soc_cname.bv_val ); + backsql_entry_clean( op, bsi->bsi_e ); + return rc; + } + + Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(%s): " + "computed structuralObjectClass %s " + "is subclass of objectClass %s associated " + "to entry\n", + bsi->bsi_e->e_name.bv_val, soc->soc_cname.bv_val, + bsi->bsi_oc->bom_oc->soc_cname.bv_val ); + } + + } else { + soc = bsi->bsi_oc->bom_oc; + } + + rc = attr_merge_normalize_one( bsi->bsi_e, + slap_schema.si_ad_structuralObjectClass, + &soc->soc_cname, + bsi->bsi_op->o_tmpmemctx ); + if ( rc != LDAP_SUCCESS ) { + backsql_entry_clean( op, bsi->bsi_e ); + return rc; + } + } + +done:; + Debug( LDAP_DEBUG_TRACE, "<==backsql_id2entry()\n", 0, 0, 0 ); + + return LDAP_SUCCESS; +} + diff --git a/servers/slapd/back-sql/init.c b/servers/slapd/back-sql/init.c new file mode 100644 index 0000000..9240830 --- /dev/null +++ b/servers/slapd/back-sql/init.c @@ -0,0 +1,672 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> +#include "ac/string.h" + +#include "slap.h" +#include "config.h" +#include "proto-sql.h" + +int +sql_back_initialize( + BackendInfo *bi ) +{ + static char *controls[] = { + LDAP_CONTROL_ASSERT, + LDAP_CONTROL_MANAGEDSAIT, + LDAP_CONTROL_NOOP, +#ifdef SLAP_CONTROL_X_TREE_DELETE + SLAP_CONTROL_X_TREE_DELETE, +#endif /* SLAP_CONTROL_X_TREE_DELETE */ +#ifndef BACKSQL_ARBITRARY_KEY + LDAP_CONTROL_PAGEDRESULTS, +#endif /* ! BACKSQL_ARBITRARY_KEY */ + NULL + }; + int rc; + + bi->bi_controls = controls; + + bi->bi_flags |= +#if 0 + SLAP_BFLAG_INCREMENT | +#endif + SLAP_BFLAG_REFERRALS; + + Debug( LDAP_DEBUG_TRACE,"==>sql_back_initialize()\n", 0, 0, 0 ); + + bi->bi_db_init = backsql_db_init; + bi->bi_db_config = config_generic_wrapper; + bi->bi_db_open = backsql_db_open; + bi->bi_db_close = backsql_db_close; + bi->bi_db_destroy = backsql_db_destroy; + + bi->bi_op_abandon = 0; + bi->bi_op_compare = backsql_compare; + bi->bi_op_bind = backsql_bind; + bi->bi_op_unbind = 0; + bi->bi_op_search = backsql_search; + bi->bi_op_modify = backsql_modify; + bi->bi_op_modrdn = backsql_modrdn; + bi->bi_op_add = backsql_add; + bi->bi_op_delete = backsql_delete; + + bi->bi_chk_referrals = 0; + bi->bi_operational = backsql_operational; + bi->bi_entry_get_rw = backsql_entry_get; + bi->bi_entry_release_rw = backsql_entry_release; + + bi->bi_connection_init = 0; + + rc = backsql_init_cf( bi ); + Debug( LDAP_DEBUG_TRACE,"<==sql_back_initialize()\n", 0, 0, 0 ); + return rc; +} + +int +backsql_destroy( + BackendInfo *bi ) +{ + Debug( LDAP_DEBUG_TRACE, "==>backsql_destroy()\n", 0, 0, 0 ); + Debug( LDAP_DEBUG_TRACE, "<==backsql_destroy()\n", 0, 0, 0 ); + return 0; +} + +int +backsql_db_init( + BackendDB *bd, + ConfigReply *cr ) +{ + backsql_info *bi; + int rc = 0; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_db_init()\n", 0, 0, 0 ); + + bi = (backsql_info *)ch_calloc( 1, sizeof( backsql_info ) ); + ldap_pvt_thread_mutex_init( &bi->sql_dbconn_mutex ); + ldap_pvt_thread_mutex_init( &bi->sql_schema_mutex ); + + if ( backsql_init_db_env( bi ) != SQL_SUCCESS ) { + rc = -1; + } + + bd->be_private = bi; + bd->be_cf_ocs = bd->bd_info->bi_cf_ocs; + + Debug( LDAP_DEBUG_TRACE, "<==backsql_db_init()\n", 0, 0, 0 ); + + return rc; +} + +int +backsql_db_destroy( + BackendDB *bd, + ConfigReply *cr ) +{ + backsql_info *bi = (backsql_info*)bd->be_private; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_db_destroy()\n", 0, 0, 0 ); + + backsql_free_db_env( bi ); + ldap_pvt_thread_mutex_destroy( &bi->sql_dbconn_mutex ); + backsql_destroy_schema_map( bi ); + ldap_pvt_thread_mutex_destroy( &bi->sql_schema_mutex ); + + if ( bi->sql_dbname ) { + ch_free( bi->sql_dbname ); + } + if ( bi->sql_dbuser ) { + ch_free( bi->sql_dbuser ); + } + if ( bi->sql_dbpasswd ) { + ch_free( bi->sql_dbpasswd ); + } + if ( bi->sql_dbhost ) { + ch_free( bi->sql_dbhost ); + } + if ( bi->sql_upper_func.bv_val ) { + ch_free( bi->sql_upper_func.bv_val ); + ch_free( bi->sql_upper_func_open.bv_val ); + ch_free( bi->sql_upper_func_close.bv_val ); + } + if ( bi->sql_concat_func ) { + ber_bvarray_free( bi->sql_concat_func ); + } + if ( !BER_BVISNULL( &bi->sql_strcast_func ) ) { + ch_free( bi->sql_strcast_func.bv_val ); + } + if ( !BER_BVISNULL( &bi->sql_children_cond ) ) { + ch_free( bi->sql_children_cond.bv_val ); + } + if ( !BER_BVISNULL( &bi->sql_dn_match_cond ) ) { + ch_free( bi->sql_dn_match_cond.bv_val ); + } + if ( !BER_BVISNULL( &bi->sql_subtree_cond ) ) { + ch_free( bi->sql_subtree_cond.bv_val ); + } + if ( !BER_BVISNULL( &bi->sql_dn_oc_aliasing ) ) { + ch_free( bi->sql_dn_oc_aliasing.bv_val ); + } + if ( bi->sql_oc_query ) { + ch_free( bi->sql_oc_query ); + } + if ( bi->sql_at_query ) { + ch_free( bi->sql_at_query ); + } + if ( bi->sql_id_query ) { + ch_free( bi->sql_id_query ); + } + if ( bi->sql_has_children_query ) { + ch_free( bi->sql_has_children_query ); + } + if ( bi->sql_insentry_stmt ) { + ch_free( bi->sql_insentry_stmt ); + } + if ( bi->sql_delentry_stmt ) { + ch_free( bi->sql_delentry_stmt ); + } + if ( bi->sql_renentry_stmt ) { + ch_free( bi->sql_renentry_stmt ); + } + if ( bi->sql_delobjclasses_stmt ) { + ch_free( bi->sql_delobjclasses_stmt ); + } + if ( !BER_BVISNULL( &bi->sql_aliasing ) ) { + ch_free( bi->sql_aliasing.bv_val ); + } + if ( !BER_BVISNULL( &bi->sql_aliasing_quote ) ) { + ch_free( bi->sql_aliasing_quote.bv_val ); + } + + if ( bi->sql_anlist ) { + int i; + + for ( i = 0; !BER_BVISNULL( &bi->sql_anlist[ i ].an_name ); i++ ) + { + ch_free( bi->sql_anlist[ i ].an_name.bv_val ); + } + ch_free( bi->sql_anlist ); + } + + if ( bi->sql_baseObject ) { + entry_free( bi->sql_baseObject ); + } + + ch_free( bi ); + + Debug( LDAP_DEBUG_TRACE, "<==backsql_db_destroy()\n", 0, 0, 0 ); + return 0; +} + +int +backsql_db_open( + BackendDB *bd, + ConfigReply *cr ) +{ + backsql_info *bi = (backsql_info*)bd->be_private; + struct berbuf bb = BB_NULL; + + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation* op; + SQLHDBC dbh = SQL_NULL_HDBC; + void *thrctx = ldap_pvt_thread_pool_context(); + + Debug( LDAP_DEBUG_TRACE, "==>backsql_db_open(): " + "testing RDBMS connection\n", 0, 0, 0 ); + if ( bi->sql_dbname == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "datasource name not specified " + "(use \"dbname\" directive in slapd.conf)\n", 0, 0, 0 ); + return 1; + } + + if ( bi->sql_concat_func == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "concat func not specified (use \"concat_pattern\" " + "directive in slapd.conf)\n", 0, 0, 0 ); + + if ( backsql_split_pattern( backsql_def_concat_func, + &bi->sql_concat_func, 2 ) ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "unable to parse pattern \"%s\"", + backsql_def_concat_func, 0, 0 ); + return 1; + } + } + + /* + * see back-sql.h for default values + */ + if ( BER_BVISNULL( &bi->sql_aliasing ) ) { + ber_str2bv( BACKSQL_ALIASING, + STRLENOF( BACKSQL_ALIASING ), + 1, &bi->sql_aliasing ); + } + + if ( BER_BVISNULL( &bi->sql_aliasing_quote ) ) { + ber_str2bv( BACKSQL_ALIASING_QUOTE, + STRLENOF( BACKSQL_ALIASING_QUOTE ), + 1, &bi->sql_aliasing_quote ); + } + + /* + * Prepare cast string as required + */ + if ( bi->sql_upper_func.bv_val ) { + char buf[1024]; + + if ( BACKSQL_UPPER_NEEDS_CAST( bi ) ) { + snprintf( buf, sizeof( buf ), + "%s(cast (" /* ? as varchar(%d))) */ , + bi->sql_upper_func.bv_val ); + ber_str2bv( buf, 0, 1, &bi->sql_upper_func_open ); + + snprintf( buf, sizeof( buf ), + /* (cast(? */ " as varchar(%d)))", + BACKSQL_MAX_DN_LEN ); + ber_str2bv( buf, 0, 1, &bi->sql_upper_func_close ); + + } else { + snprintf( buf, sizeof( buf ), "%s(" /* ?) */ , + bi->sql_upper_func.bv_val ); + ber_str2bv( buf, 0, 1, &bi->sql_upper_func_open ); + + ber_str2bv( /* (? */ ")", 0, 1, &bi->sql_upper_func_close ); + } + } + + /* normalize filter values only if necessary */ + bi->sql_caseIgnoreMatch = mr_find( "caseIgnoreMatch" ); + assert( bi->sql_caseIgnoreMatch != NULL ); + + bi->sql_telephoneNumberMatch = mr_find( "telephoneNumberMatch" ); + assert( bi->sql_telephoneNumberMatch != NULL ); + + if ( bi->sql_dbuser == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "user name not specified " + "(use \"dbuser\" directive in slapd.conf)\n", 0, 0, 0 ); + return 1; + } + + if ( BER_BVISNULL( &bi->sql_subtree_cond ) ) { + /* + * Prepare concat function for subtree search condition + */ + struct berval concat; + struct berval values[] = { + BER_BVC( "'%'" ), + BER_BVC( "?" ), + BER_BVNULL + }; + struct berbuf bb = BB_NULL; + + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "subtree search SQL condition not specified " + "(use \"subtree_cond\" directive in slapd.conf); " + "preparing default\n", + 0, 0, 0); + + if ( backsql_prepare_pattern( bi->sql_concat_func, values, + &concat ) ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "unable to prepare CONCAT pattern for subtree search", + 0, 0, 0 ); + return 1; + } + + if ( bi->sql_upper_func.bv_val ) { + + /* + * UPPER(ldap_entries.dn) LIKE UPPER(CONCAT('%',?)) + */ + + backsql_strfcat_x( &bb, NULL, "blbbb", + &bi->sql_upper_func, + (ber_len_t)STRLENOF( "(ldap_entries.dn) LIKE " ), + "(ldap_entries.dn) LIKE ", + &bi->sql_upper_func_open, + &concat, + &bi->sql_upper_func_close ); + + } else { + + /* + * ldap_entries.dn LIKE CONCAT('%',?) + */ + + backsql_strfcat_x( &bb, NULL, "lb", + (ber_len_t)STRLENOF( "ldap_entries.dn LIKE " ), + "ldap_entries.dn LIKE ", + &concat ); + } + + ch_free( concat.bv_val ); + + bi->sql_subtree_cond = bb.bb_val; + + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "setting \"%s\" as default \"subtree_cond\"\n", + bi->sql_subtree_cond.bv_val, 0, 0 ); + } + + if ( bi->sql_children_cond.bv_val == NULL ) { + /* + * Prepare concat function for children search condition + */ + struct berval concat; + struct berval values[] = { + BER_BVC( "'%,'" ), + BER_BVC( "?" ), + BER_BVNULL + }; + struct berbuf bb = BB_NULL; + + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "children search SQL condition not specified " + "(use \"children_cond\" directive in slapd.conf); " + "preparing default\n", + 0, 0, 0); + + if ( backsql_prepare_pattern( bi->sql_concat_func, values, + &concat ) ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "unable to prepare CONCAT pattern for children search", 0, 0, 0 ); + return 1; + } + + if ( bi->sql_upper_func.bv_val ) { + + /* + * UPPER(ldap_entries.dn) LIKE UPPER(CONCAT('%,',?)) + */ + + backsql_strfcat_x( &bb, NULL, "blbbb", + &bi->sql_upper_func, + (ber_len_t)STRLENOF( "(ldap_entries.dn) LIKE " ), + "(ldap_entries.dn) LIKE ", + &bi->sql_upper_func_open, + &concat, + &bi->sql_upper_func_close ); + + } else { + + /* + * ldap_entries.dn LIKE CONCAT('%,',?) + */ + + backsql_strfcat_x( &bb, NULL, "lb", + (ber_len_t)STRLENOF( "ldap_entries.dn LIKE " ), + "ldap_entries.dn LIKE ", + &concat ); + } + + ch_free( concat.bv_val ); + + bi->sql_children_cond = bb.bb_val; + + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "setting \"%s\" as default \"children_cond\"\n", + bi->sql_children_cond.bv_val, 0, 0 ); + } + + if ( bi->sql_dn_match_cond.bv_val == NULL ) { + /* + * Prepare concat function for dn match search condition + */ + struct berbuf bb = BB_NULL; + + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "DN match search SQL condition not specified " + "(use \"dn_match_cond\" directive in slapd.conf); " + "preparing default\n", + 0, 0, 0); + + if ( bi->sql_upper_func.bv_val ) { + + /* + * UPPER(ldap_entries.dn)=? + */ + + backsql_strfcat_x( &bb, NULL, "blbcb", + &bi->sql_upper_func, + (ber_len_t)STRLENOF( "(ldap_entries.dn)=" ), + "(ldap_entries.dn)=", + &bi->sql_upper_func_open, + '?', + &bi->sql_upper_func_close ); + + } else { + + /* + * ldap_entries.dn=? + */ + + backsql_strfcat_x( &bb, NULL, "l", + (ber_len_t)STRLENOF( "ldap_entries.dn=?" ), + "ldap_entries.dn=?" ); + } + + bi->sql_dn_match_cond = bb.bb_val; + + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "setting \"%s\" as default \"dn_match_cond\"\n", + bi->sql_dn_match_cond.bv_val, 0, 0 ); + } + + if ( bi->sql_oc_query == NULL ) { + if ( BACKSQL_CREATE_NEEDS_SELECT( bi ) ) { + bi->sql_oc_query = + ch_strdup( backsql_def_needs_select_oc_query ); + + } else { + bi->sql_oc_query = ch_strdup( backsql_def_oc_query ); + } + + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "objectclass mapping SQL statement not specified " + "(use \"oc_query\" directive in slapd.conf)\n", + 0, 0, 0 ); + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "setting \"%s\" by default\n", bi->sql_oc_query, 0, 0 ); + } + + if ( bi->sql_at_query == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "attribute mapping SQL statement not specified " + "(use \"at_query\" directive in slapd.conf)\n", + 0, 0, 0 ); + Debug(LDAP_DEBUG_TRACE, "backsql_db_open(): " + "setting \"%s\" by default\n", + backsql_def_at_query, 0, 0 ); + bi->sql_at_query = ch_strdup( backsql_def_at_query ); + } + + if ( bi->sql_insentry_stmt == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "entry insertion SQL statement not specified " + "(use \"insentry_stmt\" directive in slapd.conf)\n", + 0, 0, 0 ); + Debug(LDAP_DEBUG_TRACE, "backsql_db_open(): " + "setting \"%s\" by default\n", + backsql_def_insentry_stmt, 0, 0 ); + bi->sql_insentry_stmt = ch_strdup( backsql_def_insentry_stmt ); + } + + if ( bi->sql_delentry_stmt == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "entry deletion SQL statement not specified " + "(use \"delentry_stmt\" directive in slapd.conf)\n", + 0, 0, 0 ); + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "setting \"%s\" by default\n", + backsql_def_delentry_stmt, 0, 0 ); + bi->sql_delentry_stmt = ch_strdup( backsql_def_delentry_stmt ); + } + + if ( bi->sql_renentry_stmt == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "entry deletion SQL statement not specified " + "(use \"renentry_stmt\" directive in slapd.conf)\n", + 0, 0, 0 ); + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "setting \"%s\" by default\n", + backsql_def_renentry_stmt, 0, 0 ); + bi->sql_renentry_stmt = ch_strdup( backsql_def_renentry_stmt ); + } + + if ( bi->sql_delobjclasses_stmt == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "objclasses deletion SQL statement not specified " + "(use \"delobjclasses_stmt\" directive in slapd.conf)\n", + 0, 0, 0 ); + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "setting \"%s\" by default\n", + backsql_def_delobjclasses_stmt, 0, 0 ); + bi->sql_delobjclasses_stmt = ch_strdup( backsql_def_delobjclasses_stmt ); + } + + /* This should just be to force schema loading */ + connection_fake_init2( &conn, &opbuf, thrctx, 0 ); + op = &opbuf.ob_op; + op->o_bd = bd; + if ( backsql_get_db_conn( op, &dbh ) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "connection failed, exiting\n", 0, 0, 0 ); + return 1; + } + if ( backsql_load_schema_map( bi, dbh ) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "schema mapping failed, exiting\n", 0, 0, 0 ); + return 1; + } + if ( backsql_free_db_conn( op, dbh ) != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "connection free failed\n", 0, 0, 0 ); + } + if ( !BACKSQL_SCHEMA_LOADED( bi ) ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "test failed, schema map not loaded - exiting\n", + 0, 0, 0 ); + return 1; + } + + /* + * Prepare ID selection query + */ + if ( bi->sql_id_query == NULL ) { + /* no custom id_query provided */ + if ( bi->sql_upper_func.bv_val == NULL ) { + backsql_strcat_x( &bb, NULL, backsql_id_query, "dn=?", NULL ); + + } else { + if ( BACKSQL_HAS_LDAPINFO_DN_RU( bi ) ) { + backsql_strcat_x( &bb, NULL, backsql_id_query, + "dn_ru=?", NULL ); + } else { + if ( BACKSQL_USE_REVERSE_DN( bi ) ) { + backsql_strfcat_x( &bb, NULL, "sbl", + backsql_id_query, + &bi->sql_upper_func, + (ber_len_t)STRLENOF( "(dn)=?" ), "(dn)=?" ); + } else { + backsql_strfcat_x( &bb, NULL, "sblbcb", + backsql_id_query, + &bi->sql_upper_func, + (ber_len_t)STRLENOF( "(dn)=" ), "(dn)=", + &bi->sql_upper_func_open, + '?', + &bi->sql_upper_func_close ); + } + } + } + bi->sql_id_query = bb.bb_val.bv_val; + } + + /* + * Prepare children count query + */ + BER_BVZERO( &bb.bb_val ); + bb.bb_len = 0; + backsql_strfcat_x( &bb, NULL, "sbsb", + "SELECT COUNT(distinct subordinates.id) " + "FROM ldap_entries,ldap_entries ", + &bi->sql_aliasing, "subordinates " + "WHERE subordinates.parent=ldap_entries.id AND ", + &bi->sql_dn_match_cond ); + bi->sql_has_children_query = bb.bb_val.bv_val; + + /* + * Prepare DN and objectClass aliasing bit of query + */ + BER_BVZERO( &bb.bb_val ); + bb.bb_len = 0; + backsql_strfcat_x( &bb, NULL, "sbbsbsbbsb", + " ", &bi->sql_aliasing, &bi->sql_aliasing_quote, + "objectClass", &bi->sql_aliasing_quote, + ",ldap_entries.dn ", &bi->sql_aliasing, + &bi->sql_aliasing_quote, "dn", &bi->sql_aliasing_quote ); + bi->sql_dn_oc_aliasing = bb.bb_val; + + /* should never happen! */ + assert( bd->be_nsuffix != NULL ); + + if ( BER_BVISNULL( &bd->be_nsuffix[ 1 ] ) ) { + /* enable if only one suffix is defined */ + bi->sql_flags |= BSQLF_USE_SUBTREE_SHORTCUT; + } + + bi->sql_flags |= BSQLF_CHECK_SCHEMA; + + Debug( LDAP_DEBUG_TRACE, "<==backsql_db_open(): " + "test succeeded, schema map loaded\n", 0, 0, 0 ); + return 0; +} + +int +backsql_db_close( + BackendDB *bd, + ConfigReply *cr ) +{ + backsql_info *bi = (backsql_info*)bd->be_private; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_db_close()\n", 0, 0, 0 ); + + backsql_conn_destroy( bi ); + + Debug( LDAP_DEBUG_TRACE, "<==backsql_db_close()\n", 0, 0, 0 ); + + return 0; +} + +#if SLAPD_SQL == SLAPD_MOD_DYNAMIC + +/* conditionally define the init_module() function */ +SLAP_BACKEND_INIT_MODULE( sql ) + +#endif /* SLAPD_SQL == SLAPD_MOD_DYNAMIC */ + diff --git a/servers/slapd/back-sql/modify.c b/servers/slapd/back-sql/modify.c new file mode 100644 index 0000000..c0445b1 --- /dev/null +++ b/servers/slapd/back-sql/modify.c @@ -0,0 +1,214 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> +#include "ac/string.h" + +#include "slap.h" +#include "proto-sql.h" + +int +backsql_modify( Operation *op, SlapReply *rs ) +{ + backsql_info *bi = (backsql_info*)op->o_bd->be_private; + SQLHDBC dbh = SQL_NULL_HDBC; + backsql_oc_map_rec *oc = NULL; + backsql_srch_info bsi = { 0 }; + Entry m = { 0 }, *e = NULL; + int manageDSAit = get_manageDSAit( op ); + SQLUSMALLINT CompletionType = SQL_ROLLBACK; + + /* + * FIXME: in case part of the operation cannot be performed + * (missing mapping, SQL write fails or so) the entire operation + * should be rolled-back + */ + Debug( LDAP_DEBUG_TRACE, "==>backsql_modify(): modifying entry \"%s\"\n", + op->o_req_ndn.bv_val, 0, 0 ); + + rs->sr_err = backsql_get_db_conn( op, &dbh ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modify(): " + "could not get connection handle - exiting\n", + 0, 0, 0 ); + /* + * FIXME: we don't want to send back + * excessively detailed messages + */ + rs->sr_text = ( rs->sr_err == LDAP_OTHER ) + ? "SQL-backend error" : NULL; + goto done; + } + + bsi.bsi_e = &m; + rs->sr_err = backsql_init_search( &bsi, &op->o_req_ndn, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, + slap_anlist_all_attributes, + ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY | BACKSQL_ISF_GET_OC ) ); + switch ( rs->sr_err ) { + case LDAP_SUCCESS: + break; + + case LDAP_REFERRAL: + if ( manageDSAit && !BER_BVISNULL( &bsi.bsi_e->e_nname ) && + dn_match( &op->o_req_ndn, &bsi.bsi_e->e_nname ) ) + { + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + break; + } + e = &m; + /* fallthru */ + + default: + Debug( LDAP_DEBUG_TRACE, "backsql_modify(): " + "could not retrieve modifyDN ID - no such entry\n", + 0, 0, 0 ); + if ( !BER_BVISNULL( &m.e_nname ) ) { + /* FIXME: should always be true! */ + e = &m; + + } else { + e = NULL; + } + goto done; + } + + Debug( LDAP_DEBUG_TRACE, " backsql_modify(): " + "modifying entry \"%s\" (id=" BACKSQL_IDFMT ")\n", + bsi.bsi_base_id.eid_dn.bv_val, + BACKSQL_IDARG(bsi.bsi_base_id.eid_id), 0 ); + + if ( get_assert( op ) && + ( test_filter( op, &m, get_assertion( op ) ) + != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + e = &m; + goto done; + } + + slap_mods_opattrs( op, &op->orm_modlist, 1 ); + + assert( bsi.bsi_base_id.eid_oc != NULL ); + oc = bsi.bsi_base_id.eid_oc; + + if ( !acl_check_modlist( op, &m, op->orm_modlist ) ) { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + e = &m; + goto done; + } + + rs->sr_err = backsql_modify_internal( op, rs, dbh, oc, + &bsi.bsi_base_id, op->orm_modlist ); + if ( rs->sr_err != LDAP_SUCCESS ) { + e = &m; + goto do_transact; + } + + if ( BACKSQL_CHECK_SCHEMA( bi ) ) { + char textbuf[ SLAP_TEXT_BUFLEN ] = { '\0' }; + + backsql_entry_clean( op, &m ); + + bsi.bsi_e = &m; + rs->sr_err = backsql_id2entry( &bsi, &bsi.bsi_base_id ); + if ( rs->sr_err != LDAP_SUCCESS ) { + e = &m; + goto do_transact; + } + + rs->sr_err = entry_schema_check( op, &m, NULL, 0, 0, NULL, + &rs->sr_text, textbuf, sizeof( textbuf ) ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modify(\"%s\"): " + "entry failed schema check -- aborting\n", + m.e_name.bv_val, 0, 0 ); + e = NULL; + goto do_transact; + } + } + +do_transact:; + /* + * Commit only if all operations succeed + */ + if ( rs->sr_err == LDAP_SUCCESS && !op->o_noop ) { + assert( e == NULL ); + CompletionType = SQL_COMMIT; + } + + SQLTransact( SQL_NULL_HENV, dbh, CompletionType ); + +done:; + if ( e != NULL ) { + if ( !access_allowed( op, e, slap_schema.si_ad_entry, NULL, + ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + } + } + + if ( op->o_noop && rs->sr_err == LDAP_SUCCESS ) { + rs->sr_err = LDAP_X_NO_OPERATION; + } + + send_ldap_result( op, rs ); + slap_graduate_commit_csn( op ); + + if ( !BER_BVISNULL( &bsi.bsi_base_id.eid_ndn ) ) { + (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &m.e_nname ) ) { + backsql_entry_clean( op, &m ); + } + + if ( bsi.bsi_attrs != NULL ) { + op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx ); + } + + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + + Debug( LDAP_DEBUG_TRACE, "<==backsql_modify()\n", 0, 0, 0 ); + + return rs->sr_err; +} + diff --git a/servers/slapd/back-sql/modrdn.c b/servers/slapd/back-sql/modrdn.c new file mode 100644 index 0000000..a388673 --- /dev/null +++ b/servers/slapd/back-sql/modrdn.c @@ -0,0 +1,529 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> +#include "ac/string.h" + +#include "slap.h" +#include "proto-sql.h" + +int +backsql_modrdn( Operation *op, SlapReply *rs ) +{ + backsql_info *bi = (backsql_info*)op->o_bd->be_private; + SQLHDBC dbh = SQL_NULL_HDBC; + SQLHSTMT sth = SQL_NULL_HSTMT; + RETCODE rc; + backsql_entryID e_id = BACKSQL_ENTRYID_INIT, + n_id = BACKSQL_ENTRYID_INIT; + backsql_srch_info bsi = { 0 }; + backsql_oc_map_rec *oc = NULL; + struct berval pdn = BER_BVNULL, pndn = BER_BVNULL, + *new_pdn = NULL, *new_npdn = NULL, + new_dn = BER_BVNULL, new_ndn = BER_BVNULL, + realnew_dn = BER_BVNULL; + Entry r = { 0 }, + p = { 0 }, + n = { 0 }, + *e = NULL; + int manageDSAit = get_manageDSAit( op ); + struct berval *newSuperior = op->oq_modrdn.rs_newSup; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_modrdn() renaming entry \"%s\", " + "newrdn=\"%s\", newSuperior=\"%s\"\n", + op->o_req_dn.bv_val, op->oq_modrdn.rs_newrdn.bv_val, + newSuperior ? newSuperior->bv_val : "(NULL)" ); + + rs->sr_err = backsql_get_db_conn( op, &dbh ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): " + "could not get connection handle - exiting\n", + 0, 0, 0 ); + rs->sr_text = ( rs->sr_err == LDAP_OTHER ) + ? "SQL-backend error" : NULL; + e = NULL; + goto done; + } + + bsi.bsi_e = &r; + rs->sr_err = backsql_init_search( &bsi, &op->o_req_ndn, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, + slap_anlist_all_attributes, + ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY | BACKSQL_ISF_GET_OC ) ); + switch ( rs->sr_err ) { + case LDAP_SUCCESS: + break; + + case LDAP_REFERRAL: + if ( manageDSAit && !BER_BVISNULL( &bsi.bsi_e->e_nname ) && + dn_match( &op->o_req_ndn, &bsi.bsi_e->e_nname ) ) + { + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + break; + } + e = &r; + /* fallthru */ + + default: + Debug( LDAP_DEBUG_TRACE, "backsql_modrdn(): " + "could not retrieve modrdnDN ID - no such entry\n", + 0, 0, 0 ); + if ( !BER_BVISNULL( &r.e_nname ) ) { + /* FIXME: should always be true! */ + e = &r; + + } else { + e = NULL; + } + goto done; + } + + Debug( LDAP_DEBUG_TRACE, + " backsql_modrdn(): entry id=" BACKSQL_IDFMT "\n", + BACKSQL_IDARG(e_id.eid_id), 0, 0 ); + + if ( get_assert( op ) && + ( test_filter( op, &r, get_assertion( op ) ) + != LDAP_COMPARE_TRUE ) ) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + e = &r; + goto done; + } + + if ( backsql_has_children( op, dbh, &op->o_req_ndn ) == LDAP_COMPARE_TRUE ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): " + "entry \"%s\" has children\n", + op->o_req_dn.bv_val, 0, 0 ); + rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF; + rs->sr_text = "subtree rename not supported"; + e = &r; + goto done; + } + + /* + * Check for entry access to target + */ + if ( !access_allowed( op, &r, slap_schema.si_ad_entry, + NULL, ACL_WRITE, NULL ) ) { + Debug( LDAP_DEBUG_TRACE, " no access to entry\n", 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto done; + } + + dnParent( &op->o_req_dn, &pdn ); + dnParent( &op->o_req_ndn, &pndn ); + + /* + * namingContext "" is not supported + */ + if ( BER_BVISEMPTY( &pdn ) ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): " + "parent is \"\" - aborting\n", 0, 0, 0 ); + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "not allowed within namingContext"; + e = NULL; + goto done; + } + + /* + * Check for children access to parent + */ + bsi.bsi_e = &p; + e_id = bsi.bsi_base_id; + memset( &bsi.bsi_base_id, 0, sizeof( bsi.bsi_base_id ) ); + rs->sr_err = backsql_init_search( &bsi, &pndn, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, + slap_anlist_all_attributes, + BACKSQL_ISF_GET_ENTRY ); + + Debug( LDAP_DEBUG_TRACE, + " backsql_modrdn(): old parent entry id is " BACKSQL_IDFMT "\n", + BACKSQL_IDARG(bsi.bsi_base_id.eid_id), 0, 0 ); + + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_modrdn(): " + "could not retrieve renameDN ID - no such entry\n", + 0, 0, 0 ); + e = &p; + goto done; + } + + if ( !access_allowed( op, &p, slap_schema.si_ad_children, NULL, + newSuperior ? ACL_WDEL : ACL_WRITE, NULL ) ) + { + Debug( LDAP_DEBUG_TRACE, " no access to parent\n", 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto done; + } + + if ( newSuperior ) { + (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx ); + + /* + * namingContext "" is not supported + */ + if ( BER_BVISEMPTY( newSuperior ) ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): " + "newSuperior is \"\" - aborting\n", 0, 0, 0 ); + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "not allowed within namingContext"; + e = NULL; + goto done; + } + + new_pdn = newSuperior; + new_npdn = op->oq_modrdn.rs_nnewSup; + + /* + * Check for children access to new parent + */ + bsi.bsi_e = &n; + rs->sr_err = backsql_init_search( &bsi, new_npdn, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, + slap_anlist_all_attributes, + ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY ) ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_modrdn(): " + "could not retrieve renameDN ID - no such entry\n", + 0, 0, 0 ); + e = &n; + goto done; + } + + n_id = bsi.bsi_base_id; + + Debug( LDAP_DEBUG_TRACE, + " backsql_modrdn(): new parent entry id=" BACKSQL_IDFMT "\n", + BACKSQL_IDARG(n_id.eid_id), 0, 0 ); + + if ( !access_allowed( op, &n, slap_schema.si_ad_children, + NULL, ACL_WADD, NULL ) ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): " + "no access to new parent \"%s\"\n", + new_pdn->bv_val, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + e = &n; + goto done; + } + + } else { + n_id = bsi.bsi_base_id; + new_pdn = &pdn; + new_npdn = &pndn; + } + + memset( &bsi.bsi_base_id, 0, sizeof( bsi.bsi_base_id ) ); + + if ( newSuperior && dn_match( &pndn, new_npdn ) ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): " + "newSuperior is equal to old parent - ignored\n", + 0, 0, 0 ); + newSuperior = NULL; + } + + if ( newSuperior && dn_match( &op->o_req_ndn, new_npdn ) ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): " + "newSuperior is equal to entry being moved " + "- aborting\n", 0, 0, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "newSuperior is equal to old DN"; + e = &r; + goto done; + } + + build_new_dn( &new_dn, new_pdn, &op->oq_modrdn.rs_newrdn, + op->o_tmpmemctx ); + build_new_dn( &new_ndn, new_npdn, &op->oq_modrdn.rs_nnewrdn, + op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): new entry dn is \"%s\"\n", + new_dn.bv_val, 0, 0 ); + + realnew_dn = new_dn; + if ( backsql_api_dn2odbc( op, rs, &realnew_dn ) ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(\"%s\"): " + "backsql_api_dn2odbc(\"%s\") failed\n", + op->o_req_dn.bv_val, realnew_dn.bv_val, 0 ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): " + "executing renentry_stmt\n", 0, 0, 0 ); + + rc = backsql_Prepare( dbh, &sth, bi->sql_renentry_stmt, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modrdn(): " + "error preparing renentry_stmt\n", 0, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + + rc = backsql_BindParamBerVal( sth, 1, SQL_PARAM_INPUT, &realnew_dn ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modrdn(): " + "error binding DN parameter for objectClass %s\n", + oc->bom_oc->soc_cname.bv_val, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + + rc = backsql_BindParamID( sth, 2, SQL_PARAM_INPUT, &n_id.eid_id ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modrdn(): " + "error binding parent ID parameter for objectClass %s\n", + oc->bom_oc->soc_cname.bv_val, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + + rc = backsql_BindParamID( sth, 3, SQL_PARAM_INPUT, &e_id.eid_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modrdn(): " + "error binding entry ID parameter for objectClass %s\n", + oc->bom_oc->soc_cname.bv_val, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + + rc = backsql_BindParamID( sth, 4, SQL_PARAM_INPUT, &e_id.eid_id ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modrdn(): " + "error binding ID parameter for objectClass %s\n", + oc->bom_oc->soc_cname.bv_val, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + + rc = SQLExecute( sth ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): " + "could not rename ldap_entries record\n", 0, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + } + SQLFreeStmt( sth, SQL_DROP ); + + assert( op->orr_modlist != NULL ); + + slap_mods_opattrs( op, &op->orr_modlist, 1 ); + + assert( e_id.eid_oc != NULL ); + oc = e_id.eid_oc; + rs->sr_err = backsql_modify_internal( op, rs, dbh, oc, &e_id, op->orr_modlist ); + slap_graduate_commit_csn( op ); + if ( rs->sr_err != LDAP_SUCCESS ) { + e = &r; + goto done; + } + + if ( BACKSQL_CHECK_SCHEMA( bi ) ) { + char textbuf[ SLAP_TEXT_BUFLEN ] = { '\0' }; + + backsql_entry_clean( op, &r ); + (void)backsql_free_entryID( &e_id, 0, op->o_tmpmemctx ); + + bsi.bsi_e = &r; + rs->sr_err = backsql_init_search( &bsi, &new_ndn, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, + slap_anlist_all_attributes, + ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY ) ); + switch ( rs->sr_err ) { + case LDAP_SUCCESS: + break; + + case LDAP_REFERRAL: + if ( manageDSAit && !BER_BVISNULL( &bsi.bsi_e->e_nname ) && + dn_match( &new_ndn, &bsi.bsi_e->e_nname ) ) + { + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + break; + } + e = &r; + /* fallthru */ + + default: + Debug( LDAP_DEBUG_TRACE, "backsql_modrdn(): " + "could not retrieve modrdnDN ID - no such entry\n", + 0, 0, 0 ); + if ( !BER_BVISNULL( &r.e_nname ) ) { + /* FIXME: should always be true! */ + e = &r; + + } else { + e = NULL; + } + goto done; + } + + e_id = bsi.bsi_base_id; + + rs->sr_err = entry_schema_check( op, &r, NULL, 0, 0, NULL, + &rs->sr_text, textbuf, sizeof( textbuf ) ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(\"%s\"): " + "entry failed schema check -- aborting\n", + r.e_name.bv_val, 0, 0 ); + e = NULL; + goto done; + } + } + +done:; + if ( e != NULL ) { + if ( !access_allowed( op, e, slap_schema.si_ad_entry, NULL, + ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + } + } + + /* + * Commit only if all operations succeed + */ + if ( sth != SQL_NULL_HSTMT ) { + SQLUSMALLINT CompletionType = SQL_ROLLBACK; + + if ( rs->sr_err == LDAP_SUCCESS && !op->o_noop ) { + CompletionType = SQL_COMMIT; + } + + SQLTransact( SQL_NULL_HENV, dbh, CompletionType ); + } + + if ( op->o_noop && rs->sr_err == LDAP_SUCCESS ) { + rs->sr_err = LDAP_X_NO_OPERATION; + } + + send_ldap_result( op, rs ); + slap_graduate_commit_csn( op ); + + if ( !BER_BVISNULL( &realnew_dn ) && realnew_dn.bv_val != new_dn.bv_val ) { + ch_free( realnew_dn.bv_val ); + } + + if ( !BER_BVISNULL( &new_dn ) ) { + slap_sl_free( new_dn.bv_val, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &new_ndn ) ) { + slap_sl_free( new_ndn.bv_val, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &e_id.eid_ndn ) ) { + (void)backsql_free_entryID( &e_id, 0, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &n_id.eid_ndn ) ) { + (void)backsql_free_entryID( &n_id, 0, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &r.e_nname ) ) { + backsql_entry_clean( op, &r ); + } + + if ( !BER_BVISNULL( &p.e_nname ) ) { + backsql_entry_clean( op, &p ); + } + + if ( !BER_BVISNULL( &n.e_nname ) ) { + backsql_entry_clean( op, &n ); + } + + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + + Debug( LDAP_DEBUG_TRACE, "<==backsql_modrdn()\n", 0, 0, 0 ); + + return rs->sr_err; +} + diff --git a/servers/slapd/back-sql/operational.c b/servers/slapd/back-sql/operational.c new file mode 100644 index 0000000..f5e8d10 --- /dev/null +++ b/servers/slapd/back-sql/operational.c @@ -0,0 +1,250 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> + +#include "slap.h" +#include "proto-sql.h" +#include "lutil.h" + +/* + * sets the supported operational attributes (if required) + */ + +Attribute * +backsql_operational_entryUUID( backsql_info *bi, backsql_entryID *id ) +{ + int rc; + struct berval val, nval; + AttributeDescription *desc = slap_schema.si_ad_entryUUID; + Attribute *a; + + backsql_entryUUID( bi, id, &val, NULL ); + + rc = (*desc->ad_type->sat_equality->smr_normalize)( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + desc->ad_type->sat_syntax, + desc->ad_type->sat_equality, + &val, &nval, NULL ); + if ( rc != LDAP_SUCCESS ) { + ber_memfree( val.bv_val ); + return NULL; + } + + a = attr_alloc( desc ); + + a->a_numvals = 1; + a->a_vals = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) ); + a->a_vals[ 0 ] = val; + BER_BVZERO( &a->a_vals[ 1 ] ); + + a->a_nvals = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) ); + a->a_nvals[ 0 ] = nval; + BER_BVZERO( &a->a_nvals[ 1 ] ); + + return a; +} + +Attribute * +backsql_operational_entryCSN( Operation *op ) +{ + char csnbuf[ LDAP_PVT_CSNSTR_BUFSIZE ]; + struct berval entryCSN; + Attribute *a; + + a = attr_alloc( slap_schema.si_ad_entryCSN ); + a->a_numvals = 1; + a->a_vals = ch_malloc( 2 * sizeof( struct berval ) ); + BER_BVZERO( &a->a_vals[ 1 ] ); + +#ifdef BACKSQL_SYNCPROV + if ( op->o_sync && op->o_tag == LDAP_REQ_SEARCH && op->o_private != NULL ) { + assert( op->o_private != NULL ); + + entryCSN = *((struct berval *)op->o_private); + + } else +#endif /* BACKSQL_SYNCPROV */ + { + entryCSN.bv_val = csnbuf; + entryCSN.bv_len = sizeof( csnbuf ); + slap_get_csn( op, &entryCSN, 0 ); + } + + ber_dupbv( &a->a_vals[ 0 ], &entryCSN ); + + a->a_nvals = a->a_vals; + + return a; +} + +int +backsql_operational( + Operation *op, + SlapReply *rs ) +{ + + backsql_info *bi = (backsql_info*)op->o_bd->be_private; + SQLHDBC dbh = SQL_NULL_HDBC; + int rc = 0; + Attribute **ap; + enum { + BACKSQL_OP_HASSUBORDINATES = 0, + BACKSQL_OP_ENTRYUUID, + BACKSQL_OP_ENTRYCSN, + + BACKSQL_OP_LAST + }; + int get_conn = BACKSQL_OP_LAST, + got[ BACKSQL_OP_LAST ] = { 0 }; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_operational(): entry \"%s\"\n", + rs->sr_entry->e_nname.bv_val, 0, 0 ); + + for ( ap = &rs->sr_entry->e_attrs; *ap; ap = &(*ap)->a_next ) { + if ( (*ap)->a_desc == slap_schema.si_ad_hasSubordinates ) { + get_conn--; + got[ BACKSQL_OP_HASSUBORDINATES ] = 1; + + } else if ( (*ap)->a_desc == slap_schema.si_ad_entryUUID ) { + get_conn--; + got[ BACKSQL_OP_ENTRYUUID ] = 1; + + } else if ( (*ap)->a_desc == slap_schema.si_ad_entryCSN ) { + get_conn--; + got[ BACKSQL_OP_ENTRYCSN ] = 1; + } + } + + for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next ) { + if ( !got[ BACKSQL_OP_HASSUBORDINATES ] && + (*ap)->a_desc == slap_schema.si_ad_hasSubordinates ) + { + get_conn--; + got[ BACKSQL_OP_HASSUBORDINATES ] = 1; + + } else if ( !got[ BACKSQL_OP_ENTRYUUID ] && + (*ap)->a_desc == slap_schema.si_ad_entryUUID ) + { + get_conn--; + got[ BACKSQL_OP_ENTRYUUID ] = 1; + + } else if ( !got[ BACKSQL_OP_ENTRYCSN ] && + (*ap)->a_desc == slap_schema.si_ad_entryCSN ) + { + get_conn--; + got[ BACKSQL_OP_ENTRYCSN ] = 1; + } + } + + if ( !get_conn ) { + return 0; + } + + rc = backsql_get_db_conn( op, &dbh ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_operational(): " + "could not get connection handle - exiting\n", + 0, 0, 0 ); + return 1; + } + + if ( ( SLAP_OPATTRS( rs->sr_attr_flags ) || ad_inlist( slap_schema.si_ad_hasSubordinates, rs->sr_attrs ) ) + && !got[ BACKSQL_OP_HASSUBORDINATES ] + && attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_hasSubordinates ) == NULL ) + { + rc = backsql_has_children( op, dbh, &rs->sr_entry->e_nname ); + + switch( rc ) { + case LDAP_COMPARE_TRUE: + case LDAP_COMPARE_FALSE: + *ap = slap_operational_hasSubordinate( rc == LDAP_COMPARE_TRUE ); + assert( *ap != NULL ); + ap = &(*ap)->a_next; + rc = 0; + break; + + default: + Debug( LDAP_DEBUG_TRACE, "backsql_operational(): " + "has_children failed( %d)\n", rc, 0, 0 ); + return 1; + } + } + + if ( ( SLAP_OPATTRS( rs->sr_attr_flags ) || ad_inlist( slap_schema.si_ad_entryUUID, rs->sr_attrs ) ) + && !got[ BACKSQL_OP_ENTRYUUID ] + && attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryUUID ) == NULL ) + { + backsql_srch_info bsi = { 0 }; + + rc = backsql_init_search( &bsi, &rs->sr_entry->e_nname, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, NULL, + BACKSQL_ISF_GET_ID ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_operational(): " + "could not retrieve entry ID - no such entry\n", + 0, 0, 0 ); + return 1; + } + + *ap = backsql_operational_entryUUID( bi, &bsi.bsi_base_id ); + + (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx ); + + if ( bsi.bsi_attrs != NULL ) { + op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx ); + } + + if ( *ap == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_operational(): " + "could not retrieve entryUUID\n", + 0, 0, 0 ); + return 1; + } + + ap = &(*ap)->a_next; + } + + if ( ( SLAP_OPATTRS( rs->sr_attr_flags ) || ad_inlist( slap_schema.si_ad_entryCSN, rs->sr_attrs ) ) + && !got[ BACKSQL_OP_ENTRYCSN ] + && attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryCSN ) == NULL ) + { + *ap = backsql_operational_entryCSN( op ); + if ( *ap == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_operational(): " + "could not retrieve entryCSN\n", + 0, 0, 0 ); + return 1; + } + + ap = &(*ap)->a_next; + } + + Debug( LDAP_DEBUG_TRACE, "<==backsql_operational(%d)\n", rc, 0, 0); + + return rc; +} + diff --git a/servers/slapd/back-sql/proto-sql.h b/servers/slapd/back-sql/proto-sql.h new file mode 100644 index 0000000..9633bc7 --- /dev/null +++ b/servers/slapd/back-sql/proto-sql.h @@ -0,0 +1,313 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Mararati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati + */ + +/* + * The following changes have been addressed: + * + * Enhancements: + * - re-styled code for better readability + * - upgraded backend API to reflect recent changes + * - LDAP schema is checked when loading SQL/LDAP mapping + * - AttributeDescription/ObjectClass pointers used for more efficient + * mapping lookup + * - bervals used where string length is required often + * - atomized write operations by committing at the end of each operation + * and defaulting connection closure to rollback + * - added LDAP access control to write operations + * - fully implemented modrdn (with rdn attrs change, deleteoldrdn, + * access check, parent/children check and more) + * - added parent access control, children control to delete operation + * - added structuralObjectClass operational attribute check and + * value return on search + * - added hasSubordinate operational attribute on demand + * - search limits are appropriately enforced + * - function backsql_strcat() has been made more efficient + * - concat function has been made configurable by means of a pattern + * - added config switches: + * - fail_if_no_mapping write operations fail if there is no mapping + * - has_ldapinfo_dn_ru overrides autodetect + * - concat_pattern a string containing two '?' is used + * (note that "?||?" should be more portable + * than builtin function "CONCAT(?,?)") + * - strcast_func cast of string constants in "SELECT DISTINCT + * statements (needed by PostgreSQL) + * - upper_needs_cast cast the argument of upper when required + * (basically when building dn substring queries) + * - added noop control + * - added values return filter control + * - hasSubordinate can be used in search filters (with limitations) + * - eliminated oc->name; use oc->oc->soc_cname instead + * + * Todo: + * - add security checks for SQL statements that can be injected (?) + * - re-test with previously supported RDBMs + * - replace dn_ru and so with normalized dn (no need for upper() and so + * in dn match) + * - implement a backsql_normalize() function to replace the upper() + * conversion routines + * - note that subtree deletion, subtree renaming and so could be easily + * implemented (rollback and consistency checks are available :) + * - implement "lastmod" and other operational stuff (ldap_entries table ?) + * - check how to allow multiple operations with one statement, to remove + * BACKSQL_REALLOC_STMT from modify.c (a more recent unixODBC lib?) + */ + +#ifndef PROTO_SQL_H +#define PROTO_SQL_H + +#include "back-sql.h" + +/* + * add.c + */ +int backsql_modify_delete_all_values( + Operation *op, + SlapReply *rs, + SQLHDBC dbh, + backsql_entryID *e_id, + backsql_at_map_rec *at ); + +int backsql_modify_internal( + Operation *op, + SlapReply *rs, + SQLHDBC dbh, + backsql_oc_map_rec *oc, + backsql_entryID *e_id, + Modifications *modlist ); + +/* + * api.c + */ +int backsql_api_config( backsql_info *bi, const char *name, + int argc, char *argv[] ); +int backsql_api_destroy( backsql_info *bi ); +int backsql_api_register( backsql_api *ba ); +int backsql_api_dn2odbc( Operation *op, SlapReply *rs, struct berval *dn ); +int backsql_api_odbc2dn( Operation *op, SlapReply *rs, struct berval *dn ); + +/* + * entry-id.c + */ +#ifdef BACKSQL_ARBITRARY_KEY +extern struct berval backsql_baseObject_bv; +#endif /* BACKSQL_ARBITRARY_KEY */ + +/* stores in *id the ID in table ldap_entries corresponding to DN, if any */ +extern int +backsql_dn2id( Operation *op, SlapReply *rs, SQLHDBC dbh, + struct berval *ndn, backsql_entryID *id, + int matched, int muck ); + +/* stores in *nchildren the count of children for an entry */ +extern int +backsql_count_children( Operation *op, SQLHDBC dbh, + struct berval *dn, unsigned long *nchildren ); + +/* returns LDAP_COMPARE_TRUE/LDAP_COMPARE_FALSE if the entry corresponding + * to DN has/has not children */ +extern int +backsql_has_children( Operation *op, SQLHDBC dbh, struct berval *dn ); + +/* free *id and return next in list */ +extern backsql_entryID * +backsql_free_entryID( backsql_entryID *id, int freeit, void *ctx ); + +/* turn an ID into an entry */ +extern int +backsql_id2entry( backsql_srch_info *bsi, backsql_entryID *id ); + +/* duplicate an entryID */ +extern backsql_entryID * +backsql_entryID_dup( backsql_entryID *eid, void *ctx ); + +/* + * operational.c + */ + +Attribute *backsql_operational_entryUUID( backsql_info *bi, backsql_entryID *id ); + +Attribute *backsql_operational_entryCSN( Operation *op ); + +/* + * schema-map.c + */ + +int backsql_load_schema_map( backsql_info *si, SQLHDBC dbh ); + +backsql_oc_map_rec *backsql_oc2oc( backsql_info *si, ObjectClass *oc ); + +backsql_oc_map_rec *backsql_id2oc( backsql_info *si, unsigned long id ); + +backsql_oc_map_rec * backsql_name2oc( backsql_info *si, + struct berval *oc_name ); + +backsql_at_map_rec *backsql_ad2at( backsql_oc_map_rec *objclass, + AttributeDescription *ad ); + +int backsql_supad2at( backsql_oc_map_rec *objclass, + AttributeDescription *supad, backsql_at_map_rec ***pret ); + +int backsql_destroy_schema_map( backsql_info *si ); + +/* + * search.c + */ + +int backsql_init_search( backsql_srch_info *bsi, + struct berval *nbase, int scope, + time_t stoptime, Filter *filter, SQLHDBC dbh, + Operation *op, SlapReply *rs, AttributeName *attrs, + unsigned flags ); + +void backsql_entry_clean( Operation *op, Entry *e ); + +/* + * sql-wrap.h + */ + +RETCODE backsql_Prepare( SQLHDBC dbh, SQLHSTMT *sth, const char* query, int timeout ); + +#define backsql_BindParamStr( sth, par_ind, io, str, maxlen ) \ + SQLBindParameter( (sth), (SQLUSMALLINT)(par_ind), \ + (io), SQL_C_CHAR, SQL_VARCHAR, \ + (SQLULEN)(maxlen), 0, (SQLPOINTER)(str), \ + (SQLLEN)(maxlen), NULL ) + +#define backsql_BindParamBerVal( sth, par_ind, io, bv ) \ + SQLBindParameter( (sth), (SQLUSMALLINT)(par_ind), \ + (io), SQL_C_CHAR, SQL_VARCHAR, \ + (SQLULEN)(bv)->bv_len, 0, \ + (SQLPOINTER)(bv)->bv_val, \ + (SQLLEN)(bv)->bv_len, NULL ) + +#define backsql_BindParamInt( sth, par_ind, io, val ) \ + SQLBindParameter( (sth), (SQLUSMALLINT)(par_ind), \ + (io), SQL_C_ULONG, SQL_INTEGER, \ + 0, 0, (SQLPOINTER)(val), 0, (SQLLEN*)NULL ) + +#define backsql_BindParamNumID( sth, par_ind, io, val ) \ + SQLBindParameter( (sth), (SQLUSMALLINT)(par_ind), \ + (io), BACKSQL_C_NUMID, SQL_INTEGER, \ + 0, 0, (SQLPOINTER)(val), 0, (SQLLEN*)NULL ) + +#ifdef BACKSQL_ARBITRARY_KEY +#define backsql_BindParamID( sth, par_ind, io, id ) \ + backsql_BindParamBerVal( (sth), (par_ind), (io), (id) ) +#else /* ! BACKSQL_ARBITRARY_KEY */ +#define backsql_BindParamID( sth, par_ind, io, id ) \ + backsql_BindParamNumID( (sth), (par_ind), (io), (id) ) +#endif /* ! BACKSQL_ARBITRARY_KEY */ + +RETCODE backsql_BindRowAsStrings_x( SQLHSTMT sth, BACKSQL_ROW_NTS *row, void *ctx ); + +RETCODE backsql_BindRowAsStrings( SQLHSTMT sth, BACKSQL_ROW_NTS *row ); + +RETCODE backsql_FreeRow_x( BACKSQL_ROW_NTS *row, void *ctx ); + +RETCODE backsql_FreeRow( BACKSQL_ROW_NTS *row ); + +void backsql_PrintErrors( SQLHENV henv, SQLHDBC hdbc, SQLHSTMT sth, int rc ); + +int backsql_conn_destroy( backsql_info *bi ); + +int backsql_init_db_env( backsql_info *si ); + +int backsql_free_db_env( backsql_info *si ); + +int backsql_get_db_conn( Operation *op, SQLHDBC *dbh ); + +int backsql_free_db_conn( Operation *op, SQLHDBC dbh ); + +/* + * util.c + */ + +extern const char + backsql_def_oc_query[], + backsql_def_needs_select_oc_query[], + backsql_def_at_query[], + backsql_def_delentry_stmt[], + backsql_def_renentry_stmt[], + backsql_def_insentry_stmt[], + backsql_def_delobjclasses_stmt[], + backsql_def_subtree_cond[], + backsql_def_upper_subtree_cond[], + backsql_id_query[], + backsql_def_concat_func[], + backsql_check_dn_ru_query[]; + +struct berbuf * backsql_strcat_x( struct berbuf *dest, void *memctx, ... ); +struct berbuf * backsql_strfcat_x( struct berbuf *dest, void *memctx, const char *fmt, ... ); + +int backsql_entry_addattr( Entry *e, AttributeDescription *ad, + struct berval *at_val, void *memctx ); + +int backsql_merge_from_clause( backsql_info *bi, struct berbuf *dest_from, + struct berval *src_from ); + +int backsql_split_pattern( const char *pattern, BerVarray *split_pattern, + int expected ); + +int backsql_prepare_pattern( BerVarray split_pattern, BerVarray values, + struct berval *res ); + +int backsql_entryUUID( backsql_info *bi, backsql_entryID *id, + struct berval *entryUUID, void *memctx ); +int backsql_entryUUID_decode( struct berval *entryUUID, unsigned long *oc_id, +#ifdef BACKSQL_ARBITRARY_KEY + struct berval *keyval +#else /* ! BACKSQL_ARBITRARY_KEY */ + unsigned long *keyval +#endif /* ! BACKSQL_ARBITRARY_KEY */ + ); + +/* + * former external.h + */ + +extern BI_init sql_back_initialize; + +extern BI_destroy backsql_destroy; + +extern BI_db_init backsql_db_init; +extern BI_db_open backsql_db_open; +extern BI_db_close backsql_db_close; +extern BI_db_destroy backsql_db_destroy; +extern BI_db_config backsql_db_config; + +extern BI_op_bind backsql_bind; +extern BI_op_search backsql_search; +extern BI_op_compare backsql_compare; +extern BI_op_modify backsql_modify; +extern BI_op_modrdn backsql_modrdn; +extern BI_op_add backsql_add; +extern BI_op_delete backsql_delete; + +extern BI_operational backsql_operational; +extern BI_entry_get_rw backsql_entry_get; +extern BI_entry_release_rw backsql_entry_release; + +extern BI_connection_destroy backsql_connection_destroy; + +int backsql_init_cf( BackendInfo * bi ); + +#endif /* PROTO_SQL_H */ diff --git a/servers/slapd/back-sql/rdbms_depend/README b/servers/slapd/back-sql/rdbms_depend/README new file mode 100644 index 0000000..8c9ffe1 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/README @@ -0,0 +1,189 @@ +Author: Pierangelo Masarati <ando@OpenLDAP.org> + +Back-sql can be tested with sql-test000-read; it requires a bit of work +to get everything up and running appropriately. + +This document briefly describes the steps that are required to prepare +a quick'n'dirty installation of back-sql and of the related RDBMS +and ODBC; Examples are provided, but by no means they pretent +to represent an exaustive source of info about how to setup the ODBC; +refer to the docs for any problem or detail. + +Currently, the system has been tested with IBM db2, PostgreSQL and MySQL; +basic support and test data for other RDBMSes is in place, but as of +today (November 2004) it's totally untested. If you succeed in running +any of the other RDBMSes, please provide feedback about any required +change either in the code or in the test scripts by means of OpenLDAP's +Issue Tracking System (http://www.openldap.org/its/). + +1) slapd must be compiled with back-sql support, i.e. configure +with --enable-sql switch. This requires an implementation of the ODBC +to be installed. + +2) The ODBC must be set up appropriately, by editing the odbc.ini file +in /etc/ (or wherever your installation puts it) and, if appropriate, +the odbcinst.ini file. Note: you can also use custom odbc.ini and +odbcinst.ini files, provided you export in ODBCINI the full path to the +odbc.ini file, and in ODBCSYSINI the directory where the odbcinst.ini +file resides. +Relevant info for our test setup is highlighted with '<===' on the right. + +2.1) PostgreSQL + +2.1.1) Add to the odbc.ini file a block of the form + +[example] <=== +Description = Example for OpenLDAP's back-sql +Driver = PostgreSQL +Trace = No +Database = example <=== +Servername = localhost +UserName = manager <=== +Password = secret <=== +Port = 5432 +;Protocol = 6.4 +ReadOnly = No +RowVersioning = No +ShowSystemTables = No +ShowOidColumn = No +FakeOidIndex = No +ConnSettings = + +2.1.2) Add to the odbcinst.ini file a block of the form + +[PostgreSQL] +Description = ODBC for PostgreSQL +Driver = /usr/lib/libodbcpsql.so +Setup = /usr/lib/libodbcpsqlS.so +FileUsage = 1 + +2.2) MySQL + +2.2.1) Add to the odbc.ini file a block of the form + +[example] <=== +Description = Example for OpenLDAP's back-sql +Driver = MySQL +Trace = No +Database = example <=== +Servername = localhost +UserName = manager <=== +Password = secret <=== +ReadOnly = No +RowVersioning = No +ShowSystemTables = No +ShowOidColumn = No +FakeOidIndex = No +ConnSettings = +SOCKET = /var/lib/mysql/mysql.sock + +2.2.2) Add to the odbcinst.ini file a block of the form + +[MySQL] +Description = ODBC for MySQL +Driver = /usr/lib/libmyodbc.so +FileUsage = 1 + +2.3) IBM db2 +[n.a.] + +3) The RDBMS must be setup; examples are provided for my installations +of PostgreSQL and MySQL, but details may change; other RDBMSes should +be configured in a similar manner, you need to find out the details by +reading their documentation. + +3.1) PostgreSQL + +3.1.1) Start the server +on RedHat: +[root@localhost]# service postgresql start +on other systems: read the docs... + +3.1.2) Create the database: +[root@localhost]# su - postgres +[postgres@localhost]$ createdb example + +3.1.3) Create the user: +[root@localhost]# su - postgres +[postgres@localhost]$ psql example +example=> create user manager with password 'secret'; +example=> <control-D> + +3.1.4) Populate the database: +[root@localhost]# cd $SOURCES/servers/slapd/back-sql/rdbms_depend/pgsql/ +[root@localhost]# psql -U manager -W example +example=> <control-D> +[root@localhost]# psql -U manager example < backsql_create.sql +[root@localhost]# psql -U manager example < testdb_create.sql +[root@localhost]# psql -U manager example < testdb_data.sql +[root@localhost]# psql -U manager example < testdb_metadata.sql + +3.1.5) Run the test: +[root@localhost]# cd $SOURCES/tests +[root@localhost]# SLAPD_USE_SQL=pgsql ./run sql-test000 + +3.2) MySQL + +3.2.1) Start the server +on RedHat: +[root@localhost]# service mysqld start +on other systems: read the docs... + +3.2.2) Create the database: +[root@localhost]# mysqladmin -u root -p create example +(hit <return> for the empty password). + +3.2.3) Create the user: +[root@localhost]# mysql -u root -p example +(hit <return> for the empty password) +mysql> grant all privileges on *.* \ + to 'manager'@'localhost' identified by 'secret' with grant option; +mysql> exit; + +3.2.4) Populate the database: +[root@localhost]# cd $SOURCES/servers/slapd/back-sql/rdbms_depend/mysql/ +[root@localhost]# mysql -u manager -p example < backsql_create.sql +[root@localhost]# mysql -u manager -p example < testdb_create.sql +[root@localhost]# mysql -u manager -p example < testdb_data.sql +[root@localhost]# mysql -u manager -p example < testdb_metadata.sql + +3.2.5) Run the test: +[root@localhost]# cd $SOURCES/tests +[root@localhost]# SLAPD_USE_SQL=mysql ./run sql-test000 + +3.3) IBM db2 +[n.a.] + +3.3.1) Start the server: + +3.3.2) Create the database: + +3.3.3) Create the user: + +3.3.4) Populate the database: +connect to the database as user manager, and execute the test files +in auto-commit mode (-c) +[root@localhost]# su - manager +[manager@localhost]$ db2 "connect to example user manager using secret" +[manager@localhost]$ db2 -ctvf backsql_create.sql +[manager@localhost]$ db2 -ctvf testdb_create.sql +[manager@localhost]$ db2 -ctvf testdb_data.sql +[manager@localhost]$ db2 -ctvf testdb_metadata.sql +[manager@localhost]$ db2 "connect reset" + +3.3.5) Run the test: +[root@localhost]# cd $SOURCES/tests +[root@localhost]# SLAPD_USE_SQL=ibmdb2 ./run sql-test000 + +4) Cleanup: +The test is basically readonly; this can be performed by all RDBMSes +(listed above). + +There is another test, sql-test900-write, which is currently enabled +only for PostgreSQL and IBM db2. Note that after a successful run +of the write test, the database is no longer in the correct state +to restart either of the tests, and step 3.X.4 needs to be re-run first. + +More tests are to come; PostgreSQL is known to allow a full reload +of the test database starting from an empty database. + diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_create.sql new file mode 100644 index 0000000..cb2856b --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_create.sql @@ -0,0 +1,59 @@ +drop table ldap_oc_mappings; +create table ldap_oc_mappings + ( + id integer not null primary key, + name varchar(64) not null, + keytbl varchar(64) not null, + keycol varchar(64) not null, + create_proc varchar(255), + create_keyval varchar(255), + delete_proc varchar(255), + expect_return integer not null +); + +drop table ldap_attr_mappings; +create table ldap_attr_mappings + ( + id integer not null primary key, + oc_map_id integer not null references ldap_oc_mappings(id), + name varchar(255) not null, + sel_expr varchar(255) not null, + sel_expr_u varchar(255), + from_tbls varchar(255) not null, + join_where varchar(255), + add_proc varchar(255), + delete_proc varchar(255), + param_order integer not null, + expect_return integer not null +); + +drop table ldap_entries; +create table ldap_entries + ( + id integer not null primary key, + dn varchar(255) not null, + oc_map_id integer not null references ldap_oc_mappings(id), + parent int NOT NULL , + keyval int NOT NULL +); + +alter table ldap_entries add + constraint unq1_ldap_entries unique + ( + oc_map_id, + keyval + ); + +alter table ldap_entries add + constraint unq2_ldap_entries unique + ( + dn + ); + +drop table ldap_entry_objclasses; +create table ldap_entry_objclasses + ( + entry_id integer not null references ldap_entries(id), + oc_name varchar(64) + ); + diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_drop.sql new file mode 100644 index 0000000..49e7e3a --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_drop.sql @@ -0,0 +1,5 @@ +DROP TABLE ldap_referrals; +DROP TABLE ldap_entry_objclasses; +DROP TABLE ldap_attr_mappings; +DROP TABLE ldap_entries; +DROP TABLE ldap_oc_mappings; diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/slapd.conf b/servers/slapd/back-sql/rdbms_depend/ibmdb2/slapd.conf new file mode 100644 index 0000000..f6c1613 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/slapd.conf @@ -0,0 +1,36 @@ +# $OpenLDAP$ +# +# See slapd.conf(5) for details on configuration options. +# This file should NOT be world readable. +# +include /usr/local/etc/openldap/schema/core.schema +include /usr/local/etc/openldap/schema/cosine.schema +include /usr/local/etc/openldap/schema/inetorgperson.schema + +# Define global ACLs to disable default read access. + +# Do not enable referrals until AFTER you have a working directory +# service AND an understanding of referrals. +#referral ldap://root.openldap.org + +pidfile /usr/local/var/slapd.pid +argsfile /usr/local/var/slapd.args + +####################################################################### +# sql database definitions +####################################################################### + +database sql +suffix "o=sql,c=RU" +rootdn "cn=root,o=sql,c=RU" +rootpw secret +dbname ldap_db2 +dbuser db2inst1 +dbpasswd ibmdb2 +subtree_cond "upper(ldap_entries.dn) LIKE CONCAT('%',?)" +insentry_stmt "insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values ((select max(id)+1 from ldap_entries),?,?,?,?)" +upper_func "upper" +upper_needs_cast "yes" +create_needs_select "yes" +has_ldapinfo_dn_ru "no" + diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_create.sql new file mode 100644 index 0000000..b6e850c --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_create.sql @@ -0,0 +1,75 @@ +drop table persons; +CREATE TABLE persons ( + id int NOT NULL, + name varchar(255) NOT NULL, + surname varchar(255) NOT NULL, + password varchar(64) +); + +drop table institutes; +CREATE TABLE institutes ( + id int NOT NULL, + name varchar(255) +); + +drop table documents; +CREATE TABLE documents ( + id int NOT NULL, + title varchar(255) NOT NULL, + abstract varchar(255) +); + +drop table authors_docs; +CREATE TABLE authors_docs ( + pers_id int NOT NULL, + doc_id int NOT NULL +); + +drop table phones; +CREATE TABLE phones ( + id int NOT NULL , + phone varchar(255) NOT NULL , + pers_id int NOT NULL +); + +drop table referrals; +CREATE TABLE referrals ( + id int NOT NULL, + name varchar(255) NOT NULL, + url varchar(255) NOT NULL +); + + + +ALTER TABLE authors_docs ADD + CONSTRAINT PK_authors_docs PRIMARY KEY + ( + pers_id, + doc_id + ); + +ALTER TABLE documents ADD + CONSTRAINT PK_documents PRIMARY KEY + ( + id + ); + +ALTER TABLE institutes ADD + CONSTRAINT PK_institutes PRIMARY KEY + ( + id + ); + + +ALTER TABLE persons ADD + CONSTRAINT PK_persons PRIMARY KEY + ( + id + ); + +ALTER TABLE phones ADD + CONSTRAINT PK_phones PRIMARY KEY + ( + id + ); + diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_data.sql new file mode 100644 index 0000000..7bef374 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_data.sql @@ -0,0 +1,18 @@ +insert into institutes (id,name) values (1,'Example'); + +insert into persons (id,name,surname,password) values (1,'Mitya','Kovalev','mit'); +insert into persons (id,name,surname) values (2,'Torvlobnor','Puzdoy'); +insert into persons (id,name,surname) values (3,'Akakiy','Zinberstein'); + +insert into phones (id,phone,pers_id) values (1,'332-2334',1); +insert into phones (id,phone,pers_id) values (2,'222-3234',1); +insert into phones (id,phone,pers_id) values (3,'545-4563',2); + +insert into documents (id,abstract,title) values (1,'abstract1','book1'); +insert into documents (id,abstract,title) values (2,'abstract2','book2'); + +insert into authors_docs (pers_id,doc_id) values (1,1); +insert into authors_docs (pers_id,doc_id) values (1,2); +insert into authors_docs (pers_id,doc_id) values (2,1); + +insert into referrals (id,name,url) values (1,'Referral','ldap://localhost:9012/'); diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_drop.sql new file mode 100644 index 0000000..17b12af --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_drop.sql @@ -0,0 +1,5 @@ +DROP TABLE persons; +DROP TABLE institutes; +DROP TABLE documents; +DROP TABLE authors_docs; +DROP TABLE phones; diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_metadata.sql new file mode 100644 index 0000000..0b0d1c2 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_metadata.sql @@ -0,0 +1,123 @@ +--mappings + +-- objectClass mappings: these may be viewed as structuralObjectClass, the ones that are used to decide how to build an entry +-- id a unique number identifying the objectClass +-- name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema +-- keytbl the name of the table that is referenced for the primary key of an entry +-- keycol the name of the column in "keytbl" that contains the primary key of an entry; the pair "keytbl.keycol" uniquely identifies an entry of objectClass "id" +-- create_proc a procedure to create the entry +-- create_keyval a query that returns the id of the last inserted entry +-- delete_proc a procedure to delete the entry; it takes "keytbl.keycol" of the row to be deleted +-- expect_return a bitmap that marks whether create_proc (1) and delete_proc (2) return a value or not +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,create_keyval,delete_proc,expect_return) +values (1,'inetOrgPerson','persons','id','INSERT INTO persons (id,name,surname) VALUES ((SELECT max(id)+1 FROM persons),'''','''')', + 'SELECT max(id) FROM persons','DELETE FROM persons WHERE id=?',0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,create_keyval,delete_proc,expect_return) +values (2,'document','documents','id','INSERT INTO documents (id,title,abstract) VALUES ((SELECT max(id)+1 FROM documents),'''','''')', + 'SELECT max(id) FROM documents','DELETE FROM documents WHERE id=?',0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,create_keyval,delete_proc,expect_return) +values (3,'organization','institutes','id','INSERT INTO institutes (id,name) VALUES ((SELECT max(id)+1 FROM institutes),'''')', + 'SELECT max(id) FROM institutes','DELETE FROM institutes WHERE id=?',0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,create_keyval,delete_proc,expect_return) +values (4,'referral','referrals','id','INSERT INTO referrals (id,name,url) VALUES ((SELECT max(id)+1 FROM referrals),'''','''')', + 'SELECT max(id) FROM referrals','DELETE FROM referrals WHERE id=?',0); + +-- attributeType mappings: describe how an attributeType for a certain objectClass maps to the SQL data. +-- id a unique number identifying the attribute +-- oc_map_id the value of "ldap_oc_mappings.id" that identifies the objectClass this attributeType is defined for +-- name the name of the attributeType; it MUST match the name of an attributeType that is loaded in slapd's schema +-- sel_expr the expression that is used to select this attribute (the "select <sel_expr> from ..." portion) +-- from_tbls the expression that defines the table(s) this attribute is taken from (the "select ... from <from_tbls> where ..." portion) +-- join_where the expression that defines the condition to select this attribute (the "select ... where <join_where> ..." portion) +-- add_proc a procedure to insert the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to +-- delete_proc a procedure to delete the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to +-- param_order a mask that marks if the "keytbl.keycol" value comes before or after the value in add_proc (1) and delete_proc (2) +-- expect_return a mask that marks whether add_proc (1) and delete_proc(2) are expected to return a value or not +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (1,1,'cn','persons.name||'' ''||persons.surname','persons',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (2,1,'telephoneNumber','phones.phone','persons,phones', + 'phones.pers_id=persons.id','INSERT INTO phones (id,phone,pers_id) VALUES ((SELECT max(id)+1 FROM phones),?,?)', + 'DELETE FROM phones WHERE phone=? AND pers_id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (4,1,'givenName','persons.name','persons',NULL,'UPDATE persons SET name=? WHERE id=?',NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (3,1,'sn','persons.surname','persons',NULL,'UPDATE persons SET surname=? WHERE id=?',NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (5,1,'userPassword','persons.password','persons','persons.password IS NOT NULL','UPDATE persons SET password=? WHERE id=?', + 'UPDATE persons SET password=NULL WHERE password=? AND id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (6,1,'seeAlso','seeAlso.dn','ldap_entries AS seeAlso,documents,authors_docs,persons', + 'seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (7,2,'description','documents.abstract','documents',NULL,'UPDATE documents SET abstract=? WHERE id=?',NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (8,2,'documentTitle','documents.title','documents',NULL,'UPDATE documents SET title=? WHERE id=?',NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (9,2,'documentAuthor','documentAuthor.dn','ldap_entries AS documentAuthor,documents,authors_docs,persons', + 'documentAuthor.keyval=persons.id AND documentAuthor.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + 'INSERT INTO authors_docs (pers_id,doc_id) VALUES ((SELECT keyval FROM ldap_entries WHERE ucase(cast(? AS VARCHAR(255)))=ucase(dn)),?)', + 'DELETE FROM authors_docs WHERE pers_id=(SELECT keyval FROM ldap_entries WHERE ucase(cast(? AS VARCHAR(255))=ucase(dn)) AND doc_id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (10,2,'documentIdentifier','''document ''||rtrim(cast(documents.id AS CHAR(16)))','documents',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (11,3,'o','institutes.name','institutes',NULL,'UPDATE institutes SET name=? WHERE id=?',NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (12,3,'dc','lcase(institutes.name)','institutes,ldap_entries AS dcObject,ldap_entry_objclasses as auxObjectClass', + 'institutes.id=dcObject.keyval AND dcObject.oc_map_id=3 AND dcObject.id=auxObjectClass.entry_id AND auxObjectClass.oc_name=''dcObject''', + NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (13,4,'ou','referrals.name','referrals',NULL,'UPDATE referrals SET name=? WHERE id=?',NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (14,4,'ref','referrals.url','referrals',NULL,'UPDATE referrals SET url=? WHERE id=?',NULL,3,0); + +-- entries mapping: each entry must appear in this table, with a unique DN rooted at the database naming context +-- id a unique number > 0 identifying the entry +-- dn the DN of the entry, in "pretty" form +-- oc_map_id the "ldap_oc_mappings.id" of the main objectClass of this entry (view it as the structuralObjectClass) +-- parent the "ldap_entries.id" of the parent of this objectClass; 0 if it is the "suffix" of the database +-- keyval the value of the "keytbl.keycol" defined for this objectClass +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (1,'dc=example,dc=com',3,0,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (2,'cn=Mitya Kovalev,dc=example,dc=com',1,1,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (3,'cn=Torvlobnor Puzdoy,dc=example,dc=com',1,1,2); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (4,'cn=Akakiy Zinberstein,dc=example,dc=com',1,1,3); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (5,'documentTitle=book1,dc=example,dc=com',2,1,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (6,'documentTitle=book2,dc=example,dc=com',2,1,2); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (7,'ou=Referral,dc=example,dc=com',4,1,1); + +-- objectClass mapping: entries that have multiple objectClass instances are listed here with the objectClass name (view them as auxiliary objectClass) +-- entry_id the "ldap_entries.id" of the entry this objectClass value must be added +-- oc_name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema +insert into ldap_entry_objclasses (entry_id,oc_name) values (1,'dcObject'); + +insert into ldap_entry_objclasses (entry_id,oc_name) values (7,'extensibleObject'); diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/mssql/backsql_create.sql new file mode 100644 index 0000000..1f1f6d2 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mssql/backsql_create.sql @@ -0,0 +1,100 @@ +create table ldap_oc_mappings ( + id int identity (1, 1) not null , + name varchar (64) not null , + keytbl varchar (64) not null , + keycol varchar (64) not null , + create_proc varchar (255) NULL , + delete_proc varchar (255) NULL, + expect_return int not null +) +GO + +alter table ldap_oc_mappings add + constraint pk_ldap_oc_mappings primary key + ( + id + ) +GO + + +alter table ldap_oc_mappings add + constraint unq1_ldap_oc_mappings unique + ( + name + ) +GO + + +create table ldap_attr_mappings ( + id int identity (1, 1) not null , + oc_map_id int not null references ldap_oc_mappings(id), + name varchar (255) not null , + sel_expr varchar (255) not null , + sel_expr_u varchar(255), + from_tbls varchar (255) not null , + join_where varchar (255) NULL , + add_proc varchar (255) NULL , + delete_proc varchar (255) NULL , + param_order int not null, + expect_return int not null +) +GO + +alter table ldap_attr_mappings add + constraint pk_ldap_attr_mappings primary key + ( + id + ) +GO + + +create table ldap_entries ( + id int identity (1, 1) not null , + dn varchar (255) not null , + oc_map_id int not null references ldap_oc_mappings(id), + parent int not null , + keyval int not null +) +GO + + +alter table ldap_entries add + constraint pk_ldap_entries primary key + ( + id + ) +GO + +alter table ldap_entries add + constraint unq1_ldap_entries unique + ( + oc_map_id, + keyval + ) +GO + +alter table ldap_entries add + constraint unq2_ldap_entries unique + ( + dn + ) +GO + + +create table ldap_referrals + ( + entry_id int not null references ldap_entries(id), + url text not null +) +GO + +create index entry_idx on ldap_referrals(entry_id); + +create table ldap_entry_objclasses + ( + entry_id int not null references ldap_entries(id), + oc_name varchar(64) + ) +GO + +create index entry_idx on ldap_entry_objclasses(entry_id); diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/mssql/backsql_drop.sql new file mode 100644 index 0000000..0e888b3 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mssql/backsql_drop.sql @@ -0,0 +1,14 @@ +drop table ldap_attr_mappings +GO + +drop table ldap_referrals +GO + +drop table ldap_entry_objclasses +GO + +drop table ldap_entries +GO + +drop table ldap_oc_mappings +GO diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/slapd.conf b/servers/slapd/back-sql/rdbms_depend/mssql/slapd.conf new file mode 100644 index 0000000..c3032f2 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mssql/slapd.conf @@ -0,0 +1,30 @@ +# $OpenLDAP$ +# +# See slapd.conf(5) for details on configuration options. +# This file should NOT be world readable. +# +include ./schema/core.schema +include ./schema/cosine.schema +include ./schema/inetorgperson.schema + +# Define global ACLs to disable default read access. + +# Do not enable referrals until AFTER you have a working directory +# service AND an understanding of referrals. +#referral ldap://root.openldap.org + +pidfile ./slapd.pid +argsfile ./slapd.args + +####################################################################### +# sql database definitions +####################################################################### + +database sql +suffix "o=sql,c=RU" +rootdn "cn=root,o=sql,c=RU" +rootpw secret +dbname ldap_mssql +dbuser ldap +dbpasswd ldap +subtree_cond "ldap_entries.dn LIKE '%'+?" diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_create.sql new file mode 100644 index 0000000..2034afd --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_create.sql @@ -0,0 +1,74 @@ + +CREATE TABLE authors_docs ( + pers_id int NOT NULL , + doc_id int NOT NULL +) +GO + +CREATE TABLE documents ( + id int IDENTITY (1, 1) NOT NULL , + abstract varchar (255) NULL , + title varchar (255) NULL , + body binary (255) NULL +) +GO + +CREATE TABLE institutes ( + id int IDENTITY (1, 1) NOT NULL , + name varchar (255) NOT NULL +) +GO + + +CREATE TABLE persons ( + id int IDENTITY (1, 1) NOT NULL , + name varchar (255) NULL , + surname varchar (255) NULL , + password varchar (64) NULL +) +GO + +CREATE TABLE phones ( + id int IDENTITY (1, 1) NOT NULL , + phone varchar (255) NOT NULL , + pers_id int NOT NULL +) +GO + +ALTER TABLE authors_docs WITH NOCHECK ADD + CONSTRAINT PK_authors_docs PRIMARY KEY + ( + pers_id, + doc_id + ) +GO + +ALTER TABLE documents WITH NOCHECK ADD + CONSTRAINT PK_documents PRIMARY KEY + ( + id + ) +GO + +ALTER TABLE institutes WITH NOCHECK ADD + CONSTRAINT PK_institutes PRIMARY KEY + ( + id + ) +GO + + +ALTER TABLE persons WITH NOCHECK ADD + CONSTRAINT PK_persons PRIMARY KEY + ( + id + ) +GO + +ALTER TABLE phones WITH NOCHECK ADD + CONSTRAINT PK_phones PRIMARY KEY + ( + id + ) +GO + diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_data.sql new file mode 100644 index 0000000..21a51ef --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_data.sql @@ -0,0 +1,24 @@ +set IDENTITY_INSERT institutes ON +insert into institutes (id,name) values (1,'Example') +set IDENTITY_INSERT institutes OFF + +set IDENTITY_INSERT persons ON +insert into persons (id,name,surname,password) values (1,'Mitya','Kovalev','mit') +insert into persons (id,name,surname) values (2,'Torvlobnor','Puzdoy') +insert into persons (id,name,surname) values (3,'Akakiy','Zinberstein') +set IDENTITY_INSERT persons OFF + +set IDENTITY_INSERT phones ON +insert into phones (id,phone,pers_id) values (1,'332-2334',1) +insert into phones (id,phone,pers_id) values (2,'222-3234',1) +insert into phones (id,phone,pers_id) values (3,'545-4563',2) +set IDENTITY_INSERT phones OFF + +set IDENTITY_INSERT documents ON +insert into documents (id,abstract,title) values (1,'abstract1','book1') +insert into documents (id,abstract,title) values (2,'abstract2','book2') +set IDENTITY_INSERT documents OFF + +insert into authors_docs (pers_id,doc_id) values (1,1) +insert into authors_docs (pers_id,doc_id) values (1,2) +insert into authors_docs (pers_id,doc_id) values (2,1) diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_drop.sql new file mode 100644 index 0000000..4842ed8 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_drop.sql @@ -0,0 +1,39 @@ +drop procedure create_person +drop procedure set_person_name +drop procedure delete_phone +drop procedure add_phone +drop procedure make_doc_link +drop procedure del_doc_link +drop procedure delete_person + +drop procedure create_org +drop procedure set_org_name +drop procedure delete_org + +drop procedure create_document +drop procedure set_doc_title +drop procedure set_doc_abstract +drop procedure make_author_link +drop procedure del_author_link +drop procedure delete_document + +if exists (select * from sysobjects where id = object_id(N'authors_docs') and OBJECTPROPERTY(id, N'IsUserTable') = 1) +drop table authors_docs +GO + +if exists (select * from sysobjects where id = object_id(N'documents') and OBJECTPROPERTY(id, N'IsUserTable') = 1) +drop table documents +GO + +if exists (select * from sysobjects where id = object_id(N'institutes') and OBJECTPROPERTY(id, N'IsUserTable') = 1) +drop table institutes +GO + +if exists (select * from sysobjects where id = object_id(N'persons') and OBJECTPROPERTY(id, N'IsUserTable') = 1) +drop table persons +GO + +if exists (select * from sysobjects where id = object_id(N'phones') and OBJECTPROPERTY(id, N'IsUserTable') = 1) +drop table phones +GO + diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_metadata.sql new file mode 100644 index 0000000..e087523 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_metadata.sql @@ -0,0 +1,198 @@ +-- mappings + + +SET IDENTITY_INSERT ldap_oc_mappings ON +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) +values (1,'inetOrgPerson','persons','id','{call create_person(?)}','{call delete_person(?)}',0) + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) +values (2,'document','documents','id','{call create_document(?)}','{call delete_document(?)}',0) + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) +values (3,'organization','institutes','id','{call create_org(?)}','{call delete_org(?)}',0) +SET IDENTITY_INSERT ldap_oc_mappings OFF + + +SET IDENTITY_INSERT ldap_attr_mappings ON +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (1,1,'cn','persons.name+'' ''+persons.surname','persons',NULL, + NULL,NULL,0,0) + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (2,1,'telephoneNumber','phones.phone','persons,phones', + 'phones.pers_id=persons.id','{call add_phone(?,?)}', + '{call delete_phone(?,?)}',0,0) + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (3,1,'givenName','persons.name','persons',NULL, + '{call set_person_name(?,?)}',NULL,0,0) + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (4,1,'sn','persons.surname','persons',NULL, + '{call set_person_surname(?,?)}',NULL,0,0) + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (5,1,'userPassword','persons.password','persons','persons.password IS NOT NULL', + '{call set_person_password(?,?)}','call del_person_password(?,?)',0,0) + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (6,1,'seeAlso','seeAlso.dn','ldap_entries AS seeAlso,documents,authors_docs,persons', + 'seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + NULL,NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (7,2,'description','documents.abstract','documents',NULL,'{call set_doc_abstract(?,?)}', + NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (8,2,'documentTitle','documents.title','documents',NULL, '{call set_doc_title(?,?)}', + NULL,0,0) + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (9,2,'documentAuthor','documentAuthor.dn','ldap_entries AS documentAuthor,documents,authors_docs,persons', + 'documentAuthor.keyval=persons.id AND documentAuthor.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + 'INSERT INTO authors_docs (pers_id,doc_id) VALUES ((SELECT ldap_entries.keyval FROM ldap_entries WHERE upper(?)=upper(ldap_entries.dn)),?)', + 'DELETE FROM authors_docs WHERE authors_docs.pers_id=(SELECT ldap_entries.keyval FROM ldap_entries WHERE upper(?)=upper(ldap_entries.dn)) AND authors_docs.doc_id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (10,2,'documentIdentifier','''document ''+text(documents.id)','documents', + NULL,NULL,NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (11,3,'o','institutes.name','institutes',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (12,3,'dc','lower(institutes.name)','institutes,ldap_entries AS dcObject,ldap_entry_objclasses AS auxObjectClass', + 'institutes.id=dcObject.keyval AND dcObject.oc_map_id=3 AND dcObject.id=auxObjectClass.entry_id AND auxObjectClass.oc_name=''dcObject''', + '{call set_org_name(?,?)}',NULL,3,0); + +SET IDENTITY_INSERT ldap_attr_mappings OFF + +-- entries + +SET IDENTITY_INSERT ldap_entries ON +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (1,'dc=example,dc=com',3,0,1) + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (2,'cn=Mitya Kovalev,dc=example,dc=com',1,1,1) + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (3,'cn=Torvlobnor Puzdoy,dc=example,dc=com',1,1,2) + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (4,'cn=Akakiy Zinberstein,dc=example,dc=com',1,1,3) + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (5,'documentTitle=book1,dc=example,dc=com',2,1,1) + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (6,'documentTitle=book2,dc=example,dc=com',2,1,2) + +SET IDENTITY_INSERT ldap_entries OFF + +-- referrals +insert into ldap_entry_objclasses (entry_id,oc_name) +values (1,'dcObject'); + +insert into ldap_entry_objclasses (entry_id,oc_name) +values (4,'referral'); + +insert into ldap_referrals (entry_id,url) +values (4,'ldap://localhost:9012/'); + +-- support procedures + +SET QUOTED_IDENTIFIER OFF SET ANSI_NULLS ON +GO + + +CREATE PROCEDURE create_person @@keyval int OUTPUT AS +INSERT INTO example.persons (name) VALUES (''); +set @@keyval=(SELECT MAX(id) FROM example.persons) +GO + +CREATE PROCEDURE delete_person @keyval int AS +DELETE FROM example.phones WHERE pers_id=@keyval; +DELETE FROM example.authors_docs WHERE pers_id=@keyval; +DELETE FROM example.persons WHERE id=@keyval; +GO + +CREATE PROCEDURE create_org @@keyval int OUTPUT AS +INSERT INTO example.institutes (name) VALUES (''); +set @@keyval=(SELECT MAX(id) FROM example.institutes) +GO + +CREATE PROCEDURE delete_org @keyval int AS +DELETE FROM example.institutes WHERE id=@keyval; +GO + +CREATE PROCEDURE create_document @@keyval int OUTPUT AS +INSERT INTO example.documents (title) VALUES (''); +set @@keyval=(SELECT MAX(id) FROM example.documents) +GO + +CREATE PROCEDURE delete_document @keyval int AS +DELETE FROM example.authors_docs WHERE doc_id=@keyval; +DELETE FROM example.documents WHERE id=@keyval; +GO + +CREATE PROCEDURE add_phone @pers_id int, @phone varchar(255) AS +INSERT INTO example.phones (pers_id,phone) VALUES (@pers_id,@phone) +GO + +CREATE PROCEDURE delete_phone @keyval int,@phone varchar(64) AS +DELETE FROM example.phones WHERE pers_id=@keyval AND phone=@phone; +GO + +CREATE PROCEDURE set_person_name @keyval int, @new_name varchar(255) AS +UPDATE example.persons SET name=@new_name WHERE id=@keyval; +GO + +CREATE PROCEDURE set_person_surname @keyval int, @new_surname varchar(255) AS +UPDATE example.persons SET surname=@new_surname WHERE id=@keyval; +GO + +CREATE PROCEDURE set_org_name @keyval int, @new_name varchar(255) AS +UPDATE example.institutes SET name=@new_name WHERE id=@keyval; +GO + +CREATE PROCEDURE set_doc_title @keyval int, @new_title varchar(255) AS +UPDATE example.documents SET title=@new_title WHERE id=@keyval; +GO + +CREATE PROCEDURE set_doc_abstract @keyval int, @new_abstract varchar(255) AS +UPDATE example.documents SET abstract=@new_abstract WHERE id=@keyval; +GO + +CREATE PROCEDURE make_author_link @keyval int, @author_dn varchar(255) AS +DECLARE @per_id int; +SET @per_id=(SELECT keyval FROM example.ldap_entries + WHERE oc_map_id=1 AND dn=@author_dn); +IF NOT (@per_id IS NULL) + INSERT INTO example.authors_docs (doc_id,pers_id) VALUES (@keyval,@per_id); +GO + +CREATE PROCEDURE make_doc_link @keyval int, @doc_dn varchar(255) AS +DECLARE @doc_id int; +SET @doc_id=(SELECT keyval FROM example.ldap_entries + WHERE oc_map_id=2 AND dn=@doc_dn); +IF NOT (@doc_id IS NULL) + INSERT INTO example.authors_docs (pers_id,doc_id) VALUES (@keyval,@doc_id); +GO + +CREATE PROCEDURE del_doc_link @keyval int, @doc_dn varchar(255) AS +DECLARE @doc_id int; +SET @doc_id=(SELECT keyval FROM example.ldap_entries + WHERE oc_map_id=2 AND dn=@doc_dn); +IF NOT (@doc_id IS NULL) +DELETE FROM example.authors_docs WHERE pers_id=@keyval AND doc_id=@doc_id; +GO + +CREATE PROCEDURE del_author_link @keyval int, @author_dn varchar(255) AS +DECLARE @per_id int; +SET @per_id=(SELECT keyval FROM example.ldap_entries + WHERE oc_map_id=1 AND dn=@author_dn); +IF NOT (@per_id IS NULL) + DELETE FROM example.authors_docs WHERE doc_id=@keyval AND pers_id=@per_id; +GO diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/mysql/backsql_create.sql new file mode 100644 index 0000000..771c1c8 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mysql/backsql_create.sql @@ -0,0 +1,58 @@ +drop table if exists ldap_oc_mappings; +create table ldap_oc_mappings + ( + id integer unsigned not null primary key auto_increment, + name varchar(64) not null, + keytbl varchar(64) not null, + keycol varchar(64) not null, + create_proc varchar(255), + delete_proc varchar(255), + expect_return tinyint not null +); + +drop table if exists ldap_attr_mappings; +create table ldap_attr_mappings + ( + id integer unsigned not null primary key auto_increment, + oc_map_id integer unsigned not null references ldap_oc_mappings(id), + name varchar(255) not null, + sel_expr varchar(255) not null, + sel_expr_u varchar(255), + from_tbls varchar(255) not null, + join_where varchar(255), + add_proc varchar(255), + delete_proc varchar(255), + param_order tinyint not null, + expect_return tinyint not null +); + +drop table if exists ldap_entries; +create table ldap_entries + ( + id integer unsigned not null primary key auto_increment, + dn varchar(255) not null, + oc_map_id integer unsigned not null references ldap_oc_mappings(id), + parent int NOT NULL , + keyval int NOT NULL +); + +alter table ldap_entries add + constraint unq1_ldap_entries unique + ( + oc_map_id, + keyval + ); + +alter table ldap_entries add + constraint unq2_ldap_entries unique + ( + dn + ); + +drop table if exists ldap_entry_objclasses; +create table ldap_entry_objclasses + ( + entry_id integer not null references ldap_entries(id), + oc_name varchar(64) + ); + diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/mysql/backsql_drop.sql new file mode 100644 index 0000000..a81fa8b --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mysql/backsql_drop.sql @@ -0,0 +1,7 @@ +DROP TABLE IF EXISTS ldap_entry_objclasses; + +DROP TABLE IF EXISTS ldap_attr_mappings; + +DROP TABLE IF EXISTS ldap_entries; + +DROP TABLE IF EXISTS ldap_oc_mappings; diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/slapd.conf b/servers/slapd/back-sql/rdbms_depend/mysql/slapd.conf new file mode 100644 index 0000000..8f6e4e1 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mysql/slapd.conf @@ -0,0 +1,32 @@ +# $OpenLDAP$ +# +# See slapd.conf(5) for details on configuration options. +# This file should NOT be world readable. +# +include /usr/local/etc/openldap/schema/core.schema +include /usr/local/etc/openldap/schema/cosine.schema +include /usr/local/etc/openldap/schema/inetorgperson.schema + +# Define global ACLs to disable default read access. + +# Do not enable referrals until AFTER you have a working directory +# service AND an understanding of referrals. +#referral ldap://root.openldap.org + +pidfile /usr/local/var/slapd.pid +argsfile /usr/local/var/slapd.args + +####################################################################### +# sql database definitions +####################################################################### + +database sql +suffix "o=sql,c=RU" +rootdn "cn=root,o=sql,c=RU" +rootpw secret +dbname ldap_mysql +dbuser root +dbpasswd +subtree_cond "ldap_entries.dn LIKE CONCAT('%',?)" +insentry_stmt "INSERT INTO ldap_entries (dn,oc_map_id,parent,keyval) VALUES (?,?,?,?)" +has_ldapinfo_dn_ru no diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_create.sql new file mode 100644 index 0000000..b35261b --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_create.sql @@ -0,0 +1,86 @@ +drop table if exists persons; +CREATE TABLE persons ( + id int NOT NULL, + name varchar(255) NOT NULL, + surname varchar(255) NOT NULL, + password varchar(64) +); + +drop table if exists institutes; +CREATE TABLE institutes ( + id int NOT NULL, + name varchar(255) +); + +drop table if exists documents; +CREATE TABLE documents ( + id int NOT NULL, + title varchar(255) NOT NULL, + abstract varchar(255) +); + +drop table if exists authors_docs; +CREATE TABLE authors_docs ( + pers_id int NOT NULL, + doc_id int NOT NULL +); + +drop table if exists phones; +CREATE TABLE phones ( + id int NOT NULL , + phone varchar(255) NOT NULL , + pers_id int NOT NULL +); + +drop table if exists certs; +CREATE TABLE certs ( + id int NOT NULL , + cert LONGBLOB NOT NULL, + pers_id int NOT NULL +); + +ALTER TABLE authors_docs ADD + CONSTRAINT PK_authors_docs PRIMARY KEY + ( + pers_id, + doc_id + ); + +ALTER TABLE documents ADD + CONSTRAINT PK_documents PRIMARY KEY + ( + id + ); + +ALTER TABLE institutes ADD + CONSTRAINT PK_institutes PRIMARY KEY + ( + id + ); + + +ALTER TABLE persons ADD + CONSTRAINT PK_persons PRIMARY KEY + ( + id + ); + +ALTER TABLE phones ADD + CONSTRAINT PK_phones PRIMARY KEY + ( + id + ); + +ALTER TABLE certs ADD + CONSTRAINT PK_certs PRIMARY KEY + ( + id + ); + +drop table if exists referrals; +CREATE TABLE referrals ( + id int NOT NULL, + name varchar(255) NOT NULL, + url varchar(255) NOT NULL +); + diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_data.sql new file mode 100644 index 0000000..0ccbfb7 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_data.sql @@ -0,0 +1,21 @@ +insert into institutes (id,name) values (1,'Example'); + +insert into persons (id,name,surname,password) values (1,'Mitya','Kovalev','mit'); +insert into persons (id,name,surname) values (2,'Torvlobnor','Puzdoy'); +insert into persons (id,name,surname) values (3,'Akakiy','Zinberstein'); + +insert into phones (id,phone,pers_id) values (1,'332-2334',1); +insert into phones (id,phone,pers_id) values (2,'222-3234',1); +insert into phones (id,phone,pers_id) values (3,'545-4563',2); + +insert into documents (id,abstract,title) values (1,'abstract1','book1'); +insert into documents (id,abstract,title) values (2,'abstract2','book2'); + +insert into authors_docs (pers_id,doc_id) values (1,1); +insert into authors_docs (pers_id,doc_id) values (1,2); +insert into authors_docs (pers_id,doc_id) values (2,1); + +insert into referrals (id,name,url) values (1,'Referral','ldap://localhost:9012/'); + +insert into certs (id,cert,pers_id) values (1,UNHEX('3082036b308202d4a003020102020102300d06092a864886f70d01010405003077310b3009060355040613025553311330110603550408130a43616c69666f726e6961311f301d060355040a13164f70656e4c444150204578616d706c652c204c74642e311330110603550403130a4578616d706c65204341311d301b06092a864886f70d010901160e6361406578616d706c652e636f6d301e170d3033313031373136333331395a170d3034313031363136333331395a307e310b3009060355040613025553311330110603550408130a43616c69666f726e6961311f301d060355040a13164f70656e4c444150204578616d706c652c204c74642e311830160603550403130f557273756c612048616d7073746572311f301d06092a864886f70d01090116107568616d406578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100eec60a7910b57d2e687158ca55eea738d36f10413dfecf31435e1aeeb9713b8e2da7dd2dde6bc6cec03b4987eaa7b037b9eb50e11c71e58088cc282883122cd8329c6f24f6045e6be9d21b9190c8292998267a5f7905292de936262747ab4b76a88a63872c41629a69d32e894d44c896a8d06fab0a1bc7de343c6c1458478f290203010001a381ff3081fc30090603551d1304023000302c06096086480186f842010d041f161d4f70656e53534c2047656e657261746564204365727469666963617465301d0603551d0e04160414a323de136c19ae0c479450e882dfb10ad147f45e3081a10603551d2304819930819680144b6f211a3624d290f943b053472d7de1c0e69823a17ba4793077310b3009060355040613025553311330110603550408130a43616c69666f726e6961311f301d060355040a13164f70656e4c444150204578616d706c652c204c74642e311330110603550403130a4578616d706c65204341311d301b06092a864886f70d010901160e6361406578616d706c652e636f6d820100300d06092a864886f70d010104050003818100881470045bdce95660d6e6af59e6a844aec4b9f5eaea88d4eb7a5a47080afa64750f81a3e47d00fd39c69a17a1c66d29d36f06edc537107f8c592239c2d4da55fb3f1d488e7b2387ad2a551cbd1ceb070ae9e020a9467275cb28798abb4cbfff98ddb3f1e7689b067072392511bb08125b5bec2bc207b7b6b275c47248f29acd'),3); + diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_drop.sql new file mode 100644 index 0000000..7c5e9e7 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_drop.sql @@ -0,0 +1,5 @@ +DROP TABLE IF EXISTS persons; +DROP TABLE IF EXISTS institutes; +DROP TABLE IF EXISTS documents; +DROP TABLE IF EXISTS authors_docs; +DROP TABLE IF EXISTS phones; diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_metadata.sql new file mode 100644 index 0000000..d7e88e4 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_metadata.sql @@ -0,0 +1,125 @@ +-- mappings + +-- objectClass mappings: these may be viewed as structuralObjectClass, the ones that are used to decide how to build an entry +-- id a unique number identifying the objectClass +-- name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema +-- keytbl the name of the table that is referenced for the primary key of an entry +-- keycol the name of the column in "keytbl" that contains the primary key of an entry; the pair "keytbl.keycol" uniquely identifies an entry of objectClass "id" +-- create_proc a procedure to create the entry +-- delete_proc a procedure to delete the entry; it takes "keytbl.keycol" of the row to be deleted +-- expect_return a bitmap that marks whether create_proc (1) and delete_proc (2) return a value or not +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) +values (1,'inetOrgPerson','persons','id',NULL,NULL,0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) +values (2,'document','documents','id',NULL,NULL,0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) +values (3,'organization','institutes','id',NULL,NULL,0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) +values (4,'referral','referrals','id',NULL,NULL,0); + +-- attributeType mappings: describe how an attributeType for a certain objectClass maps to the SQL data. +-- id a unique number identifying the attribute +-- oc_map_id the value of "ldap_oc_mappings.id" that identifies the objectClass this attributeType is defined for +-- name the name of the attributeType; it MUST match the name of an attributeType that is loaded in slapd's schema +-- sel_expr the expression that is used to select this attribute (the "select <sel_expr> from ..." portion) +-- from_tbls the expression that defines the table(s) this attribute is taken from (the "select ... from <from_tbls> where ..." portion) +-- join_where the expression that defines the condition to select this attribute (the "select ... where <join_where> ..." portion) +-- add_proc a procedure to insert the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to +-- delete_proc a procedure to delete the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to +-- param_order a mask that marks if the "keytbl.keycol" value comes before or after the value in add_proc (1) and delete_proc (2) +-- expect_return a mask that marks whether add_proc (1) and delete_proc(2) are expected to return a value or not +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (1,1,'cn',"concat(persons.name,' ',persons.surname)",'persons',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (2,1,'telephoneNumber','phones.phone','persons,phones', + 'phones.pers_id=persons.id',NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (3,1,'givenName','persons.name','persons',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (4,1,'sn','persons.surname','persons',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (5,1,'userPassword','persons.password','persons','persons.password IS NOT NULL',NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (6,1,'seeAlso','seeAlso.dn','ldap_entries AS seeAlso,documents,authors_docs,persons', + 'seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (7,2,'description','documents.abstract','documents',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (8,2,'documentTitle','documents.title','documents',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (9,2,'documentAuthor','documentAuthor.dn','ldap_entries AS documentAuthor,documents,authors_docs,persons', + 'documentAuthor.keyval=persons.id AND documentAuthor.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (10,2,'documentIdentifier','concat(''document '',documents.id)','documents',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (11,3,'o','institutes.name','institutes',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (12,3,'dc','lower(institutes.name)','institutes,ldap_entries AS dcObject,ldap_entry_objclasses as auxObjectClass', + 'institutes.id=dcObject.keyval AND dcObject.oc_map_id=3 AND dcObject.id=auxObjectClass.entry_id AND auxObjectClass.oc_name=''dcObject''', + NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (13,4,'ou','referrals.name','referrals',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (14,4,'ref','referrals.url','referrals',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (15,1,'userCertificate','certs.cert','persons,certs', + 'certs.pers_id=persons.id',NULL,NULL,3,0); + +-- entries mapping: each entry must appear in this table, with a unique DN rooted at the database naming context +-- id a unique number > 0 identifying the entry +-- dn the DN of the entry, in "pretty" form +-- oc_map_id the "ldap_oc_mappings.id" of the main objectClass of this entry (view it as the structuralObjectClass) +-- parent the "ldap_entries.id" of the parent of this objectClass; 0 if it is the "suffix" of the database +-- keyval the value of the "keytbl.keycol" defined for this objectClass +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (1,'dc=example,dc=com',3,0,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (2,'cn=Mitya Kovalev,dc=example,dc=com',1,1,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (3,'cn=Torvlobnor Puzdoy,dc=example,dc=com',1,1,2); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (4,'cn=Akakiy Zinberstein,dc=example,dc=com',1,1,3); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (5,'documentTitle=book1,dc=example,dc=com',2,1,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (6,'documentTitle=book2,dc=example,dc=com',2,1,2); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (7,'ou=Referral,dc=example,dc=com',4,1,1); + +-- objectClass mapping: entries that have multiple objectClass instances are listed here with the objectClass name (view them as auxiliary objectClass) +-- entry_id the "ldap_entries.id" of the entry this objectClass value must be added +-- oc_name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema +insert into ldap_entry_objclasses (entry_id,oc_name) +values (1,'dcObject'); + +insert into ldap_entry_objclasses (entry_id,oc_name) +values (4,'pkiUser'); + +insert into ldap_entry_objclasses (entry_id,oc_name) +values (7,'extensibleObject'); + diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/oracle/backsql_create.sql new file mode 100644 index 0000000..2e4e6ec --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/oracle/backsql_create.sql @@ -0,0 +1,90 @@ +create table ldap_oc_mappings ( + id number not null , + name varchar2(64) not null , + keytbl varchar2(64) not null , + keycol varchar2(64) not null , + create_proc varchar2(255), + delete_proc varchar2(255), + expect_return number not null +); + +alter table ldap_oc_mappings add + constraint PK_ldap_oc_mappings primary key + ( + id + ); + +alter table ldap_oc_mappings add + constraint unq_ldap_oc_mappings unique + ( + name + ); + +create table ldap_attr_mappings ( + id number not null, + oc_map_id number not null references ldap_oc_mappings(id), + name varchar2(255) not null, + sel_expr varchar2(255) not null, + sel_expr_u varchar2(255), + from_tbls varchar2(255) not null, + join_where varchar2(255), + add_proc varchar2(255), + delete_proc varchar2(255), + param_order number not null, + expect_return number not null +); + +alter table ldap_attr_mappings add + constraint pk_ldap_attr_mappings primary key + ( + id + ); + + +create table ldap_entries ( + id number not null , + dn varchar2(255) not null , + dn_ru varchar2(255), + oc_map_id number not null references ldap_oc_mappings(id), + parent number not null , + keyval number not null +); + +alter table ldap_entries add + constraint PK_ldap_entries primary key + ( + id + ); + +alter table ldap_entries add + constraint unq1_ldap_entries unique + ( + oc_map_id, + keyval + ); + +alter table ldap_entries add + constraint unq2_ldap_entries unique + ( + dn + ); + +create sequence ldap_objclass_ids start with 1 increment by 1; + +create sequence ldap_attr_ids start with 1 increment by 1; + +create sequence ldap_entry_ids start with 1 increment by 1; + +create table ldap_referrals + ( + entry_id number not null references ldap_entries(id), + url varchar(1023) not null +); + +create table ldap_entry_objclasses + ( + entry_id number not null references ldap_entries(id), + oc_name varchar(64) + ); + +quit diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/oracle/backsql_drop.sql new file mode 100644 index 0000000..19bb8b6 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/oracle/backsql_drop.sql @@ -0,0 +1,8 @@ +drop table ldap_attr_mappings; +drop table ldap_entry_objclasses; +drop table ldap_referrals; +drop sequence ldap_entry_ids; +drop sequence ldap_attr_ids; +drop sequence ldap_objclass_ids; +drop table ldap_entries; +drop table ldap_oc_mappings; diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/slapd.conf b/servers/slapd/back-sql/rdbms_depend/oracle/slapd.conf new file mode 100644 index 0000000..cc195d9 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/oracle/slapd.conf @@ -0,0 +1,32 @@ +# $OpenLDAP$ +# +# See slapd.conf(5) for details on configuration options. +# This file should NOT be world readable. +# +include /usr/local/etc/openldap/schema/core.schema +include /usr/local/etc/openldap/schema/cosine.schema +include /usr/local/etc/openldap/schema/inetorgperson.schema + +# Define global ACLs to disable default read access. + +# Do not enable referrals until AFTER you have a working directory +# service AND an understanding of referrals. +#referral ldap://root.openldap.org + +pidfile /usr/local/var/slapd.pid +argsfile /usr/local/var/slapd.args + +####################################################################### +# sql database definitions +####################################################################### + +database sql +suffix "o=sql,c=RU" +rootdn "cn=root,o=sql,c=RU" +rootpw secret +dbname ldap_ora8 +dbuser ldap +dbpasswd ldap +subtree_cond "UPPER(ldap_entries.dn) LIKE CONCAT('%',UPPER(?))" +insentry_stmt "INSERT INTO ldap_entries (id,dn,oc_map_id,parent,keyval) VALUES (ldap_entry_ids.nextval,?,?,?,?)" +upper_func UPPER diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_create.sql new file mode 100644 index 0000000..710a5fa --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_create.sql @@ -0,0 +1,68 @@ +CREATE TABLE persons ( + id NUMBER NOT NULL, + name varchar2(255) NOT NULL, + surname varchar2(255) NOT NULL, + password varchar2(64) NOT NULL +); + +CREATE TABLE institutes ( + id NUMBER NOT NULL, + name varchar2(255) +); + +CREATE TABLE documents ( + id NUMBER NOT NULL, + title varchar2(255) NOT NULL, + abstract varchar2(255) +); + +CREATE TABLE authors_docs ( + pers_id NUMBER NOT NULL, + doc_id NUMBER NOT NULL +); + +CREATE TABLE phones ( + id NUMBER NOT NULL , + phone varchar2(255) NOT NULL , + pers_id NUMBER NOT NULL +); + + +ALTER TABLE authors_docs ADD + CONSTRAINT PK_authors_docs PRIMARY KEY + ( + pers_id, + doc_id + ); + +ALTER TABLE documents ADD + CONSTRAINT PK_documents PRIMARY KEY + ( + id + ); + +ALTER TABLE institutes ADD + CONSTRAINT PK_institutes PRIMARY KEY + ( + id + ); + +ALTER TABLE persons ADD + CONSTRAINT PK_persons PRIMARY KEY + ( + id + ); + +ALTER TABLE phones ADD + CONSTRAINT PK_phones PRIMARY KEY + ( + id + ); + +CREATE SEQUENCE person_ids START WITH 1 INCREMENT BY 1; + +CREATE SEQUENCE document_ids START WITH 1 INCREMENT BY 1; + +CREATE SEQUENCE institute_ids START WITH 1 INCREMENT BY 1; + +CREATE SEQUENCE phone_ids START WITH 1 INCREMENT BY 1; diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_data.sql new file mode 100644 index 0000000..4fc1977 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_data.sql @@ -0,0 +1,27 @@ +insert into institutes (id,name) values (institute_ids.nextval,'example'); + +insert into persons (id,name,surname,password) values (person_ids.nextval,'Mitya','Kovalev','mit'); + +insert into persons (id,name,surname) values (person_ids.nextval,'Torvlobnor','Puzdoy'); + +insert into persons (id,name,surname) values (person_ids.nextval,'Akakiy','Zinberstein'); + + +insert into phones (id,phone,pers_id) values (phone_ids.nextval,'332-2334',1); + +insert into phones (id,phone,pers_id) values (phone_ids.nextval,'222-3234',1); + +insert into phones (id,phone,pers_id) values (phone_ids.nextval,'545-4563',2); + + +insert into documents (id,abstract,title) values (document_ids.nextval,'abstract1','book1'); + +insert into documents (id,abstract,title) values (document_ids.nextval,'abstract2','book2'); + + +insert into authors_docs (pers_id,doc_id) values (1,1); + +insert into authors_docs (pers_id,doc_id) values (1,2); + +insert into authors_docs (pers_id,doc_id) values (2,1); + diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_drop.sql new file mode 100644 index 0000000..0cf4463 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_drop.sql @@ -0,0 +1,25 @@ +DROP TABLE persons; +DROP TABLE institutes; +DROP TABLE documents; +DROP TABLE authors_docs; +DROP TABLE phones; +DROP SEQUENCE person_ids; +DROP SEQUENCE institute_ids; +DROP SEQUENCE document_ids; +DROP SEQUENCE phone_ids; +DROP PROCEDURE create_person; +DROP PROCEDURE delete_person; +DROP PROCEDURE add_phone; +DROP PROCEDURE delete_phone; +DROP PROCEDURE set_person_name; +DROP PROCEDURE set_org_name; +DROP PROCEDURE set_doc_title; +DROP PROCEDURE set_doc_abstract; +DROP PROCEDURE create_document; +DROP PROCEDURE create_org; +DROP PROCEDURE delete_document; +DROP PROCEDURE delete_org; +DROP PROCEDURE make_doc_link; +DROP PROCEDURE del_doc_link; +DROP PROCEDURE make_author_link; +DROP PROCEDURE del_author_link; diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_metadata.sql new file mode 100644 index 0000000..354d7bd --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_metadata.sql @@ -0,0 +1,252 @@ +-- mappings + +-- objectClass mappings: these may be viewed as structuralObjectClass, the ones that are used to decide how to build an entry +-- id a unique number identifying the objectClass +-- name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema +-- keytbl the name of the table that is referenced for the primary key of an entry +-- keycol the name of the column in "keytbl" that contains the primary key of an entry; the pair "keytbl.keycol" uniquely identifies an entry of objectClass "id" +-- create_proc a procedure to create the entry +-- delete_proc a procedure to delete the entry; it takes "keytbl.keycol" of the row to be deleted +-- expect_return a bitmap that marks whether create_proc (1) and delete_proc (2) return a value or not +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) +values (1,'inetOrgPerson','persons','id','call create_person(?)','call delete_person(?)',0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) +values (2,'document','documents','id','call create_document(?)','call delete_document(?)',0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) +values (3,'organization','institutes','id','call create_org(?)','call delete_org(?)',0); + +-- attributeType mappings: describe how an attributeType for a certain objectClass maps to the SQL data. +-- id a unique number identifying the attribute +-- oc_map_id the value of "ldap_oc_mappings.id" that identifies the objectClass this attributeType is defined for +-- name the name of the attributeType; it MUST match the name of an attributeType that is loaded in slapd's schema +-- sel_expr the expression that is used to select this attribute (the "select <sel_expr> from ..." portion) +-- from_tbls the expression that defines the table(s) this attribute is taken from (the "select ... from <from_tbls> where ..." portion) +-- join_where the expression that defines the condition to select this attribute (the "select ... where <join_where> ..." portion) +-- add_proc a procedure to insert the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to +-- delete_proc a procedure to delete the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to +-- param_order a mask that marks if the "keytbl.keycol" value comes before or after the value in add_proc (1) and delete_proc (2) +-- expect_return a mask that marks whether add_proc (1) and delete_proc(2) are expected to return a value or not +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (1,1,'cn','persons.name||'' ''||persons.surname','persons',NULL, + NULL,NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (2,1,'telephoneNumber','phones.phone','persons,phones', + 'phones.pers_id=persons.id','call add_phone(?,?)', + 'call delete_phone(?,?)',0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (3,1,'givenName','persons.name','persons',NULL,'call set_person_name(?,?)', + NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (4,1,'sn','persons.surname','persons',NULL,'call set_person_surname(?,?)', + NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (5,1,'userPassword','persons.password','persons', + 'persons.password IS NOT NULL','call set_person_password(?,?)', + NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (6,1,'seeAlso','seeAlso.dn','ldap_entries seeAlso,documents,authors_docs,persons', + 'seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + NULL,NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (7,2,'description','documents.abstract','documents',NULL,'call set_doc_abstract(?,?)', + NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (8,2,'documentTitle','documents.title','documents',NULL,'call set_doc_title(?,?)',NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (9,2,'documentAuthor','documentAuthor.dn','ldap_entries documentAuthor,documents,authors_docs,persons', + 'documentAuthor.keyval=persons.id AND documentAuthor.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + '?=call make_author_link(?,?)','?=call del_author_link(?,?)',0,3); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (10,2,'documentIdentifier','''document ''||text(documents.id)','documents',NULL,NULL,NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (11,3,'o','institutes.name','institutes',NULL,'call set_org_name(?,?)',NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (12,3,'dc','lower(institutes.name)','institutes,ldap_entries dcObject,ldap_entry_objclasses auxObjectClass', + 'institutes.id=dcObject.keyval AND dcObject.oc_map_id=3 AND dcObject.id=auxObjectClass.entry_id AND auxObjectClass.oc_name=''dcObject''', + NULL,NULL,0,0); + +-- entries mapping: each entry must appear in this table, with a unique DN rooted at the database naming context +-- id a unique number > 0 identifying the entry +-- dn the DN of the entry, in "pretty" form +-- oc_map_id the "ldap_oc_mappings.id" of the main objectClass of this entry (view it as the structuralObjectClass) +-- parent the "ldap_entries.id" of the parent of this objectClass; 0 if it is the "suffix" of the database +-- keyval the value of the "keytbl.keycol" defined for this objectClass +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (ldap_entry_ids.nextval,'dc=example,dc=com',3,0,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (ldap_entry_ids.nextval,'cn=Mitya Kovalev,dc=example,dc=com',1,1,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (ldap_entry_ids.nextval,'cn=Torvlobnor Puzdoy,dc=example,dc=com',1,1,2); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (ldap_entry_ids.nextval,'cn=Akakiy Zinberstein,dc=example,dc=com',1,1,3); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (ldap_entry_ids.nextval,'documentTitle=book1,dc=example,dc=com',2,1,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (ldap_entry_ids.nextval,'documentTitle=book2,dc=example,dc=com',2,1,2); + +-- objectClass mapping: entries that have multiple objectClass instances are listed here with the objectClass name (view them as auxiliary objectClass) +-- entry_id the "ldap_entries.id" of the entry this objectClass value must be added +-- oc_name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema +insert into ldap_entry_objclasses (entry_id,oc_name) +values (1,'dcObject'); + +insert into ldap_entry_objclasses (entry_id,oc_name) +values (4,'referral'); + +-- referrals mapping: entries that should be treated as referrals are stored here +-- entry_id the "ldap_entries.id" of the entry that should be treated as a referral +-- url the URI of the referral +insert into ldap_referrals (entry_id,url) +values (4,'ldap://localhost:9012/'); + + +-- procedures +-- these procedures are specific for this RDBMS and are used in mapping objectClass and attributeType creation/modify/deletion +CREATE OR REPLACE PROCEDURE create_person(keyval OUT NUMBER) AS +BEGIN +INSERT INTO persons (id,name) VALUES (person_ids.nextval,' '); +SELECT person_ids.currval INTO keyval FROM DUAL; +END; +/ + +CREATE OR REPLACE PROCEDURE delete_person(keyval IN NUMBER) AS +BEGIN +DELETE FROM phones WHERE pers_id=keyval; +DELETE FROM authors_docs WHERE pers_id=keyval; +DELETE FROM persons WHERE id=keyval; +END; +/ + +CREATE OR REPLACE PROCEDURE create_org(keyval OUT NUMBER) AS +BEGIN +INSERT INTO institutes (id,name) VALUES (institute_ids.nextval,' '); +SELECT institute_ids.currval INTO keyval FROM DUAL; +END; +/ + +CREATE OR REPLACE PROCEDURE delete_org(keyval IN NUMBER) AS +BEGIN +DELETE FROM institutes WHERE id=keyval; +END; +/ + +CREATE OR REPLACE PROCEDURE create_document(keyval OUT NUMBER) AS +BEGIN +INSERT INTO documents (id,title) VALUES (document_ids.nextval,' '); +SELECT document_ids.currval INTO keyval FROM DUAL; +END; +/ + +CREATE OR REPLACE PROCEDURE delete_document (keyval IN NUMBER) AS +BEGIN +DELETE FROM authors_docs WHERE doc_id=keyval; +DELETE FROM documents WHERE id=keyval; +END; +/ + +CREATE OR REPLACE PROCEDURE add_phone(pers_id IN NUMBER, phone IN varchar2) AS +BEGIN +INSERT INTO phones (id,pers_id,phone) VALUES (phone_ids.nextval,pers_id,phone); +END; +/ + +CREATE OR REPLACE PROCEDURE delete_phone(keyval IN NUMBER, phone IN varchar2) AS +BEGIN +DELETE FROM phones WHERE pers_id=keyval AND phone=phone; +END; +/ + +CREATE OR REPLACE PROCEDURE set_person_name(keyval IN NUMBER, new_name IN varchar2) AS +BEGIN +UPDATE persons SET name=new_name WHERE id=keyval; +END; +/ + +CREATE OR REPLACE PROCEDURE set_org_name(keyval IN NUMBER, new_name IN varchar2) AS +BEGIN +UPDATE institutes SET name=new_name WHERE id=keyval; +END; +/ + +CREATE OR REPLACE PROCEDURE set_doc_title (keyval IN NUMBER, new_title IN varchar2) AS +BEGIN +UPDATE documents SET title=new_title WHERE id=keyval; +END; +/ + +CREATE OR REPLACE PROCEDURE set_doc_abstract (keyval IN NUMBER, new_abstract IN varchar2) AS +BEGIN +UPDATE documents SET abstract=new_abstract WHERE id=keyval; +END; +/ + +CREATE OR REPLACE FUNCTION make_author_link (keyval IN NUMBER, author_dn IN varchar2) RETURN NUMBER AS +per_id NUMBER; +BEGIN +SELECT keyval INTO per_id FROM ldap_entries + WHERE oc_map_id=1 AND dn=author_dn; +IF NOT (per_id IS NULL) THEN + INSERT INTO authors_docs (doc_id,pers_id) VALUES (keyval,per_id); + RETURN 1; +END IF; +RETURN 0; +END; +/ + +CREATE OR REPLACE FUNCTION make_doc_link (keyval IN NUMBER, doc_dn IN varchar2) RETURN NUMBER AS +docid NUMBER; +BEGIN +SELECT keyval INTO docid FROM ldap_entries + WHERE oc_map_id=2 AND dn=doc_dn; +IF NOT (docid IS NULL) THEN + INSERT INTO authors_docs (pers_id,doc_id) VALUES (keyval,docid); + RETURN 1; +END IF; +RETURN 0; +END; +/ + +CREATE OR REPLACE FUNCTION del_doc_link (keyval IN NUMBER, doc_dn IN varchar2) RETURN NUMBER AS +docid NUMBER; +BEGIN +SELECT keyval INTO docid FROM ldap_entries + WHERE oc_map_id=2 AND dn=doc_dn; +IF NOT (docid IS NULL) THEN + DELETE FROM authors_docs WHERE pers_id=keyval AND doc_id=docid; + RETURN 1; +END IF; +RETURN 0; +END; +/ + +CREATE OR REPLACE FUNCTION del_author_link (keyval IN NUMBER, author_dn IN varchar2) RETURN NUMBER AS +per_id NUMBER; +BEGIN +SELECT keyval INTO per_id FROM ldap_entries + WHERE oc_map_id=1 AND dn=author_dn; + +IF NOT (per_id IS NULL) THEN + DELETE FROM authors_docs WHERE doc_id=keyval AND pers_id=per_id; + RETURN 1; +END IF; + RETURN 0; +END; +/ diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_create.sql new file mode 100644 index 0000000..a4baa70 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_create.sql @@ -0,0 +1,50 @@ +drop table ldap_oc_mappings; +drop sequence ldap_oc_mappings_id_seq; +create table ldap_oc_mappings + ( + id serial not null primary key, + name varchar(64) not null, + keytbl varchar(64) not null, + keycol varchar(64) not null, + create_proc varchar(255), + delete_proc varchar(255), + expect_return int not null +); + +drop table ldap_attr_mappings; +drop sequence ldap_attr_mappings_id_seq; +create table ldap_attr_mappings + ( + id serial not null primary key, + oc_map_id integer not null references ldap_oc_mappings(id), + name varchar(255) not null, + sel_expr varchar(255) not null, + sel_expr_u varchar(255), + from_tbls varchar(255) not null, + join_where varchar(255), + add_proc varchar(255), + delete_proc varchar(255), + param_order int not null, + expect_return int not null +); + +drop table ldap_entries; +drop sequence ldap_entries_id_seq; +create table ldap_entries + ( + id serial not null primary key, + dn varchar(255) not null, + oc_map_id integer not null references ldap_oc_mappings(id), + parent int NOT NULL, + keyval int NOT NULL, + UNIQUE ( oc_map_id, keyval ), + UNIQUE ( dn ) +); + +drop table ldap_entry_objclasses; +create table ldap_entry_objclasses + ( + entry_id integer not null references ldap_entries(id), + oc_name varchar(64) + ); + diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_drop.sql new file mode 100644 index 0000000..eff0a9e --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_drop.sql @@ -0,0 +1,4 @@ +DROP TABLE ldap_entry_objclasses; +DROP TABLE ldap_attr_mappings; +DROP TABLE ldap_entries; +DROP TABLE ldap_oc_mappings; diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/slapd.conf b/servers/slapd/back-sql/rdbms_depend/pgsql/slapd.conf new file mode 100644 index 0000000..70a8dee --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/pgsql/slapd.conf @@ -0,0 +1,35 @@ +# $OpenLDAP$ +# +# See slapd.conf(5) for details on configuration options. +# This file should NOT be world readable. +# +include /usr/local/etc/openldap/schema/core.schema +include /usr/local/etc/openldap/schema/cosine.schema +include /usr/local/etc/openldap/schema/inetorgperson.schema + +# Define global ACLs to disable default read access. + +# Do not enable referrals until AFTER you have a working directory +# service AND an understanding of referrals. +#referral ldap://root.openldap.org + +pidfile /usr/local/var/slapd.pid +argsfile /usr/local/var/slapd.args + +####################################################################### +# sql database definitions +####################################################################### + +database sql +suffix "o=sql,c=RU" +rootdn "cn=root,o=sql,c=RU" +rootpw secret +dbname PostgreSQL +dbuser postgres +dbpasswd postgres +insentry_stmt "insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values ((select max(id)+1 from ldap_entries),?,?,?,?)" +upper_func "upper" +strcast_func "text" +concat_pattern "?||?" +has_ldapinfo_dn_ru no + diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_create.sql new file mode 100644 index 0000000..e1c57e7 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_create.sql @@ -0,0 +1,55 @@ +drop table persons; +drop sequence persons_id_seq; +create table persons ( + id serial not null primary key, + name varchar(255) not null, + surname varchar(255) not null, + password varchar(64) +); + +drop table institutes; +drop sequence institutes_id_seq; +create table institutes ( + id serial not null primary key, + name varchar(255) +); + +drop table documents; +drop sequence documents_id_seq; +create table documents ( + id serial not null primary key, + title varchar(255) not null, + abstract varchar(255) +); + +drop table authors_docs; +create table authors_docs ( + pers_id int not null, + doc_id int not null, + primary key ( pers_id, doc_id ) +); + +drop table phones; +drop sequence phones_id_seq; +create table phones ( + id serial not null primary key, + phone varchar(255) not null , + pers_id int not null +); + +drop table certs; +drop sequence certs_id_seq; +CREATE TABLE certs ( + id int not null primary key, + cert bytea not null, + pers_id int not null +); + +drop table referrals; +drop sequence referrals_id_seq; +create table referrals ( + id serial not null primary key, + name varchar(255) not null, + url varchar(255) not null +); + diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_data.sql new file mode 100644 index 0000000..0e661d4 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_data.sql @@ -0,0 +1,21 @@ +insert into institutes (id,name) values (1,'Example'); + +insert into persons (id,name,surname,password) values (1,'Mitya','Kovalev','mit'); +insert into persons (id,name,surname) values (2,'Torvlobnor','Puzdoy'); +insert into persons (id,name,surname) values (3,'Akakiy','Zinberstein'); + +insert into phones (id,phone,pers_id) values (1,'332-2334',1); +insert into phones (id,phone,pers_id) values (2,'222-3234',1); +insert into phones (id,phone,pers_id) values (3,'545-4563',2); + +insert into documents (id,abstract,title) values (1,'abstract1','book1'); +insert into documents (id,abstract,title) values (2,'abstract2','book2'); + +insert into authors_docs (pers_id,doc_id) values (1,1); +insert into authors_docs (pers_id,doc_id) values (1,2); +insert into authors_docs (pers_id,doc_id) values (2,1); + +insert into referrals (id,name,url) values (1,'Referral','ldap://localhost:9012/'); + +insert into certs (id,cert,pers_id) values (1,decode('MIIDazCCAtSgAwIBAgIBAjANBgkqhkiG9w0BAQQFADB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEfMB0GA1UEChMWT3BlbkxEQVAgRXhhbXBsZSwgTHRkLjETMBEGA1UEAxMKRXhhbXBsZSBDQTEdMBsGCSqGSIb3DQEJARYOY2FAZXhhbXBsZS5jb20wHhcNMDMxMDE3MTYzMzE5WhcNMDQxMDE2MTYzMzE5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEfMB0GA1UEChMWT3BlbkxEQVAgRXhhbXBsZSwgTHRkLjEYMBYGA1UEAxMPVXJzdWxhIEhhbXBzdGVyMR8wHQYJKoZIhvcNAQkBFhB1aGFtQGV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDuxgp5ELV9LmhxWMpV7qc4028QQT3+zzFDXhruuXE7ji2n3S3ea8bOwDtJh+qnsDe561DhHHHlgIjMKCiDEizYMpxvJPYEXmvp0huRkMgpKZgmel95BSkt6TYmJ0erS3aoimOHLEFimmnTLolNRMiWqNBvqwobx940PGwUWEePKQIDAQABo4H/MIH8MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBSjI94TbBmuDEeUUOiC37EK0Uf0XjCBoQYDVR0jBIGZMIGWgBRLbyEaNiTSkPlDsFNHLX3hwOaYI6F7pHkwdzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExHzAdBgNVBAoTFk9wZW5MREFQIEV4YW1wbGUsIEx0ZC4xEzARBgNVBAMTCkV4YW1wbGUgQ0ExHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tggEAMA0GCSqGSIb3DQEBBAUAA4GBAIgUcARb3OlWYNbmr1nmqESuxLn16uqI1Ot6WkcICvpkdQ+Bo+R9AP05xpoXocZtKdNvBu3FNxB/jFkiOcLU2lX7Px1Ijnsjh60qVRy9HOsHCungIKlGcnXLKHmKu0y//5jds/HnaJsGcHI5JRG7CBJbW+wrwge3trJ1xHJI8prN','base64'),3); + diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_drop.sql new file mode 100644 index 0000000..c061ff8 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_drop.sql @@ -0,0 +1,13 @@ +DROP TABLE persons; +DROP TABLE institutes; +DROP TABLE documents; +DROP TABLE authors_docs; +DROP TABLE phones; +DROP TABLE referrals; +DROP FUNCTION create_person (); +DROP FUNCTION update_person_cn (varchar, int); +DROP FUNCTION add_phone (varchar, int); +DROP FUNCTION create_doc (); +DROP FUNCTION create_o (); +DROP FUNCTION create_referral (); + diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_metadata.sql new file mode 100644 index 0000000..d645cf2 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_metadata.sql @@ -0,0 +1,146 @@ +-- mappings + +-- objectClass mappings: these may be viewed as structuralObjectClass, the ones that are used to decide how to build an entry +-- id a unique number identifying the objectClass +-- name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema +-- keytbl the name of the table that is referenced for the primary key of an entry +-- keycol the name of the column in "keytbl" that contains the primary key of an entry; the pair "keytbl.keycol" uniquely identifies an entry of objectClass "id" +-- create_proc a procedure to create the entry +-- delete_proc a procedure to delete the entry; it takes "keytbl.keycol" of the row to be deleted +-- expect_return a bitmap that marks whether create_proc (1) and delete_proc (2) return a value or not +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) values (1,'inetOrgPerson','persons','id','SELECT create_person()','DELETE FROM persons WHERE id=?',0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) values (2,'document','documents','id','SELECT create_doc()','DELETE FROM documents WHERE id=?',0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) values (3,'organization','institutes','id','SELECT create_o()','DELETE FROM institutes WHERE id=?',0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) values (4,'referral','referrals','id','SELECT create_referral()','DELETE FROM referrals WHERE id=?',0); + +-- attributeType mappings: describe how an attributeType for a certain objectClass maps to the SQL data. +-- id a unique number identifying the attribute +-- oc_map_id the value of "ldap_oc_mappings.id" that identifies the objectClass this attributeType is defined for +-- name the name of the attributeType; it MUST match the name of an attributeType that is loaded in slapd's schema +-- sel_expr the expression that is used to select this attribute (the "select <sel_expr> from ..." portion) +-- from_tbls the expression that defines the table(s) this attribute is taken from (the "select ... from <from_tbls> where ..." portion) +-- join_where the expression that defines the condition to select this attribute (the "select ... where <join_where> ..." portion) +-- add_proc a procedure to insert the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to +-- delete_proc a procedure to delete the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to +-- param_order a mask that marks if the "keytbl.keycol" value comes before or after the value in add_proc (1) and delete_proc (2) +-- expect_return a mask that marks whether add_proc (1) and delete_proc(2) are expected to return a value or not +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (1,1,'cn','text(persons.name||'' ''||persons.surname)','persons',NULL,'SELECT update_person_cn(?,?)','SELECT 1 FROM persons WHERE persons.name=? AND persons.id=? AND 1=0',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (2,1,'telephoneNumber','phones.phone','persons,phones','phones.pers_id=persons.id','SELECT add_phone(?,?)','DELETE FROM phones WHERE phone=? AND pers_id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (3,1,'givenName','persons.name','persons',NULL,'UPDATE persons SET name=? WHERE id=?','UPDATE persons SET name='''' WHERE (name=? OR name='''') AND id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (4,1,'sn','persons.surname','persons',NULL,'UPDATE persons SET surname=? WHERE id=?','UPDATE persons SET surname='''' WHERE (surname=? OR surname='''') AND id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (5,1,'userPassword','persons.password','persons','persons.password IS NOT NULL','UPDATE persons SET password=? WHERE id=?','UPDATE persons SET password=NULL WHERE password=? AND id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (6,1,'seeAlso','seeAlso.dn','ldap_entries AS seeAlso,documents,authors_docs,persons','seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',NULL,'DELETE from authors_docs WHERE authors_docs.doc_id=(SELECT documents.id FROM documents,ldap_entries AS seeAlso WHERE seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND seeAlso.dn=?) AND authors_docs.pers_id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (7,2,'description','documents.abstract','documents',NULL,'UPDATE documents SET abstract=? WHERE id=?','UPDATE documents SET abstract='''' WHERE abstract=? AND id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (8,2,'documentTitle','documents.title','documents',NULL,'UPDATE documents SET title=? WHERE id=?','UPDATE documents SET title='''' WHERE title=? AND id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (9,2,'documentAuthor','documentAuthor.dn','ldap_entries AS documentAuthor,documents,authors_docs,persons','documentAuthor.keyval=persons.id AND documentAuthor.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id','INSERT INTO authors_docs (pers_id,doc_id) VALUES ((SELECT ldap_entries.keyval FROM ldap_entries WHERE upper(?)=upper(ldap_entries.dn)),?)','DELETE FROM authors_docs WHERE authors_docs.pers_id=(SELECT ldap_entries.keyval FROM ldap_entries WHERE upper(?)=upper(ldap_entries.dn)) AND authors_docs.doc_id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (10,2,'documentIdentifier','''document ''||text(documents.id)','documents',NULL,NULL,'SELECT 1 FROM documents WHERE title=? AND id=? AND 1=0',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (11,3,'o','institutes.name','institutes',NULL,'UPDATE institutes SET name=? WHERE id=?','UPDATE institutes SET name='''' WHERE name=? AND id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (12,3,'dc','lower(institutes.name)','institutes,ldap_entries AS dcObject,ldap_entry_objclasses AS auxObjectClass','institutes.id=dcObject.keyval AND dcObject.oc_map_id=3 AND dcObject.id=auxObjectClass.entry_id AND auxObjectClass.oc_name=''dcObject''',NULL,'SELECT 1 FROM institutes WHERE lower(name)=? AND id=? and 1=0',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (13,4,'ou','referrals.name','referrals',NULL,'UPDATE referrals SET name=? WHERE id=?','SELECT 1 FROM referrals WHERE name=? AND id=? and 1=0',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (14,4,'ref','referrals.url','referrals',NULL,'UPDATE referrals SET url=? WHERE id=?','SELECT 1 FROM referrals WHERE url=? and id=? and 1=0',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (15,1,'userCertificate','certs.cert','persons,certs','certs.pers_id=persons.id',NULL,NULL,3,0); + +-- entries mapping: each entry must appear in this table, with a unique DN rooted at the database naming context +-- id a unique number > 0 identifying the entry +-- dn the DN of the entry, in "pretty" form +-- oc_map_id the "ldap_oc_mappings.id" of the main objectClass of this entry (view it as the structuralObjectClass) +-- parent the "ldap_entries.id" of the parent of this objectClass; 0 if it is the "suffix" of the database +-- keyval the value of the "keytbl.keycol" defined for this objectClass +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (1,'dc=example,dc=com',3,0,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (2,'cn=Mitya Kovalev,dc=example,dc=com',1,1,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (3,'cn=Torvlobnor Puzdoy,dc=example,dc=com',1,1,2); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (4,'cn=Akakiy Zinberstein,dc=example,dc=com',1,1,3); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (5,'documentTitle=book1,dc=example,dc=com',2,1,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (6,'documentTitle=book2,dc=example,dc=com',2,1,2); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (7,'ou=Referral,dc=example,dc=com',4,1,1); + +-- objectClass mapping: entries that have multiple objectClass instances are listed here with the objectClass name (view them as auxiliary objectClass) +-- entry_id the "ldap_entries.id" of the entry this objectClass value must be added +-- oc_name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema +insert into ldap_entry_objclasses (entry_id,oc_name) values (1,'dcObject'); + +insert into ldap_entry_objclasses (entry_id,oc_name) values (4,'pkiUser'); + +insert into ldap_entry_objclasses (entry_id,oc_name) values (7,'extensibleObject'); + +-- procedures +-- these procedures are specific for this RDBMS and are used in mapping objectClass and attributeType creation/modify/deletion +create function create_person () returns int +as ' + select setval (''persons_id_seq'', (select case when max(id) is null then 1 else max(id) end from persons)); + insert into persons (id,name,surname) + values ((select case when max(id) is null then 1 else nextval(''persons_id_seq'') end from persons),'''',''''); + select max(id) from persons +' language 'sql'; + +create function update_person_cn (varchar, int) returns int +as ' + update persons set name = ( + select case + when position('' '' in $1) = 0 then $1 + else substr($1, 1, position('' '' in $1) - 1) + end + ),surname = ( + select case + when position('' '' in $1) = 0 then '''' + else substr($1, position('' '' in $1) + 1) + end + ) where id = $2; + select $2 as return +' language 'sql'; + +create function add_phone (varchar, int) returns int +as ' + select setval (''phones_id_seq'', (select case when max(id) is null then 1 else max(id) end from phones)); + insert into phones (id,phone,pers_id) + values (nextval(''phones_id_seq''),$1,$2); + select max(id) from phones +' language 'sql'; + +create function create_doc () returns int +as ' + select setval (''documents_id_seq'', (select case when max(id) is null then 1 else max(id) end from documents)); + insert into documents (id,title,abstract) + values ((select case when max(id) is null then 1 else nextval(''documents_id_seq'') end from documents),'''',''''); + select max(id) from documents +' language 'sql'; + +create function create_o () returns int +as ' + select setval (''institutes_id_seq'', (select case when max(id) is null then 1 else max(id) end from institutes)); + insert into institutes (id,name) + values ((select case when max(id) is null then 1 else nextval(''institutes_id_seq'') end from institutes),''''); + select max(id) from institutes +' language 'sql'; + +create function create_referral () returns int +as ' + select setval (''referrals_id_seq'', (select case when max(id) is null then 1 else max(id) end from referrals)); + insert into referrals (id,name,url) + values ((select case when max(id) is null then 1 else nextval(''referrals_id_seq'') end from referrals),'''',''''); + select max(id) from referrals +' language 'sql'; + diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/timesten/backsql_create.sql new file mode 100644 index 0000000..055e9df --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/backsql_create.sql @@ -0,0 +1,66 @@ + +create table ldap_oc_mappings + ( + id integer not null primary key, + name varchar(64) not null, + keytbl varchar(64) not null, + keycol varchar(64) not null, + create_proc varchar(255), + delete_proc varchar(255), + expect_return tinyint not null +); + +create table ldap_attr_mappings + ( + id integer not null primary key, + oc_map_id integer not null, + name varchar(255) not null, + sel_expr varchar(255) not null, + sel_expr_u varchar(255), + from_tbls varchar(255) not null, + join_where varchar(255), + add_proc varchar(255), + delete_proc varchar(255), + param_order tinyint not null, + expect_return tinyint not null, + foreign key (oc_map_id) references ldap_oc_mappings(id) +); + +create table ldap_entries + ( + id integer not null primary key, + dn varchar(255) not null, + dn_ru varchar(255), + oc_map_id integer not null, + parent int NOT NULL , + keyval int NOT NULL, + foreign key (oc_map_id) references ldap_oc_mappings(id) +); + +create index ldap_entriesx1 on ldap_entries(dn_ru); + +create unique index unq1_ldap_entries on ldap_entries + ( + oc_map_id, + keyval + ); + +create unique index unq2_ldap_entries on ldap_entries + ( + dn + ); + +create table ldap_referrals + ( + entry_id integer not null, + url varchar(4096) not null, + foreign key (entry_id) references ldap_entries(id) +); + +create table ldap_entry_objclasses + ( + entry_id integer not null, + oc_name varchar(64), + foreign key (entry_id) references ldap_entries(id) + ); + diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/timesten/backsql_drop.sql new file mode 100644 index 0000000..7aa0b83 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/backsql_drop.sql @@ -0,0 +1,9 @@ +DROP TABLE ldap_referrals; + +DROP TABLE ldap_entry_objclasses; + +DROP TABLE ldap_attr_mappings; + +DROP TABLE ldap_entries; + +DROP TABLE ldap_oc_mappings; diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/create_schema.sh b/servers/slapd/back-sql/rdbms_depend/timesten/create_schema.sh new file mode 100755 index 0000000..947db21 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/create_schema.sh @@ -0,0 +1,4 @@ +ttIsql -connStr "DSN=ldap_tt;Overwrite=1" -f backsql_create.sql +ttIsql -connStr "DSN=ldap_tt" -f testdb_create.sql +ttIsql -connStr "DSN=ldap_tt" -f testdb_data.sql +ttIsql -connStr "DSN=ldap_tt" -f testdb_metadata.sql diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/Makefile b/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/Makefile new file mode 100644 index 0000000..7ecfc98 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/Makefile @@ -0,0 +1,48 @@ +## Copyright 1997-2021 The OpenLDAP Foundation, All Rights Reserved. +## COPYING RESTRICTIONS APPLY, see COPYRIGHT file + +# +# Build TimesTen ODBC Sample Programs for Solaris 2.5.1. +# (c) Copyright 1996-1998, TimesTen Performance Software. +# All rights reserved. +## Note: This file was contributed by Sam Drake of TimesTen Performance +## Software for use and redistribution as an intregal part of +## OpenLDAP Software. -Kdz + +CPLUSPLUS = CC +TTCLASSES = ../../../../../../../../../cs/classes +ODBC = /opt/TimesTen4.1/32 +CFLAGS = -g -I$(ODBC)/include -I. -I$(TTCLASSES) -DUNIX +LDFLAGS = -g +DIRLIBS = $(TTCLASSES)/ttclasses.a -L $(ODBC)/lib -R $(ODBC)/lib -ltten -lpthread -lm -lrt +XLALIB = -L $(ODBC)/lib -lxla + +DIRPROGS= dnreverse + +DNREVERSE= dnreverse.o + +# +# Top-level targets +# + +all: $(DIRPROGS) + +direct: $(DIRPROGS) + +clean: + rm -rf $(DIRPROGS) *.o + + +# +# Direct-linked programs +# + +dnreverse: $(DNREVERSE) + $(CPLUSPLUS) -o dnreverse $(LDFLAGS) $(DNREVERSE) $(DIRLIBS) $(XLALIB) + +# +# .o files +# + +dnreverse.o: dnreverse.cpp + $(CPLUSPLUS) $(CFLAGS) -c dnreverse.cpp diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/dnreverse.cpp b/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/dnreverse.cpp new file mode 100644 index 0000000..73ef048 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/dnreverse.cpp @@ -0,0 +1,387 @@ +// Copyright 1997-2021 The OpenLDAP Foundation, All Rights Reserved. +// COPYING RESTRICTIONS APPLY, see COPYRIGHT file + +// (c) Copyright 1999-2001 TimesTen Performance Software. All rights reserved. + +//// Note: This file was contributed by Sam Drake of TimesTen Performance +//// Software for use and redistribution as an intregal part of +//// OpenLDAP Software. -Kdz + +#include <stdlib.h> + +#include <TTConnectionPool.h> +#include <TTConnection.h> +#include <TTCmd.h> +#include <TTXla.h> + +#include <signal.h> + +TTConnectionPool pool; +TTXlaConnection conn; +TTConnection conn2; +TTCmd assignDn_ru; +TTCmd getNullDNs; + +//---------------------------------------------------------------------- +// This class contains all the logic to be implemented whenever +// the SCOTT.MYDATA table is changed. This is the table that is +// created by "sample.cpp", one of the other TTClasses demos. +// That application should be executed before this one in order to +// create and populate the table. +//---------------------------------------------------------------------- + +class LDAPEntriesHandler: public TTXlaTableHandler { +private: + // Definition of the columns in the table + int Id; + int Dn; + int Oc_map_id; + int Parent; + int Keyval; + int Dn_ru; + +protected: + +public: + LDAPEntriesHandler(TTXlaConnection& conn, const char* ownerP, const char* nameP); + ~LDAPEntriesHandler(); + + virtual void HandleDelete(ttXlaUpdateDesc_t*); + virtual void HandleInsert(ttXlaUpdateDesc_t*); + virtual void HandleUpdate(ttXlaUpdateDesc_t*); + + static void ReverseAndUpper(char* dnP, int id, bool commit=true); + +}; + +LDAPEntriesHandler::LDAPEntriesHandler(TTXlaConnection& conn, + const char* ownerP, const char* nameP) : + TTXlaTableHandler(conn, ownerP, nameP) +{ + Id = Dn = Oc_map_id = Parent = Keyval = Dn_ru = -1; + + // We are looking for several particular named columns. We need to get + // the ordinal position of the columns by name for later use. + + Id = tbl.getColNumber("ID"); + if (Id < 0) { + cerr << "target table has no 'ID' column" << endl; + exit(1); + } + Dn = tbl.getColNumber("DN"); + if (Dn < 0) { + cerr << "target table has no 'DN' column" << endl; + exit(1); + } + Oc_map_id = tbl.getColNumber("OC_MAP_ID"); + if (Oc_map_id < 0) { + cerr << "target table has no 'OC_MAP_ID' column" << endl; + exit(1); + } + Parent = tbl.getColNumber("PARENT"); + if (Parent < 0) { + cerr << "target table has no 'PARENT' column" << endl; + exit(1); + } + Keyval = tbl.getColNumber("KEYVAL"); + if (Keyval < 0) { + cerr << "target table has no 'KEYVAL' column" << endl; + exit(1); + } + Dn_ru = tbl.getColNumber("DN_RU"); + if (Dn_ru < 0) { + cerr << "target table has no 'DN_RU' column" << endl; + exit(1); + } + +} + +LDAPEntriesHandler::~LDAPEntriesHandler() +{ + +} + +void LDAPEntriesHandler::ReverseAndUpper(char* dnP, int id, bool commit) +{ + TTStatus stat; + char dn_rn[512]; + int i; + int j; + + // Reverse and upper case the given DN + + for ((j=0, i = strlen(dnP)-1); i > -1; (j++, i--)) { + dn_rn[j] = toupper(*(dnP+i)); + } + dn_rn[j] = '\0'; + + + // Update the database + + try { + assignDn_ru.setParam(1, (char*) &dn_rn[0]); + assignDn_ru.setParam(2, id); + assignDn_ru.Execute(stat); + } + catch (TTStatus stat) { + cerr << "Error updating id " << id << " ('" << dnP << "' to '" + << dn_rn << "'): " << stat; + exit(1); + } + + // Commit the transaction + + if (commit) { + try { + conn2.Commit(stat); + } + catch (TTStatus stat) { + cerr << "Error committing update: " << stat; + exit(1); + } + } + +} + + + +void LDAPEntriesHandler::HandleInsert(ttXlaUpdateDesc_t* p) +{ + char* dnP; + int id; + + row.Get(Dn, &dnP); + cerr << "DN '" << dnP << "': Inserted "; + row.Get(Id, &id); + + ReverseAndUpper(dnP, id); + +} + +void LDAPEntriesHandler::HandleUpdate(ttXlaUpdateDesc_t* p) +{ + char* newDnP; + char* oldDnP; + char oDn[512]; + int id; + + // row is 'old'; row2 is 'new' + row.Get(Dn, &oldDnP); + strcpy(oDn, oldDnP); + row.Get(Id, &id); + row2.Get(Dn, &newDnP); + + cerr << "old DN '" << oDn << "' / new DN '" << newDnP << "' : Updated "; + + if (strcmp(oDn, newDnP) != 0) { + // The DN field changed, update it + cerr << "(new DN: '" << newDnP << "')"; + ReverseAndUpper(newDnP, id); + } + else { + // The DN field did NOT change, leave it alone + } + + cerr << endl; + +} + +void LDAPEntriesHandler::HandleDelete(ttXlaUpdateDesc_t* p) +{ + char* dnP; + + row.Get(Dn, &dnP); + cerr << "DN '" << dnP << "': Deleted "; +} + + + + +//---------------------------------------------------------------------- + +int pleaseStop = 0; + +extern "C" { + void + onintr(int sig) + { + pleaseStop = 1; + cerr << "Stopping...\n"; + } +}; + +//---------------------------------------------------------------------- + +int +main(int argc, char* argv[]) +{ + + char* ownerP; + + TTXlaTableList list(&conn); // List of tables to monitor + + // Handlers, one for each table we want to monitor + + LDAPEntriesHandler* sampP = NULL; + + // Misc stuff + + TTStatus stat; + + ttXlaUpdateDesc_t ** arry; + + int records; + + SQLUBIGINT oldsize; + int j; + + if (argc < 2) { + cerr << "syntax: " << argv[0] << " <username>" << endl; + exit(3); + } + + ownerP = argv[1]; + + signal(SIGINT, onintr); /* signal for CTRL-C */ +#ifdef _WIN32 + signal(SIGBREAK, onintr); /* signal for CTRL-BREAK */ +#endif + + // Before we do anything related to XLA, first we connect + // to the database. This is the connection we will use + // to perform non-XLA operations on the tables. + + try { + cerr << "Connecting..." << endl; + + conn2.Connect("DSN=ldap_tt", stat); + } + catch (TTStatus stat) { + cerr << "Error connecting to TimesTen: " << stat; + exit(1); + } + + try { + assignDn_ru.Prepare(&conn2, + "update ldap_entries set dn_ru=? where id=?", + "", stat); + getNullDNs.Prepare(&conn2, + "select dn, id from ldap_entries " + "where dn_ru is null " + "for update", + "", stat); + conn2.Commit(stat); + } + catch (TTStatus stat) { + cerr << "Error preparing update: " << stat; + exit(1); + } + + // If there are any entries with a NULL reversed/upper cased DN, + // fix them now. + + try { + cerr << "Fixing NULL reversed DNs" << endl; + getNullDNs.Execute(stat); + for (int k = 0;; k++) { + getNullDNs.FetchNext(stat); + if (stat.rc == SQL_NO_DATA_FOUND) break; + char* dnP; + int id; + getNullDNs.getColumn(1, &dnP); + getNullDNs.getColumn(2, &id); + // cerr << "Id " << id << ", Dn '" << dnP << "'" << endl; + LDAPEntriesHandler::ReverseAndUpper(dnP, id, false); + if (k % 1000 == 0) + cerr << "."; + } + getNullDNs.Close(stat); + conn2.Commit(stat); + } + catch (TTStatus stat) { + cerr << "Error updating NULL rows: " << stat; + exit(1); + } + + + // Go ahead and start up the change monitoring application + + cerr << "Starting change monitoring..." << endl; + try { + conn.Connect("DSN=ldap_tt", stat); + } + catch (TTStatus stat) { + cerr << "Error connecting to TimesTen: " << stat; + exit(1); + } + + /* set and configure size of buffer */ + conn.setXlaBufferSize((SQLUBIGINT) 1000000, &oldsize, stat); + if (stat.rc) { + cerr << "Error setting buffer size " << stat << endl; + exit(1); + } + + // Make a handler to process changes to the MYDATA table and + // add the handler to the list of all handlers + + sampP = new LDAPEntriesHandler(conn, ownerP, "ldap_entries"); + if (!sampP) { + cerr << "Could not create LDAPEntriesHandler" << endl; + exit(3); + } + list.add(sampP); + + // Enable transaction logging for the table we're interested in + + sampP->EnableTracking(stat); + + // Get updates. Dispatch them to the appropriate handler. + // This loop will handle updates to all the tables. + + while (pleaseStop == 0) { + conn.fetchUpdates(&arry, 1000, &records, stat); + if (stat.rc) { + cerr << "Error fetching updates" << stat << endl; + exit(1); + } + + // Interpret the updates + + for(j=0;j < records;j++){ + ttXlaUpdateDesc_t *p; + + p = arry[j]; + + list.HandleChange(p, stat); + + } // end for each record fetched + + if (records) { + cerr << "Processed " << records << " records\n"; + } + + if (records == 0) { +#ifdef _WIN32 + Sleep(250); +#else + struct timeval t; + t.tv_sec = 0; + t.tv_usec = 250000; // .25 seconds + select(0, NULL, NULL, NULL, &t); +#endif + } + } // end while pleasestop == 0 + + + // When we get to here, the program is exiting. + + list.del(sampP); // Take the table out of the list + delete sampP; + + conn.setXlaBufferSize(oldsize, NULL, stat); + + return 0; + +} + diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/slapd.conf b/servers/slapd/back-sql/rdbms_depend/timesten/slapd.conf new file mode 100644 index 0000000..f93de8b --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/slapd.conf @@ -0,0 +1,31 @@ +# $OpenLDAP$ +# +# See slapd.conf(5) for details on configuration options. +# This file should NOT be world readable. +# +include /usr/local/etc/openldap/schema/core.schema +include /usr/local/etc/openldap/schema/cosine.schema +include /usr/local/etc/openldap/schema/inetorgperson.schema + +# Define global ACLs to disable default read access. + +# Do not enable referrals until AFTER you have a working directory +# service AND an understanding of referrals. +#referral ldap://root.openldap.org + +pidfile /usr/local/var/slapd.pid +argsfile /usr/local/var/slapd.args + +####################################################################### +# sql database definitions +####################################################################### + +database sql +suffix "o=sql,c=RU" +rootdn "cn=root,o=sql,c=RU" +rootpw secret +dbname ldap_tt +dbuser root +dbpasswd +subtree_cond "ldap_entries.dn LIKE ?" +insentry_stmt "INSERT INTO ldap_entries (dn,oc_map_id,parent,keyval) VALUES (?,?,?,?)" diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_create.sql new file mode 100644 index 0000000..768aec8 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_create.sql @@ -0,0 +1,36 @@ +CREATE TABLE persons ( + id int NOT NULL primary key, + name varchar(255) NOT NULL +) +unique hash on (id) pages=100; + +CREATE TABLE institutes ( + id int NOT NULL primary key, + name varchar(255) +) +unique hash on (id) pages=100; + +CREATE TABLE documents ( + id int NOT NULL primary key, + title varchar(255) NOT NULL, + abstract varchar(255) +) +unique hash on (id) pages=100; + +CREATE TABLE authors_docs ( + pers_id int NOT NULL, + doc_id int NOT NULL, + PRIMARY KEY + ( + pers_id, + doc_id + ) +) unique hash on (pers_id, doc_id) pages=100; + +CREATE TABLE phones ( + id int NOT NULL primary key, + phone varchar(255) NOT NULL , + pers_id int NOT NULL +) +unique hash on (id) pages=100; + diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_data.sql new file mode 100644 index 0000000..f141f41 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_data.sql @@ -0,0 +1,16 @@ +insert into institutes (id,name) values (1,'sql'); + +insert into persons (id,name) values (1,'Mitya Kovalev'); +insert into persons (id,name) values (2,'Torvlobnor Puzdoy'); +insert into persons (id,name) values (3,'Akakiy Zinberstein'); + +insert into phones (id,phone,pers_id) values (1,'332-2334',1); +insert into phones (id,phone,pers_id) values (2,'222-3234',1); +insert into phones (id,phone,pers_id) values (3,'545-4563',2); + +insert into documents (id,abstract,title) values (1,'abstract1','book1'); +insert into documents (id,abstract,title) values (2,'abstract2','book2'); + +insert into authors_docs (pers_id,doc_id) values (1,1); +insert into authors_docs (pers_id,doc_id) values (1,2); +insert into authors_docs (pers_id,doc_id) values (2,1); diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_drop.sql new file mode 100644 index 0000000..17b12af --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_drop.sql @@ -0,0 +1,5 @@ +DROP TABLE persons; +DROP TABLE institutes; +DROP TABLE documents; +DROP TABLE authors_docs; +DROP TABLE phones; diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_metadata.sql new file mode 100644 index 0000000..f9e3419 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_metadata.sql @@ -0,0 +1,108 @@ + +insert into ldap_oc_mappings +(id,name, keytbl, keycol, create_proc, +delete_proc,expect_return) +values +(1,'inetOrgPerson','persons','id', 'insert into persons (name) values ('');\n select last_insert_id();', +NULL,0); + +insert into ldap_oc_mappings +(id, name, keytbl, keycol,create_proc,delete_proc,expect_return) +values +(2, 'document','documents','id', NULL, NULL, 0); + +insert into ldap_oc_mappings +(id,name, keytbl, keycol,create_proc,delete_proc,expect_return) +values +(3,'organization','institutes','id', NULL, NULL, 0); + + +insert into ldap_attr_mappings +(id, oc_map_id, name, sel_expr, from_tbls,join_where,add_proc, +delete_proc,param_order,expect_return) +values +(1, 1, 'cn', 'persons.name', 'persons',NULL, NULL, +NULL, 3, 0); + +insert into ldap_attr_mappings +(id, oc_map_id,name, sel_expr, from_tbls, +join_where, add_proc,delete_proc,param_order,expect_return) +values +(2, 1, 'telephoneNumber','phones.phone','persons,phones', +'phones.pers_id=persons.id', NULL, NULL, 3, 0); + +insert into ldap_attr_mappings +(id,oc_map_id, name, sel_expr, from_tbls, join_where,add_proc, +delete_proc,param_order,expect_return) +values +(3, 1, 'sn', 'persons.name','persons', NULL, NULL, +NULL, 3, 0); + +insert into ldap_attr_mappings +(id, oc_map_id, name, sel_expr, from_tbls, join_where, +add_proc,delete_proc,param_order,expect_return) +values +(4, 2, 'description', 'documents.abstract','documents', NULL, +NULL, NULL, 3, 0); + +insert into ldap_attr_mappings +(id, oc_map_id, name, sel_expr, from_tbls, join_where, +add_proc,delete_proc,param_order,expect_return) +values +(5, 2, 'documentTitle','documents.title','documents',NULL, +NULL, NULL, 3, 0); + +-- insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +-- values (6,2,'documentAuthor','persons.name','persons,documents,authors_docs', +-- 'persons.id=authors_docs.pers_id AND documents.id=authors_docs.doc_id', +-- NULL,NULL,3,0); + +insert into ldap_attr_mappings +(id, oc_map_id, name, sel_expr, from_tbls, join_where,add_proc, +delete_proc,param_order,expect_return) +values +(7, 3, 'o', 'institutes.name', 'institutes', NULL, NULL, +NULL, 3, 0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (8,1,'documentDN','ldap_entries.dn','ldap_entries,documents,authors_docs,persons', + 'ldap_entries.keyval=documents.id AND ldap_entries.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (9,2,'documentAuthor','ldap_entries.dn','ldap_entries,documents,authors_docs,persons', + 'ldap_entries.keyval=persons.id AND ldap_entries.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + NULL,NULL,3,0); + +-- entries + +insert into ldap_entries +(id, dn, oc_map_id, parent, keyval) +values +(1, 'o=sql,c=RU', 3, 0, 1); + +insert into ldap_entries +(id, dn, oc_map_id, parent, keyval) +values +(2, 'cn=Mitya Kovalev,o=sql,c=RU', 1, 1, 1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (3,'cn=Torvlobnor Puzdoy,o=sql,c=RU',1,1,2); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (4,'cn=Akakiy Zinberstein,o=sql,c=RU',1,1,3); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (5,'documentTitle=book1,o=sql,c=RU',2,1,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (6,'documentTitle=book2,o=sql,c=RU',2,1,2); + + +-- referrals + +insert into ldap_entry_objclasses (entry_id,oc_name) +values (4,'referral'); + +insert into ldap_referrals (entry_id,url) +values (4,'ldap://localhost:9012'); diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/ttcreate_schema.sh b/servers/slapd/back-sql/rdbms_depend/timesten/ttcreate_schema.sh new file mode 100755 index 0000000..c4c5df2 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/ttcreate_schema.sh @@ -0,0 +1,4 @@ +ttIsql -connStr "DSN=ldap_tt;Overwrite=1" -f backsql_create.sql +ttIsql -connStr "DSN=ldap_tt" -f tttestdb_create.sql +ttIsql -connStr "DSN=ldap_tt" -f tttestdb_data.sql +ttIsql -connStr "DSN=ldap_tt" -f tttestdb_metadata.sql diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_create.sql b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_create.sql new file mode 100644 index 0000000..f5955d2 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_create.sql @@ -0,0 +1,42 @@ +CREATE TABLE persons ( + id int NOT NULL primary key, + name varchar(255) NOT NULL, + name_u varchar(255), + title varchar(255), + title_U varchar(255), + organization varchar(255) +) +unique hash on (id) pages=100; +create index personsx1 on persons(title_U); +create index personsx2 on persons(name_u); + +CREATE TABLE institutes ( + id int NOT NULL primary key, + name varchar(255) +) +unique hash on (id) pages=100; + +CREATE TABLE documents ( + id int NOT NULL primary key, + title varchar(255) NOT NULL, + abstract varchar(255) +) +unique hash on (id) pages=100; + +CREATE TABLE authors_docs ( + pers_id int NOT NULL, + doc_id int NOT NULL, + PRIMARY KEY + ( + pers_id, + doc_id + ) +) unique hash on (pers_id, doc_id) pages=100; + +CREATE TABLE phones ( + id int NOT NULL primary key, + phone varchar(255) NOT NULL , + pers_id int NOT NULL +) +unique hash on (id) pages=100; + diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_data.sql b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_data.sql new file mode 100644 index 0000000..ca75339 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_data.sql @@ -0,0 +1,20 @@ +insert into institutes (id,name) values (1,'sql'); + +insert into persons (id,name, title, title_U, organization) values +(1,'Mitya Kovalev', 'Engineer', 'ENGINEER', 'Development'); +insert into persons (id,name, title, title_U, organization) values +(2,'Torvlobnor Puzdoy', 'Engineer', 'ENGINEER', 'Sales'); +insert into persons (id,name, title, title_U, organization) values +(3,'Akakiy Zinberstein', 'Engineer', 'ENGINEER', 'Marketing'); +update persons set name_u = upper(name); + +insert into phones (id,phone,pers_id) values (1,'332-2334',1); +insert into phones (id,phone,pers_id) values (2,'222-3234',1); +insert into phones (id,phone,pers_id) values (3,'545-4563',2); + +insert into documents (id,abstract,title) values (1,'abstract1','book1'); +insert into documents (id,abstract,title) values (2,'abstract2','book2'); + +insert into authors_docs (pers_id,doc_id) values (1,1); +insert into authors_docs (pers_id,doc_id) values (1,2); +insert into authors_docs (pers_id,doc_id) values (2,1); diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_drop.sql new file mode 100644 index 0000000..17b12af --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_drop.sql @@ -0,0 +1,5 @@ +DROP TABLE persons; +DROP TABLE institutes; +DROP TABLE documents; +DROP TABLE authors_docs; +DROP TABLE phones; diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_metadata.sql new file mode 100644 index 0000000..69bda8a --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_metadata.sql @@ -0,0 +1,122 @@ + +insert into ldap_oc_mappings +(id,name, keytbl, keycol, create_proc, +delete_proc,expect_return) +values +(1,'inetOrgPerson','persons','id', 'insert into persons (name) values ('');\n select last_insert_id();', +NULL,0); + +insert into ldap_oc_mappings +(id, name, keytbl, keycol,create_proc,delete_proc,expect_return) +values +(2, 'document','documents','id', NULL, NULL, 0); + +insert into ldap_oc_mappings +(id,name, keytbl, keycol,create_proc,delete_proc,expect_return) +values +(3,'organization','institutes','id', NULL, NULL, 0); + + +insert into ldap_attr_mappings +(id, oc_map_id, name, sel_expr, sel_expr_u, from_tbls, +join_where,add_proc, delete_proc,param_order,expect_return) +values +(1, 1, 'cn', 'persons.name', 'persons.name_u','persons', +NULL, NULL, NULL, 3, 0); + +insert into ldap_attr_mappings +(id, oc_map_id, name, sel_expr, sel_expr_u, from_tbls,join_where, +add_proc, delete_proc,param_order,expect_return) +values +(10, 1, 'title', 'persons.title', 'persons.title_u', 'persons',NULL, NULL, +NULL, 3, 0); + +insert into ldap_attr_mappings +(id, oc_map_id,name, sel_expr, from_tbls, +join_where, add_proc,delete_proc,param_order,expect_return) +values +(2, 1, 'telephoneNumber','phones.phone','persons,phones', +'phones.pers_id=persons.id', NULL, NULL, 3, 0); + +insert into ldap_attr_mappings +(id,oc_map_id, name, sel_expr, from_tbls, join_where,add_proc, +delete_proc,param_order,expect_return) +values +(3, 1, 'sn', 'persons.name','persons', NULL, NULL, +NULL, 3, 0); + +insert into ldap_attr_mappings +(id, oc_map_id, name, sel_expr, from_tbls, join_where,add_proc, +delete_proc,param_order,expect_return) +values +(30, 1, 'ou', 'persons.organization','persons', NULL, NULL, +NULL, 3, 0); + +insert into ldap_attr_mappings +(id, oc_map_id, name, sel_expr, from_tbls, join_where, +add_proc,delete_proc,param_order,expect_return) +values +(4, 2, 'description', 'documents.abstract','documents', NULL, +NULL, NULL, 3, 0); + +insert into ldap_attr_mappings +(id, oc_map_id, name, sel_expr, from_tbls, join_where, +add_proc,delete_proc,param_order,expect_return) +values +(5, 2, 'documentTitle','documents.title','documents',NULL, +NULL, NULL, 3, 0); + +-- insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +-- values (6,2,'documentAuthor','persons.name','persons,documents,authors_docs', +-- 'persons.id=authors_docs.pers_id AND documents.id=authors_docs.doc_id', +-- NULL,NULL,3,0); + +insert into ldap_attr_mappings +(id, oc_map_id, name, sel_expr, from_tbls, join_where,add_proc, +delete_proc,param_order,expect_return) +values +(7, 3, 'o', 'institutes.name', 'institutes', NULL, NULL, +NULL, 3, 0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (8,1,'documentDN','ldap_entries.dn','ldap_entries,documents,authors_docs,persons', + 'ldap_entries.keyval=documents.id AND ldap_entries.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (9,2,'documentAuthor','ldap_entries.dn','ldap_entries,documents,authors_docs,persons', + 'ldap_entries.keyval=persons.id AND ldap_entries.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + NULL,NULL,3,0); + +-- entries + +insert into ldap_entries +(id, dn, oc_map_id, parent, keyval) +values +(1, 'o=sql,c=RU', 3, 0, 1); + +insert into ldap_entries +(id, dn, oc_map_id, parent, keyval) +values +(2, 'cn=Mitya Kovalev,o=sql,c=RU', 1, 1, 1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (3,'cn=Torvlobnor Puzdoy,o=sql,c=RU',1,1,2); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (4,'cn=Akakiy Zinberstein,o=sql,c=RU',1,1,3); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (5,'documentTitle=book1,o=sql,c=RU',2,1,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (6,'documentTitle=book2,o=sql,c=RU',2,1,2); + + +-- referrals + +insert into ldap_entry_objclasses (entry_id,oc_name) +values (4,'referral'); + +insert into ldap_referrals (entry_id,url) +values (4,'http://localhost'); diff --git a/servers/slapd/back-sql/schema-map.c b/servers/slapd/back-sql/schema-map.c new file mode 100644 index 0000000..191ea7b --- /dev/null +++ b/servers/slapd/back-sql/schema-map.c @@ -0,0 +1,1043 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * Portions Copyright 2004 Mark Adamson. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati and Mark Adamson. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> +#include "ac/string.h" + +#include "lutil.h" +#include "slap.h" +#include "proto-sql.h" + +#define BACKSQL_DUPLICATE (-1) + +/* NOTE: by default, cannot just compare pointers because + * objectClass/attributeType order would be machine-dependent + * (and tests would fail!); however, if you don't want to run + * tests, or see attributeTypes written in the same order + * they are defined, define */ +/* #undef BACKSQL_USE_PTR_CMP */ + +/* + * Uses the pointer to the ObjectClass structure + */ +static int +backsql_cmp_oc( const void *v_m1, const void *v_m2 ) +{ + const backsql_oc_map_rec *m1 = v_m1, + *m2 = v_m2; + +#ifdef BACKSQL_USE_PTR_CMP + return SLAP_PTRCMP( m1->bom_oc, m2->bom_oc ); +#else /* ! BACKSQL_USE_PTR_CMP */ + return ber_bvcmp( &m1->bom_oc->soc_cname, &m2->bom_oc->soc_cname ); +#endif /* ! BACKSQL_USE_PTR_CMP */ +} + +static int +backsql_cmp_oc_id( const void *v_m1, const void *v_m2 ) +{ + const backsql_oc_map_rec *m1 = v_m1, + *m2 = v_m2; + + return ( m1->bom_id < m2->bom_id ? -1 : ( m1->bom_id > m2->bom_id ? 1 : 0 ) ); +} + +/* + * Uses the pointer to the AttributeDescription structure + */ +static int +backsql_cmp_attr( const void *v_m1, const void *v_m2 ) +{ + const backsql_at_map_rec *m1 = v_m1, + *m2 = v_m2; + + if ( slap_ad_is_binary( m1->bam_ad ) || slap_ad_is_binary( m2->bam_ad ) ) { +#ifdef BACKSQL_USE_PTR_CMP + return SLAP_PTRCMP( m1->bam_ad->ad_type, m2->bam_ad->ad_type ); +#else /* ! BACKSQL_USE_PTR_CMP */ + return ber_bvcmp( &m1->bam_ad->ad_type->sat_cname, &m2->bam_ad->ad_type->sat_cname ); +#endif /* ! BACKSQL_USE_PTR_CMP */ + } + +#ifdef BACKSQL_USE_PTR_CMP + return SLAP_PTRCMP( m1->bam_ad, m2->bam_ad ); +#else /* ! BACKSQL_USE_PTR_CMP */ + return ber_bvcmp( &m1->bam_ad->ad_cname, &m2->bam_ad->ad_cname ); +#endif /* ! BACKSQL_USE_PTR_CMP */ +} + +int +backsql_dup_attr( void *v_m1, void *v_m2 ) +{ + backsql_at_map_rec *m1 = v_m1, + *m2 = v_m2; + + if ( slap_ad_is_binary( m1->bam_ad ) || slap_ad_is_binary( m2->bam_ad ) ) { +#ifdef BACKSQL_USE_PTR_CMP + assert( m1->bam_ad->ad_type == m2->bam_ad->ad_type ); +#else /* ! BACKSQL_USE_PTR_CMP */ + assert( ber_bvcmp( &m1->bam_ad->ad_type->sat_cname, &m2->bam_ad->ad_type->sat_cname ) == 0 ); +#endif /* ! BACKSQL_USE_PTR_CMP */ + + } else { +#ifdef BACKSQL_USE_PTR_CMP + assert( m1->bam_ad == m2->bam_ad ); +#else /* ! BACKSQL_USE_PTR_CMP */ + assert( ber_bvcmp( &m1->bam_ad->ad_cname, &m2->bam_ad->ad_cname ) == 0 ); +#endif /* ! BACKSQL_USE_PTR_CMP */ + } + + /* duplicate definitions of attributeTypes are appended; + * this allows to define multiple rules for the same + * attributeType. Use with care! */ + for ( ; m1->bam_next ; m1 = m1->bam_next ); + + m1->bam_next = m2; + m2->bam_next = NULL; + + return BACKSQL_DUPLICATE; +} + +static int +backsql_make_attr_query( + backsql_info *bi, + backsql_oc_map_rec *oc_map, + backsql_at_map_rec *at_map ) +{ + struct berbuf bb = BB_NULL; + + backsql_strfcat_x( &bb, NULL, "lblbbbblblbcbl", + (ber_len_t)STRLENOF( "SELECT " ), "SELECT ", + &at_map->bam_sel_expr, + (ber_len_t)STRLENOF( " " ), " ", + &bi->sql_aliasing, + &bi->sql_aliasing_quote, + &at_map->bam_ad->ad_cname, + &bi->sql_aliasing_quote, + (ber_len_t)STRLENOF( " FROM " ), " FROM ", + &at_map->bam_from_tbls, + (ber_len_t)STRLENOF( " WHERE " ), " WHERE ", + &oc_map->bom_keytbl, + '.', + &oc_map->bom_keycol, + (ber_len_t)STRLENOF( "=?" ), "=?" ); + + if ( !BER_BVISNULL( &at_map->bam_join_where ) ) { + backsql_strfcat_x( &bb, NULL, "lb", + (ber_len_t)STRLENOF( " AND " ), " AND ", + &at_map->bam_join_where ); + } + + backsql_strfcat_x( &bb, NULL, "lbbb", + (ber_len_t)STRLENOF( " ORDER BY " ), " ORDER BY ", + &bi->sql_aliasing_quote, + &at_map->bam_ad->ad_cname, + &bi->sql_aliasing_quote ); + + at_map->bam_query = bb.bb_val.bv_val; + +#ifdef BACKSQL_COUNTQUERY + /* Query to count how many rows will be returned. + + SELECT COUNT(*) FROM <from_tbls> WHERE <keytbl>.<keycol>=? + [ AND <join_where> ] + + */ + BER_BVZERO( &bb.bb_val ); + bb.bb_len = 0; + backsql_strfcat_x( &bb, NULL, "lblbcbl", + (ber_len_t)STRLENOF( "SELECT COUNT(*) FROM " ), + "SELECT COUNT(*) FROM ", + &at_map->bam_from_tbls, + (ber_len_t)STRLENOF( " WHERE " ), " WHERE ", + &oc_map->bom_keytbl, + '.', + &oc_map->bom_keycol, + (ber_len_t)STRLENOF( "=?" ), "=?" ); + + if ( !BER_BVISNULL( &at_map->bam_join_where ) ) { + backsql_strfcat_x( &bb, NULL, "lb", + (ber_len_t)STRLENOF( " AND " ), " AND ", + &at_map->bam_join_where ); + } + + at_map->bam_countquery = bb.bb_val.bv_val; +#endif /* BACKSQL_COUNTQUERY */ + + return 0; +} + +static int +backsql_add_sysmaps( backsql_info *bi, backsql_oc_map_rec *oc_map ) +{ + backsql_at_map_rec *at_map; + char s[LDAP_PVT_INTTYPE_CHARS(long)]; + struct berval sbv; + struct berbuf bb; + + sbv.bv_val = s; + sbv.bv_len = snprintf( s, sizeof( s ), BACKSQL_IDNUMFMT, oc_map->bom_id ); + + /* extra objectClasses */ + at_map = (backsql_at_map_rec *)ch_calloc(1, + sizeof( backsql_at_map_rec ) ); + at_map->bam_ad = slap_schema.si_ad_objectClass; + at_map->bam_true_ad = slap_schema.si_ad_objectClass; + ber_str2bv( "ldap_entry_objclasses.oc_name", 0, 1, + &at_map->bam_sel_expr ); + ber_str2bv( "ldap_entry_objclasses,ldap_entries", 0, 1, + &at_map->bam_from_tbls ); + + bb.bb_len = at_map->bam_from_tbls.bv_len + 1; + bb.bb_val = at_map->bam_from_tbls; + backsql_merge_from_clause( bi, &bb, &oc_map->bom_keytbl ); + at_map->bam_from_tbls = bb.bb_val; + + BER_BVZERO( &bb.bb_val ); + bb.bb_len = 0; + backsql_strfcat_x( &bb, NULL, "lbcblb", + (ber_len_t)STRLENOF( "ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entries.keyval=" ), + "ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entries.keyval=", + &oc_map->bom_keytbl, + '.', + &oc_map->bom_keycol, + (ber_len_t)STRLENOF( " and ldap_entries.oc_map_id=" ), + " and ldap_entries.oc_map_id=", + &sbv ); + at_map->bam_join_where = bb.bb_val; + + at_map->bam_oc = oc_map->bom_oc; + + at_map->bam_add_proc = NULL; + { + char tmp[STRLENOF("INSERT INTO ldap_entry_objclasses " + "(entry_id,oc_name) VALUES " + "((SELECT id FROM ldap_entries " + "WHERE oc_map_id= " + "AND keyval=?),?)") + LDAP_PVT_INTTYPE_CHARS(unsigned long)]; + snprintf( tmp, sizeof(tmp), + "INSERT INTO ldap_entry_objclasses " + "(entry_id,oc_name) VALUES " + "((SELECT id FROM ldap_entries " + "WHERE oc_map_id=" BACKSQL_IDNUMFMT " " + "AND keyval=?),?)", oc_map->bom_id ); + at_map->bam_add_proc = ch_strdup( tmp ); + } + + at_map->bam_delete_proc = NULL; + { + char tmp[STRLENOF("DELETE FROM ldap_entry_objclasses " + "WHERE entry_id=(SELECT id FROM ldap_entries " + "WHERE oc_map_id= " + "AND keyval=?) AND oc_name=?") + LDAP_PVT_INTTYPE_CHARS(unsigned long)]; + snprintf( tmp, sizeof(tmp), + "DELETE FROM ldap_entry_objclasses " + "WHERE entry_id=(SELECT id FROM ldap_entries " + "WHERE oc_map_id=" BACKSQL_IDNUMFMT " " + "AND keyval=?) AND oc_name=?", + oc_map->bom_id ); + at_map->bam_delete_proc = ch_strdup( tmp ); + } + + at_map->bam_param_order = 0; + at_map->bam_expect_return = 0; + at_map->bam_next = NULL; + + backsql_make_attr_query( bi, oc_map, at_map ); + if ( avl_insert( &oc_map->bom_attrs, at_map, backsql_cmp_attr, backsql_dup_attr ) == BACKSQL_DUPLICATE ) { + Debug( LDAP_DEBUG_TRACE, "backsql_add_sysmaps(): " + "duplicate attribute \"%s\" in objectClass \"%s\" map\n", + at_map->bam_ad->ad_cname.bv_val, + oc_map->bom_oc->soc_cname.bv_val, 0 ); + } + + /* FIXME: we need to correct the objectClass join_where + * after the attribute query is built */ + ch_free( at_map->bam_join_where.bv_val ); + BER_BVZERO( &bb.bb_val ); + bb.bb_len = 0; + backsql_strfcat_x( &bb, NULL, "lbcblb", + (ber_len_t)STRLENOF( /* "ldap_entries.id=ldap_entry_objclasses.entry_id AND " */ "ldap_entries.keyval=" ), + /* "ldap_entries.id=ldap_entry_objclasses.entry_id AND " */ "ldap_entries.keyval=", + &oc_map->bom_keytbl, + '.', + &oc_map->bom_keycol, + (ber_len_t)STRLENOF( " AND ldap_entries.oc_map_id=" ), + " AND ldap_entries.oc_map_id=", + &sbv ); + at_map->bam_join_where = bb.bb_val; + + return 1; +} + +struct backsql_attr_schema_info { + backsql_info *bas_bi; + SQLHDBC bas_dbh; + SQLHSTMT bas_sth; + backsql_key_t *bas_oc_id; + int bas_rc; +}; + +static int +backsql_oc_get_attr_mapping( void *v_oc, void *v_bas ) +{ + RETCODE rc; + BACKSQL_ROW_NTS at_row; + backsql_oc_map_rec *oc_map = (backsql_oc_map_rec *)v_oc; + backsql_at_map_rec *at_map; + struct backsql_attr_schema_info *bas = (struct backsql_attr_schema_info *)v_bas; + + /* bas->bas_oc_id has been bound to bas->bas_sth */ + *bas->bas_oc_id = oc_map->bom_id; + + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): " + "executing at_query\n" + " \"%s\"\n" + " for objectClass \"%s\"\n" + " with param oc_id=" BACKSQL_IDNUMFMT "\n", + bas->bas_bi->sql_at_query, + BACKSQL_OC_NAME( oc_map ), + *bas->bas_oc_id ); + + rc = SQLExecute( bas->bas_sth ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): " + "error executing at_query\n" + " \"%s\"\n" + " for objectClass \"%s\"\n" + " with param oc_id=" BACKSQL_IDNUMFMT "\n", + bas->bas_bi->sql_at_query, + BACKSQL_OC_NAME( oc_map ), + *bas->bas_oc_id ); + backsql_PrintErrors( bas->bas_bi->sql_db_env, + bas->bas_dbh, bas->bas_sth, rc ); + bas->bas_rc = LDAP_OTHER; + return BACKSQL_AVL_STOP; + } + + backsql_BindRowAsStrings( bas->bas_sth, &at_row ); + for ( ; rc = SQLFetch( bas->bas_sth ), BACKSQL_SUCCESS( rc ); ) { + const char *text = NULL; + struct berval bv; + struct berbuf bb = BB_NULL; + AttributeDescription *ad = NULL; + + { + struct { + int idx; + char *name; + } required[] = { + { 0, "name" }, + { 1, "sel_expr" }, + { 2, "from" }, + { -1, NULL }, + }; + int i; + + for ( i = 0; required[ i ].name != NULL; i++ ) { + if ( at_row.value_len[ i ] <= 0 ) { + Debug( LDAP_DEBUG_ANY, + "backsql_oc_get_attr_mapping(): " + "required column #%d \"%s\" is empty\n", + required[ i ].idx, required[ i ].name, 0 ); + bas->bas_rc = LDAP_OTHER; + return BACKSQL_AVL_STOP; + } + } + } + + { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "attributeType: " + "name=\"%s\" " + "sel_expr=\"%s\" " + "from=\"%s\" " + "join_where=\"%s\" " + "add_proc=\"%s\" " + "delete_proc=\"%s\" " + "sel_expr_u=\"%s\"", + at_row.cols[ 0 ], + at_row.cols[ 1 ], + at_row.cols[ 2 ], + at_row.cols[ 3 ] ? at_row.cols[ 3 ] : "", + at_row.cols[ 4 ] ? at_row.cols[ 4 ] : "", + at_row.cols[ 5 ] ? at_row.cols[ 5 ] : "", + at_row.cols[ 8 ] ? at_row.cols[ 8 ] : ""); + Debug( LDAP_DEBUG_TRACE, "%s\n", buf, 0, 0 ); + } + + rc = slap_str2ad( at_row.cols[ 0 ], &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): " + "attribute \"%s\" for objectClass \"%s\" " + "is not defined in schema: %s\n", + at_row.cols[ 0 ], + BACKSQL_OC_NAME( oc_map ), text ); + bas->bas_rc = LDAP_CONSTRAINT_VIOLATION; + return BACKSQL_AVL_STOP; + } + at_map = (backsql_at_map_rec *)ch_calloc( 1, + sizeof( backsql_at_map_rec ) ); + at_map->bam_ad = ad; + at_map->bam_true_ad = ad; + if ( slap_syntax_is_binary( ad->ad_type->sat_syntax ) + && !slap_ad_is_binary( ad ) ) + { + char buf[ SLAP_TEXT_BUFLEN ]; + struct berval bv; + const char *text = NULL; + + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%s;binary", + ad->ad_cname.bv_val ); + at_map->bam_true_ad = NULL; + bas->bas_rc = slap_bv2ad( &bv, &at_map->bam_true_ad, &text ); + if ( bas->bas_rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): " + "unable to fetch attribute \"%s\": %s (%d)\n", + buf, text, rc ); + ch_free( at_map ); + return BACKSQL_AVL_STOP; + } + } + + ber_str2bv( at_row.cols[ 1 ], 0, 1, &at_map->bam_sel_expr ); + if ( at_row.value_len[ 8 ] <= 0 ) { + BER_BVZERO( &at_map->bam_sel_expr_u ); + + } else { + ber_str2bv( at_row.cols[ 8 ], 0, 1, + &at_map->bam_sel_expr_u ); + } + + ber_str2bv( at_row.cols[ 2 ], 0, 0, &bv ); + backsql_merge_from_clause( bas->bas_bi, &bb, &bv ); + at_map->bam_from_tbls = bb.bb_val; + if ( at_row.value_len[ 3 ] <= 0 ) { + BER_BVZERO( &at_map->bam_join_where ); + + } else { + ber_str2bv( at_row.cols[ 3 ], 0, 1, + &at_map->bam_join_where ); + } + at_map->bam_add_proc = NULL; + if ( at_row.value_len[ 4 ] > 0 ) { + at_map->bam_add_proc = ch_strdup( at_row.cols[ 4 ] ); + } + at_map->bam_delete_proc = NULL; + if ( at_row.value_len[ 5 ] > 0 ) { + at_map->bam_delete_proc = ch_strdup( at_row.cols[ 5 ] ); + } + if ( lutil_atoix( &at_map->bam_param_order, at_row.cols[ 6 ], 0 ) != 0 ) { + /* error */ + } + if ( lutil_atoix( &at_map->bam_expect_return, at_row.cols[ 7 ], 0 ) != 0 ) { + /* error */ + } + backsql_make_attr_query( bas->bas_bi, oc_map, at_map ); + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): " + "preconstructed query \"%s\"\n", + at_map->bam_query, 0, 0 ); + at_map->bam_next = NULL; + if ( avl_insert( &oc_map->bom_attrs, at_map, backsql_cmp_attr, backsql_dup_attr ) == BACKSQL_DUPLICATE ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): " + "duplicate attribute \"%s\" " + "in objectClass \"%s\" map\n", + at_map->bam_ad->ad_cname.bv_val, + oc_map->bom_oc->soc_cname.bv_val, 0 ); + ch_free( at_map ); + } + + if ( !BER_BVISNULL( &bas->bas_bi->sql_upper_func ) && + BER_BVISNULL( &at_map->bam_sel_expr_u ) ) + { + struct berbuf bb = BB_NULL; + + backsql_strfcat_x( &bb, NULL, "bcbc", + &bas->bas_bi->sql_upper_func, + '(' /* ) */ , + &at_map->bam_sel_expr, + /* ( */ ')' ); + at_map->bam_sel_expr_u = bb.bb_val; + } + } + backsql_FreeRow( &at_row ); + SQLFreeStmt( bas->bas_sth, SQL_CLOSE ); + + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(\"%s\"): " + "autoadding 'objectClass' and 'ref' mappings\n", + BACKSQL_OC_NAME( oc_map ), 0, 0 ); + + (void)backsql_add_sysmaps( bas->bas_bi, oc_map ); + + return BACKSQL_AVL_CONTINUE; +} + + +int +backsql_load_schema_map( backsql_info *bi, SQLHDBC dbh ) +{ + SQLHSTMT sth = SQL_NULL_HSTMT; + RETCODE rc; + BACKSQL_ROW_NTS oc_row; + backsql_key_t oc_id; + backsql_oc_map_rec *oc_map; + struct backsql_attr_schema_info bas; + + int delete_proc_idx = 5; + int create_hint_idx = delete_proc_idx + 2; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_load_schema_map()\n", 0, 0, 0 ); + + /* + * TimesTen : See if the ldap_entries.dn_ru field exists in the schema + */ + if ( !BACKSQL_DONTCHECK_LDAPINFO_DN_RU( bi ) ) { + rc = backsql_Prepare( dbh, &sth, + backsql_check_dn_ru_query, 0 ); + if ( rc == SQL_SUCCESS ) { + /* Yes, the field exists */ + bi->sql_flags |= BSQLF_HAS_LDAPINFO_DN_RU; + Debug( LDAP_DEBUG_TRACE, "ldapinfo.dn_ru field exists " + "in the schema\n", 0, 0, 0 ); + } else { + /* No such field exists */ + bi->sql_flags &= ~BSQLF_HAS_LDAPINFO_DN_RU; + } + + SQLFreeStmt( sth, SQL_DROP ); + } + + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): oc_query \"%s\"\n", + bi->sql_oc_query, 0, 0 ); + + rc = backsql_Prepare( dbh, &sth, bi->sql_oc_query, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): " + "error preparing oc_query: \"%s\"\n", + bi->sql_oc_query, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + return LDAP_OTHER; + } + + rc = SQLExecute( sth ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): " + "error executing oc_query: \n", 0, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + return LDAP_OTHER; + } + + backsql_BindRowAsStrings( sth, &oc_row ); + rc = SQLFetch( sth ); + + if ( BACKSQL_CREATE_NEEDS_SELECT( bi ) ) { + delete_proc_idx++; + create_hint_idx++; + } + + for ( ; BACKSQL_SUCCESS( rc ); rc = SQLFetch( sth ) ) { + { + struct { + int idx; + char *name; + } required[] = { + { 0, "id" }, + { 1, "name" }, + { 2, "keytbl" }, + { 3, "keycol" }, + { -1, "expect_return" }, + { -1, NULL }, + }; + int i; + + required[4].idx = delete_proc_idx + 1; + + for ( i = 0; required[ i ].name != NULL; i++ ) { + if ( oc_row.value_len[ required[ i ].idx ] <= 0 ) { + Debug( LDAP_DEBUG_ANY, + "backsql_load_schema_map(): " + "required column #%d \"%s\" is empty\n", + required[ i ].idx, required[ i ].name, 0 ); + return LDAP_OTHER; + } + } + } + + { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "objectClass: " + "id=\"%s\" " + "name=\"%s\" " + "keytbl=\"%s\" " + "keycol=\"%s\" " + "create_proc=\"%s\" " + "create_keyval=\"%s\" " + "delete_proc=\"%s\" " + "expect_return=\"%s\"" + "create_hint=\"%s\" ", + oc_row.cols[ 0 ], + oc_row.cols[ 1 ], + oc_row.cols[ 2 ], + oc_row.cols[ 3 ], + oc_row.cols[ 4 ] ? oc_row.cols[ 4 ] : "", + ( BACKSQL_CREATE_NEEDS_SELECT( bi ) && oc_row.cols[ 5 ] ) ? oc_row.cols[ 5 ] : "", + oc_row.cols[ delete_proc_idx ] ? oc_row.cols[ delete_proc_idx ] : "", + oc_row.cols[ delete_proc_idx + 1 ], + ( ( oc_row.ncols > create_hint_idx ) && oc_row.cols[ create_hint_idx ] ) ? oc_row.cols[ create_hint_idx ] : "" ); + Debug( LDAP_DEBUG_TRACE, "%s\n", buf, 0, 0 ); + } + + oc_map = (backsql_oc_map_rec *)ch_calloc( 1, + sizeof( backsql_oc_map_rec ) ); + + if ( BACKSQL_STR2ID( &oc_map->bom_id, oc_row.cols[ 0 ], 0 ) != 0 ) { + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): " + "unable to parse id=\"%s\"\n", + oc_row.cols[ 0 ], 0, 0 ); + ch_free( oc_map ); + return LDAP_OTHER; + } + + oc_map->bom_oc = oc_find( oc_row.cols[ 1 ] ); + if ( oc_map->bom_oc == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): " + "objectClass \"%s\" is not defined in schema\n", + oc_row.cols[ 1 ], 0, 0 ); + ch_free( oc_map ); + return LDAP_OTHER; /* undefined objectClass ? */ + } + + ber_str2bv( oc_row.cols[ 2 ], 0, 1, &oc_map->bom_keytbl ); + ber_str2bv( oc_row.cols[ 3 ], 0, 1, &oc_map->bom_keycol ); + oc_map->bom_create_proc = ( oc_row.value_len[ 4 ] <= 0 ) ? NULL + : ch_strdup( oc_row.cols[ 4 ] ); + + if ( BACKSQL_CREATE_NEEDS_SELECT( bi ) ) { + oc_map->bom_create_keyval = ( oc_row.value_len[ 5 ] <= 0 ) + ? NULL : ch_strdup( oc_row.cols[ 5 ] ); + } + oc_map->bom_delete_proc = ( oc_row.value_len[ delete_proc_idx ] <= 0 ) ? NULL + : ch_strdup( oc_row.cols[ delete_proc_idx ] ); + if ( lutil_atoix( &oc_map->bom_expect_return, oc_row.cols[ delete_proc_idx + 1 ], 0 ) != 0 ) { + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): " + "unable to parse expect_return=\"%s\" for objectClass \"%s\"\n", + oc_row.cols[ delete_proc_idx + 1 ], oc_row.cols[ 1 ], 0 ); + ch_free( oc_map ); + return LDAP_OTHER; + } + + if ( ( oc_row.ncols > create_hint_idx ) && + ( oc_row.value_len[ create_hint_idx ] > 0 ) ) + { + const char *text; + + oc_map->bom_create_hint = NULL; + rc = slap_str2ad( oc_row.cols[ create_hint_idx ], + &oc_map->bom_create_hint, &text ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "load_schema_map(): " + "error matching " + "AttributeDescription %s " + "in create_hint: %s (%d)\n", + oc_row.cols[ create_hint_idx ], + text, rc ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + ch_free( oc_map ); + return LDAP_OTHER; + } + } + + /* + * FIXME: first attempt to check for offending + * instructions in {create|delete}_proc + */ + + oc_map->bom_attrs = NULL; + if ( avl_insert( &bi->sql_oc_by_oc, oc_map, backsql_cmp_oc, avl_dup_error ) == -1 ) { + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): " + "duplicate objectClass \"%s\" in objectClass map\n", + oc_map->bom_oc->soc_cname.bv_val, 0, 0 ); + ch_free( oc_map ); + return LDAP_OTHER; + } + if ( avl_insert( &bi->sql_oc_by_id, oc_map, backsql_cmp_oc_id, avl_dup_error ) == -1 ) { + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): " + "duplicate objectClass \"%s\" in objectClass by ID map\n", + oc_map->bom_oc->soc_cname.bv_val, 0, 0 ); + return LDAP_OTHER; + } + oc_id = oc_map->bom_id; + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): " + "objectClass \"%s\":\n keytbl=\"%s\" keycol=\"%s\"\n", + BACKSQL_OC_NAME( oc_map ), + oc_map->bom_keytbl.bv_val, oc_map->bom_keycol.bv_val ); + if ( oc_map->bom_create_proc ) { + Debug( LDAP_DEBUG_TRACE, " create_proc=\"%s\"\n", + oc_map->bom_create_proc, 0, 0 ); + } + if ( oc_map->bom_create_keyval ) { + Debug( LDAP_DEBUG_TRACE, " create_keyval=\"%s\"\n", + oc_map->bom_create_keyval, 0, 0 ); + } + if ( oc_map->bom_create_hint ) { + Debug( LDAP_DEBUG_TRACE, " create_hint=\"%s\"\n", + oc_map->bom_create_hint->ad_cname.bv_val, + 0, 0 ); + } + if ( oc_map->bom_delete_proc ) { + Debug( LDAP_DEBUG_TRACE, " delete_proc=\"%s\"\n", + oc_map->bom_delete_proc, 0, 0 ); + } + Debug( LDAP_DEBUG_TRACE, " expect_return: " + "add=%d, del=%d; attributes:\n", + BACKSQL_IS_ADD( oc_map->bom_expect_return ), + BACKSQL_IS_DEL( oc_map->bom_expect_return ), 0 ); + } + + backsql_FreeRow( &oc_row ); + SQLFreeStmt( sth, SQL_DROP ); + + /* prepare for attribute fetching */ + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): at_query \"%s\"\n", + bi->sql_at_query, 0, 0 ); + + rc = backsql_Prepare( dbh, &sth, bi->sql_at_query, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): " + "error preparing at_query: \"%s\"\n", + bi->sql_at_query, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + return LDAP_OTHER; + } + + rc = backsql_BindParamNumID( sth, 1, SQL_PARAM_INPUT, &oc_id ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): " + "error binding param \"oc_id\" for at_query\n", 0, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + return LDAP_OTHER; + } + + bas.bas_bi = bi; + bas.bas_dbh = dbh; + bas.bas_sth = sth; + bas.bas_oc_id = &oc_id; + bas.bas_rc = LDAP_SUCCESS; + + (void)avl_apply( bi->sql_oc_by_oc, backsql_oc_get_attr_mapping, + &bas, BACKSQL_AVL_STOP, AVL_INORDER ); + + SQLFreeStmt( sth, SQL_DROP ); + + bi->sql_flags |= BSQLF_SCHEMA_LOADED; + + Debug( LDAP_DEBUG_TRACE, "<==backsql_load_schema_map()\n", 0, 0, 0 ); + + return bas.bas_rc; +} + +backsql_oc_map_rec * +backsql_oc2oc( backsql_info *bi, ObjectClass *oc ) +{ + backsql_oc_map_rec tmp, *res; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "==>backsql_oc2oc(): " + "searching for objectclass with name=\"%s\"\n", + oc->soc_cname.bv_val, 0, 0 ); +#endif /* BACKSQL_TRACE */ + + tmp.bom_oc = oc; + res = (backsql_oc_map_rec *)avl_find( bi->sql_oc_by_oc, &tmp, backsql_cmp_oc ); +#ifdef BACKSQL_TRACE + if ( res != NULL ) { + Debug( LDAP_DEBUG_TRACE, "<==backsql_oc2oc(): " + "found name=\"%s\", id=%d\n", + BACKSQL_OC_NAME( res ), res->bom_id, 0 ); + } else { + Debug( LDAP_DEBUG_TRACE, "<==backsql_oc2oc(): " + "not found\n", 0, 0, 0 ); + } +#endif /* BACKSQL_TRACE */ + + return res; +} + +backsql_oc_map_rec * +backsql_name2oc( backsql_info *bi, struct berval *oc_name ) +{ + backsql_oc_map_rec tmp, *res; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "==>oc_with_name(): " + "searching for objectclass with name=\"%s\"\n", + oc_name->bv_val, 0, 0 ); +#endif /* BACKSQL_TRACE */ + + tmp.bom_oc = oc_bvfind( oc_name ); + if ( tmp.bom_oc == NULL ) { + return NULL; + } + + res = (backsql_oc_map_rec *)avl_find( bi->sql_oc_by_oc, &tmp, backsql_cmp_oc ); +#ifdef BACKSQL_TRACE + if ( res != NULL ) { + Debug( LDAP_DEBUG_TRACE, "<==oc_with_name(): " + "found name=\"%s\", id=%d\n", + BACKSQL_OC_NAME( res ), res->bom_id, 0 ); + } else { + Debug( LDAP_DEBUG_TRACE, "<==oc_with_name(): " + "not found\n", 0, 0, 0 ); + } +#endif /* BACKSQL_TRACE */ + + return res; +} + +backsql_oc_map_rec * +backsql_id2oc( backsql_info *bi, unsigned long id ) +{ + backsql_oc_map_rec tmp, *res; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "==>oc_with_id(): " + "searching for objectclass with id=%lu\n", id, 0, 0 ); +#endif /* BACKSQL_TRACE */ + + tmp.bom_id = id; + res = (backsql_oc_map_rec *)avl_find( bi->sql_oc_by_id, &tmp, + backsql_cmp_oc_id ); + +#ifdef BACKSQL_TRACE + if ( res != NULL ) { + Debug( LDAP_DEBUG_TRACE, "<==oc_with_id(): " + "found name=\"%s\", id=%lu\n", + BACKSQL_OC_NAME( res ), res->bom_id, 0 ); + } else { + Debug( LDAP_DEBUG_TRACE, "<==oc_with_id(): " + "id=%lu not found\n", res->bom_id, 0, 0 ); + } +#endif /* BACKSQL_TRACE */ + + return res; +} + +backsql_at_map_rec * +backsql_ad2at( backsql_oc_map_rec* objclass, AttributeDescription *ad ) +{ + backsql_at_map_rec tmp = { 0 }, *res; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "==>backsql_ad2at(): " + "searching for attribute \"%s\" for objectclass \"%s\"\n", + ad->ad_cname.bv_val, BACKSQL_OC_NAME( objclass ), 0 ); +#endif /* BACKSQL_TRACE */ + + tmp.bam_ad = ad; + res = (backsql_at_map_rec *)avl_find( objclass->bom_attrs, &tmp, + backsql_cmp_attr ); + +#ifdef BACKSQL_TRACE + if ( res != NULL ) { + Debug( LDAP_DEBUG_TRACE, "<==backsql_ad2at(): " + "found name=\"%s\", sel_expr=\"%s\"\n", + res->bam_ad->ad_cname.bv_val, + res->bam_sel_expr.bv_val, 0 ); + } else { + Debug( LDAP_DEBUG_TRACE, "<==backsql_ad2at(): " + "not found\n", 0, 0, 0 ); + } +#endif /* BACKSQL_TRACE */ + + return res; +} + +/* attributeType inheritance */ +struct supad2at_t { + backsql_at_map_rec **ret; + AttributeDescription *ad; + unsigned n; +}; + +#define SUPAD2AT_STOP (-1) + +static int +supad2at_f( void *v_at, void *v_arg ) +{ + backsql_at_map_rec *at = (backsql_at_map_rec *)v_at; + struct supad2at_t *va = (struct supad2at_t *)v_arg; + + if ( is_at_subtype( at->bam_ad->ad_type, va->ad->ad_type ) ) { + backsql_at_map_rec **ret = NULL; + unsigned i; + + /* if already listed, holler! (should never happen) */ + if ( va->ret ) { + for ( i = 0; i < va->n; i++ ) { + if ( va->ret[ i ]->bam_ad == at->bam_ad ) { + break; + } + } + + if ( i < va->n ) { + return 0; + } + } + + ret = ch_realloc( va->ret, + sizeof( backsql_at_map_rec * ) * ( va->n + 2 ) ); + if ( ret == NULL ) { + ch_free( va->ret ); + va->ret = NULL; + va->n = 0; + return SUPAD2AT_STOP; + } + + ret[ va->n ] = at; + va->n++; + ret[ va->n ] = NULL; + va->ret = ret; + } + + return 0; +} + +/* + * stores in *pret a NULL terminated array of pointers + * to backsql_at_map_rec whose attributeType is supad->ad_type + * or derived from it + */ +int +backsql_supad2at( backsql_oc_map_rec *objclass, AttributeDescription *supad, + backsql_at_map_rec ***pret ) +{ + struct supad2at_t va = { 0 }; + int rc; + + assert( objclass != NULL ); + assert( supad != NULL ); + assert( pret != NULL ); + + *pret = NULL; + + va.ad = supad; + + rc = avl_apply( objclass->bom_attrs, supad2at_f, &va, + SUPAD2AT_STOP, AVL_INORDER ); + if ( rc == SUPAD2AT_STOP ) { + return -1; + } + + *pret = va.ret; + + return 0; +} + +static void +backsql_free_attr( void *v_at ) +{ + backsql_at_map_rec *at = v_at; + + Debug( LDAP_DEBUG_TRACE, "==>free_attr(): \"%s\"\n", + at->bam_ad->ad_cname.bv_val, 0, 0 ); + ch_free( at->bam_sel_expr.bv_val ); + if ( !BER_BVISNULL( &at->bam_from_tbls ) ) { + ch_free( at->bam_from_tbls.bv_val ); + } + if ( !BER_BVISNULL( &at->bam_join_where ) ) { + ch_free( at->bam_join_where.bv_val ); + } + if ( at->bam_add_proc != NULL ) { + ch_free( at->bam_add_proc ); + } + if ( at->bam_delete_proc != NULL ) { + ch_free( at->bam_delete_proc ); + } + if ( at->bam_query != NULL ) { + ch_free( at->bam_query ); + } + +#ifdef BACKSQL_COUNTQUERY + if ( at->bam_countquery != NULL ) { + ch_free( at->bam_countquery ); + } +#endif /* BACKSQL_COUNTQUERY */ + + /* TimesTen */ + if ( !BER_BVISNULL( &at->bam_sel_expr_u ) ) { + ch_free( at->bam_sel_expr_u.bv_val ); + } + + if ( at->bam_next ) { + backsql_free_attr( at->bam_next ); + } + + ch_free( at ); + + Debug( LDAP_DEBUG_TRACE, "<==free_attr()\n", 0, 0, 0 ); +} + +static void +backsql_free_oc( void *v_oc ) +{ + backsql_oc_map_rec *oc = v_oc; + + Debug( LDAP_DEBUG_TRACE, "==>free_oc(): \"%s\"\n", + BACKSQL_OC_NAME( oc ), 0, 0 ); + avl_free( oc->bom_attrs, backsql_free_attr ); + ch_free( oc->bom_keytbl.bv_val ); + ch_free( oc->bom_keycol.bv_val ); + if ( oc->bom_create_proc != NULL ) { + ch_free( oc->bom_create_proc ); + } + if ( oc->bom_create_keyval != NULL ) { + ch_free( oc->bom_create_keyval ); + } + if ( oc->bom_delete_proc != NULL ) { + ch_free( oc->bom_delete_proc ); + } + ch_free( oc ); + + Debug( LDAP_DEBUG_TRACE, "<==free_oc()\n", 0, 0, 0 ); +} + +int +backsql_destroy_schema_map( backsql_info *bi ) +{ + Debug( LDAP_DEBUG_TRACE, "==>destroy_schema_map()\n", 0, 0, 0 ); + avl_free( bi->sql_oc_by_oc, 0 ); + avl_free( bi->sql_oc_by_id, backsql_free_oc ); + Debug( LDAP_DEBUG_TRACE, "<==destroy_schema_map()\n", 0, 0, 0 ); + return 0; +} + diff --git a/servers/slapd/back-sql/search.c b/servers/slapd/back-sql/search.c new file mode 100644 index 0000000..bb0f1e2 --- /dev/null +++ b/servers/slapd/back-sql/search.c @@ -0,0 +1,2792 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * Portions Copyright 2004 Mark Adamson. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati and Mark Adamson. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> +#include "ac/string.h" +#include "ac/ctype.h" + +#include "lutil.h" +#include "slap.h" +#include "proto-sql.h" + +static int backsql_process_filter( backsql_srch_info *bsi, Filter *f ); +static int backsql_process_filter_eq( backsql_srch_info *bsi, + backsql_at_map_rec *at, + int casefold, struct berval *filter_value ); +static int backsql_process_filter_like( backsql_srch_info *bsi, + backsql_at_map_rec *at, + int casefold, struct berval *filter_value ); +static int backsql_process_filter_attr( backsql_srch_info *bsi, Filter *f, + backsql_at_map_rec *at ); + +/* For LDAP_CONTROL_PAGEDRESULTS, a 32 bit cookie is available to keep track of + the state of paged results. The ldap_entries.id and oc_map_id values of the + last entry returned are used as the cookie, so 6 bits are used for the OC id + and the other 26 for ldap_entries ID number. If your max(oc_map_id) is more + than 63, you will need to steal more bits from ldap_entries ID number and + put them into the OC ID part of the cookie. */ + +/* NOTE: not supported when BACKSQL_ARBITRARY_KEY is defined */ +#ifndef BACKSQL_ARBITRARY_KEY +#define SQL_TO_PAGECOOKIE(id, oc) (((id) << 6 ) | ((oc) & 0x3F)) +#define PAGECOOKIE_TO_SQL_ID(pc) ((pc) >> 6) +#define PAGECOOKIE_TO_SQL_OC(pc) ((pc) & 0x3F) + +static int parse_paged_cookie( Operation *op, SlapReply *rs ); + +static void send_paged_response( + Operation *op, + SlapReply *rs, + ID *lastid ); +#endif /* ! BACKSQL_ARBITRARY_KEY */ + +static int +backsql_attrlist_add( backsql_srch_info *bsi, AttributeDescription *ad ) +{ + int n_attrs = 0; + AttributeName *an = NULL; + + if ( bsi->bsi_attrs == NULL ) { + return 1; + } + + /* + * clear the list (retrieve all attrs) + */ + if ( ad == NULL ) { + bsi->bsi_op->o_tmpfree( bsi->bsi_attrs, bsi->bsi_op->o_tmpmemctx ); + bsi->bsi_attrs = NULL; + bsi->bsi_flags |= BSQL_SF_ALL_ATTRS; + return 1; + } + + /* strip ';binary' */ + if ( slap_ad_is_binary( ad ) ) { + ad = ad->ad_type->sat_ad; + } + + for ( ; !BER_BVISNULL( &bsi->bsi_attrs[ n_attrs ].an_name ); n_attrs++ ) { + an = &bsi->bsi_attrs[ n_attrs ]; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_attrlist_add(): " + "attribute \"%s\" is in list\n", + an->an_name.bv_val, 0, 0 ); + /* + * We can live with strcmp because the attribute + * list has been normalized before calling be_search + */ + if ( !BACKSQL_NCMP( &an->an_name, &ad->ad_cname ) ) { + return 1; + } + } + + Debug( LDAP_DEBUG_TRACE, "==>backsql_attrlist_add(): " + "adding \"%s\" to list\n", ad->ad_cname.bv_val, 0, 0 ); + + an = (AttributeName *)bsi->bsi_op->o_tmprealloc( bsi->bsi_attrs, + sizeof( AttributeName ) * ( n_attrs + 2 ), + bsi->bsi_op->o_tmpmemctx ); + if ( an == NULL ) { + return -1; + } + + an[ n_attrs ].an_name = ad->ad_cname; + an[ n_attrs ].an_desc = ad; + BER_BVZERO( &an[ n_attrs + 1 ].an_name ); + + bsi->bsi_attrs = an; + + return 1; +} + +/* + * Initializes the search structure. + * + * If get_base_id != 0, the field bsi_base_id is filled + * with the entryID of bsi_base_ndn; it must be freed + * by backsql_free_entryID() when no longer required. + * + * NOTE: base must be normalized + */ +int +backsql_init_search( + backsql_srch_info *bsi, + struct berval *nbase, + int scope, + time_t stoptime, + Filter *filter, + SQLHDBC dbh, + Operation *op, + SlapReply *rs, + AttributeName *attrs, + unsigned flags ) +{ + backsql_info *bi = (backsql_info *)op->o_bd->be_private; + int rc = LDAP_SUCCESS; + + bsi->bsi_base_ndn = nbase; + bsi->bsi_use_subtree_shortcut = 0; + BER_BVZERO( &bsi->bsi_base_id.eid_dn ); + BER_BVZERO( &bsi->bsi_base_id.eid_ndn ); + bsi->bsi_scope = scope; + bsi->bsi_filter = filter; + bsi->bsi_dbh = dbh; + bsi->bsi_op = op; + bsi->bsi_rs = rs; + bsi->bsi_flags = BSQL_SF_NONE; + + bsi->bsi_attrs = NULL; + + if ( BACKSQL_FETCH_ALL_ATTRS( bi ) ) { + /* + * if requested, simply try to fetch all attributes + */ + bsi->bsi_flags |= BSQL_SF_ALL_ATTRS; + + } else { + if ( BACKSQL_FETCH_ALL_USERATTRS( bi ) ) { + bsi->bsi_flags |= BSQL_SF_ALL_USER; + + } else if ( BACKSQL_FETCH_ALL_OPATTRS( bi ) ) { + bsi->bsi_flags |= BSQL_SF_ALL_OPER; + } + + if ( attrs == NULL ) { + /* NULL means all user attributes */ + bsi->bsi_flags |= BSQL_SF_ALL_USER; + + } else { + AttributeName *p; + int got_oc = 0; + + bsi->bsi_attrs = (AttributeName *)bsi->bsi_op->o_tmpalloc( + sizeof( AttributeName ), + bsi->bsi_op->o_tmpmemctx ); + BER_BVZERO( &bsi->bsi_attrs[ 0 ].an_name ); + + for ( p = attrs; !BER_BVISNULL( &p->an_name ); p++ ) { + if ( BACKSQL_NCMP( &p->an_name, slap_bv_all_user_attrs ) == 0 ) { + /* handle "*" */ + bsi->bsi_flags |= BSQL_SF_ALL_USER; + + /* if all attrs are requested, there's + * no need to continue */ + if ( BSQL_ISF_ALL_ATTRS( bsi ) ) { + bsi->bsi_op->o_tmpfree( bsi->bsi_attrs, + bsi->bsi_op->o_tmpmemctx ); + bsi->bsi_attrs = NULL; + break; + } + continue; + + } else if ( BACKSQL_NCMP( &p->an_name, slap_bv_all_operational_attrs ) == 0 ) { + /* handle "+" */ + bsi->bsi_flags |= BSQL_SF_ALL_OPER; + + /* if all attrs are requested, there's + * no need to continue */ + if ( BSQL_ISF_ALL_ATTRS( bsi ) ) { + bsi->bsi_op->o_tmpfree( bsi->bsi_attrs, + bsi->bsi_op->o_tmpmemctx ); + bsi->bsi_attrs = NULL; + break; + } + continue; + + } else if ( BACKSQL_NCMP( &p->an_name, slap_bv_no_attrs ) == 0 ) { + /* ignore "1.1" */ + continue; + + } else if ( p->an_desc == slap_schema.si_ad_objectClass ) { + got_oc = 1; + } + + backsql_attrlist_add( bsi, p->an_desc ); + } + + if ( got_oc == 0 && !( bsi->bsi_flags & BSQL_SF_ALL_USER ) ) { + /* add objectClass if not present, + * because it is required to understand + * if an entry is a referral, an alias + * or so... */ + backsql_attrlist_add( bsi, slap_schema.si_ad_objectClass ); + } + } + + if ( !BSQL_ISF_ALL_ATTRS( bsi ) && bi->sql_anlist ) { + AttributeName *p; + + /* use hints if available */ + for ( p = bi->sql_anlist; !BER_BVISNULL( &p->an_name ); p++ ) { + if ( BACKSQL_NCMP( &p->an_name, slap_bv_all_user_attrs ) == 0 ) { + /* handle "*" */ + bsi->bsi_flags |= BSQL_SF_ALL_USER; + + /* if all attrs are requested, there's + * no need to continue */ + if ( BSQL_ISF_ALL_ATTRS( bsi ) ) { + bsi->bsi_op->o_tmpfree( bsi->bsi_attrs, + bsi->bsi_op->o_tmpmemctx ); + bsi->bsi_attrs = NULL; + break; + } + continue; + + } else if ( BACKSQL_NCMP( &p->an_name, slap_bv_all_operational_attrs ) == 0 ) { + /* handle "+" */ + bsi->bsi_flags |= BSQL_SF_ALL_OPER; + + /* if all attrs are requested, there's + * no need to continue */ + if ( BSQL_ISF_ALL_ATTRS( bsi ) ) { + bsi->bsi_op->o_tmpfree( bsi->bsi_attrs, + bsi->bsi_op->o_tmpmemctx ); + bsi->bsi_attrs = NULL; + break; + } + continue; + } + + backsql_attrlist_add( bsi, p->an_desc ); + } + + } + } + + bsi->bsi_id_list = NULL; + bsi->bsi_id_listtail = &bsi->bsi_id_list; + bsi->bsi_n_candidates = 0; + bsi->bsi_stoptime = stoptime; + BER_BVZERO( &bsi->bsi_sel.bb_val ); + bsi->bsi_sel.bb_len = 0; + BER_BVZERO( &bsi->bsi_from.bb_val ); + bsi->bsi_from.bb_len = 0; + BER_BVZERO( &bsi->bsi_join_where.bb_val ); + bsi->bsi_join_where.bb_len = 0; + BER_BVZERO( &bsi->bsi_flt_where.bb_val ); + bsi->bsi_flt_where.bb_len = 0; + bsi->bsi_filter_oc = NULL; + + if ( BACKSQL_IS_GET_ID( flags ) ) { + int matched = BACKSQL_IS_MATCHED( flags ); + int getentry = BACKSQL_IS_GET_ENTRY( flags ); + int gotit = 0; + + assert( op->o_bd->be_private != NULL ); + + rc = backsql_dn2id( op, rs, dbh, nbase, &bsi->bsi_base_id, + matched, 1 ); + + /* the entry is collected either if requested for by getentry + * or if get noSuchObject and requested to climb the tree, + * so that a matchedDN or a referral can be returned */ + if ( ( rc == LDAP_NO_SUCH_OBJECT && matched ) || getentry ) { + if ( !BER_BVISNULL( &bsi->bsi_base_id.eid_ndn ) ) { + assert( bsi->bsi_e != NULL ); + + if ( dn_match( nbase, &bsi->bsi_base_id.eid_ndn ) ) + { + gotit = 1; + } + + /* + * let's see if it is a referral and, in case, get it + */ + backsql_attrlist_add( bsi, slap_schema.si_ad_ref ); + rc = backsql_id2entry( bsi, &bsi->bsi_base_id ); + if ( rc == LDAP_SUCCESS ) { + if ( is_entry_referral( bsi->bsi_e ) ) + { + BerVarray erefs = get_entry_referrals( op, bsi->bsi_e ); + if ( erefs ) { + rc = rs->sr_err = LDAP_REFERRAL; + rs->sr_ref = referral_rewrite( erefs, + &bsi->bsi_e->e_nname, + &op->o_req_dn, + scope ); + ber_bvarray_free( erefs ); + + } else { + rc = rs->sr_err = LDAP_OTHER; + rs->sr_text = "bad referral object"; + } + + } else if ( !gotit ) { + rc = rs->sr_err = LDAP_NO_SUCH_OBJECT; + } + } + + } else { + rs->sr_err = rc; + } + } + + if ( gotit && BACKSQL_IS_GET_OC( flags ) ) { + bsi->bsi_base_id.eid_oc = backsql_id2oc( bi, + bsi->bsi_base_id.eid_oc_id ); + if ( bsi->bsi_base_id.eid_oc == NULL ) { + /* error? */ + backsql_free_entryID( &bsi->bsi_base_id, 1, + op->o_tmpmemctx ); + rc = rs->sr_err = LDAP_OTHER; + } + } + } + + bsi->bsi_status = rc; + + switch ( rc ) { + case LDAP_SUCCESS: + case LDAP_REFERRAL: + break; + + default: + bsi->bsi_op->o_tmpfree( bsi->bsi_attrs, + bsi->bsi_op->o_tmpmemctx ); + break; + } + + return rc; +} + +static int +backsql_process_filter_list( backsql_srch_info *bsi, Filter *f, int op ) +{ + int res; + + if ( !f ) { + return 0; + } + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, "c", '(' /* ) */ ); + + while ( 1 ) { + res = backsql_process_filter( bsi, f ); + if ( res < 0 ) { + /* + * TimesTen : If the query has no answers, + * don't bother to run the query. + */ + return -1; + } + + f = f->f_next; + if ( f == NULL ) { + break; + } + + switch ( op ) { + case LDAP_FILTER_AND: + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, "l", + (ber_len_t)STRLENOF( " AND " ), + " AND " ); + break; + + case LDAP_FILTER_OR: + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, "l", + (ber_len_t)STRLENOF( " OR " ), + " OR " ); + break; + } + } + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, "c", /* ( */ ')' ); + + return 1; +} + +static int +backsql_process_sub_filter( backsql_srch_info *bsi, Filter *f, + backsql_at_map_rec *at ) +{ + backsql_info *bi = (backsql_info *)bsi->bsi_op->o_bd->be_private; + int i; + int casefold = 0; + + if ( !f ) { + return 0; + } + + /* always uppercase strings by now */ +#ifdef BACKSQL_UPPERCASE_FILTER + if ( f->f_sub_desc->ad_type->sat_substr && + SLAP_MR_ASSOCIATED( f->f_sub_desc->ad_type->sat_substr, + bi->sql_caseIgnoreMatch ) ) +#endif /* BACKSQL_UPPERCASE_FILTER */ + { + casefold = 1; + } + + if ( f->f_sub_desc->ad_type->sat_substr && + SLAP_MR_ASSOCIATED( f->f_sub_desc->ad_type->sat_substr, + bi->sql_telephoneNumberMatch ) ) + { + + struct berval bv; + ber_len_t i, s, a; + + /* + * to check for matching telephone numbers + * with intermixed chars, e.g. val='1234' + * use + * + * val LIKE '%1%2%3%4%' + */ + + BER_BVZERO( &bv ); + if ( f->f_sub_initial.bv_val ) { + bv.bv_len += f->f_sub_initial.bv_len; + } + if ( f->f_sub_any != NULL ) { + for ( a = 0; f->f_sub_any[ a ].bv_val != NULL; a++ ) { + bv.bv_len += f->f_sub_any[ a ].bv_len; + } + } + if ( f->f_sub_final.bv_val ) { + bv.bv_len += f->f_sub_final.bv_len; + } + bv.bv_len = 2 * bv.bv_len - 1; + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + + s = 0; + if ( !BER_BVISNULL( &f->f_sub_initial ) ) { + bv.bv_val[ s ] = f->f_sub_initial.bv_val[ 0 ]; + for ( i = 1; i < f->f_sub_initial.bv_len; i++ ) { + bv.bv_val[ s + 2 * i - 1 ] = '%'; + bv.bv_val[ s + 2 * i ] = f->f_sub_initial.bv_val[ i ]; + } + bv.bv_val[ s + 2 * i - 1 ] = '%'; + s += 2 * i; + } + + if ( f->f_sub_any != NULL ) { + for ( a = 0; !BER_BVISNULL( &f->f_sub_any[ a ] ); a++ ) { + bv.bv_val[ s ] = f->f_sub_any[ a ].bv_val[ 0 ]; + for ( i = 1; i < f->f_sub_any[ a ].bv_len; i++ ) { + bv.bv_val[ s + 2 * i - 1 ] = '%'; + bv.bv_val[ s + 2 * i ] = f->f_sub_any[ a ].bv_val[ i ]; + } + bv.bv_val[ s + 2 * i - 1 ] = '%'; + s += 2 * i; + } + } + + if ( !BER_BVISNULL( &f->f_sub_final ) ) { + bv.bv_val[ s ] = f->f_sub_final.bv_val[ 0 ]; + for ( i = 1; i < f->f_sub_final.bv_len; i++ ) { + bv.bv_val[ s + 2 * i - 1 ] = '%'; + bv.bv_val[ s + 2 * i ] = f->f_sub_final.bv_val[ i ]; + } + bv.bv_val[ s + 2 * i - 1 ] = '%'; + s += 2 * i; + } + + bv.bv_val[ s - 1 ] = '\0'; + + (void)backsql_process_filter_like( bsi, at, casefold, &bv ); + ch_free( bv.bv_val ); + + return 1; + } + + /* + * When dealing with case-sensitive strings + * we may omit normalization; however, normalized + * SQL filters are more liberal. + */ + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, "c", '(' /* ) */ ); + + /* TimesTen */ + Debug( LDAP_DEBUG_TRACE, "backsql_process_sub_filter(%s):\n", + at->bam_ad->ad_cname.bv_val, 0, 0 ); + Debug(LDAP_DEBUG_TRACE, " expr: '%s%s%s'\n", at->bam_sel_expr.bv_val, + at->bam_sel_expr_u.bv_val ? "' '" : "", + at->bam_sel_expr_u.bv_val ? at->bam_sel_expr_u.bv_val : "" ); + if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) { + /* + * If a pre-upper-cased version of the column + * or a precompiled upper function exists, use it + */ + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "bl", + &at->bam_sel_expr_u, + (ber_len_t)STRLENOF( " LIKE '" ), + " LIKE '" ); + + } else { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "bl", + &at->bam_sel_expr, + (ber_len_t)STRLENOF( " LIKE '" ), " LIKE '" ); + } + + if ( !BER_BVISNULL( &f->f_sub_initial ) ) { + ber_len_t start; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, + "==>backsql_process_sub_filter(%s): " + "sub_initial=\"%s\"\n", at->bam_ad->ad_cname.bv_val, + f->f_sub_initial.bv_val, 0 ); +#endif /* BACKSQL_TRACE */ + + start = bsi->bsi_flt_where.bb_val.bv_len; + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "b", + &f->f_sub_initial ); + if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) { + ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] ); + } + } + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "c", '%' ); + + if ( f->f_sub_any != NULL ) { + for ( i = 0; !BER_BVISNULL( &f->f_sub_any[ i ] ); i++ ) { + ber_len_t start; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, + "==>backsql_process_sub_filter(%s): " + "sub_any[%d]=\"%s\"\n", at->bam_ad->ad_cname.bv_val, + i, f->f_sub_any[ i ].bv_val ); +#endif /* BACKSQL_TRACE */ + + start = bsi->bsi_flt_where.bb_val.bv_len; + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "bc", + &f->f_sub_any[ i ], + '%' ); + if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) { + /* + * Note: toupper('%') = '%' + */ + ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] ); + } + } + } + + if ( !BER_BVISNULL( &f->f_sub_final ) ) { + ber_len_t start; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, + "==>backsql_process_sub_filter(%s): " + "sub_final=\"%s\"\n", at->bam_ad->ad_cname.bv_val, + f->f_sub_final.bv_val, 0 ); +#endif /* BACKSQL_TRACE */ + + start = bsi->bsi_flt_where.bb_val.bv_len; + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "b", + &f->f_sub_final ); + if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) { + ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] ); + } + } + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( /* (' */ "')" ), /* (' */ "')" ); + + return 1; +} + +static int +backsql_merge_from_tbls( backsql_srch_info *bsi, struct berval *from_tbls ) +{ + if ( BER_BVISNULL( from_tbls ) ) { + return LDAP_SUCCESS; + } + + if ( !BER_BVISNULL( &bsi->bsi_from.bb_val ) ) { + char *start, *end; + struct berval tmp; + + ber_dupbv_x( &tmp, from_tbls, bsi->bsi_op->o_tmpmemctx ); + + for ( start = tmp.bv_val, end = strchr( start, ',' ); start; ) { + if ( end ) { + end[0] = '\0'; + } + + if ( strstr( bsi->bsi_from.bb_val.bv_val, start) == NULL ) + { + backsql_strfcat_x( &bsi->bsi_from, + bsi->bsi_op->o_tmpmemctx, + "cs", ',', start ); + } + + if ( end ) { + /* in case there are spaces after the comma... */ + for ( start = &end[1]; isspace( start[0] ); start++ ); + if ( start[0] ) { + end = strchr( start, ',' ); + } else { + start = NULL; + } + } else { + start = NULL; + } + } + + bsi->bsi_op->o_tmpfree( tmp.bv_val, bsi->bsi_op->o_tmpmemctx ); + + } else { + backsql_strfcat_x( &bsi->bsi_from, + bsi->bsi_op->o_tmpmemctx, + "b", from_tbls ); + } + + return LDAP_SUCCESS; +} + +static int +backsql_process_filter( backsql_srch_info *bsi, Filter *f ) +{ + backsql_at_map_rec **vat = NULL; + AttributeDescription *ad = NULL; + unsigned i; + int done = 0; + int rc = 0; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_process_filter()\n", 0, 0, 0 ); + if ( f->f_choice == SLAPD_FILTER_COMPUTED ) { + struct berval flt; + char *msg = NULL; + + switch ( f->f_result ) { + case LDAP_COMPARE_TRUE: + BER_BVSTR( &flt, "10=10" ); + msg = "TRUE"; + break; + + case LDAP_COMPARE_FALSE: + BER_BVSTR( &flt, "11=0" ); + msg = "FALSE"; + break; + + case SLAPD_COMPARE_UNDEFINED: + BER_BVSTR( &flt, "12=0" ); + msg = "UNDEFINED"; + break; + + default: + rc = -1; + goto done; + } + + Debug( LDAP_DEBUG_TRACE, "backsql_process_filter(): " + "filter computed (%s)\n", msg, 0, 0 ); + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, "b", &flt ); + rc = 1; + goto done; + } + + if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "1=0" ), "1=0" ); + done = 1; + rc = 1; + goto done; + } + + switch( f->f_choice ) { + case LDAP_FILTER_OR: + rc = backsql_process_filter_list( bsi, f->f_or, + LDAP_FILTER_OR ); + done = 1; + break; + + case LDAP_FILTER_AND: + rc = backsql_process_filter_list( bsi, f->f_and, + LDAP_FILTER_AND ); + done = 1; + break; + + case LDAP_FILTER_NOT: + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "NOT (" /* ) */ ), + "NOT (" /* ) */ ); + rc = backsql_process_filter( bsi, f->f_not ); + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "c", /* ( */ ')' ); + done = 1; + break; + + case LDAP_FILTER_PRESENT: + ad = f->f_desc; + break; + + case LDAP_FILTER_EXT: + ad = f->f_mra->ma_desc; + if ( f->f_mr_dnattrs ) { + /* + * if dn attrs filtering is requested, better return + * success and let test_filter() deal with candidate + * selection; otherwise we'd need to set conditions + * on the contents of the DN, e.g. "SELECT ... FROM + * ldap_entries AS attributeName WHERE attributeName.dn + * like '%attributeName=value%'" + */ + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "1=1" ), "1=1" ); + bsi->bsi_status = LDAP_SUCCESS; + rc = 1; + goto done; + } + break; + + default: + ad = f->f_av_desc; + break; + } + + if ( rc == -1 ) { + goto done; + } + + if ( done ) { + rc = 1; + goto done; + } + + /* + * Turn structuralObjectClass into objectClass + */ + if ( ad == slap_schema.si_ad_objectClass + || ad == slap_schema.si_ad_structuralObjectClass ) + { + /* + * If the filter is LDAP_FILTER_PRESENT, then it's done; + * otherwise, let's see if we are lucky: filtering + * for "structural" objectclass or ancestor... + */ + switch ( f->f_choice ) { + case LDAP_FILTER_EQUALITY: + { + ObjectClass *oc = oc_bvfind( &f->f_av_value ); + + if ( oc == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "backsql_process_filter(): " + "unknown objectClass \"%s\" " + "in filter\n", + f->f_av_value.bv_val, 0, 0 ); + bsi->bsi_status = LDAP_OTHER; + rc = -1; + goto done; + } + + /* + * "structural" objectClass inheritance: + * - a search for "person" will also return + * "inetOrgPerson" + * - a search for "top" will return everything + */ + if ( is_object_subclass( oc, bsi->bsi_oc->bom_oc ) ) { + static struct berval ldap_entry_objclasses = BER_BVC( "ldap_entry_objclasses" ); + + backsql_merge_from_tbls( bsi, &ldap_entry_objclasses ); + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "lbl", + (ber_len_t)STRLENOF( "(2=2 OR (ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entry_objclasses.oc_name='" /* ')) */ ), + "(2=2 OR (ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entry_objclasses.oc_name='" /* ')) */, + &bsi->bsi_oc->bom_oc->soc_cname, + (ber_len_t)STRLENOF( /* ((' */ "'))" ), + /* ((' */ "'))" ); + bsi->bsi_status = LDAP_SUCCESS; + rc = 1; + goto done; + } + + break; + } + + case LDAP_FILTER_PRESENT: + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "3=3" ), "3=3" ); + bsi->bsi_status = LDAP_SUCCESS; + rc = 1; + goto done; + + /* FIXME: LDAP_FILTER_EXT? */ + + default: + Debug( LDAP_DEBUG_TRACE, + "backsql_process_filter(): " + "illegal/unhandled filter " + "on objectClass attribute", + 0, 0, 0 ); + bsi->bsi_status = LDAP_OTHER; + rc = -1; + goto done; + } + + } else if ( ad == slap_schema.si_ad_entryUUID ) { + unsigned long oc_id; +#ifdef BACKSQL_ARBITRARY_KEY + struct berval keyval; +#else /* ! BACKSQL_ARBITRARY_KEY */ + unsigned long keyval; + char keyvalbuf[LDAP_PVT_INTTYPE_CHARS(unsigned long)]; +#endif /* ! BACKSQL_ARBITRARY_KEY */ + + switch ( f->f_choice ) { + case LDAP_FILTER_EQUALITY: + backsql_entryUUID_decode( &f->f_av_value, &oc_id, &keyval ); + + if ( oc_id != bsi->bsi_oc->bom_id ) { + bsi->bsi_status = LDAP_SUCCESS; + rc = -1; + goto done; + } + +#ifdef BACKSQL_ARBITRARY_KEY + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "bcblbc", + &bsi->bsi_oc->bom_keytbl, '.', + &bsi->bsi_oc->bom_keycol, + STRLENOF( " LIKE '" ), " LIKE '", + &keyval, '\'' ); +#else /* ! BACKSQL_ARBITRARY_KEY */ + snprintf( keyvalbuf, sizeof( keyvalbuf ), "%lu", keyval ); + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "bcbcs", + &bsi->bsi_oc->bom_keytbl, '.', + &bsi->bsi_oc->bom_keycol, '=', keyvalbuf ); +#endif /* ! BACKSQL_ARBITRARY_KEY */ + break; + + case LDAP_FILTER_PRESENT: + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "4=4" ), "4=4" ); + break; + + default: + rc = -1; + goto done; + } + + bsi->bsi_flags |= BSQL_SF_FILTER_ENTRYUUID; + rc = 1; + goto done; + +#ifdef BACKSQL_SYNCPROV + } else if ( ad == slap_schema.si_ad_entryCSN ) { + /* + * support for syncrepl as provider... + */ +#if 0 + if ( !bsi->bsi_op->o_sync ) { + /* unsupported at present... */ + bsi->bsi_status = LDAP_OTHER; + rc = -1; + goto done; + } +#endif + + bsi->bsi_flags |= ( BSQL_SF_FILTER_ENTRYCSN | BSQL_SF_RETURN_ENTRYUUID); + + /* if doing a syncrepl, try to return as much as possible, + * and always match the filter */ + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "5=5" ), "5=5" ); + + /* save for later use in operational attributes */ + /* FIXME: saves only the first occurrence, because + * the filter during updates is written as + * "(&(entryCSN<={contextCSN})(entryCSN>={oldContextCSN})({filter}))" + * so we want our fake entryCSN to match the greatest + * value + */ + if ( bsi->bsi_op->o_private == NULL ) { + bsi->bsi_op->o_private = &f->f_av_value; + } + bsi->bsi_status = LDAP_SUCCESS; + + rc = 1; + goto done; +#endif /* BACKSQL_SYNCPROV */ + + } else if ( ad == slap_schema.si_ad_hasSubordinates || ad == NULL ) { + /* + * FIXME: this is not robust; e.g. a filter + * '(!(hasSubordinates=TRUE))' fails because + * in SQL it would read 'NOT (1=1)' instead + * of no condition. + * Note however that hasSubordinates is boolean, + * so a more appropriate filter would be + * '(hasSubordinates=FALSE)' + * + * A more robust search for hasSubordinates + * would * require joining the ldap_entries table + * selecting if there are descendants of the + * candidate. + */ + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "6=6" ), "6=6" ); + if ( ad == slap_schema.si_ad_hasSubordinates ) { + /* + * instruct candidate selection algorithm + * and attribute list to try to detect + * if an entry has subordinates + */ + bsi->bsi_flags |= BSQL_SF_FILTER_HASSUBORDINATE; + + } else { + /* + * clear attributes to fetch, to require ALL + * and try extended match on all attributes + */ + backsql_attrlist_add( bsi, NULL ); + } + rc = 1; + goto done; + } + + /* + * attribute inheritance: + */ + if ( backsql_supad2at( bsi->bsi_oc, ad, &vat ) ) { + bsi->bsi_status = LDAP_OTHER; + rc = -1; + goto done; + } + + if ( vat == NULL ) { + /* search anyway; other parts of the filter + * may succeeed */ + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "7=7" ), "7=7" ); + bsi->bsi_status = LDAP_SUCCESS; + rc = 1; + goto done; + } + + /* if required, open extra level of parens */ + done = 0; + if ( vat[0]->bam_next || vat[1] ) { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "c", '(' ); + done = 1; + } + + i = 0; +next:; + /* apply attr */ + if ( backsql_process_filter_attr( bsi, f, vat[i] ) == -1 ) { + return -1; + } + + /* if more definitions of the same attr, apply */ + if ( vat[i]->bam_next ) { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + STRLENOF( " OR " ), " OR " ); + vat[i] = vat[i]->bam_next; + goto next; + } + + /* if more descendants of the same attr, apply */ + i++; + if ( vat[i] ) { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + STRLENOF( " OR " ), " OR " ); + goto next; + } + + /* if needed, close extra level of parens */ + if ( done ) { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "c", ')' ); + } + + rc = 1; + +done:; + if ( vat ) { + ch_free( vat ); + } + + Debug( LDAP_DEBUG_TRACE, + "<==backsql_process_filter() %s\n", + rc == 1 ? "succeeded" : "failed", 0, 0); + + return rc; +} + +static int +backsql_process_filter_eq( backsql_srch_info *bsi, backsql_at_map_rec *at, + int casefold, struct berval *filter_value ) +{ + /* + * maybe we should check type of at->sel_expr here somehow, + * to know whether upper_func is applicable, but for now + * upper_func stuff is made for Oracle, where UPPER is + * safely applicable to NUMBER etc. + */ + if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) { + ber_len_t start; + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "cbl", + '(', /* ) */ + &at->bam_sel_expr_u, + (ber_len_t)STRLENOF( "='" ), + "='" ); + + start = bsi->bsi_flt_where.bb_val.bv_len; + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "bl", + filter_value, + (ber_len_t)STRLENOF( /* (' */ "')" ), + /* (' */ "')" ); + + ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] ); + + } else { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "cblbl", + '(', /* ) */ + &at->bam_sel_expr, + (ber_len_t)STRLENOF( "='" ), "='", + filter_value, + (ber_len_t)STRLENOF( /* (' */ "')" ), + /* (' */ "')" ); + } + + return 1; +} + +static int +backsql_process_filter_like( backsql_srch_info *bsi, backsql_at_map_rec *at, + int casefold, struct berval *filter_value ) +{ + /* + * maybe we should check type of at->sel_expr here somehow, + * to know whether upper_func is applicable, but for now + * upper_func stuff is made for Oracle, where UPPER is + * safely applicable to NUMBER etc. + */ + if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) { + ber_len_t start; + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "cbl", + '(', /* ) */ + &at->bam_sel_expr_u, + (ber_len_t)STRLENOF( " LIKE '%" ), + " LIKE '%" ); + + start = bsi->bsi_flt_where.bb_val.bv_len; + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "bl", + filter_value, + (ber_len_t)STRLENOF( /* (' */ "%')" ), + /* (' */ "%')" ); + + ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] ); + + } else { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "cblbl", + '(', /* ) */ + &at->bam_sel_expr, + (ber_len_t)STRLENOF( " LIKE '%" ), + " LIKE '%", + filter_value, + (ber_len_t)STRLENOF( /* (' */ "%')" ), + /* (' */ "%')" ); + } + + return 1; +} + +static int +backsql_process_filter_attr( backsql_srch_info *bsi, Filter *f, backsql_at_map_rec *at ) +{ + backsql_info *bi = (backsql_info *)bsi->bsi_op->o_bd->be_private; + int casefold = 0; + struct berval *filter_value = NULL; + MatchingRule *matching_rule = NULL; + struct berval ordering = BER_BVC("<="); + + Debug( LDAP_DEBUG_TRACE, "==>backsql_process_filter_attr(%s)\n", + at->bam_ad->ad_cname.bv_val, 0, 0 ); + + /* + * need to add this attribute to list of attrs to load, + * so that we can do test_filter() later + */ + backsql_attrlist_add( bsi, at->bam_ad ); + + backsql_merge_from_tbls( bsi, &at->bam_from_tbls ); + + if ( !BER_BVISNULL( &at->bam_join_where ) + && strstr( bsi->bsi_join_where.bb_val.bv_val, + at->bam_join_where.bv_val ) == NULL ) + { + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "lb", + (ber_len_t)STRLENOF( " AND " ), " AND ", + &at->bam_join_where ); + } + + if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "1=0" ), "1=0" ); + return 1; + } + + switch ( f->f_choice ) { + case LDAP_FILTER_EQUALITY: + filter_value = &f->f_av_value; + matching_rule = at->bam_ad->ad_type->sat_equality; + + goto equality_match; + + /* fail over into next case */ + + case LDAP_FILTER_EXT: + filter_value = &f->f_mra->ma_value; + matching_rule = f->f_mr_rule; + +equality_match:; + /* always uppercase strings by now */ +#ifdef BACKSQL_UPPERCASE_FILTER + if ( SLAP_MR_ASSOCIATED( matching_rule, + bi->sql_caseIgnoreMatch ) ) +#endif /* BACKSQL_UPPERCASE_FILTER */ + { + casefold = 1; + } + + /* FIXME: directoryString filtering should use a similar + * approach to deal with non-prettified values like + * " A non prettified value ", by using a LIKE + * filter with all whitespaces collapsed to a single '%' */ + if ( SLAP_MR_ASSOCIATED( matching_rule, + bi->sql_telephoneNumberMatch ) ) + { + struct berval bv; + ber_len_t i; + + /* + * to check for matching telephone numbers + * with intermized chars, e.g. val='1234' + * use + * + * val LIKE '%1%2%3%4%' + */ + + bv.bv_len = 2 * filter_value->bv_len - 1; + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + + bv.bv_val[ 0 ] = filter_value->bv_val[ 0 ]; + for ( i = 1; i < filter_value->bv_len; i++ ) { + bv.bv_val[ 2 * i - 1 ] = '%'; + bv.bv_val[ 2 * i ] = filter_value->bv_val[ i ]; + } + bv.bv_val[ 2 * i - 1 ] = '\0'; + + (void)backsql_process_filter_like( bsi, at, casefold, &bv ); + ch_free( bv.bv_val ); + + break; + } + + /* NOTE: this is required by objectClass inheritance + * and auxiliary objectClass use in filters for slightly + * more efficient candidate selection. */ + /* FIXME: a bit too many specializations to deal with + * very specific cases... */ + if ( at->bam_ad == slap_schema.si_ad_objectClass + || at->bam_ad == slap_schema.si_ad_structuralObjectClass ) + { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "lbl", + (ber_len_t)STRLENOF( "(ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entry_objclasses.oc_name='" /* ') */ ), + "(ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entry_objclasses.oc_name='" /* ') */, + filter_value, + (ber_len_t)STRLENOF( /* (' */ "')" ), + /* (' */ "')" ); + break; + } + + /* + * maybe we should check type of at->sel_expr here somehow, + * to know whether upper_func is applicable, but for now + * upper_func stuff is made for Oracle, where UPPER is + * safely applicable to NUMBER etc. + */ + (void)backsql_process_filter_eq( bsi, at, casefold, filter_value ); + break; + + case LDAP_FILTER_GE: + ordering.bv_val = ">="; + + /* fall thru to next case */ + + case LDAP_FILTER_LE: + filter_value = &f->f_av_value; + + /* always uppercase strings by now */ +#ifdef BACKSQL_UPPERCASE_FILTER + if ( at->bam_ad->ad_type->sat_ordering && + SLAP_MR_ASSOCIATED( at->bam_ad->ad_type->sat_ordering, + bi->sql_caseIgnoreMatch ) ) +#endif /* BACKSQL_UPPERCASE_FILTER */ + { + casefold = 1; + } + + /* + * FIXME: should we uppercase the operands? + */ + if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) { + ber_len_t start; + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "cbbc", + '(', /* ) */ + &at->bam_sel_expr_u, + &ordering, + '\'' ); + + start = bsi->bsi_flt_where.bb_val.bv_len; + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "bl", + filter_value, + (ber_len_t)STRLENOF( /* (' */ "')" ), + /* (' */ "')" ); + + ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] ); + + } else { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "cbbcbl", + '(' /* ) */ , + &at->bam_sel_expr, + &ordering, + '\'', + &f->f_av_value, + (ber_len_t)STRLENOF( /* (' */ "')" ), + /* ( */ "')" ); + } + break; + + case LDAP_FILTER_PRESENT: + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "lbl", + (ber_len_t)STRLENOF( "NOT (" /* ) */), + "NOT (", /* ) */ + &at->bam_sel_expr, + (ber_len_t)STRLENOF( /* ( */ " IS NULL)" ), + /* ( */ " IS NULL)" ); + break; + + case LDAP_FILTER_SUBSTRINGS: + backsql_process_sub_filter( bsi, f, at ); + break; + + case LDAP_FILTER_APPROX: + /* we do our best */ + + /* + * maybe we should check type of at->sel_expr here somehow, + * to know whether upper_func is applicable, but for now + * upper_func stuff is made for Oracle, where UPPER is + * safely applicable to NUMBER etc. + */ + (void)backsql_process_filter_like( bsi, at, 1, &f->f_av_value ); + break; + + default: + /* unhandled filter type; should not happen */ + assert( 0 ); + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "8=8" ), "8=8" ); + break; + + } + + Debug( LDAP_DEBUG_TRACE, "<==backsql_process_filter_attr(%s)\n", + at->bam_ad->ad_cname.bv_val, 0, 0 ); + + return 1; +} + +static int +backsql_srch_query( backsql_srch_info *bsi, struct berval *query ) +{ + backsql_info *bi = (backsql_info *)bsi->bsi_op->o_bd->be_private; + int rc; + + assert( query != NULL ); + BER_BVZERO( query ); + + bsi->bsi_use_subtree_shortcut = 0; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_srch_query()\n", 0, 0, 0 ); + BER_BVZERO( &bsi->bsi_sel.bb_val ); + BER_BVZERO( &bsi->bsi_sel.bb_val ); + bsi->bsi_sel.bb_len = 0; + BER_BVZERO( &bsi->bsi_from.bb_val ); + bsi->bsi_from.bb_len = 0; + BER_BVZERO( &bsi->bsi_join_where.bb_val ); + bsi->bsi_join_where.bb_len = 0; + BER_BVZERO( &bsi->bsi_flt_where.bb_val ); + bsi->bsi_flt_where.bb_len = 0; + + backsql_strfcat_x( &bsi->bsi_sel, + bsi->bsi_op->o_tmpmemctx, + "lbcbc", + (ber_len_t)STRLENOF( "SELECT DISTINCT ldap_entries.id," ), + "SELECT DISTINCT ldap_entries.id,", + &bsi->bsi_oc->bom_keytbl, + '.', + &bsi->bsi_oc->bom_keycol, + ',' ); + + if ( !BER_BVISNULL( &bi->sql_strcast_func ) ) { + backsql_strfcat_x( &bsi->bsi_sel, + bsi->bsi_op->o_tmpmemctx, + "blbl", + &bi->sql_strcast_func, + (ber_len_t)STRLENOF( "('" /* ') */ ), + "('" /* ') */ , + &bsi->bsi_oc->bom_oc->soc_cname, + (ber_len_t)STRLENOF( /* (' */ "')" ), + /* (' */ "')" ); + } else { + backsql_strfcat_x( &bsi->bsi_sel, + bsi->bsi_op->o_tmpmemctx, + "cbc", + '\'', + &bsi->bsi_oc->bom_oc->soc_cname, + '\'' ); + } + + backsql_strfcat_x( &bsi->bsi_sel, + bsi->bsi_op->o_tmpmemctx, + "b", + &bi->sql_dn_oc_aliasing ); + backsql_strfcat_x( &bsi->bsi_from, + bsi->bsi_op->o_tmpmemctx, + "lb", + (ber_len_t)STRLENOF( " FROM ldap_entries," ), + " FROM ldap_entries,", + &bsi->bsi_oc->bom_keytbl ); + + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "lbcbl", + (ber_len_t)STRLENOF( " WHERE " ), " WHERE ", + &bsi->bsi_oc->bom_keytbl, + '.', + &bsi->bsi_oc->bom_keycol, + (ber_len_t)STRLENOF( "=ldap_entries.keyval AND ldap_entries.oc_map_id=? AND " ), + "=ldap_entries.keyval AND ldap_entries.oc_map_id=? AND " ); + + switch ( bsi->bsi_scope ) { + case LDAP_SCOPE_BASE: + if ( BACKSQL_CANUPPERCASE( bi ) ) { + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "bl", + &bi->sql_upper_func, + (ber_len_t)STRLENOF( "(ldap_entries.dn)=?" ), + "(ldap_entries.dn)=?" ); + } else { + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "ldap_entries.dn=?" ), + "ldap_entries.dn=?" ); + } + break; + + case BACKSQL_SCOPE_BASE_LIKE: + if ( BACKSQL_CANUPPERCASE( bi ) ) { + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "bl", + &bi->sql_upper_func, + (ber_len_t)STRLENOF( "(ldap_entries.dn) LIKE ?" ), + "(ldap_entries.dn) LIKE ?" ); + } else { + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "ldap_entries.dn LIKE ?" ), + "ldap_entries.dn LIKE ?" ); + } + break; + + case LDAP_SCOPE_ONELEVEL: + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "ldap_entries.parent=?" ), + "ldap_entries.parent=?" ); + break; + + case LDAP_SCOPE_SUBORDINATE: + case LDAP_SCOPE_SUBTREE: + if ( BACKSQL_USE_SUBTREE_SHORTCUT( bi ) ) { + int i; + BackendDB *bd = bsi->bsi_op->o_bd; + + assert( bd->be_nsuffix != NULL ); + + for ( i = 0; !BER_BVISNULL( &bd->be_nsuffix[ i ] ); i++ ) + { + if ( dn_match( &bd->be_nsuffix[ i ], + bsi->bsi_base_ndn ) ) + { + /* pass this to the candidate selection + * routine so that the DN is not bound + * to the select statement */ + bsi->bsi_use_subtree_shortcut = 1; + break; + } + } + } + + if ( bsi->bsi_use_subtree_shortcut ) { + /* Skip the base DN filter, as every entry will match it */ + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "9=9"), "9=9"); + + } else if ( !BER_BVISNULL( &bi->sql_subtree_cond ) ) { + /* This should always be true... */ + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "b", + &bi->sql_subtree_cond ); + + } else if ( BACKSQL_CANUPPERCASE( bi ) ) { + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "bl", + &bi->sql_upper_func, + (ber_len_t)STRLENOF( "(ldap_entries.dn) LIKE ?" ), + "(ldap_entries.dn) LIKE ?" ); + + } else { + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "ldap_entries.dn LIKE ?" ), + "ldap_entries.dn LIKE ?" ); + } + + break; + + default: + assert( 0 ); + } + +#ifndef BACKSQL_ARBITRARY_KEY + /* If paged results are in effect, ignore low ldap_entries.id numbers */ + if ( get_pagedresults(bsi->bsi_op) > SLAP_CONTROL_IGNORED ) { + unsigned long lowid = 0; + + /* Pick up the previous ldap_entries.id if the previous page ended in this objectClass */ + if ( bsi->bsi_oc->bom_id == PAGECOOKIE_TO_SQL_OC( ((PagedResultsState *)bsi->bsi_op->o_pagedresults_state)->ps_cookie ) ) + { + lowid = PAGECOOKIE_TO_SQL_ID( ((PagedResultsState *)bsi->bsi_op->o_pagedresults_state)->ps_cookie ); + } + + if ( lowid ) { + char lowidstring[48]; + int lowidlen; + + lowidlen = snprintf( lowidstring, sizeof( lowidstring ), + " AND ldap_entries.id>%lu", lowid ); + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)lowidlen, + lowidstring ); + } + } +#endif /* ! BACKSQL_ARBITRARY_KEY */ + + rc = backsql_process_filter( bsi, bsi->bsi_filter ); + if ( rc > 0 ) { + struct berbuf bb = BB_NULL; + + backsql_strfcat_x( &bb, + bsi->bsi_op->o_tmpmemctx, + "bbblb", + &bsi->bsi_sel.bb_val, + &bsi->bsi_from.bb_val, + &bsi->bsi_join_where.bb_val, + (ber_len_t)STRLENOF( " AND " ), " AND ", + &bsi->bsi_flt_where.bb_val ); + + *query = bb.bb_val; + + } else if ( rc < 0 ) { + /* + * Indicates that there's no possible way the filter matches + * anything. No need to issue the query + */ + free( query->bv_val ); + BER_BVZERO( query ); + } + + bsi->bsi_op->o_tmpfree( bsi->bsi_sel.bb_val.bv_val, bsi->bsi_op->o_tmpmemctx ); + BER_BVZERO( &bsi->bsi_sel.bb_val ); + bsi->bsi_sel.bb_len = 0; + bsi->bsi_op->o_tmpfree( bsi->bsi_from.bb_val.bv_val, bsi->bsi_op->o_tmpmemctx ); + BER_BVZERO( &bsi->bsi_from.bb_val ); + bsi->bsi_from.bb_len = 0; + bsi->bsi_op->o_tmpfree( bsi->bsi_join_where.bb_val.bv_val, bsi->bsi_op->o_tmpmemctx ); + BER_BVZERO( &bsi->bsi_join_where.bb_val ); + bsi->bsi_join_where.bb_len = 0; + bsi->bsi_op->o_tmpfree( bsi->bsi_flt_where.bb_val.bv_val, bsi->bsi_op->o_tmpmemctx ); + BER_BVZERO( &bsi->bsi_flt_where.bb_val ); + bsi->bsi_flt_where.bb_len = 0; + + Debug( LDAP_DEBUG_TRACE, "<==backsql_srch_query() returns %s\n", + query->bv_val ? query->bv_val : "NULL", 0, 0 ); + + return ( rc <= 0 ? 1 : 0 ); +} + +static int +backsql_oc_get_candidates( void *v_oc, void *v_bsi ) +{ + backsql_oc_map_rec *oc = v_oc; + backsql_srch_info *bsi = v_bsi; + Operation *op = bsi->bsi_op; + backsql_info *bi = (backsql_info *)bsi->bsi_op->o_bd->be_private; + struct berval query; + SQLHSTMT sth = SQL_NULL_HSTMT; + RETCODE rc; + int res; + BACKSQL_ROW_NTS row; + int i; + int j; + int n_candidates = bsi->bsi_n_candidates; + + /* + * + 1 because we need room for '%'; + * + 1 because we need room for ',' for LDAP_SCOPE_SUBORDINATE; + * this makes a subtree + * search for a DN BACKSQL_MAX_DN_LEN long legal + * if it returns that DN only + */ + char tmp_base_ndn[ BACKSQL_MAX_DN_LEN + 1 + 1 ]; + + bsi->bsi_status = LDAP_SUCCESS; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_oc_get_candidates(): oc=\"%s\"\n", + BACKSQL_OC_NAME( oc ), 0, 0 ); + + /* check for abandon */ + if ( op->o_abandon ) { + bsi->bsi_status = SLAPD_ABANDON; + return BACKSQL_AVL_STOP; + } + +#ifndef BACKSQL_ARBITRARY_KEY + /* If paged results have already completed this objectClass, skip it */ + if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) { + if ( oc->bom_id < PAGECOOKIE_TO_SQL_OC( ((PagedResultsState *)op->o_pagedresults_state)->ps_cookie ) ) + { + return BACKSQL_AVL_CONTINUE; + } + } +#endif /* ! BACKSQL_ARBITRARY_KEY */ + + if ( bsi->bsi_n_candidates == -1 ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): " + "unchecked limit has been overcome\n", 0, 0, 0 ); + /* should never get here */ + assert( 0 ); + bsi->bsi_status = LDAP_ADMINLIMIT_EXCEEDED; + return BACKSQL_AVL_STOP; + } + + bsi->bsi_oc = oc; + res = backsql_srch_query( bsi, &query ); + if ( res ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): " + "error while constructing query for objectclass \"%s\"\n", + oc->bom_oc->soc_cname.bv_val, 0, 0 ); + /* + * FIXME: need to separate errors from legally + * impossible filters + */ + switch ( bsi->bsi_status ) { + case LDAP_SUCCESS: + case LDAP_UNDEFINED_TYPE: + case LDAP_NO_SUCH_OBJECT: + /* we are conservative... */ + default: + bsi->bsi_status = LDAP_SUCCESS; + /* try next */ + return BACKSQL_AVL_CONTINUE; + + case LDAP_ADMINLIMIT_EXCEEDED: + case LDAP_OTHER: + /* don't try any more */ + return BACKSQL_AVL_STOP; + } + } + + if ( BER_BVISNULL( &query ) ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): " + "could not construct query for objectclass \"%s\"\n", + oc->bom_oc->soc_cname.bv_val, 0, 0 ); + bsi->bsi_status = LDAP_SUCCESS; + return BACKSQL_AVL_CONTINUE; + } + + Debug( LDAP_DEBUG_TRACE, "Constructed query: %s\n", + query.bv_val, 0, 0 ); + + rc = backsql_Prepare( bsi->bsi_dbh, &sth, query.bv_val, 0 ); + bsi->bsi_op->o_tmpfree( query.bv_val, bsi->bsi_op->o_tmpmemctx ); + BER_BVZERO( &query ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): " + "error preparing query\n", 0, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc ); + bsi->bsi_status = LDAP_OTHER; + return BACKSQL_AVL_CONTINUE; + } + + Debug( LDAP_DEBUG_TRACE, "id: '" BACKSQL_IDNUMFMT "'\n", + bsi->bsi_oc->bom_id, 0, 0 ); + + rc = backsql_BindParamNumID( sth, 1, SQL_PARAM_INPUT, + &bsi->bsi_oc->bom_id ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): " + "error binding objectclass id parameter\n", 0, 0, 0 ); + bsi->bsi_status = LDAP_OTHER; + return BACKSQL_AVL_CONTINUE; + } + + switch ( bsi->bsi_scope ) { + case LDAP_SCOPE_BASE: + case BACKSQL_SCOPE_BASE_LIKE: + /* + * We do not accept DNs longer than BACKSQL_MAX_DN_LEN; + * however this should be handled earlier + */ + if ( bsi->bsi_base_ndn->bv_len > BACKSQL_MAX_DN_LEN ) { + bsi->bsi_status = LDAP_OTHER; + return BACKSQL_AVL_CONTINUE; + } + + AC_MEMCPY( tmp_base_ndn, bsi->bsi_base_ndn->bv_val, + bsi->bsi_base_ndn->bv_len + 1 ); + + /* uppercase DN only if the stored DN can be uppercased + * for comparison */ + if ( BACKSQL_CANUPPERCASE( bi ) ) { + ldap_pvt_str2upper( tmp_base_ndn ); + } + + Debug( LDAP_DEBUG_TRACE, "(base)dn: \"%s\"\n", + tmp_base_ndn, 0, 0 ); + + rc = backsql_BindParamStr( sth, 2, SQL_PARAM_INPUT, + tmp_base_ndn, BACKSQL_MAX_DN_LEN ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): " + "error binding base_ndn parameter\n", 0, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, + sth, rc ); + bsi->bsi_status = LDAP_OTHER; + return BACKSQL_AVL_CONTINUE; + } + break; + + case LDAP_SCOPE_SUBORDINATE: + case LDAP_SCOPE_SUBTREE: + { + /* if short-cutting the search base, + * don't bind any parameter */ + if ( bsi->bsi_use_subtree_shortcut ) { + break; + } + + /* + * We do not accept DNs longer than BACKSQL_MAX_DN_LEN; + * however this should be handled earlier + */ + if ( bsi->bsi_base_ndn->bv_len > BACKSQL_MAX_DN_LEN ) { + bsi->bsi_status = LDAP_OTHER; + return BACKSQL_AVL_CONTINUE; + } + + /* + * Sets the parameters for the SQL built earlier + * NOTE that all the databases could actually use + * the TimesTen version, which would be cleaner + * and would also eliminate the need for the + * subtree_cond line in the configuration file. + * For now, I'm leaving it the way it is, + * so non-TimesTen databases use the original code. + * But at some point this should get cleaned up. + * + * If "dn" is being used, do a suffix search. + * If "dn_ru" is being used, do a prefix search. + */ + if ( BACKSQL_HAS_LDAPINFO_DN_RU( bi ) ) { + tmp_base_ndn[ 0 ] = '\0'; + + for ( i = 0, j = bsi->bsi_base_ndn->bv_len - 1; + j >= 0; i++, j--) { + tmp_base_ndn[ i ] = bsi->bsi_base_ndn->bv_val[ j ]; + } + + if ( bsi->bsi_scope == LDAP_SCOPE_SUBORDINATE ) { + tmp_base_ndn[ i++ ] = ','; + } + + tmp_base_ndn[ i ] = '%'; + tmp_base_ndn[ i + 1 ] = '\0'; + + } else { + i = 0; + + tmp_base_ndn[ i++ ] = '%'; + + if ( bsi->bsi_scope == LDAP_SCOPE_SUBORDINATE ) { + tmp_base_ndn[ i++ ] = ','; + } + + AC_MEMCPY( &tmp_base_ndn[ i ], bsi->bsi_base_ndn->bv_val, + bsi->bsi_base_ndn->bv_len + 1 ); + } + + /* uppercase DN only if the stored DN can be uppercased + * for comparison */ + if ( BACKSQL_CANUPPERCASE( bi ) ) { + ldap_pvt_str2upper( tmp_base_ndn ); + } + + if ( bsi->bsi_scope == LDAP_SCOPE_SUBORDINATE ) { + Debug( LDAP_DEBUG_TRACE, "(children)dn: \"%s\"\n", + tmp_base_ndn, 0, 0 ); + } else { + Debug( LDAP_DEBUG_TRACE, "(sub)dn: \"%s\"\n", + tmp_base_ndn, 0, 0 ); + } + + rc = backsql_BindParamStr( sth, 2, SQL_PARAM_INPUT, + tmp_base_ndn, BACKSQL_MAX_DN_LEN ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): " + "error binding base_ndn parameter (2)\n", + 0, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, + sth, rc ); + bsi->bsi_status = LDAP_OTHER; + return BACKSQL_AVL_CONTINUE; + } + break; + } + + case LDAP_SCOPE_ONELEVEL: + assert( !BER_BVISNULL( &bsi->bsi_base_id.eid_ndn ) ); + + Debug( LDAP_DEBUG_TRACE, "(one)id=" BACKSQL_IDFMT "\n", + BACKSQL_IDARG(bsi->bsi_base_id.eid_id), 0, 0 ); + rc = backsql_BindParamID( sth, 2, SQL_PARAM_INPUT, + &bsi->bsi_base_id.eid_id ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): " + "error binding base id parameter\n", 0, 0, 0 ); + bsi->bsi_status = LDAP_OTHER; + return BACKSQL_AVL_CONTINUE; + } + break; + } + + rc = SQLExecute( sth ); + if ( !BACKSQL_SUCCESS( rc ) ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): " + "error executing query\n", 0, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + bsi->bsi_status = LDAP_OTHER; + return BACKSQL_AVL_CONTINUE; + } + + backsql_BindRowAsStrings_x( sth, &row, bsi->bsi_op->o_tmpmemctx ); + rc = SQLFetch( sth ); + for ( ; BACKSQL_SUCCESS( rc ); rc = SQLFetch( sth ) ) { + struct berval dn, pdn, ndn; + backsql_entryID *c_id = NULL; + int ret; + + ber_str2bv( row.cols[ 3 ], 0, 0, &dn ); + + if ( backsql_api_odbc2dn( bsi->bsi_op, bsi->bsi_rs, &dn ) ) { + continue; + } + + ret = dnPrettyNormal( NULL, &dn, &pdn, &ndn, op->o_tmpmemctx ); + if ( dn.bv_val != row.cols[ 3 ] ) { + free( dn.bv_val ); + } + + if ( ret != LDAP_SUCCESS ) { + continue; + } + + if ( bi->sql_baseObject && dn_match( &ndn, &bi->sql_baseObject->e_nname ) ) { + goto cleanup; + } + + c_id = (backsql_entryID *)op->o_tmpcalloc( 1, + sizeof( backsql_entryID ), op->o_tmpmemctx ); +#ifdef BACKSQL_ARBITRARY_KEY + ber_str2bv_x( row.cols[ 0 ], 0, 1, &c_id->eid_id, + op->o_tmpmemctx ); + ber_str2bv_x( row.cols[ 1 ], 0, 1, &c_id->eid_keyval, + op->o_tmpmemctx ); +#else /* ! BACKSQL_ARBITRARY_KEY */ + if ( BACKSQL_STR2ID( &c_id->eid_id, row.cols[ 0 ], 0 ) != 0 ) { + goto cleanup; + } + if ( BACKSQL_STR2ID( &c_id->eid_keyval, row.cols[ 1 ], 0 ) != 0 ) { + goto cleanup; + } +#endif /* ! BACKSQL_ARBITRARY_KEY */ + c_id->eid_oc = bsi->bsi_oc; + c_id->eid_oc_id = bsi->bsi_oc->bom_id; + + c_id->eid_dn = pdn; + c_id->eid_ndn = ndn; + + /* append at end of list ... */ + c_id->eid_next = NULL; + *bsi->bsi_id_listtail = c_id; + bsi->bsi_id_listtail = &c_id->eid_next; + + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): " + "added entry id=" BACKSQL_IDFMT " keyval=" BACKSQL_IDFMT " dn=\"%s\"\n", + BACKSQL_IDARG(c_id->eid_id), + BACKSQL_IDARG(c_id->eid_keyval), + row.cols[ 3 ] ); + + /* count candidates, for unchecked limit */ + bsi->bsi_n_candidates--; + if ( bsi->bsi_n_candidates == -1 ) { + break; + } + continue; + +cleanup:; + if ( !BER_BVISNULL( &pdn ) ) { + op->o_tmpfree( pdn.bv_val, op->o_tmpmemctx ); + } + if ( !BER_BVISNULL( &ndn ) ) { + op->o_tmpfree( ndn.bv_val, op->o_tmpmemctx ); + } + if ( c_id != NULL ) { + ch_free( c_id ); + } + } + backsql_FreeRow_x( &row, bsi->bsi_op->o_tmpmemctx ); + SQLFreeStmt( sth, SQL_DROP ); + + Debug( LDAP_DEBUG_TRACE, "<==backsql_oc_get_candidates(): %d\n", + n_candidates - bsi->bsi_n_candidates, 0, 0 ); + + return ( bsi->bsi_n_candidates == -1 ? BACKSQL_AVL_STOP : BACKSQL_AVL_CONTINUE ); +} + +int +backsql_search( Operation *op, SlapReply *rs ) +{ + backsql_info *bi = (backsql_info *)op->o_bd->be_private; + SQLHDBC dbh = SQL_NULL_HDBC; + int sres; + Entry user_entry = { 0 }, + base_entry = { 0 }; + int manageDSAit = get_manageDSAit( op ); + time_t stoptime = 0; + backsql_srch_info bsi = { 0 }; + backsql_entryID *eid = NULL; + struct berval nbase = BER_BVNULL; +#ifndef BACKSQL_ARBITRARY_KEY + ID lastid = 0; +#endif /* ! BACKSQL_ARBITRARY_KEY */ + + Debug( LDAP_DEBUG_TRACE, "==>backsql_search(): " + "base=\"%s\", filter=\"%s\", scope=%d,", + op->o_req_ndn.bv_val, + op->ors_filterstr.bv_val, + op->ors_scope ); + Debug( LDAP_DEBUG_TRACE, " deref=%d, attrsonly=%d, " + "attributes to load: %s\n", + op->ors_deref, + op->ors_attrsonly, + op->ors_attrs == NULL ? "all" : "custom list" ); + + if ( op->o_req_ndn.bv_len > BACKSQL_MAX_DN_LEN ) { + Debug( LDAP_DEBUG_TRACE, "backsql_search(): " + "search base length (%ld) exceeds max length (%d)\n", + op->o_req_ndn.bv_len, BACKSQL_MAX_DN_LEN, 0 ); + /* + * FIXME: a LDAP_NO_SUCH_OBJECT could be appropriate + * since it is impossible that such a long DN exists + * in the backend + */ + rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED; + send_ldap_result( op, rs ); + return 1; + } + + sres = backsql_get_db_conn( op, &dbh ); + if ( sres != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_search(): " + "could not get connection handle - exiting\n", + 0, 0, 0 ); + rs->sr_err = sres; + rs->sr_text = sres == LDAP_OTHER ? "SQL-backend error" : NULL; + send_ldap_result( op, rs ); + return 1; + } + + /* compute it anyway; root does not use it */ + stoptime = op->o_time + op->ors_tlimit; + + /* init search */ + bsi.bsi_e = &base_entry; + rs->sr_err = backsql_init_search( &bsi, &op->o_req_ndn, + op->ors_scope, + stoptime, op->ors_filter, + dbh, op, rs, op->ors_attrs, + ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY ) ); + switch ( rs->sr_err ) { + case LDAP_SUCCESS: + break; + + case LDAP_REFERRAL: + if ( manageDSAit && !BER_BVISNULL( &bsi.bsi_e->e_nname ) && + dn_match( &op->o_req_ndn, &bsi.bsi_e->e_nname ) ) + { + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + break; + } + + /* an entry was created; free it */ + entry_clean( bsi.bsi_e ); + + /* fall thru */ + + default: + if ( !BER_BVISNULL( &base_entry.e_nname ) + && !access_allowed( op, &base_entry, + slap_schema.si_ad_entry, NULL, + ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + rs->sr_matched = NULL; + rs->sr_text = NULL; + } + + send_ldap_result( op, rs ); + + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + + if ( !BER_BVISNULL( &base_entry.e_nname ) ) { + entry_clean( &base_entry ); + } + + goto done; + } + /* NOTE: __NEW__ "search" access is required + * on searchBase object */ + { + slap_mask_t mask; + + if ( get_assert( op ) && + ( test_filter( op, &base_entry, get_assertion( op ) ) + != LDAP_COMPARE_TRUE ) ) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + + } + if ( ! access_allowed_mask( op, &base_entry, + slap_schema.si_ad_entry, + NULL, ACL_SEARCH, NULL, &mask ) ) + { + if ( rs->sr_err == LDAP_SUCCESS ) { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + } + } + + if ( rs->sr_err != LDAP_SUCCESS ) { + if ( !ACL_GRANT( mask, ACL_DISCLOSE ) ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = NULL; + } + send_ldap_result( op, rs ); + goto done; + } + } + + bsi.bsi_e = NULL; + + bsi.bsi_n_candidates = + ( op->ors_limit == NULL /* isroot == TRUE */ ? -2 : + ( op->ors_limit->lms_s_unchecked == -1 ? -2 : + ( op->ors_limit->lms_s_unchecked ) ) ); + +#ifndef BACKSQL_ARBITRARY_KEY + /* If paged results are in effect, check the paging cookie */ + if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) { + rs->sr_err = parse_paged_cookie( op, rs ); + if ( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto done; + } + } +#endif /* ! BACKSQL_ARBITRARY_KEY */ + + switch ( bsi.bsi_scope ) { + case LDAP_SCOPE_BASE: + case BACKSQL_SCOPE_BASE_LIKE: + /* + * probably already found... + */ + bsi.bsi_id_list = &bsi.bsi_base_id; + bsi.bsi_id_listtail = &bsi.bsi_base_id.eid_next; + break; + + case LDAP_SCOPE_SUBTREE: + /* + * if baseObject is defined, and if it is the root + * of the search, add it to the candidate list + */ + if ( bi->sql_baseObject && BACKSQL_IS_BASEOBJECT_ID( &bsi.bsi_base_id.eid_id ) ) + { + bsi.bsi_id_list = &bsi.bsi_base_id; + bsi.bsi_id_listtail = &bsi.bsi_base_id.eid_next; + } + + /* FALLTHRU */ + default: + + /* + * for each objectclass we try to construct query which gets IDs + * of entries matching LDAP query filter and scope (or at least + * candidates), and get the IDs. Do this in ID order for paging. + */ + avl_apply( bi->sql_oc_by_id, backsql_oc_get_candidates, + &bsi, BACKSQL_AVL_STOP, AVL_INORDER ); + + /* check for abandon */ + if ( op->o_abandon ) { + eid = bsi.bsi_id_list; + rs->sr_err = SLAPD_ABANDON; + goto send_results; + } + } + + if ( op->ors_limit != NULL /* isroot == FALSE */ + && op->ors_limit->lms_s_unchecked != -1 + && bsi.bsi_n_candidates == -1 ) + { + rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED; + send_ldap_result( op, rs ); + goto done; + } + + /* + * now we load candidate entries (only those attributes + * mentioned in attrs and filter), test it against full filter + * and then send to client; don't free entry_id if baseObject... + */ + for ( eid = bsi.bsi_id_list; + eid != NULL; + eid = backsql_free_entryID( + eid, eid == &bsi.bsi_base_id ? 0 : 1, op->o_tmpmemctx ) ) + { + int rc; + Attribute *a_hasSubordinate = NULL, + *a_entryUUID = NULL, + *a_entryCSN = NULL, + **ap = NULL; + Entry *e = NULL; + + /* check for abandon */ + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto send_results; + } + + /* check time limit */ + if ( op->ors_tlimit != SLAP_NO_LIMIT + && slap_get_time() > stoptime ) + { + rs->sr_err = LDAP_TIMELIMIT_EXCEEDED; + rs->sr_ctrls = NULL; + rs->sr_ref = rs->sr_v2ref; + goto send_results; + } + + Debug(LDAP_DEBUG_TRACE, "backsql_search(): loading data " + "for entry id=" BACKSQL_IDFMT " oc_id=" BACKSQL_IDNUMFMT ", keyval=" BACKSQL_IDFMT "\n", + BACKSQL_IDARG(eid->eid_id), + eid->eid_oc_id, + BACKSQL_IDARG(eid->eid_keyval) ); + + /* check scope */ + switch ( op->ors_scope ) { + case LDAP_SCOPE_BASE: + case BACKSQL_SCOPE_BASE_LIKE: + if ( !dn_match( &eid->eid_ndn, &op->o_req_ndn ) ) { + goto next_entry2; + } + break; + + case LDAP_SCOPE_ONE: + { + struct berval rdn = eid->eid_ndn; + + rdn.bv_len -= op->o_req_ndn.bv_len + STRLENOF( "," ); + if ( !dnIsOneLevelRDN( &rdn ) ) { + goto next_entry2; + } + /* fall thru */ + } + + case LDAP_SCOPE_SUBORDINATE: + /* discard the baseObject entry */ + if ( dn_match( &eid->eid_ndn, &op->o_req_ndn ) ) { + goto next_entry2; + } + /* FALLTHRU */ + case LDAP_SCOPE_SUBTREE: + /* FIXME: this should never fail... */ + if ( !dnIsSuffix( &eid->eid_ndn, &op->o_req_ndn ) ) { + goto next_entry2; + } + break; + } + + if ( BACKSQL_IS_BASEOBJECT_ID( &eid->eid_id ) ) { + /* don't recollect baseObject... */ + e = bi->sql_baseObject; + + } else if ( eid == &bsi.bsi_base_id ) { + /* don't recollect searchBase object... */ + e = &base_entry; + + } else { + bsi.bsi_e = &user_entry; + rc = backsql_id2entry( &bsi, eid ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_search(): " + "error %d in backsql_id2entry() " + "- skipping\n", rc, 0, 0 ); + continue; + } + e = &user_entry; + } + + if ( !manageDSAit && + op->ors_scope != LDAP_SCOPE_BASE && + op->ors_scope != BACKSQL_SCOPE_BASE_LIKE && + is_entry_referral( e ) ) + { + BerVarray refs; + + refs = get_entry_referrals( op, e ); + if ( !refs ) { + backsql_srch_info bsi2 = { 0 }; + Entry user_entry2 = { 0 }; + + /* retry with the full entry... */ + bsi2.bsi_e = &user_entry2; + rc = backsql_init_search( &bsi2, + &e->e_nname, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, + dbh, op, rs, NULL, + BACKSQL_ISF_GET_ENTRY ); + if ( rc == LDAP_SUCCESS ) { + if ( is_entry_referral( &user_entry2 ) ) + { + refs = get_entry_referrals( op, + &user_entry2 ); + } else { + rs->sr_err = LDAP_OTHER; + } + backsql_entry_clean( op, &user_entry2 ); + } + if ( bsi2.bsi_attrs != NULL ) { + op->o_tmpfree( bsi2.bsi_attrs, + op->o_tmpmemctx ); + } + } + + if ( refs ) { + rs->sr_ref = referral_rewrite( refs, + &e->e_name, + &op->o_req_dn, + op->ors_scope ); + ber_bvarray_free( refs ); + } + + if ( rs->sr_ref ) { + rs->sr_err = LDAP_REFERRAL; + + } else { + rs->sr_text = "bad referral object"; + } + + rs->sr_entry = e; + rs->sr_matched = user_entry.e_name.bv_val; + send_search_reference( op, rs ); + + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + rs->sr_matched = NULL; + rs->sr_entry = NULL; + if ( rs->sr_err == LDAP_REFERRAL ) { + rs->sr_err = LDAP_SUCCESS; + } + + goto next_entry; + } + + /* + * We use this flag since we need to parse the filter + * anyway; we should have used the frontend API function + * filter_has_subordinates() + */ + if ( bsi.bsi_flags & BSQL_SF_FILTER_HASSUBORDINATE ) { + rc = backsql_has_children( op, dbh, &e->e_nname ); + + switch ( rc ) { + case LDAP_COMPARE_TRUE: + case LDAP_COMPARE_FALSE: + a_hasSubordinate = slap_operational_hasSubordinate( rc == LDAP_COMPARE_TRUE ); + if ( a_hasSubordinate != NULL ) { + for ( ap = &user_entry.e_attrs; + *ap; + ap = &(*ap)->a_next ); + + *ap = a_hasSubordinate; + } + rc = 0; + break; + + default: + Debug(LDAP_DEBUG_TRACE, + "backsql_search(): " + "has_children failed( %d)\n", + rc, 0, 0 ); + rc = 1; + goto next_entry; + } + } + + if ( bsi.bsi_flags & BSQL_SF_FILTER_ENTRYUUID ) { + a_entryUUID = backsql_operational_entryUUID( bi, eid ); + if ( a_entryUUID != NULL ) { + if ( ap == NULL ) { + ap = &user_entry.e_attrs; + } + + for ( ; *ap; ap = &(*ap)->a_next ); + + *ap = a_entryUUID; + } + } + +#ifdef BACKSQL_SYNCPROV + if ( bsi.bsi_flags & BSQL_SF_FILTER_ENTRYCSN ) { + a_entryCSN = backsql_operational_entryCSN( op ); + if ( a_entryCSN != NULL ) { + if ( ap == NULL ) { + ap = &user_entry.e_attrs; + } + + for ( ; *ap; ap = &(*ap)->a_next ); + + *ap = a_entryCSN; + } + } +#endif /* BACKSQL_SYNCPROV */ + + if ( test_filter( op, e, op->ors_filter ) == LDAP_COMPARE_TRUE ) + { +#ifndef BACKSQL_ARBITRARY_KEY + /* If paged results are in effect, see if the page limit was exceeded */ + if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) { + if ( rs->sr_nentries >= ((PagedResultsState *)op->o_pagedresults_state)->ps_size ) + { + e = NULL; + send_paged_response( op, rs, &lastid ); + goto done; + } + lastid = SQL_TO_PAGECOOKIE( eid->eid_id, eid->eid_oc_id ); + } +#endif /* ! BACKSQL_ARBITRARY_KEY */ + rs->sr_attrs = op->ors_attrs; + rs->sr_operational_attrs = NULL; + rs->sr_entry = e; + e->e_private = (void *)eid; + rs->sr_flags = ( e == &user_entry ) ? REP_ENTRY_MODIFIABLE : 0; + /* FIXME: need the whole entry (ITS#3480) */ + rs->sr_err = send_search_entry( op, rs ); + e->e_private = NULL; + rs->sr_entry = NULL; + rs->sr_attrs = NULL; + rs->sr_operational_attrs = NULL; + + switch ( rs->sr_err ) { + case LDAP_UNAVAILABLE: + /* + * FIXME: send_search_entry failed; + * better stop + */ + Debug( LDAP_DEBUG_TRACE, "backsql_search(): " + "connection lost\n", 0, 0, 0 ); + goto end_of_search; + + case LDAP_SIZELIMIT_EXCEEDED: + case LDAP_BUSY: + goto send_results; + } + } + +next_entry:; + if ( e == &user_entry ) { + backsql_entry_clean( op, &user_entry ); + } + +next_entry2:; + } + +end_of_search:; + if ( rs->sr_nentries > 0 ) { + rs->sr_ref = rs->sr_v2ref; + rs->sr_err = (rs->sr_v2ref == NULL) ? LDAP_SUCCESS + : LDAP_REFERRAL; + + } else { + rs->sr_err = bsi.bsi_status; + } + +send_results:; + if ( rs->sr_err != SLAPD_ABANDON ) { +#ifndef BACKSQL_ARBITRARY_KEY + if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) { + send_paged_response( op, rs, NULL ); + } else +#endif /* ! BACKSQL_ARBITRARY_KEY */ + { + send_ldap_result( op, rs ); + } + } + + /* cleanup in case of abandon */ + for ( ; eid != NULL; + eid = backsql_free_entryID( + eid, eid == &bsi.bsi_base_id ? 0 : 1, op->o_tmpmemctx ) ) + ; + + backsql_entry_clean( op, &base_entry ); + + /* in case we got here accidentally */ + backsql_entry_clean( op, &user_entry ); + + if ( rs->sr_v2ref ) { + ber_bvarray_free( rs->sr_v2ref ); + rs->sr_v2ref = NULL; + } + +#ifdef BACKSQL_SYNCPROV + if ( op->o_sync ) { + Operation op2 = *op; + SlapReply rs2 = { REP_RESULT }; + Entry *e = entry_alloc(); + slap_callback cb = { 0 }; + + op2.o_tag = LDAP_REQ_ADD; + op2.o_bd = select_backend( &op->o_bd->be_nsuffix[0], 0 ); + op2.ora_e = e; + op2.o_callback = &cb; + + ber_dupbv( &e->e_name, op->o_bd->be_suffix ); + ber_dupbv( &e->e_nname, op->o_bd->be_nsuffix ); + + cb.sc_response = slap_null_cb; + + op2.o_bd->be_add( &op2, &rs2 ); + + if ( op2.ora_e == e ) + entry_free( e ); + } +#endif /* BACKSQL_SYNCPROV */ + +done:; + (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx ); + + if ( bsi.bsi_attrs != NULL ) { + op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &nbase ) + && nbase.bv_val != op->o_req_ndn.bv_val ) + { + ch_free( nbase.bv_val ); + } + + /* restore scope ... FIXME: this should be done before ANY + * frontend call that uses op */ + if ( op->ors_scope == BACKSQL_SCOPE_BASE_LIKE ) { + op->ors_scope = LDAP_SCOPE_BASE; + } + + Debug( LDAP_DEBUG_TRACE, "<==backsql_search()\n", 0, 0, 0 ); + + return rs->sr_err; +} + +/* return LDAP_SUCCESS IFF we can retrieve the specified entry. + */ +int +backsql_entry_get( + Operation *op, + struct berval *ndn, + ObjectClass *oc, + AttributeDescription *at, + int rw, + Entry **ent ) +{ + backsql_srch_info bsi = { 0 }; + SQLHDBC dbh = SQL_NULL_HDBC; + int rc; + SlapReply rs = { 0 }; + AttributeName anlist[ 2 ]; + + *ent = NULL; + + rc = backsql_get_db_conn( op, &dbh ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( at ) { + anlist[ 0 ].an_name = at->ad_cname; + anlist[ 0 ].an_desc = at; + BER_BVZERO( &anlist[ 1 ].an_name ); + } + + bsi.bsi_e = entry_alloc(); + rc = backsql_init_search( &bsi, + ndn, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, + dbh, op, &rs, at ? anlist : NULL, + BACKSQL_ISF_GET_ENTRY ); + + if ( !BER_BVISNULL( &bsi.bsi_base_id.eid_ndn ) ) { + (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx ); + } + + if ( rc == LDAP_SUCCESS ) { + +#if 0 /* not supported at present */ + /* find attribute values */ + if ( is_entry_alias( bsi.bsi_e ) ) { + Debug( LDAP_DEBUG_ACL, + "<= backsql_entry_get: entry is an alias\n", + 0, 0, 0 ); + rc = LDAP_ALIAS_PROBLEM; + goto return_results; + } +#endif + + if ( is_entry_referral( bsi.bsi_e ) ) { + Debug( LDAP_DEBUG_ACL, + "<= backsql_entry_get: entry is a referral\n", + 0, 0, 0 ); + rc = LDAP_REFERRAL; + goto return_results; + } + + if ( oc && !is_entry_objectclass( bsi.bsi_e, oc, 0 ) ) { + Debug( LDAP_DEBUG_ACL, + "<= backsql_entry_get: " + "failed to find objectClass\n", + 0, 0, 0 ); + rc = LDAP_NO_SUCH_ATTRIBUTE; + goto return_results; + } + + *ent = bsi.bsi_e; + } + +return_results:; + if ( bsi.bsi_attrs != NULL ) { + op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx ); + } + + if ( rc != LDAP_SUCCESS ) { + if ( bsi.bsi_e ) { + entry_free( bsi.bsi_e ); + } + } + + return rc; +} + +void +backsql_entry_clean( + Operation *op, + Entry *e ) +{ + void *ctx; + + ctx = ldap_pvt_thread_pool_context(); + + if ( ctx == NULL || ctx != op->o_tmpmemctx ) { + if ( !BER_BVISNULL( &e->e_name ) ) { + op->o_tmpfree( e->e_name.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &e->e_name ); + } + + if ( !BER_BVISNULL( &e->e_nname ) ) { + op->o_tmpfree( e->e_nname.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &e->e_nname ); + } + } + + entry_clean( e ); +} + +int +backsql_entry_release( + Operation *op, + Entry *e, + int rw ) +{ + backsql_entry_clean( op, e ); + + entry_free( e ); + + return 0; +} + +#ifndef BACKSQL_ARBITRARY_KEY +/* This function is copied verbatim from back-bdb/search.c */ +static int +parse_paged_cookie( Operation *op, SlapReply *rs ) +{ + int rc = LDAP_SUCCESS; + PagedResultsState *ps = op->o_pagedresults_state; + + /* this function must be invoked only if the pagedResults + * control has been detected, parsed and partially checked + * by the frontend */ + assert( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ); + + /* cookie decoding/checks deferred to backend... */ + if ( ps->ps_cookieval.bv_len ) { + PagedResultsCookie reqcookie; + if( ps->ps_cookieval.bv_len != sizeof( reqcookie ) ) { + /* bad cookie */ + rs->sr_text = "paged results cookie is invalid"; + rc = LDAP_PROTOCOL_ERROR; + goto done; + } + + AC_MEMCPY( &reqcookie, ps->ps_cookieval.bv_val, sizeof( reqcookie )); + + if ( reqcookie > ps->ps_cookie ) { + /* bad cookie */ + rs->sr_text = "paged results cookie is invalid"; + rc = LDAP_PROTOCOL_ERROR; + goto done; + + } else if ( reqcookie < ps->ps_cookie ) { + rs->sr_text = "paged results cookie is invalid or old"; + rc = LDAP_UNWILLING_TO_PERFORM; + goto done; + } + + } else { + /* Initial request. Initialize state. */ + ps->ps_cookie = 0; + ps->ps_count = 0; + } + +done:; + + return rc; +} + +/* This function is copied nearly verbatim from back-bdb/search.c */ +static void +send_paged_response( + Operation *op, + SlapReply *rs, + ID *lastid ) +{ + LDAPControl ctrl, *ctrls[2]; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + PagedResultsCookie respcookie; + struct berval cookie; + + Debug(LDAP_DEBUG_ARGS, + "send_paged_response: lastid=0x%08lx nentries=%d\n", + lastid ? *lastid : 0, rs->sr_nentries, NULL ); + + BER_BVZERO( &ctrl.ldctl_value ); + ctrls[0] = &ctrl; + ctrls[1] = NULL; + + ber_init2( ber, NULL, LBER_USE_DER ); + + if ( lastid ) { + respcookie = ( PagedResultsCookie )(*lastid); + cookie.bv_len = sizeof( respcookie ); + cookie.bv_val = (char *)&respcookie; + + } else { + respcookie = ( PagedResultsCookie )0; + BER_BVSTR( &cookie, "" ); + } + + op->o_conn->c_pagedresults_state.ps_cookie = respcookie; + op->o_conn->c_pagedresults_state.ps_count = + ((PagedResultsState *)op->o_pagedresults_state)->ps_count + + rs->sr_nentries; + + /* return size of 0 -- no estimate */ + ber_printf( ber, "{iO}", 0, &cookie ); + + if ( ber_flatten2( ber, &ctrls[0]->ldctl_value, 0 ) == -1 ) { + goto done; + } + + ctrls[0]->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS; + ctrls[0]->ldctl_iscritical = 0; + + rs->sr_ctrls = ctrls; + rs->sr_err = LDAP_SUCCESS; + send_ldap_result( op, rs ); + rs->sr_ctrls = NULL; + +done: + (void) ber_free_buf( ber ); +} +#endif /* ! BACKSQL_ARBITRARY_KEY */ diff --git a/servers/slapd/back-sql/sql-wrap.c b/servers/slapd/back-sql/sql-wrap.c new file mode 100644 index 0000000..b2a4aa8 --- /dev/null +++ b/servers/slapd/back-sql/sql-wrap.c @@ -0,0 +1,538 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * Portions Copyright 2004 Mark Adamson. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati and Mark Adamson. + */ + +#include "portable.h" + +#include <stdio.h> +#include "ac/string.h" +#include <sys/types.h> + +#include "slap.h" +#include "proto-sql.h" + +#define MAX_ATTR_LEN 16384 + +void +backsql_PrintErrors( SQLHENV henv, SQLHDBC hdbc, SQLHSTMT sth, int rc ) +{ + SQLCHAR msg[SQL_MAX_MESSAGE_LENGTH]; /* msg. buffer */ + SQLCHAR state[SQL_SQLSTATE_SIZE]; /* statement buf. */ + SDWORD iSqlCode; /* return code */ + SWORD len = SQL_MAX_MESSAGE_LENGTH - 1; /* return length */ + + Debug( LDAP_DEBUG_TRACE, "Return code: %d\n", rc, 0, 0 ); + + for ( ; rc = SQLError( henv, hdbc, sth, state, &iSqlCode, msg, + SQL_MAX_MESSAGE_LENGTH - 1, &len ), BACKSQL_SUCCESS( rc ); ) + { + Debug( LDAP_DEBUG_TRACE, + " nativeErrCode=%d SQLengineState=%s msg=\"%s\"\n", + (int)iSqlCode, state, msg ); + } +} + +RETCODE +backsql_Prepare( SQLHDBC dbh, SQLHSTMT *sth, const char *query, int timeout ) +{ + RETCODE rc; + + rc = SQLAllocStmt( dbh, sth ); + if ( rc != SQL_SUCCESS ) { + return rc; + } + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "==>backsql_Prepare()\n", 0, 0, 0 ); +#endif /* BACKSQL_TRACE */ + +#ifdef BACKSQL_MSSQL_WORKAROUND + { + char drv_name[ 30 ]; + SWORD len; + + SQLGetInfo( dbh, SQL_DRIVER_NAME, drv_name, sizeof( drv_name ), &len ); + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_Prepare(): driver name=\"%s\"\n", + drv_name, 0, 0 ); +#endif /* BACKSQL_TRACE */ + + ldap_pvt_str2upper( drv_name ); + if ( !strncmp( drv_name, "SQLSRV32.DLL", STRLENOF( "SQLSRV32.DLL" ) ) ) { + /* + * stupid default result set in MS SQL Server + * does not support multiple active statements + * on the same connection -- so we are trying + * to make it not to use default result set... + */ + Debug( LDAP_DEBUG_TRACE, "_SQLprepare(): " + "enabling MS SQL Server default result " + "set workaround\n", 0, 0, 0 ); + rc = SQLSetStmtOption( *sth, SQL_CONCURRENCY, + SQL_CONCUR_ROWVER ); + if ( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO ) { + Debug( LDAP_DEBUG_TRACE, "backsql_Prepare(): " + "SQLSetStmtOption(SQL_CONCURRENCY," + "SQL_CONCUR_ROWVER) failed:\n", + 0, 0, 0 ); + backsql_PrintErrors( SQL_NULL_HENV, dbh, *sth, rc ); + SQLFreeStmt( *sth, SQL_DROP ); + return rc; + } + } + } +#endif /* BACKSQL_MSSQL_WORKAROUND */ + + if ( timeout > 0 ) { + Debug( LDAP_DEBUG_TRACE, "_SQLprepare(): " + "setting query timeout to %d sec.\n", + timeout, 0, 0 ); + rc = SQLSetStmtOption( *sth, SQL_QUERY_TIMEOUT, timeout ); + if ( rc != SQL_SUCCESS ) { + backsql_PrintErrors( SQL_NULL_HENV, dbh, *sth, rc ); + SQLFreeStmt( *sth, SQL_DROP ); + return rc; + } + } + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "<==backsql_Prepare() calling SQLPrepare()\n", + 0, 0, 0 ); +#endif /* BACKSQL_TRACE */ + + return SQLPrepare( *sth, (SQLCHAR *)query, SQL_NTS ); +} + +RETCODE +backsql_BindRowAsStrings_x( SQLHSTMT sth, BACKSQL_ROW_NTS *row, void *ctx ) +{ + RETCODE rc; + + if ( row == NULL ) { + return SQL_ERROR; + } + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "==> backsql_BindRowAsStrings()\n", 0, 0, 0 ); +#endif /* BACKSQL_TRACE */ + + rc = SQLNumResultCols( sth, &row->ncols ); + if ( rc != SQL_SUCCESS ) { +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings(): " + "SQLNumResultCols() failed:\n", 0, 0, 0 ); +#endif /* BACKSQL_TRACE */ + + backsql_PrintErrors( SQL_NULL_HENV, SQL_NULL_HDBC, sth, rc ); + + } else { + SQLCHAR colname[ 64 ]; + SQLSMALLINT name_len, col_type, col_scale, col_null; + SQLLEN col_prec; + int i; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: " + "ncols=%d\n", (int)row->ncols, 0, 0 ); +#endif /* BACKSQL_TRACE */ + + row->col_names = (BerVarray)ber_memcalloc_x( row->ncols + 1, + sizeof( struct berval ), ctx ); + if ( row->col_names == NULL ) { + goto nomem; + } + + row->col_prec = (UDWORD *)ber_memcalloc_x( row->ncols, + sizeof( UDWORD ), ctx ); + if ( row->col_prec == NULL ) { + goto nomem; + } + + row->col_type = (SQLSMALLINT *)ber_memcalloc_x( row->ncols, + sizeof( SQLSMALLINT ), ctx ); + if ( row->col_type == NULL ) { + goto nomem; + } + + row->cols = (char **)ber_memcalloc_x( row->ncols + 1, + sizeof( char * ), ctx ); + if ( row->cols == NULL ) { + goto nomem; + } + + row->value_len = (SQLLEN *)ber_memcalloc_x( row->ncols, + sizeof( SQLLEN ), ctx ); + if ( row->value_len == NULL ) { + goto nomem; + } + + if ( 0 ) { +nomem: + ber_memfree_x( row->col_names, ctx ); + row->col_names = NULL; + ber_memfree_x( row->col_prec, ctx ); + row->col_prec = NULL; + ber_memfree_x( row->col_type, ctx ); + row->col_type = NULL; + ber_memfree_x( row->cols, ctx ); + row->cols = NULL; + ber_memfree_x( row->value_len, ctx ); + row->value_len = NULL; + + Debug( LDAP_DEBUG_ANY, "backsql_BindRowAsStrings: " + "out of memory\n", 0, 0, 0 ); + + return LDAP_NO_MEMORY; + } + + for ( i = 0; i < row->ncols; i++ ) { + SQLSMALLINT TargetType; + + rc = SQLDescribeCol( sth, (SQLSMALLINT)(i + 1), &colname[ 0 ], + (SQLUINTEGER)( sizeof( colname ) - 1 ), + &name_len, &col_type, + &col_prec, &col_scale, &col_null ); + /* FIXME: test rc? */ + + ber_str2bv_x( (char *)colname, 0, 1, + &row->col_names[ i ], ctx ); +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: " + "col_name=%s, col_prec[%d]=%d\n", + colname, (int)(i + 1), (int)col_prec ); +#endif /* BACKSQL_TRACE */ + if ( col_type != SQL_CHAR && col_type != SQL_VARCHAR ) + { + col_prec = MAX_ATTR_LEN; + } + + row->cols[ i ] = (char *)ber_memcalloc_x( col_prec + 1, + sizeof( char ), ctx ); + row->col_prec[ i ] = col_prec; + row->col_type[ i ] = col_type; + + /* + * ITS#3386, ITS#3113 - 20070308 + * Note: there are many differences between various DPMS and ODBC + * Systems; some support SQL_C_BLOB, SQL_C_BLOB_LOCATOR. YMMV: + * This has only been tested on Linux/MySQL/UnixODBC + * For BINARY-type Fields (BLOB, etc), read the data as BINARY + */ + if ( BACKSQL_IS_BINARY( col_type ) ) { +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: " + "col_name=%s, col_type[%d]=%d: reading binary data\n", + colname, (int)(i + 1), (int)col_type); +#endif /* BACKSQL_TRACE */ + TargetType = SQL_C_BINARY; + + } else { + /* Otherwise read it as Character data */ +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: " + "col_name=%s, col_type[%d]=%d: reading character data\n", + colname, (int)(i + 1), (int)col_type); +#endif /* BACKSQL_TRACE */ + TargetType = SQL_C_CHAR; + } + + rc = SQLBindCol( sth, (SQLUSMALLINT)(i + 1), + TargetType, + (SQLPOINTER)row->cols[ i ], + col_prec + 1, + &row->value_len[ i ] ); + + /* FIXME: test rc? */ + } + + BER_BVZERO( &row->col_names[ i ] ); + row->cols[ i ] = NULL; + } + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "<== backsql_BindRowAsStrings()\n", 0, 0, 0 ); +#endif /* BACKSQL_TRACE */ + + return rc; +} + +RETCODE +backsql_BindRowAsStrings( SQLHSTMT sth, BACKSQL_ROW_NTS *row ) +{ + return backsql_BindRowAsStrings_x( sth, row, NULL ); +} + +RETCODE +backsql_FreeRow_x( BACKSQL_ROW_NTS *row, void *ctx ) +{ + if ( row->cols == NULL ) { + return SQL_ERROR; + } + + ber_bvarray_free_x( row->col_names, ctx ); + ber_memfree_x( row->col_prec, ctx ); + ber_memfree_x( row->col_type, ctx ); + ber_memvfree_x( (void **)row->cols, ctx ); + ber_memfree_x( row->value_len, ctx ); + + return SQL_SUCCESS; +} + + +RETCODE +backsql_FreeRow( BACKSQL_ROW_NTS *row ) +{ + return backsql_FreeRow_x( row, NULL ); +} + +static void +backsql_close_db_handle( SQLHDBC dbh ) +{ + if ( dbh == SQL_NULL_HDBC ) { + return; + } + + Debug( LDAP_DEBUG_TRACE, "==>backsql_close_db_handle(%p)\n", + (void *)dbh, 0, 0 ); + + /* + * Default transact is SQL_ROLLBACK; commit is required only + * by write operations, and it is explicitly performed after + * each atomic operation succeeds. + */ + + /* TimesTen */ + SQLTransact( SQL_NULL_HENV, dbh, SQL_ROLLBACK ); + SQLDisconnect( dbh ); + SQLFreeConnect( dbh ); + + Debug( LDAP_DEBUG_TRACE, "<==backsql_close_db_handle(%p)\n", + (void *)dbh, 0, 0 ); +} + +int +backsql_conn_destroy( + backsql_info *bi ) +{ + return 0; +} + +int +backsql_init_db_env( backsql_info *bi ) +{ + RETCODE rc; + int ret = SQL_SUCCESS; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_init_db_env()\n", 0, 0, 0 ); + + rc = SQLAllocEnv( &bi->sql_db_env ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "init_db_env: SQLAllocEnv failed:\n", + 0, 0, 0 ); + backsql_PrintErrors( SQL_NULL_HENV, SQL_NULL_HDBC, + SQL_NULL_HENV, rc ); + ret = SQL_ERROR; + } + + Debug( LDAP_DEBUG_TRACE, "<==backsql_init_db_env()=%d\n", ret, 0, 0 ); + + return ret; +} + +int +backsql_free_db_env( backsql_info *bi ) +{ + Debug( LDAP_DEBUG_TRACE, "==>backsql_free_db_env()\n", 0, 0, 0 ); + + (void)SQLFreeEnv( bi->sql_db_env ); + bi->sql_db_env = SQL_NULL_HENV; + + /* + * stop, if frontend waits for all threads to shutdown + * before calling this -- then what are we going to delete?? + * everything is already deleted... + */ + Debug( LDAP_DEBUG_TRACE, "<==backsql_free_db_env()\n", 0, 0, 0 ); + + return SQL_SUCCESS; +} + +static int +backsql_open_db_handle( + backsql_info *bi, + SQLHDBC *dbhp ) +{ + /* TimesTen */ + char DBMSName[ 32 ]; + int rc; + + assert( dbhp != NULL ); + *dbhp = SQL_NULL_HDBC; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_open_db_handle()\n", + 0, 0, 0 ); + + rc = SQLAllocConnect( bi->sql_db_env, dbhp ); + if ( !BACKSQL_SUCCESS( rc ) ) { + Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): " + "SQLAllocConnect() failed:\n", + 0, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, SQL_NULL_HDBC, + SQL_NULL_HENV, rc ); + return LDAP_UNAVAILABLE; + } + + rc = SQLConnect( *dbhp, + (SQLCHAR*)bi->sql_dbname, SQL_NTS, + (SQLCHAR*)bi->sql_dbuser, SQL_NTS, + (SQLCHAR*)bi->sql_dbpasswd, SQL_NTS ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): " + "SQLConnect() to database \"%s\" %s.\n", + bi->sql_dbname, + rc == SQL_SUCCESS_WITH_INFO ? + "succeeded with info" : "failed", + 0 ); + backsql_PrintErrors( bi->sql_db_env, *dbhp, SQL_NULL_HENV, rc ); + if ( rc != SQL_SUCCESS_WITH_INFO ) { + SQLFreeConnect( *dbhp ); + return LDAP_UNAVAILABLE; + } + } + + /* + * TimesTen : Turn off autocommit. We must explicitly + * commit any transactions. + */ + SQLSetConnectOption( *dbhp, SQL_AUTOCOMMIT, + BACKSQL_AUTOCOMMIT_ON( bi ) ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF ); + + /* + * See if this connection is to TimesTen. If it is, + * remember that fact for later use. + */ + /* Assume until proven otherwise */ + bi->sql_flags &= ~BSQLF_USE_REVERSE_DN; + DBMSName[ 0 ] = '\0'; + rc = SQLGetInfo( *dbhp, SQL_DBMS_NAME, (PTR)&DBMSName, + sizeof( DBMSName ), NULL ); + if ( rc == SQL_SUCCESS ) { + if ( strcmp( DBMSName, "TimesTen" ) == 0 || + strcmp( DBMSName, "Front-Tier" ) == 0 ) + { + Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): " + "TimesTen database!\n", + 0, 0, 0 ); + bi->sql_flags |= BSQLF_USE_REVERSE_DN; + } + + } else { + Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): " + "SQLGetInfo() failed.\n", + 0, 0, 0 ); + backsql_PrintErrors( bi->sql_db_env, *dbhp, SQL_NULL_HENV, rc ); + SQLDisconnect( *dbhp ); + SQLFreeConnect( *dbhp ); + return LDAP_UNAVAILABLE; + } + /* end TimesTen */ + + Debug( LDAP_DEBUG_TRACE, "<==backsql_open_db_handle()\n", + 0, 0, 0 ); + + return LDAP_SUCCESS; +} + +static void *backsql_db_conn_dummy; + +static void +backsql_db_conn_keyfree( + void *key, + void *data ) +{ + (void)backsql_close_db_handle( (SQLHDBC)data ); +} + +int +backsql_free_db_conn( Operation *op, SQLHDBC dbh ) +{ + Debug( LDAP_DEBUG_TRACE, "==>backsql_free_db_conn()\n", 0, 0, 0 ); + + (void)backsql_close_db_handle( dbh ); + ldap_pvt_thread_pool_setkey( op->o_threadctx, + &backsql_db_conn_dummy, (void *)SQL_NULL_HDBC, + backsql_db_conn_keyfree, NULL, NULL ); + + Debug( LDAP_DEBUG_TRACE, "<==backsql_free_db_conn()\n", 0, 0, 0 ); + + return LDAP_SUCCESS; +} + +int +backsql_get_db_conn( Operation *op, SQLHDBC *dbhp ) +{ + backsql_info *bi = (backsql_info *)op->o_bd->be_private; + int rc = LDAP_SUCCESS; + SQLHDBC dbh = SQL_NULL_HDBC; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_get_db_conn()\n", 0, 0, 0 ); + + assert( dbhp != NULL ); + *dbhp = SQL_NULL_HDBC; + + if ( op->o_threadctx ) { + void *data = NULL; + + ldap_pvt_thread_pool_getkey( op->o_threadctx, + &backsql_db_conn_dummy, &data, NULL ); + dbh = (SQLHDBC)data; + + } else { + dbh = bi->sql_dbh; + } + + if ( dbh == SQL_NULL_HDBC ) { + rc = backsql_open_db_handle( bi, &dbh ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( op->o_threadctx ) { + void *data = (void *)dbh; + + ldap_pvt_thread_pool_setkey( op->o_threadctx, + &backsql_db_conn_dummy, data, + backsql_db_conn_keyfree, NULL, NULL ); + + } else { + bi->sql_dbh = dbh; + } + } + + *dbhp = dbh; + + Debug( LDAP_DEBUG_TRACE, "<==backsql_get_db_conn()\n", 0, 0, 0 ); + + return LDAP_SUCCESS; +} + diff --git a/servers/slapd/back-sql/util.c b/servers/slapd/back-sql/util.c new file mode 100644 index 0000000..8b9822d --- /dev/null +++ b/servers/slapd/back-sql/util.c @@ -0,0 +1,574 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> +#include "ac/string.h" +#include "ac/ctype.h" +#include "ac/stdarg.h" + +#include "slap.h" +#include "proto-sql.h" +#include "lutil.h" + +#define BACKSQL_MAX(a,b) ((a)>(b)?(a):(b)) +#define BACKSQL_MIN(a,b) ((a)<(b)?(a):(b)) + +#define BACKSQL_STR_GROW 256 + +const char backsql_def_oc_query[] = + "SELECT id,name,keytbl,keycol,create_proc,delete_proc,expect_return " + "FROM ldap_oc_mappings"; +const char backsql_def_needs_select_oc_query[] = + "SELECT id,name,keytbl,keycol,create_proc,create_keyval,delete_proc," + "expect_return FROM ldap_oc_mappings"; +const char backsql_def_at_query[] = + "SELECT name,sel_expr,from_tbls,join_where,add_proc,delete_proc," + "param_order,expect_return,sel_expr_u FROM ldap_attr_mappings " + "WHERE oc_map_id=?"; +const char backsql_def_delentry_stmt[] = "DELETE FROM ldap_entries WHERE id=?"; +const char backsql_def_renentry_stmt[] = + "UPDATE ldap_entries SET dn=?,parent=?,keyval=? WHERE id=?"; +const char backsql_def_insentry_stmt[] = + "INSERT INTO ldap_entries (dn,oc_map_id,parent,keyval) " + "VALUES (?,?,?,?)"; +const char backsql_def_delobjclasses_stmt[] = "DELETE FROM ldap_entry_objclasses " + "WHERE entry_id=?"; +const char backsql_def_subtree_cond[] = "ldap_entries.dn LIKE CONCAT('%',?)"; +const char backsql_def_upper_subtree_cond[] = "(ldap_entries.dn) LIKE CONCAT('%',?)"; +const char backsql_id_query[] = "SELECT id,keyval,oc_map_id,dn FROM ldap_entries WHERE "; +/* better ?||? or cast(?||? as varchar) */ +const char backsql_def_concat_func[] = "CONCAT(?,?)"; + +/* TimesTen */ +const char backsql_check_dn_ru_query[] = "SELECT dn_ru FROM ldap_entries"; + +struct berbuf * +backsql_strcat_x( struct berbuf *dest, void *memctx, ... ) +{ + va_list strs; + ber_len_t cdlen, cslen, grow; + char *cstr; + + assert( dest != NULL ); + assert( dest->bb_val.bv_val == NULL + || dest->bb_val.bv_len == strlen( dest->bb_val.bv_val ) ); + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "==>backsql_strcat()\n", 0, 0, 0 ); +#endif /* BACKSQL_TRACE */ + + va_start( strs, memctx ); + if ( dest->bb_val.bv_val == NULL || dest->bb_len == 0 ) { + dest->bb_val.bv_val = (char *)ber_memalloc_x( BACKSQL_STR_GROW * sizeof( char ), memctx ); + dest->bb_val.bv_len = 0; + dest->bb_len = BACKSQL_STR_GROW; + } + cdlen = dest->bb_val.bv_len; + while ( ( cstr = va_arg( strs, char * ) ) != NULL ) { + cslen = strlen( cstr ); + grow = BACKSQL_MAX( BACKSQL_STR_GROW, cslen ); + if ( dest->bb_len - cdlen <= cslen ) { + char *tmp_dest; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_strcat(): " + "buflen=%d, cdlen=%d, cslen=%d " + "-- reallocating dest\n", + dest->bb_len, cdlen + 1, cslen ); +#endif /* BACKSQL_TRACE */ + + tmp_dest = (char *)ber_memrealloc_x( dest->bb_val.bv_val, + dest->bb_len + grow * sizeof( char ), memctx ); + if ( tmp_dest == NULL ) { + Debug( LDAP_DEBUG_ANY, "backsql_strcat(): " + "could not reallocate string buffer.\n", + 0, 0, 0 ); + va_end( strs ); + return NULL; + } + dest->bb_val.bv_val = tmp_dest; + dest->bb_len += grow; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_strcat(): " + "new buflen=%d, dest=%p\n", + dest->bb_len, dest, 0 ); +#endif /* BACKSQL_TRACE */ + } + AC_MEMCPY( dest->bb_val.bv_val + cdlen, cstr, cslen + 1 ); + cdlen += cslen; + } + va_end( strs ); + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "<==backsql_strcat() (dest=\"%s\")\n", + dest->bb_val.bv_val, 0, 0 ); +#endif /* BACKSQL_TRACE */ + + dest->bb_val.bv_len = cdlen; + + return dest; +} + +struct berbuf * +backsql_strfcat_x( struct berbuf *dest, void *memctx, const char *fmt, ... ) +{ + va_list strs; + ber_len_t cdlen; + + assert( dest != NULL ); + assert( fmt != NULL ); + assert( dest->bb_len == 0 || dest->bb_len > dest->bb_val.bv_len ); + assert( dest->bb_val.bv_val == NULL + || dest->bb_val.bv_len == strlen( dest->bb_val.bv_val ) ); + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "==>backsql_strfcat()\n", 0, 0, 0 ); +#endif /* BACKSQL_TRACE */ + + va_start( strs, fmt ); + if ( dest->bb_val.bv_val == NULL || dest->bb_len == 0 ) { + dest->bb_val.bv_val = (char *)ber_memalloc_x( BACKSQL_STR_GROW * sizeof( char ), memctx ); + dest->bb_val.bv_len = 0; + dest->bb_len = BACKSQL_STR_GROW; + } + + cdlen = dest->bb_val.bv_len; + for ( ; fmt[0]; fmt++ ) { + ber_len_t cslen, grow; + char *cstr, cc[ 2 ] = { '\0', '\0' }; + struct berval *cbv; + + switch ( fmt[ 0 ] ) { + + /* berval */ + case 'b': + cbv = va_arg( strs, struct berval * ); + cstr = cbv->bv_val; + cslen = cbv->bv_len; + break; + + /* length + string */ + case 'l': + cslen = va_arg( strs, ber_len_t ); + cstr = va_arg( strs, char * ); + break; + + /* string */ + case 's': + cstr = va_arg( strs, char * ); + cslen = strlen( cstr ); + break; + + /* char */ + case 'c': + /* + * `char' is promoted to `int' when passed through `...' + */ + cc[0] = va_arg( strs, int ); + cstr = cc; + cslen = 1; + break; + + default: + assert( 0 ); + } + + grow = BACKSQL_MAX( BACKSQL_STR_GROW, cslen ); + if ( dest->bb_len - cdlen <= cslen ) { + char *tmp_dest; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_strfcat(): " + "buflen=%d, cdlen=%d, cslen=%d " + "-- reallocating dest\n", + dest->bb_len, cdlen + 1, cslen ); +#endif /* BACKSQL_TRACE */ + + tmp_dest = (char *)ber_memrealloc_x( dest->bb_val.bv_val, + ( dest->bb_len ) + grow * sizeof( char ), memctx ); + if ( tmp_dest == NULL ) { + Debug( LDAP_DEBUG_ANY, "backsql_strfcat(): " + "could not reallocate string buffer.\n", + 0, 0, 0 ); + va_end( strs ); + return NULL; + } + dest->bb_val.bv_val = tmp_dest; + dest->bb_len += grow * sizeof( char ); + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_strfcat(): " + "new buflen=%d, dest=%p\n", dest->bb_len, dest, 0 ); +#endif /* BACKSQL_TRACE */ + } + + assert( cstr != NULL ); + + AC_MEMCPY( dest->bb_val.bv_val + cdlen, cstr, cslen + 1 ); + cdlen += cslen; + } + + va_end( strs ); + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "<==backsql_strfcat() (dest=\"%s\")\n", + dest->bb_val.bv_val, 0, 0 ); +#endif /* BACKSQL_TRACE */ + + dest->bb_val.bv_len = cdlen; + + return dest; +} + +int +backsql_entry_addattr( + Entry *e, + AttributeDescription *ad, + struct berval *val, + void *memctx ) +{ + int rc; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_entry_addattr(\"%s\"): %s=%s\n", + e->e_name.bv_val, ad->ad_cname.bv_val, val->bv_val ); +#endif /* BACKSQL_TRACE */ + + rc = attr_merge_normalize_one( e, ad, val, memctx ); + + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_entry_addattr(\"%s\"): " + "failed to merge value \"%s\" for attribute \"%s\"\n", + e->e_name.bv_val, val->bv_val, ad->ad_cname.bv_val ); + return rc; + } + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "<==backsql_entry_addattr(\"%s\")\n", + e->e_name.bv_val, 0, 0 ); +#endif /* BACKSQL_TRACE */ + + return LDAP_SUCCESS; +} + +static char * +backsql_get_table_spec( backsql_info *bi, char **p ) +{ + char *s, *q; + struct berbuf res = BB_NULL; + + assert( p != NULL ); + assert( *p != NULL ); + + s = *p; + while ( **p && **p != ',' ) { + (*p)++; + } + + if ( **p ) { + *(*p)++ = '\0'; + } + +#define BACKSQL_NEXT_WORD { \ + while ( *s && isspace( (unsigned char)*s ) ) s++; \ + if ( !*s ) return res.bb_val.bv_val; \ + q = s; \ + while ( *q && !isspace( (unsigned char)*q ) ) q++; \ + if ( *q ) *q++='\0'; \ + } + + BACKSQL_NEXT_WORD; + /* table name */ + backsql_strcat_x( &res, NULL, s, NULL ); + s = q; + + BACKSQL_NEXT_WORD; + if ( strcasecmp( s, "AS" ) == 0 ) { + s = q; + BACKSQL_NEXT_WORD; + } + + /* oracle doesn't understand "AS" :( and other RDBMSes don't need it */ + backsql_strfcat_x( &res, NULL, "lbbsb", + STRLENOF( " " ), " ", + &bi->sql_aliasing, + &bi->sql_aliasing_quote, + s, + &bi->sql_aliasing_quote ); + + return res.bb_val.bv_val; +} + +int +backsql_merge_from_clause( + backsql_info *bi, + struct berbuf *dest_from, + struct berval *src_from ) +{ + char *s, *p, *srcc, *pos, e; + struct berbuf res = BB_NULL; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "==>backsql_merge_from_clause(): " + "dest_from=\"%s\",src_from=\"%s\"\n", + dest_from ? dest_from->bb_val.bv_val : "<NULL>", + src_from->bv_val, 0 ); +#endif /* BACKSQL_TRACE */ + + srcc = ch_strdup( src_from->bv_val ); + p = srcc; + + if ( dest_from != NULL ) { + res = *dest_from; + } + + while ( *p ) { + s = backsql_get_table_spec( bi, &p ); + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_merge_from_clause(): " + "p=\"%s\" s=\"%s\"\n", p, s, 0 ); +#endif /* BACKSQL_TRACE */ + + if ( BER_BVISNULL( &res.bb_val ) ) { + backsql_strcat_x( &res, NULL, s, NULL ); + + } else { + pos = strstr( res.bb_val.bv_val, s ); + if ( pos == NULL || ( ( e = pos[ strlen( s ) ] ) != '\0' && e != ',' ) ) { + backsql_strfcat_x( &res, NULL, "cs", ',', s ); + } + } + + if ( s ) { + ch_free( s ); + } + } + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "<==backsql_merge_from_clause()\n", 0, 0, 0 ); +#endif /* BACKSQL_TRACE */ + + free( srcc ); + *dest_from = res; + + return 1; +} + +/* + * splits a pattern in components separated by '?' + * (double ?? are turned into single ? and left in the string) + * expected contains the number of expected occurrences of '?' + * (a negative value means parse as many as possible) + */ + +int +backsql_split_pattern( + const char *_pattern, + BerVarray *split_pattern, + int expected ) +{ + char *pattern, *start, *end; + struct berval bv; + int rc = 0; + +#define SPLIT_CHAR '?' + + assert( _pattern != NULL ); + assert( split_pattern != NULL ); + + pattern = ch_strdup( _pattern ); + + start = pattern; + end = strchr( start, SPLIT_CHAR ); + for ( ; start; expected-- ) { + char *real_end = end; + ber_len_t real_len; + + if ( real_end == NULL ) { + real_end = start + strlen( start ); + + } else if ( real_end[ 1 ] == SPLIT_CHAR ) { + expected++; + AC_MEMCPY( real_end, real_end + 1, strlen( real_end ) ); + end = strchr( real_end + 1, SPLIT_CHAR ); + continue; + } + + real_len = real_end - start; + if ( real_len == 0 ) { + ber_str2bv( "", 0, 1, &bv ); + } else { + ber_str2bv( start, real_len, 1, &bv ); + } + + ber_bvarray_add( split_pattern, &bv ); + + if ( expected == 0 ) { + if ( end != NULL ) { + rc = -1; + goto done; + } + break; + } + + if ( end != NULL ) { + start = end + 1; + end = strchr( start, SPLIT_CHAR ); + } + } + +done:; + + ch_free( pattern ); + + return rc; +} + +int +backsql_prepare_pattern( + BerVarray split_pattern, + BerVarray values, + struct berval *res ) +{ + int i; + struct berbuf bb = BB_NULL; + + assert( res != NULL ); + + for ( i = 0; values[i].bv_val; i++ ) { + if ( split_pattern[i].bv_val == NULL ) { + ch_free( bb.bb_val.bv_val ); + return -1; + } + backsql_strfcat_x( &bb, NULL, "b", &split_pattern[ i ] ); + backsql_strfcat_x( &bb, NULL, "b", &values[ i ] ); + } + + if ( split_pattern[ i ].bv_val == NULL ) { + ch_free( bb.bb_val.bv_val ); + return -1; + } + + backsql_strfcat_x( &bb, NULL, "b", &split_pattern[ i ] ); + + *res = bb.bb_val; + + return 0; +} + +int +backsql_entryUUID( + backsql_info *bi, + backsql_entryID *id, + struct berval *entryUUID, + void *memctx ) +{ + char uuidbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE ]; + struct berval uuid; +#ifdef BACKSQL_ARBITRARY_KEY + int i; + ber_len_t l, lmax; +#endif /* BACKSQL_ARBITRARY_KEY */ + + /* entryUUID is generated as "%08x-%04x-%04x-0000-eaddrXXX" + * with eid_oc_id as %08x and hi and lo eid_id as %04x-%04x */ + assert( bi != NULL ); + assert( id != NULL ); + assert( entryUUID != NULL ); + +#ifdef BACKSQL_ARBITRARY_KEY + snprintf( uuidbuf, sizeof( uuidbuf ), + "%08x-0000-0000-0000-000000000000", + ( id->eid_oc_id & 0xFFFFFFFF ) ); + lmax = id->eid_keyval.bv_len < 12 ? id->eid_keyval.bv_len : 12; + for ( l = 0, i = 9; l < lmax; l++, i += 2 ) { + switch ( i ) { + case STRLENOF( "00000000-0000" ): + case STRLENOF( "00000000-0000-0000" ): + case STRLENOF( "00000000-0000-0000-0000" ): + uuidbuf[ i++ ] = '-'; + /* FALLTHRU */ + + default: + snprintf( &uuidbuf[ i ], 3, "%2x", id->eid_keyval.bv_val[ l ] ); + break; + } + } +#else /* ! BACKSQL_ARBITRARY_KEY */ + /* note: works only with 32 bit architectures... */ + snprintf( uuidbuf, sizeof( uuidbuf ), + "%08x-%04x-%04x-0000-000000000000", + ( (unsigned)id->eid_oc_id & 0xFFFFFFFF ), + ( ( (unsigned)id->eid_keyval & 0xFFFF0000 ) >> 020 /* 16 */ ), + ( (unsigned)id->eid_keyval & 0xFFFF ) ); +#endif /* ! BACKSQL_ARBITRARY_KEY */ + + uuid.bv_val = uuidbuf; + uuid.bv_len = strlen( uuidbuf ); + + ber_dupbv_x( entryUUID, &uuid, memctx ); + + return 0; +} + +int +backsql_entryUUID_decode( + struct berval *entryUUID, + unsigned long *oc_id, +#ifdef BACKSQL_ARBITRARY_KEY + struct berval *keyval +#else /* ! BACKSQL_ARBITRARY_KEY */ + unsigned long *keyval +#endif /* ! BACKSQL_ARBITRARY_KEY */ + ) +{ +#if 0 + fprintf( stderr, "==> backsql_entryUUID_decode()\n" ); +#endif + + *oc_id = ( entryUUID->bv_val[0] << 030 /* 24 */ ) + + ( entryUUID->bv_val[1] << 020 /* 16 */ ) + + ( entryUUID->bv_val[2] << 010 /* 8 */ ) + + entryUUID->bv_val[3]; + +#ifdef BACKSQL_ARBITRARY_KEY + /* FIXME */ +#else /* ! BACKSQL_ARBITRARY_KEY */ + *keyval = ( entryUUID->bv_val[4] << 030 /* 24 */ ) + + ( entryUUID->bv_val[5] << 020 /* 16 */ ) + + ( entryUUID->bv_val[6] << 010 /* 8 */ ) + + entryUUID->bv_val[7]; +#endif /* ! BACKSQL_ARBITRARY_KEY */ + +#if 0 + fprintf( stderr, "<== backsql_entryUUID_decode(): oc=%lu id=%lu\n", + *oc_id, *keyval ); +#endif + + return LDAP_SUCCESS; +} + diff --git a/servers/slapd/backend.c b/servers/slapd/backend.c new file mode 100644 index 0000000..d40b734 --- /dev/null +++ b/servers/slapd/backend.c @@ -0,0 +1,1983 @@ +/* backend.c - routines for dealing with back-end databases */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> +#include <sys/stat.h> + +#include "slap.h" +#include "config.h" +#include "lutil.h" +#include "lber_pvt.h" + +/* + * If a module is configured as dynamic, its header should not + * get included into slapd. While this is a general rule and does + * not have much of an effect in UNIX, this rule should be adhered + * to for Windows, where dynamic object code should not be implicitly + * imported into slapd without appropriate __declspec(dllimport) directives. + */ + +int nBackendInfo = 0; +slap_bi_head backendInfo = LDAP_STAILQ_HEAD_INITIALIZER(backendInfo); + +int nBackendDB = 0; +slap_be_head backendDB = LDAP_STAILQ_HEAD_INITIALIZER(backendDB); + +static int +backend_init_controls( BackendInfo *bi ) +{ + if ( bi->bi_controls ) { + int i; + + for ( i = 0; bi->bi_controls[ i ]; i++ ) { + int cid; + + if ( slap_find_control_id( bi->bi_controls[ i ], &cid ) + == LDAP_CONTROL_NOT_FOUND ) + { + if ( !( slapMode & SLAP_TOOL_MODE ) ) { + assert( 0 ); + } + + return -1; + } + + bi->bi_ctrls[ cid ] = 1; + } + } + + return 0; +} + +int backend_init(void) +{ + int rc = -1; + BackendInfo *bi; + + if((nBackendInfo != 0) || !LDAP_STAILQ_EMPTY(&backendInfo)) { + /* already initialized */ + Debug( LDAP_DEBUG_ANY, + "backend_init: already initialized\n", 0, 0, 0 ); + return -1; + } + + for( bi=slap_binfo; bi->bi_type != NULL; bi++,nBackendInfo++ ) { + assert( bi->bi_init != 0 ); + + rc = bi->bi_init( bi ); + + if(rc != 0) { + Debug( LDAP_DEBUG_ANY, + "backend_init: initialized for type \"%s\"\n", + bi->bi_type, 0, 0 ); + /* destroy those we've already inited */ + for( nBackendInfo--; + nBackendInfo >= 0 ; + nBackendInfo-- ) + { + if ( slap_binfo[nBackendInfo].bi_destroy ) { + slap_binfo[nBackendInfo].bi_destroy( + &slap_binfo[nBackendInfo] ); + } + } + return rc; + } + + LDAP_STAILQ_INSERT_TAIL(&backendInfo, bi, bi_next); + } + + if ( nBackendInfo > 0) { + return 0; + } + +#ifdef SLAPD_MODULES + return 0; +#else + + Debug( LDAP_DEBUG_ANY, + "backend_init: failed\n", + 0, 0, 0 ); + + return rc; +#endif /* SLAPD_MODULES */ +} + +int backend_add(BackendInfo *aBackendInfo) +{ + int rc = 0; + + if ( aBackendInfo->bi_init == NULL ) { + Debug( LDAP_DEBUG_ANY, "backend_add: " + "backend type \"%s\" does not have the (mandatory)init function\n", + aBackendInfo->bi_type, 0, 0 ); + return -1; + } + + rc = aBackendInfo->bi_init(aBackendInfo); + if ( rc != 0) { + Debug( LDAP_DEBUG_ANY, + "backend_add: initialization for type \"%s\" failed\n", + aBackendInfo->bi_type, 0, 0 ); + return rc; + } + + (void)backend_init_controls( aBackendInfo ); + + /* now add the backend type to the Backend Info List */ + LDAP_STAILQ_INSERT_TAIL( &backendInfo, aBackendInfo, bi_next ); + nBackendInfo++; + return 0; +} + +static int +backend_set_controls( BackendDB *be ) +{ + BackendInfo *bi = be->bd_info; + + /* back-relay takes care of itself; so may do other */ + if ( overlay_is_over( be ) ) { + bi = ((slap_overinfo *)be->bd_info->bi_private)->oi_orig; + } + + if ( bi->bi_controls ) { + if ( be->be_ctrls[ SLAP_MAX_CIDS ] == 0 ) { + AC_MEMCPY( be->be_ctrls, bi->bi_ctrls, + sizeof( be->be_ctrls ) ); + be->be_ctrls[ SLAP_MAX_CIDS ] = 1; + + } else { + int i; + + for ( i = 0; i < SLAP_MAX_CIDS; i++ ) { + if ( bi->bi_ctrls[ i ] ) { + be->be_ctrls[ i ] = bi->bi_ctrls[ i ]; + } + } + } + + } + + return 0; +} + +/* startup a specific backend database */ +int backend_startup_one(Backend *be, ConfigReply *cr) +{ + int rc = 0; + + assert( be != NULL ); + + be->be_pending_csn_list = (struct be_pcl *) + ch_calloc( 1, sizeof( struct be_pcl ) ); + + LDAP_TAILQ_INIT( be->be_pending_csn_list ); + + Debug( LDAP_DEBUG_TRACE, + "backend_startup_one: starting \"%s\"\n", + be->be_suffix ? be->be_suffix[0].bv_val : "(unknown)", + 0, 0 ); + + /* set database controls */ + (void)backend_set_controls( be ); + +#if 0 + if ( !BER_BVISEMPTY( &be->be_rootndn ) + && select_backend( &be->be_rootndn, 0 ) == be + && BER_BVISNULL( &be->be_rootpw ) ) + { + /* warning: if rootdn entry is created, + * it can take rootdn privileges; + * set empty rootpw to prevent */ + } +#endif + + if ( be->bd_info->bi_db_open ) { + rc = be->bd_info->bi_db_open( be, cr ); + if ( rc == 0 ) { + (void)backend_set_controls( be ); + + } else { + char *type = be->bd_info->bi_type; + char *suffix = "(null)"; + + if ( overlay_is_over( be ) ) { + slap_overinfo *oi = (slap_overinfo *)be->bd_info->bi_private; + type = oi->oi_orig->bi_type; + } + + if ( be->be_suffix != NULL && !BER_BVISNULL( &be->be_suffix[0] ) ) { + suffix = be->be_suffix[0].bv_val; + } + + Debug( LDAP_DEBUG_ANY, + "backend_startup_one (type=%s, suffix=\"%s\"): " + "bi_db_open failed! (%d)\n", + type, suffix, rc ); + } + } + + return rc; +} + +int backend_startup(Backend *be) +{ + int i; + int rc = 0; + BackendInfo *bi; + ConfigReply cr={0, ""}; + + if( ! ( nBackendDB > 0 ) ) { + /* no databases */ + Debug( LDAP_DEBUG_ANY, + "backend_startup: %d databases to startup.\n", + nBackendDB, 0, 0 ); + return 1; + } + + if(be != NULL) { + if ( be->bd_info->bi_open ) { + rc = be->bd_info->bi_open( be->bd_info ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "backend_startup: bi_open failed!\n", + 0, 0, 0 ); + + return rc; + } + } + + return backend_startup_one( be, &cr ); + } + + /* open frontend, if required */ + if ( frontendDB->bd_info->bi_db_open ) { + rc = frontendDB->bd_info->bi_db_open( frontendDB, &cr ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "backend_startup: bi_db_open(frontend) failed! (%d)\n", + rc, 0, 0 ); + return rc; + } + } + + /* open each backend type */ + i = -1; + LDAP_STAILQ_FOREACH(bi, &backendInfo, bi_next) { + i++; + if( bi->bi_nDB == 0) { + /* no database of this type, don't open */ + continue; + } + + if( bi->bi_open ) { + rc = bi->bi_open( bi ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "backend_startup: bi_open %d (%s) failed!\n", + i, bi->bi_type, 0 ); + return rc; + } + } + + (void)backend_init_controls( bi ); + } + + /* open each backend database */ + i = -1; + LDAP_STAILQ_FOREACH(be, &backendDB, be_next) { + i++; + if ( be->be_suffix == NULL ) { + Debug( LDAP_DEBUG_ANY, + "backend_startup: warning, database %d (%s) " + "has no suffix\n", + i, be->bd_info->bi_type, 0 ); + } + + rc = backend_startup_one( be, &cr ); + + if ( rc ) return rc; + } + + return rc; +} + +int backend_num( Backend *be ) +{ + int i = 0; + BackendDB *b2; + + if( be == NULL ) return -1; + + LDAP_STAILQ_FOREACH( b2, &backendDB, be_next ) { + if( be == b2 ) return i; + i++; + } + return -1; +} + +int backend_shutdown( Backend *be ) +{ + int rc = 0; + BackendInfo *bi; + + if( be != NULL ) { + /* shutdown a specific backend database */ + + if ( be->bd_info->bi_nDB == 0 ) { + /* no database of this type, we never opened it */ + return 0; + } + + if ( be->bd_info->bi_db_close ) { + rc = be->bd_info->bi_db_close( be, NULL ); + if ( rc ) return rc; + } + + if( be->bd_info->bi_close ) { + rc = be->bd_info->bi_close( be->bd_info ); + if ( rc ) return rc; + } + + return 0; + } + + /* close each backend database */ + LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) { + if ( be->bd_info->bi_db_close ) { + be->bd_info->bi_db_close( be, NULL ); + } + + if(rc != 0) { + Debug( LDAP_DEBUG_ANY, + "backend_close: bi_db_close %s failed!\n", + be->be_type, 0, 0 ); + } + } + + /* close each backend type */ + LDAP_STAILQ_FOREACH( bi, &backendInfo, bi_next ) { + if( bi->bi_nDB == 0 ) { + /* no database of this type */ + continue; + } + + if( bi->bi_close ) { + bi->bi_close( bi ); + } + } + + /* close frontend, if required */ + if ( frontendDB->bd_info->bi_db_close ) { + rc = frontendDB->bd_info->bi_db_close ( frontendDB, NULL ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "backend_startup: bi_db_close(frontend) failed! (%d)\n", + rc, 0, 0 ); + } + } + + return 0; +} + +/* + * This function is supposed to be the exact counterpart + * of backend_startup_one(), although this one calls bi_db_destroy() + * while backend_startup_one() calls bi_db_open(). + * + * Make sure backend_stopdown_one() destroys resources allocated + * by backend_startup_one(); only call backend_destroy_one() when + * all stuff in a BackendDB needs to be destroyed + */ +void +backend_stopdown_one( BackendDB *bd ) +{ + if ( bd->be_pending_csn_list ) { + struct slap_csn_entry *csne; + csne = LDAP_TAILQ_FIRST( bd->be_pending_csn_list ); + while ( csne ) { + struct slap_csn_entry *tmp_csne = csne; + + LDAP_TAILQ_REMOVE( bd->be_pending_csn_list, csne, ce_csn_link ); + ch_free( csne->ce_csn.bv_val ); + csne = LDAP_TAILQ_NEXT( csne, ce_csn_link ); + ch_free( tmp_csne ); + } + ch_free( bd->be_pending_csn_list ); + } + + if ( bd->bd_info->bi_db_destroy ) { + bd->bd_info->bi_db_destroy( bd, NULL ); + } +} + +void backend_destroy_one( BackendDB *bd, int dynamic ) +{ + if ( dynamic ) { + LDAP_STAILQ_REMOVE(&backendDB, bd, BackendDB, be_next ); + } + + if ( bd->be_syncinfo ) { + syncinfo_free( bd->be_syncinfo, 1 ); + } + + backend_stopdown_one( bd ); + + ber_bvarray_free( bd->be_suffix ); + ber_bvarray_free( bd->be_nsuffix ); + if ( !BER_BVISNULL( &bd->be_rootdn ) ) { + free( bd->be_rootdn.bv_val ); + } + if ( !BER_BVISNULL( &bd->be_rootndn ) ) { + free( bd->be_rootndn.bv_val ); + } + if ( !BER_BVISNULL( &bd->be_rootpw ) ) { + free( bd->be_rootpw.bv_val ); + } + acl_destroy( bd->be_acl ); + limits_destroy( bd->be_limits ); + if ( bd->be_extra_anlist ) { + anlist_free( bd->be_extra_anlist, 1, NULL ); + } + if ( !BER_BVISNULL( &bd->be_update_ndn ) ) { + ch_free( bd->be_update_ndn.bv_val ); + } + if ( bd->be_update_refs ) { + ber_bvarray_free( bd->be_update_refs ); + } + + ldap_pvt_thread_mutex_destroy( &bd->be_pcl_mutex ); + + if ( dynamic ) { + free( bd ); + } +} + +int backend_destroy(void) +{ + BackendDB *bd; + BackendInfo *bi; + + /* destroy each backend database */ + while (( bd = LDAP_STAILQ_FIRST(&backendDB))) { + backend_destroy_one( bd, 1 ); + } + + /* destroy each backend type */ + LDAP_STAILQ_FOREACH( bi, &backendInfo, bi_next ) { + if( bi->bi_destroy ) { + bi->bi_destroy( bi ); + } + } + + nBackendInfo = 0; + LDAP_STAILQ_INIT(&backendInfo); + + /* destroy frontend database */ + bd = frontendDB; + if ( bd ) { + if ( bd->bd_info->bi_db_destroy ) { + bd->bd_info->bi_db_destroy( bd, NULL ); + } + ber_bvarray_free( bd->be_suffix ); + ber_bvarray_free( bd->be_nsuffix ); + if ( !BER_BVISNULL( &bd->be_rootdn ) ) { + free( bd->be_rootdn.bv_val ); + } + if ( !BER_BVISNULL( &bd->be_rootndn ) ) { + free( bd->be_rootndn.bv_val ); + } + if ( !BER_BVISNULL( &bd->be_rootpw ) ) { + free( bd->be_rootpw.bv_val ); + } + acl_destroy( bd->be_acl ); + frontendDB = NULL; + } + + return 0; +} + +BackendInfo* backend_info(const char *type) +{ + BackendInfo *bi; + + /* search for the backend type */ + LDAP_STAILQ_FOREACH(bi,&backendInfo,bi_next) { + if( strcasecmp(bi->bi_type, type) == 0 ) { + return bi; + } + } + + return NULL; +} + +void +backend_db_insert( + BackendDB *be, + int idx +) +{ + /* If idx < 0, just add to end of list */ + if ( idx < 0 ) { + LDAP_STAILQ_INSERT_TAIL(&backendDB, be, be_next); + } else if ( idx == 0 ) { + LDAP_STAILQ_INSERT_HEAD(&backendDB, be, be_next); + } else { + int i; + BackendDB *b2; + + b2 = LDAP_STAILQ_FIRST(&backendDB); + idx--; + for (i=0; i<idx; i++) { + b2 = LDAP_STAILQ_NEXT(b2, be_next); + } + LDAP_STAILQ_INSERT_AFTER(&backendDB, b2, be, be_next); + } +} + +void +backend_db_move( + BackendDB *be, + int idx +) +{ + LDAP_STAILQ_REMOVE(&backendDB, be, BackendDB, be_next); + backend_db_insert(be, idx); +} + +BackendDB * +backend_db_init( + const char *type, + BackendDB *b0, + int idx, + ConfigReply *cr) +{ + BackendInfo *bi = backend_info(type); + BackendDB *be = b0; + int rc = 0; + + if( bi == NULL ) { + fprintf( stderr, "Unrecognized database type (%s)\n", type ); + return NULL; + } + + /* If be is provided, treat it as private. Otherwise allocate + * one and add it to the global list. + */ + if ( !be ) { + be = ch_calloc( 1, sizeof(Backend) ); + /* Just append */ + if ( idx >= nbackends ) + idx = -1; + nbackends++; + backend_db_insert( be, idx ); + } + + be->bd_info = bi; + be->bd_self = be; + + be->be_def_limit = frontendDB->be_def_limit; + be->be_dfltaccess = frontendDB->be_dfltaccess; + + be->be_restrictops = frontendDB->be_restrictops; + be->be_requires = frontendDB->be_requires; + be->be_ssf_set = frontendDB->be_ssf_set; + + ldap_pvt_thread_mutex_init( &be->be_pcl_mutex ); + + /* assign a default depth limit for alias deref */ + be->be_max_deref_depth = SLAPD_DEFAULT_MAXDEREFDEPTH; + + if ( bi->bi_db_init ) { + rc = bi->bi_db_init( be, cr ); + } + + if ( rc != 0 ) { + fprintf( stderr, "database init failed (%s)\n", type ); + /* If we created and linked this be, remove it and free it */ + if ( !b0 ) { + LDAP_STAILQ_REMOVE(&backendDB, be, BackendDB, be_next); + ldap_pvt_thread_mutex_destroy( &be->be_pcl_mutex ); + ch_free( be ); + be = NULL; + nbackends--; + } + } else { + if ( !bi->bi_nDB ) { + backend_init_controls( bi ); + } + bi->bi_nDB++; + } + return( be ); +} + +void +be_db_close( void ) +{ + BackendDB *be; + + LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) { + if ( be->bd_info->bi_db_close ) { + be->bd_info->bi_db_close( be, NULL ); + } + } + + if ( frontendDB->bd_info->bi_db_close ) { + frontendDB->bd_info->bi_db_close( frontendDB, NULL ); + } + +} + +Backend * +select_backend( + struct berval * dn, + int noSubs ) +{ + int j; + ber_len_t len, dnlen = dn->bv_len; + Backend *be; + + LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) { + if ( be->be_nsuffix == NULL || SLAP_DBHIDDEN( be )) { + continue; + } + + for ( j = 0; !BER_BVISNULL( &be->be_nsuffix[j] ); j++ ) + { + if ( ( SLAP_GLUE_SUBORDINATE( be ) ) && noSubs ) + { + continue; + } + + len = be->be_nsuffix[j].bv_len; + + if ( len > dnlen ) { + /* suffix is longer than DN */ + continue; + } + + /* + * input DN is normalized, so the separator check + * need not look at escaping + */ + if ( len && len < dnlen && + !DN_SEPARATOR( dn->bv_val[(dnlen-len)-1] )) + { + continue; + } + + if ( strcmp( be->be_nsuffix[j].bv_val, + &dn->bv_val[dnlen-len] ) == 0 ) + { + return be; + } + } + } + + return be; +} + +int +be_issuffix( + Backend *be, + struct berval *bvsuffix ) +{ + int i; + + if ( be->be_nsuffix == NULL ) { + return 0; + } + + for ( i = 0; !BER_BVISNULL( &be->be_nsuffix[i] ); i++ ) { + if ( bvmatch( &be->be_nsuffix[i], bvsuffix ) ) { + return 1; + } + } + + return 0; +} + +int +be_issubordinate( + Backend *be, + struct berval *bvsubordinate ) +{ + int i; + + if ( be->be_nsuffix == NULL ) { + return 0; + } + + for ( i = 0; !BER_BVISNULL( &be->be_nsuffix[i] ); i++ ) { + if ( dnIsSuffix( bvsubordinate, &be->be_nsuffix[i] ) ) { + return 1; + } + } + + return 0; +} + +int +be_isroot_dn( Backend *be, struct berval *ndn ) +{ + if ( BER_BVISEMPTY( ndn ) || BER_BVISEMPTY( &be->be_rootndn ) ) { + return 0; + } + + return dn_match( &be->be_rootndn, ndn ); +} + +int +be_slurp_update( Operation *op ) +{ + return ( SLAP_SLURP_SHADOW( op->o_bd ) && + be_isupdate_dn( op->o_bd, &op->o_ndn ) ); +} + +int +be_shadow_update( Operation *op ) +{ + /* This assumes that all internal ops (connid <= -1000) on a syncrepl + * database are syncrepl operations. + */ + return ( ( SLAP_SYNC_SHADOW( op->o_bd ) && SLAPD_SYNC_IS_SYNCCONN( op->o_connid ) ) || + ( SLAP_SHADOW( op->o_bd ) && be_isupdate_dn( op->o_bd, &op->o_ndn ) ) ); +} + +int +be_isupdate_dn( Backend *be, struct berval *ndn ) +{ + if ( BER_BVISEMPTY( ndn ) || BER_BVISEMPTY( &be->be_update_ndn ) ) { + return 0; + } + + return dn_match( &be->be_update_ndn, ndn ); +} + +struct berval * +be_root_dn( Backend *be ) +{ + return &be->be_rootdn; +} + +int +be_isroot( Operation *op ) +{ + return be_isroot_dn( op->o_bd, &op->o_ndn ); +} + +int +be_isroot_pw( Operation *op ) +{ + return be_rootdn_bind( op, NULL ) == LDAP_SUCCESS; +} + +/* + * checks if binding as rootdn + * + * return value: + * SLAP_CB_CONTINUE if not the rootdn, or if rootpw is null + * LDAP_SUCCESS if rootdn & rootpw + * LDAP_INVALID_CREDENTIALS if rootdn & !rootpw + * + * if rs != NULL + * if LDAP_SUCCESS, op->orb_edn is set + * if LDAP_INVALID_CREDENTIALS, response is sent to client + */ +int +be_rootdn_bind( Operation *op, SlapReply *rs ) +{ + int rc; +#ifdef SLAPD_SPASSWD + void *old_authctx = NULL; +#endif + + assert( op->o_tag == LDAP_REQ_BIND ); + assert( op->orb_method == LDAP_AUTH_SIMPLE ); + + if ( !be_isroot_dn( op->o_bd, &op->o_req_ndn ) ) { + return SLAP_CB_CONTINUE; + } + + if ( BER_BVISNULL( &op->o_bd->be_rootpw ) ) { + /* give the database a chance */ + return SLAP_CB_CONTINUE; + } + + if ( BER_BVISEMPTY( &op->o_bd->be_rootpw ) ) { + /* rootdn bind explicitly disallowed */ + rc = LDAP_INVALID_CREDENTIALS; + if ( rs ) { + goto send_result; + } + + return rc; + } + +#ifdef SLAPD_SPASSWD + ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)slap_sasl_bind, + op->o_conn->c_sasl_authctx, 0, &old_authctx, NULL ); +#endif + + rc = lutil_passwd( &op->o_bd->be_rootpw, &op->orb_cred, NULL, NULL ); + +#ifdef SLAPD_SPASSWD + ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)slap_sasl_bind, + old_authctx, 0, NULL, NULL ); +#endif + + rc = ( rc == 0 ? LDAP_SUCCESS : LDAP_INVALID_CREDENTIALS ); + if ( rs ) { +send_result:; + rs->sr_err = rc; + + Debug( LDAP_DEBUG_TRACE, "%s: rootdn=\"%s\" bind%s\n", + op->o_log_prefix, op->o_bd->be_rootdn.bv_val, + rc == LDAP_SUCCESS ? " succeeded" : " failed" ); + + if ( rc == LDAP_SUCCESS ) { + /* Set to the pretty rootdn */ + ber_dupbv( &op->orb_edn, &op->o_bd->be_rootdn ); + + } else { + send_ldap_result( op, rs ); + } + } + + return rc; +} + +int +be_entry_release_rw( + Operation *op, + Entry *e, + int rw ) +{ + if ( op->o_bd->be_release ) { + /* free and release entry from backend */ + return op->o_bd->be_release( op, e, rw ); + } else { + /* free entry */ + entry_free( e ); + return 0; + } +} + +int +backend_unbind( Operation *op, SlapReply *rs ) +{ + BackendDB *be; + + LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) { + if ( be->be_unbind ) { + op->o_bd = be; + be->be_unbind( op, rs ); + } + } + + return 0; +} + +int +backend_connection_init( + Connection *conn ) +{ + BackendDB *be; + + LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) { + if ( be->be_connection_init ) { + be->be_connection_init( be, conn ); + } + } + + return 0; +} + +int +backend_connection_destroy( + Connection *conn ) +{ + BackendDB *be; + + LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) { + if ( be->be_connection_destroy ) { + be->be_connection_destroy( be, conn); + } + } + + return 0; +} + +int +backend_check_controls( + Operation *op, + SlapReply *rs ) +{ + LDAPControl **ctrls = op->o_ctrls; + rs->sr_err = LDAP_SUCCESS; + + if( ctrls ) { + for( ; *ctrls != NULL ; ctrls++ ) { + int cid; + + switch ( slap_global_control( op, (*ctrls)->ldctl_oid, &cid ) ) { + case LDAP_CONTROL_NOT_FOUND: + /* unrecognized control */ + if ( (*ctrls)->ldctl_iscritical ) { + /* should not be reachable */ + Debug( LDAP_DEBUG_ANY, "backend_check_controls: " + "unrecognized critical control: %s\n", + (*ctrls)->ldctl_oid, 0, 0 ); + assert( 0 ); + } else { + Debug( LDAP_DEBUG_TRACE, "backend_check_controls: " + "unrecognized non-critical control: %s\n", + (*ctrls)->ldctl_oid, 0, 0 ); + } + break; + + case LDAP_COMPARE_FALSE: + if ( !op->o_bd->be_ctrls[cid] && (*ctrls)->ldctl_iscritical ) { +#ifdef SLAP_CONTROL_X_WHATFAILED + if ( get_whatFailed( op ) ) { + char *oids[ 2 ]; + oids[ 0 ] = (*ctrls)->ldctl_oid; + oids[ 1 ] = NULL; + slap_ctrl_whatFailed_add( op, rs, oids ); + } +#endif + /* RFC 4511 allows unavailableCriticalExtension to be + * returned when the server is unwilling to perform + * an operation extended by a recognized critical + * control. + */ + rs->sr_text = "critical control unavailable in context"; + rs->sr_err = LDAP_UNAVAILABLE_CRITICAL_EXTENSION; + goto done; + } + break; + + case LDAP_COMPARE_TRUE: + break; + + default: + /* unreachable */ + Debug( LDAP_DEBUG_ANY, + "backend_check_controls: unable to check control: %s\n", + (*ctrls)->ldctl_oid, 0, 0 ); + assert( 0 ); + + rs->sr_text = "unable to check control"; + rs->sr_err = LDAP_OTHER; + goto done; + } + } + } + +#if 0 /* temporarily removed */ + /* check should be generalized */ + if( get_relax(op) && !be_isroot(op)) { + rs->sr_text = "requires manager authorization"; + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + } +#endif + +done:; + return rs->sr_err; +} + +int +backend_check_restrictions( + Operation *op, + SlapReply *rs, + struct berval *opdata ) +{ + slap_mask_t restrictops; + slap_mask_t requires; + slap_mask_t opflag; + slap_mask_t exopflag = 0; + slap_ssf_set_t ssfs, *ssf; + int updateop = 0; + int starttls = 0; + int session = 0; + + restrictops = frontendDB->be_restrictops; + requires = frontendDB->be_requires; + ssfs = frontendDB->be_ssf_set; + ssf = &ssfs; + + if ( op->o_bd ) { + slap_ssf_t *fssf, *bssf; + int rc = SLAP_CB_CONTINUE, i; + + if ( op->o_bd->be_chk_controls ) { + rc = ( *op->o_bd->be_chk_controls )( op, rs ); + } + + if ( rc == SLAP_CB_CONTINUE ) { + rc = backend_check_controls( op, rs ); + } + + if ( rc != LDAP_SUCCESS ) { + return rs->sr_err; + } + + restrictops |= op->o_bd->be_restrictops; + requires |= op->o_bd->be_requires; + bssf = &op->o_bd->be_ssf_set.sss_ssf; + fssf = &ssfs.sss_ssf; + for ( i=0; i < (int)(sizeof(ssfs)/sizeof(slap_ssf_t)); i++ ) { + if ( bssf[i] ) fssf[i] = bssf[i]; + } + } + + switch( op->o_tag ) { + case LDAP_REQ_ADD: + opflag = SLAP_RESTRICT_OP_ADD; + updateop++; + break; + case LDAP_REQ_BIND: + opflag = SLAP_RESTRICT_OP_BIND; + session++; + break; + case LDAP_REQ_COMPARE: + opflag = SLAP_RESTRICT_OP_COMPARE; + break; + case LDAP_REQ_DELETE: + updateop++; + opflag = SLAP_RESTRICT_OP_DELETE; + break; + case LDAP_REQ_EXTENDED: + opflag = SLAP_RESTRICT_OP_EXTENDED; + + if( !opdata ) { + /* treat unspecified as a modify */ + opflag = SLAP_RESTRICT_OP_MODIFY; + updateop++; + break; + } + + if( bvmatch( opdata, &slap_EXOP_START_TLS ) ) { + session++; + starttls++; + exopflag = SLAP_RESTRICT_EXOP_START_TLS; + break; + } + + if( bvmatch( opdata, &slap_EXOP_WHOAMI ) ) { + exopflag = SLAP_RESTRICT_EXOP_WHOAMI; + break; + } + + if ( bvmatch( opdata, &slap_EXOP_CANCEL ) ) { + exopflag = SLAP_RESTRICT_EXOP_CANCEL; + break; + } + + if ( bvmatch( opdata, &slap_EXOP_MODIFY_PASSWD ) ) { + exopflag = SLAP_RESTRICT_EXOP_MODIFY_PASSWD; + updateop++; + break; + } + + /* treat everything else as a modify */ + opflag = SLAP_RESTRICT_OP_MODIFY; + updateop++; + break; + + case LDAP_REQ_MODIFY: + updateop++; + opflag = SLAP_RESTRICT_OP_MODIFY; + break; + case LDAP_REQ_RENAME: + updateop++; + opflag = SLAP_RESTRICT_OP_RENAME; + break; + case LDAP_REQ_SEARCH: + opflag = SLAP_RESTRICT_OP_SEARCH; + break; + case LDAP_REQ_UNBIND: + session++; + opflag = 0; + break; + default: + rs->sr_text = "restrict operations internal error"; + rs->sr_err = LDAP_OTHER; + return rs->sr_err; + } + + if ( !starttls ) { + /* these checks don't apply to StartTLS */ + + rs->sr_err = LDAP_CONFIDENTIALITY_REQUIRED; + if( op->o_transport_ssf < ssf->sss_transport ) { + rs->sr_text = op->o_transport_ssf + ? "stronger transport confidentiality required" + : "transport confidentiality required"; + return rs->sr_err; + } + + if( op->o_tls_ssf < ssf->sss_tls ) { + rs->sr_text = op->o_tls_ssf + ? "stronger TLS confidentiality required" + : "TLS confidentiality required"; + return rs->sr_err; + } + + + if( op->o_tag == LDAP_REQ_BIND && opdata == NULL ) { + /* simple bind specific check */ + if( op->o_ssf < ssf->sss_simple_bind ) { + rs->sr_text = op->o_ssf + ? "stronger confidentiality required" + : "confidentiality required"; + return rs->sr_err; + } + } + + if( op->o_tag != LDAP_REQ_BIND || opdata == NULL ) { + /* these checks don't apply to SASL bind */ + + if( op->o_sasl_ssf < ssf->sss_sasl ) { + rs->sr_text = op->o_sasl_ssf + ? "stronger SASL confidentiality required" + : "SASL confidentiality required"; + return rs->sr_err; + } + + if( op->o_ssf < ssf->sss_ssf ) { + rs->sr_text = op->o_ssf + ? "stronger confidentiality required" + : "confidentiality required"; + return rs->sr_err; + } + } + + if( updateop ) { + if( op->o_transport_ssf < ssf->sss_update_transport ) { + rs->sr_text = op->o_transport_ssf + ? "stronger transport confidentiality required for update" + : "transport confidentiality required for update"; + return rs->sr_err; + } + + if( op->o_tls_ssf < ssf->sss_update_tls ) { + rs->sr_text = op->o_tls_ssf + ? "stronger TLS confidentiality required for update" + : "TLS confidentiality required for update"; + return rs->sr_err; + } + + if( op->o_sasl_ssf < ssf->sss_update_sasl ) { + rs->sr_text = op->o_sasl_ssf + ? "stronger SASL confidentiality required for update" + : "SASL confidentiality required for update"; + return rs->sr_err; + } + + if( op->o_ssf < ssf->sss_update_ssf ) { + rs->sr_text = op->o_ssf + ? "stronger confidentiality required for update" + : "confidentiality required for update"; + return rs->sr_err; + } + + if( !( global_allows & SLAP_ALLOW_UPDATE_ANON ) && + BER_BVISEMPTY( &op->o_ndn ) ) + { + rs->sr_text = "modifications require authentication"; + rs->sr_err = LDAP_STRONG_AUTH_REQUIRED; + return rs->sr_err; + } + +#ifdef SLAP_X_LISTENER_MOD + if ( op->o_conn->c_listener && + ! ( op->o_conn->c_listener->sl_perms & ( !BER_BVISEMPTY( &op->o_ndn ) + ? (S_IWUSR|S_IWOTH) : S_IWOTH ) ) ) + { + /* no "w" mode means readonly */ + rs->sr_text = "modifications not allowed on this listener"; + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + return rs->sr_err; + } +#endif /* SLAP_X_LISTENER_MOD */ + } + } + + if ( !session ) { + /* these checks don't apply to Bind, StartTLS, or Unbind */ + + if( requires & SLAP_REQUIRE_STRONG ) { + /* should check mechanism */ + if( ( op->o_transport_ssf < ssf->sss_transport + && op->o_authtype == LDAP_AUTH_SIMPLE ) + || BER_BVISEMPTY( &op->o_dn ) ) + { + rs->sr_text = "strong(er) authentication required"; + rs->sr_err = LDAP_STRONG_AUTH_REQUIRED; + return rs->sr_err; + } + } + + if( requires & SLAP_REQUIRE_SASL ) { + if( op->o_authtype != LDAP_AUTH_SASL || BER_BVISEMPTY( &op->o_dn ) ) { + rs->sr_text = "SASL authentication required"; + rs->sr_err = LDAP_STRONG_AUTH_REQUIRED; + return rs->sr_err; + } + } + + if( requires & SLAP_REQUIRE_AUTHC ) { + if( BER_BVISEMPTY( &op->o_dn ) ) { + rs->sr_text = "authentication required"; + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + return rs->sr_err; + } + } + + if( requires & SLAP_REQUIRE_BIND ) { + int version; + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + version = op->o_conn->c_protocol; + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + + if( !version ) { + /* no bind has occurred */ + rs->sr_text = "BIND required"; + rs->sr_err = LDAP_OPERATIONS_ERROR; + return rs->sr_err; + } + } + + if( requires & SLAP_REQUIRE_LDAP_V3 ) { + if( op->o_protocol < LDAP_VERSION3 ) { + /* no bind has occurred */ + rs->sr_text = "operation restricted to LDAPv3 clients"; + rs->sr_err = LDAP_OPERATIONS_ERROR; + return rs->sr_err; + } + } + +#ifdef SLAP_X_LISTENER_MOD + if ( !starttls && BER_BVISEMPTY( &op->o_dn ) ) { + if ( op->o_conn->c_listener && + !( op->o_conn->c_listener->sl_perms & S_IXOTH )) + { + /* no "x" mode means bind required */ + rs->sr_text = "bind required on this listener"; + rs->sr_err = LDAP_STRONG_AUTH_REQUIRED; + return rs->sr_err; + } + } + + if ( !starttls && !updateop ) { + if ( op->o_conn->c_listener && + !( op->o_conn->c_listener->sl_perms & + ( !BER_BVISEMPTY( &op->o_dn ) + ? (S_IRUSR|S_IROTH) : S_IROTH ))) + { + /* no "r" mode means no read */ + rs->sr_text = "read not allowed on this listener"; + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + return rs->sr_err; + } + } +#endif /* SLAP_X_LISTENER_MOD */ + + } + + if( ( restrictops & opflag ) + || ( exopflag && ( restrictops & exopflag ) ) + || (( restrictops & SLAP_RESTRICT_READONLY ) && updateop )) { + if( ( restrictops & SLAP_RESTRICT_OP_MASK) == SLAP_RESTRICT_OP_READS ) { + rs->sr_text = "read operations restricted"; + } else if ( restrictops & exopflag ) { + rs->sr_text = "extended operation restricted"; + } else { + rs->sr_text = "operation restricted"; + } + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + return rs->sr_err; + } + + rs->sr_err = LDAP_SUCCESS; + return rs->sr_err; +} + +int backend_check_referrals( Operation *op, SlapReply *rs ) +{ + rs->sr_err = LDAP_SUCCESS; + + if( op->o_bd->be_chk_referrals ) { + rs->sr_err = op->o_bd->be_chk_referrals( op, rs ); + + if( rs->sr_err != LDAP_SUCCESS && rs->sr_err != LDAP_REFERRAL ) { + send_ldap_result( op, rs ); + } + } + + return rs->sr_err; +} + +int +be_entry_get_rw( + Operation *op, + struct berval *ndn, + ObjectClass *oc, + AttributeDescription *at, + int rw, + Entry **e ) +{ + *e = NULL; + + if ( op->o_bd == NULL ) { + return LDAP_NO_SUCH_OBJECT; + } + + if ( op->o_bd->be_fetch ) { + return op->o_bd->be_fetch( op, ndn, oc, at, rw, e ); + } + + return LDAP_UNWILLING_TO_PERFORM; +} + +int +fe_acl_group( + Operation *op, + Entry *target, + struct berval *gr_ndn, + struct berval *op_ndn, + ObjectClass *group_oc, + AttributeDescription *group_at ) +{ + Entry *e; + void *o_priv = op->o_private, *e_priv = NULL; + Attribute *a; + int rc; + GroupAssertion *g; + Backend *be = op->o_bd; + OpExtra *oex; + + LDAP_SLIST_FOREACH(oex, &op->o_extra, oe_next) { + if ( oex->oe_key == (void *)backend_group ) + break; + } + + if ( oex && ((OpExtraDB *)oex)->oe_db ) + op->o_bd = ((OpExtraDB *)oex)->oe_db; + + if ( !op->o_bd || !SLAP_DBHIDDEN( op->o_bd )) + op->o_bd = select_backend( gr_ndn, 0 ); + + for ( g = op->o_groups; g; g = g->ga_next ) { + if ( g->ga_be != op->o_bd || g->ga_oc != group_oc || + g->ga_at != group_at || g->ga_len != gr_ndn->bv_len ) + { + continue; + } + if ( strcmp( g->ga_ndn, gr_ndn->bv_val ) == 0 ) { + break; + } + } + + if ( g ) { + rc = g->ga_res; + goto done; + } + + if ( target && dn_match( &target->e_nname, gr_ndn ) ) { + e = target; + rc = 0; + + } else { + op->o_private = NULL; + rc = be_entry_get_rw( op, gr_ndn, group_oc, group_at, 0, &e ); + e_priv = op->o_private; + op->o_private = o_priv; + } + + if ( e ) { + a = attr_find( e->e_attrs, group_at ); + if ( a ) { + /* If the attribute is a subtype of labeledURI, + * treat this as a dynamic group ala groupOfURLs + */ + if ( is_at_subtype( group_at->ad_type, + slap_schema.si_ad_labeledURI->ad_type ) ) + { + int i; + LDAPURLDesc *ludp; + struct berval bv, nbase; + Filter *filter; + Entry *user = NULL; + void *user_priv = NULL; + Backend *b2 = op->o_bd; + + if ( target && dn_match( &target->e_nname, op_ndn ) ) { + user = target; + } + + rc = LDAP_COMPARE_FALSE; + for ( i = 0; !BER_BVISNULL( &a->a_vals[i] ); i++ ) { + if ( ldap_url_parse( a->a_vals[i].bv_val, &ludp ) != + LDAP_URL_SUCCESS ) + { + continue; + } + + BER_BVZERO( &nbase ); + + /* host, attrs and extensions parts must be empty */ + if ( ( ludp->lud_host && *ludp->lud_host ) + || ludp->lud_attrs + || ludp->lud_exts ) + { + goto loopit; + } + + ber_str2bv( ludp->lud_dn, 0, 0, &bv ); + if ( dnNormalize( 0, NULL, NULL, &bv, &nbase, + op->o_tmpmemctx ) != LDAP_SUCCESS ) + { + goto loopit; + } + + switch ( ludp->lud_scope ) { + case LDAP_SCOPE_BASE: + if ( !dn_match( &nbase, op_ndn ) ) { + goto loopit; + } + break; + case LDAP_SCOPE_ONELEVEL: + dnParent( op_ndn, &bv ); + if ( !dn_match( &nbase, &bv ) ) { + goto loopit; + } + break; + case LDAP_SCOPE_SUBTREE: + if ( !dnIsSuffix( op_ndn, &nbase ) ) { + goto loopit; + } + break; + case LDAP_SCOPE_SUBORDINATE: + if ( dn_match( &nbase, op_ndn ) || + !dnIsSuffix( op_ndn, &nbase ) ) + { + goto loopit; + } + } + + /* NOTE: this could be NULL + * if no filter is provided, + * or if filter parsing fails. + * In the latter case, + * we should give up. */ + if ( ludp->lud_filter != NULL && *ludp->lud_filter != '\0') { + filter = str2filter_x( op, ludp->lud_filter ); + if ( filter == NULL ) { + /* give up... */ + rc = LDAP_OTHER; + goto loopit; + } + + /* only get user if required + * and not available yet */ + if ( user == NULL ) { + int rc2; + + op->o_bd = select_backend( op_ndn, 0 ); + op->o_private = NULL; + rc2 = be_entry_get_rw( op, op_ndn, NULL, NULL, 0, &user ); + user_priv = op->o_private; + op->o_private = o_priv; + if ( rc2 != 0 ) { + /* give up... */ + rc = (rc2 == LDAP_NO_SUCH_OBJECT) ? rc2 : LDAP_OTHER; + goto loopit; + } + } + + if ( test_filter( NULL, user, filter ) == + LDAP_COMPARE_TRUE ) + { + rc = 0; + } + filter_free_x( op, filter, 1 ); + } +loopit: + ldap_free_urldesc( ludp ); + if ( !BER_BVISNULL( &nbase ) ) { + op->o_tmpfree( nbase.bv_val, op->o_tmpmemctx ); + } + if ( rc != LDAP_COMPARE_FALSE ) { + break; + } + } + + if ( user != NULL && user != target ) { + op->o_private = user_priv; + be_entry_release_r( op, user ); + op->o_private = o_priv; + } + op->o_bd = b2; + + } else { + rc = attr_valfind( a, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + op_ndn, NULL, op->o_tmpmemctx ); + if ( rc == LDAP_NO_SUCH_ATTRIBUTE ) { + rc = LDAP_COMPARE_FALSE; + } + } + + } else { + rc = LDAP_NO_SUCH_ATTRIBUTE; + } + + if ( e != target ) { + op->o_private = e_priv; + be_entry_release_r( op, e ); + op->o_private = o_priv; + } + + } else { + rc = LDAP_NO_SUCH_OBJECT; + } + + if ( op->o_tag != LDAP_REQ_BIND && !op->o_do_not_cache ) { + g = op->o_tmpalloc( sizeof( GroupAssertion ) + gr_ndn->bv_len, + op->o_tmpmemctx ); + g->ga_be = op->o_bd; + g->ga_oc = group_oc; + g->ga_at = group_at; + g->ga_res = rc; + g->ga_len = gr_ndn->bv_len; + strcpy( g->ga_ndn, gr_ndn->bv_val ); + g->ga_next = op->o_groups; + op->o_groups = g; + } + +done: + op->o_bd = be; + return rc; +} + +int +backend_group( + Operation *op, + Entry *target, + struct berval *gr_ndn, + struct berval *op_ndn, + ObjectClass *group_oc, + AttributeDescription *group_at ) +{ + int rc; + BackendDB *be_orig; + OpExtraDB oex; + + if ( op->o_abandon ) { + return SLAPD_ABANDON; + } + + oex.oe_db = op->o_bd; + oex.oe.oe_key = (void *)backend_group; + LDAP_SLIST_INSERT_HEAD(&op->o_extra, &oex.oe, oe_next); + + be_orig = op->o_bd; + op->o_bd = frontendDB; + rc = frontendDB->be_group( op, target, gr_ndn, + op_ndn, group_oc, group_at ); + op->o_bd = be_orig; + LDAP_SLIST_REMOVE(&op->o_extra, &oex.oe, OpExtra, oe_next); + + return rc; +} + +int +fe_acl_attribute( + Operation *op, + Entry *target, + struct berval *edn, + AttributeDescription *entry_at, + BerVarray *vals, + slap_access_t access ) +{ + Entry *e = NULL; + void *o_priv = op->o_private, *e_priv = NULL; + Attribute *a = NULL; + int freeattr = 0, i, j, rc = LDAP_SUCCESS; + AccessControlState acl_state = ACL_STATE_INIT; + Backend *be = op->o_bd; + OpExtra *oex; + + LDAP_SLIST_FOREACH(oex, &op->o_extra, oe_next) { + if ( oex->oe_key == (void *)backend_attribute ) + break; + } + + if ( oex && ((OpExtraDB *)oex)->oe_db ) + op->o_bd = ((OpExtraDB *)oex)->oe_db; + + if ( !op->o_bd || !SLAP_DBHIDDEN( op->o_bd )) + op->o_bd = select_backend( edn, 0 ); + + if ( target && dn_match( &target->e_nname, edn ) ) { + e = target; + + } else { + op->o_private = NULL; + rc = be_entry_get_rw( op, edn, NULL, entry_at, 0, &e ); + e_priv = op->o_private; + op->o_private = o_priv; + } + + if ( e ) { + if ( entry_at == slap_schema.si_ad_entry || entry_at == slap_schema.si_ad_children ) { + assert( vals == NULL ); + + rc = LDAP_SUCCESS; + if ( op->o_conn && access > ACL_NONE && + access_allowed( op, e, entry_at, NULL, + access, &acl_state ) == 0 ) + { + rc = LDAP_INSUFFICIENT_ACCESS; + } + goto freeit; + } + + a = attr_find( e->e_attrs, entry_at ); + if ( a == NULL ) { + SlapReply rs = { REP_SEARCH }; + AttributeName anlist[ 2 ]; + + anlist[ 0 ].an_name = entry_at->ad_cname; + anlist[ 0 ].an_desc = entry_at; + BER_BVZERO( &anlist[ 1 ].an_name ); + rs.sr_attrs = anlist; + + /* NOTE: backend_operational() is also called + * when returning results, so it's supposed + * to do no harm to entries */ + rs.sr_entry = e; + rc = backend_operational( op, &rs ); + + if ( rc == LDAP_SUCCESS ) { + if ( rs.sr_operational_attrs ) { + freeattr = 1; + a = rs.sr_operational_attrs; + + } else { + rc = LDAP_NO_SUCH_ATTRIBUTE; + } + } + } + + if ( a ) { + BerVarray v; + + if ( op->o_conn && access > ACL_NONE && + access_allowed( op, e, entry_at, NULL, + access, &acl_state ) == 0 ) + { + rc = LDAP_INSUFFICIENT_ACCESS; + goto freeit; + } + + i = a->a_numvals; + v = op->o_tmpalloc( sizeof(struct berval) * ( i + 1 ), + op->o_tmpmemctx ); + for ( i = 0, j = 0; !BER_BVISNULL( &a->a_vals[i] ); i++ ) + { + if ( op->o_conn && access > ACL_NONE && + access_allowed( op, e, entry_at, + &a->a_nvals[i], + access, + &acl_state ) == 0 ) + { + continue; + } + ber_dupbv_x( &v[j], &a->a_nvals[i], + op->o_tmpmemctx ); + if ( !BER_BVISNULL( &v[j] ) ) { + j++; + } + } + if ( j == 0 ) { + op->o_tmpfree( v, op->o_tmpmemctx ); + *vals = NULL; + rc = LDAP_INSUFFICIENT_ACCESS; + + } else { + BER_BVZERO( &v[j] ); + *vals = v; + rc = LDAP_SUCCESS; + } + } +freeit: if ( e != target ) { + op->o_private = e_priv; + be_entry_release_r( op, e ); + op->o_private = o_priv; + } + if ( freeattr ) { + attr_free( a ); + } + } + + op->o_bd = be; + return rc; +} + +int +backend_attribute( + Operation *op, + Entry *target, + struct berval *edn, + AttributeDescription *entry_at, + BerVarray *vals, + slap_access_t access ) +{ + int rc; + BackendDB *be_orig; + OpExtraDB oex; + + oex.oe_db = op->o_bd; + oex.oe.oe_key = (void *)backend_attribute; + LDAP_SLIST_INSERT_HEAD(&op->o_extra, &oex.oe, oe_next); + + be_orig = op->o_bd; + op->o_bd = frontendDB; + rc = frontendDB->be_attribute( op, target, edn, + entry_at, vals, access ); + op->o_bd = be_orig; + LDAP_SLIST_REMOVE(&op->o_extra, &oex.oe, OpExtra, oe_next); + + return rc; +} + +int +backend_access( + Operation *op, + Entry *target, + struct berval *edn, + AttributeDescription *entry_at, + struct berval *nval, + slap_access_t access, + slap_mask_t *mask ) +{ + Entry *e = NULL; + void *o_priv, *e_priv = NULL; + int rc = LDAP_INSUFFICIENT_ACCESS; + Backend *be; + + /* pedantic */ + assert( op != NULL ); + assert( op->o_conn != NULL ); + assert( edn != NULL ); + assert( access > ACL_NONE ); + + be = op->o_bd; + o_priv = op->o_private; + + if ( !op->o_bd ) { + op->o_bd = select_backend( edn, 0 ); + } + + if ( target && dn_match( &target->e_nname, edn ) ) { + e = target; + + } else { + op->o_private = NULL; + rc = be_entry_get_rw( op, edn, NULL, entry_at, 0, &e ); + e_priv = op->o_private; + op->o_private = o_priv; + } + + if ( e ) { + Attribute *a = NULL; + int freeattr = 0; + + if ( entry_at == NULL ) { + entry_at = slap_schema.si_ad_entry; + } + + if ( entry_at == slap_schema.si_ad_entry || entry_at == slap_schema.si_ad_children ) + { + if ( access_allowed_mask( op, e, entry_at, + NULL, access, NULL, mask ) == 0 ) + { + rc = LDAP_INSUFFICIENT_ACCESS; + + } else { + rc = LDAP_SUCCESS; + } + + } else { + a = attr_find( e->e_attrs, entry_at ); + if ( a == NULL ) { + SlapReply rs = { REP_SEARCH }; + AttributeName anlist[ 2 ]; + + anlist[ 0 ].an_name = entry_at->ad_cname; + anlist[ 0 ].an_desc = entry_at; + BER_BVZERO( &anlist[ 1 ].an_name ); + rs.sr_attrs = anlist; + + rs.sr_attr_flags = slap_attr_flags( rs.sr_attrs ); + + /* NOTE: backend_operational() is also called + * when returning results, so it's supposed + * to do no harm to entries */ + rs.sr_entry = e; + rc = backend_operational( op, &rs ); + + if ( rc == LDAP_SUCCESS ) { + if ( rs.sr_operational_attrs ) { + freeattr = 1; + a = rs.sr_operational_attrs; + + } else { + rc = LDAP_NO_SUCH_OBJECT; + } + } + } + + if ( a ) { + if ( access_allowed_mask( op, e, entry_at, + nval, access, NULL, mask ) == 0 ) + { + rc = LDAP_INSUFFICIENT_ACCESS; + goto freeit; + } + rc = LDAP_SUCCESS; + } + } +freeit: if ( e != target ) { + op->o_private = e_priv; + be_entry_release_r( op, e ); + op->o_private = o_priv; + } + if ( freeattr ) { + attr_free( a ); + } + } + + op->o_bd = be; + return rc; +} + +int +fe_aux_operational( + Operation *op, + SlapReply *rs ) +{ + Attribute **ap; + int rc = LDAP_SUCCESS; + BackendDB *be_orig = op->o_bd; + OpExtra *oex; + + LDAP_SLIST_FOREACH(oex, &op->o_extra, oe_next) { + if ( oex->oe_key == (void *)backend_operational ) + break; + } + + for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next ) + /* just count them */ ; + + /* + * If operational attributes (allegedly) are required, + * and the backend supports specific operational attributes, + * add them to the attribute list + */ + if ( !( rs->sr_flags & REP_NO_ENTRYDN ) + && ( SLAP_OPATTRS( rs->sr_attr_flags ) || ( rs->sr_attrs && + ad_inlist( slap_schema.si_ad_entryDN, rs->sr_attrs ) ) ) ) + { + *ap = slap_operational_entryDN( rs->sr_entry ); + ap = &(*ap)->a_next; + } + + if ( !( rs->sr_flags & REP_NO_SUBSCHEMA) + && ( SLAP_OPATTRS( rs->sr_attr_flags ) || ( rs->sr_attrs && + ad_inlist( slap_schema.si_ad_subschemaSubentry, rs->sr_attrs ) ) ) ) + { + *ap = slap_operational_subschemaSubentry( op->o_bd ); + ap = &(*ap)->a_next; + } + + /* Let the overlays have a chance at this */ + if ( oex && ((OpExtraDB *)oex)->oe_db ) + op->o_bd = ((OpExtraDB *)oex)->oe_db; + + if ( !op->o_bd || !SLAP_DBHIDDEN( op->o_bd )) + op->o_bd = select_backend( &op->o_req_ndn, 0 ); + + if ( op->o_bd != NULL && !be_match( op->o_bd, frontendDB ) && + ( SLAP_OPATTRS( rs->sr_attr_flags ) || rs->sr_attrs ) && + op->o_bd->be_operational != NULL ) + { + rc = op->o_bd->be_operational( op, rs ); + } + op->o_bd = be_orig; + + return rc; +} + +int backend_operational( Operation *op, SlapReply *rs ) +{ + int rc; + BackendDB *be_orig; + OpExtraDB oex; + + oex.oe_db = op->o_bd; + oex.oe.oe_key = (void *)backend_operational; + LDAP_SLIST_INSERT_HEAD(&op->o_extra, &oex.oe, oe_next); + + /* Moved this into the frontend so global overlays are called */ + + be_orig = op->o_bd; + op->o_bd = frontendDB; + rc = frontendDB->be_operational( op, rs ); + op->o_bd = be_orig; + LDAP_SLIST_REMOVE(&op->o_extra, &oex.oe, OpExtra, oe_next); + + return rc; +} + +/* helper that calls the bi_tool_entry_first_x() variant with default args; + * use to initialize a backend's bi_tool_entry_first() when appropriate + */ +ID +backend_tool_entry_first( BackendDB *be ) +{ + return be->bd_info->bi_tool_entry_first_x( be, + NULL, LDAP_SCOPE_DEFAULT, NULL ); +} diff --git a/servers/slapd/backglue.c b/servers/slapd/backglue.c new file mode 100644 index 0000000..1c3354e --- /dev/null +++ b/servers/slapd/backglue.c @@ -0,0 +1,1549 @@ +/* backglue.c - backend glue */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +/* + * Functions to glue a bunch of other backends into a single tree. + * All of the glued backends must share a common suffix. E.g., you + * can glue o=foo and ou=bar,o=foo but you can't glue o=foo and o=bar. + * + * The purpose of these functions is to allow you to split a single database + * into pieces (for load balancing purposes, whatever) but still be able + * to treat it as a single database after it's been split. As such, each + * of the glued backends should have identical rootdn. + * -- Howard Chu + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#define SLAPD_TOOLS +#include "slap.h" +#include "lutil.h" +#include "config.h" + +typedef struct gluenode { + BackendDB *gn_be; + struct berval gn_pdn; +} gluenode; + +typedef struct glueinfo { + int gi_nodes; + struct berval gi_pdn; + gluenode gi_n[1]; +} glueinfo; + +static slap_overinst glue; + +static int glueMode; +static BackendDB *glueBack; +static BackendDB glueBackDone; +#define GLUEBACK_DONE (&glueBackDone) + +static slap_overinst * glue_tool_inst( BackendInfo *bi); + +static slap_response glue_op_response; + +/* Just like select_backend, but only for our backends */ +static BackendDB * +glue_back_select ( + BackendDB *be, + struct berval *dn +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + glueinfo *gi = (glueinfo *)on->on_bi.bi_private; + int i; + + for (i = gi->gi_nodes-1; i >= 0; i--) { + assert( gi->gi_n[i].gn_be->be_nsuffix != NULL ); + + if (dnIsSuffix(dn, &gi->gi_n[i].gn_be->be_nsuffix[0])) { + return gi->gi_n[i].gn_be; + } + } + be->bd_info = on->on_info->oi_orig; + return be; +} + + +typedef struct glue_state { + char *matched; + BerVarray refs; + LDAPControl **ctrls; + int err; + int matchlen; + int nrefs; + int nctrls; +} glue_state; + +static int +glue_op_cleanup( Operation *op, SlapReply *rs ) +{ + /* This is not a final result */ + if (rs->sr_type == REP_RESULT ) + rs->sr_type = REP_GLUE_RESULT; + return SLAP_CB_CONTINUE; +} + +static int +glue_op_response ( Operation *op, SlapReply *rs ) +{ + glue_state *gs = op->o_callback->sc_private; + + switch(rs->sr_type) { + case REP_SEARCH: + case REP_SEARCHREF: + case REP_INTERMEDIATE: + return SLAP_CB_CONTINUE; + + default: + if (rs->sr_err == LDAP_SUCCESS || + rs->sr_err == LDAP_SIZELIMIT_EXCEEDED || + rs->sr_err == LDAP_TIMELIMIT_EXCEEDED || + rs->sr_err == LDAP_ADMINLIMIT_EXCEEDED || + rs->sr_err == LDAP_NO_SUCH_OBJECT || + gs->err != LDAP_SUCCESS) + gs->err = rs->sr_err; + if (gs->err == LDAP_SUCCESS && gs->matched) { + ch_free (gs->matched); + gs->matched = NULL; + gs->matchlen = 0; + } + if (gs->err != LDAP_SUCCESS && rs->sr_matched) { + int len; + len = strlen (rs->sr_matched); + if (len > gs->matchlen) { + if (gs->matched) + ch_free (gs->matched); + gs->matched = ch_strdup (rs->sr_matched); + gs->matchlen = len; + } + } + if (rs->sr_ref) { + int i, j, k; + BerVarray new; + + for (i=0; rs->sr_ref[i].bv_val; i++); + + j = gs->nrefs; + if (!j) { + new = ch_malloc ((i+1)*sizeof(struct berval)); + } else { + new = ch_realloc(gs->refs, + (j+i+1)*sizeof(struct berval)); + } + for (k=0; k<i; j++,k++) { + ber_dupbv( &new[j], &rs->sr_ref[k] ); + } + new[j].bv_val = NULL; + gs->nrefs = j; + gs->refs = new; + } + if (rs->sr_ctrls) { + int i, j, k; + LDAPControl **newctrls; + + for (i=0; rs->sr_ctrls[i]; i++); + + j = gs->nctrls; + if (!j) { + newctrls = op->o_tmpalloc((i+1)*sizeof(LDAPControl *), + op->o_tmpmemctx); + } else { + /* Forget old pagedResults response if we're sending + * a new one now + */ + if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) { + int newpage = 0; + for ( k=0; k<i; k++ ) { + if ( !strcmp(rs->sr_ctrls[k]->ldctl_oid, + LDAP_CONTROL_PAGEDRESULTS )) { + newpage = 1; + break; + } + } + if ( newpage ) { + for ( k=0; k<j; k++ ) { + if ( !strcmp(gs->ctrls[k]->ldctl_oid, + LDAP_CONTROL_PAGEDRESULTS )) + { + op->o_tmpfree(gs->ctrls[k], op->o_tmpmemctx); + gs->ctrls[k] = gs->ctrls[--j]; + gs->ctrls[j] = NULL; + break; + } + } + } + } + newctrls = op->o_tmprealloc(gs->ctrls, + (j+i+1)*sizeof(LDAPControl *), op->o_tmpmemctx); + } + for (k=0; k<i; j++,k++) { + ber_len_t oidlen = strlen( rs->sr_ctrls[k]->ldctl_oid ); + newctrls[j] = op->o_tmpalloc(sizeof(LDAPControl) + oidlen + 1 + rs->sr_ctrls[k]->ldctl_value.bv_len + 1, + op->o_tmpmemctx); + newctrls[j]->ldctl_iscritical = rs->sr_ctrls[k]->ldctl_iscritical; + newctrls[j]->ldctl_oid = (char *)&newctrls[j][1]; + lutil_strcopy( newctrls[j]->ldctl_oid, rs->sr_ctrls[k]->ldctl_oid ); + if ( !BER_BVISNULL( &rs->sr_ctrls[k]->ldctl_value ) ) { + newctrls[j]->ldctl_value.bv_val = &newctrls[j]->ldctl_oid[oidlen + 1]; + newctrls[j]->ldctl_value.bv_len = rs->sr_ctrls[k]->ldctl_value.bv_len; + lutil_memcopy( newctrls[j]->ldctl_value.bv_val, + rs->sr_ctrls[k]->ldctl_value.bv_val, + rs->sr_ctrls[k]->ldctl_value.bv_len + 1 ); + } else { + BER_BVZERO( &newctrls[j]->ldctl_value ); + } + } + newctrls[j] = NULL; + gs->nctrls = j; + gs->ctrls = newctrls; + } + } + return 0; +} + +static int +glue_op_func ( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + BackendDB *b0 = op->o_bd; + BackendInfo *bi0 = op->o_bd->bd_info; + BI_op_modify **func; + slap_operation_t which = op_bind; + int rc; + + op->o_bd = glue_back_select (b0, &op->o_req_ndn); + + /* If we're on the primary backend, let overlay framework handle it */ + if ( op->o_bd == b0 ) + return SLAP_CB_CONTINUE; + + b0->bd_info = on->on_info->oi_orig; + + switch(op->o_tag) { + case LDAP_REQ_ADD: which = op_add; break; + case LDAP_REQ_DELETE: which = op_delete; break; + case LDAP_REQ_MODIFY: which = op_modify; break; + case LDAP_REQ_MODRDN: which = op_modrdn; break; + case LDAP_REQ_EXTENDED: which = op_extended; break; + default: assert( 0 ); break; + } + + func = &op->o_bd->bd_info->bi_op_bind; + if ( func[which] ) + rc = func[which]( op, rs ); + else + rc = SLAP_CB_BYPASS; + + op->o_bd = b0; + op->o_bd->bd_info = bi0; + return rc; +} + +static int +glue_op_abandon( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + glueinfo *gi = (glueinfo *)on->on_bi.bi_private; + BackendDB *b0 = op->o_bd; + BackendInfo *bi0 = op->o_bd->bd_info; + int i; + + b0->bd_info = on->on_info->oi_orig; + + for (i = gi->gi_nodes-1; i >= 0; i--) { + assert( gi->gi_n[i].gn_be->be_nsuffix != NULL ); + op->o_bd = gi->gi_n[i].gn_be; + if ( op->o_bd == b0 ) + continue; + if ( op->o_bd->bd_info->bi_op_abandon ) + op->o_bd->bd_info->bi_op_abandon( op, rs ); + } + op->o_bd = b0; + op->o_bd->bd_info = bi0; + return SLAP_CB_CONTINUE; +} + +static int +glue_response ( Operation *op, SlapReply *rs ) +{ + BackendDB *be = op->o_bd; + be = glue_back_select (op->o_bd, &op->o_req_ndn); + + /* If we're on the primary backend, let overlay framework handle it. + * Otherwise, bail out. + */ + return ( op->o_bd == be ) ? SLAP_CB_CONTINUE : SLAP_CB_BYPASS; +} + +static int +glue_chk_referrals ( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + BackendDB *b0 = op->o_bd; + BackendInfo *bi0 = op->o_bd->bd_info; + int rc; + + op->o_bd = glue_back_select (b0, &op->o_req_ndn); + if ( op->o_bd == b0 ) + return SLAP_CB_CONTINUE; + + b0->bd_info = on->on_info->oi_orig; + + if ( op->o_bd->bd_info->bi_chk_referrals ) + rc = ( *op->o_bd->bd_info->bi_chk_referrals )( op, rs ); + else + rc = SLAP_CB_CONTINUE; + + op->o_bd = b0; + op->o_bd->bd_info = bi0; + return rc; +} + +static int +glue_chk_controls ( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + BackendDB *b0 = op->o_bd; + BackendInfo *bi0 = op->o_bd->bd_info; + int rc = SLAP_CB_CONTINUE; + + op->o_bd = glue_back_select (b0, &op->o_req_ndn); + if ( op->o_bd == b0 ) + return SLAP_CB_CONTINUE; + + b0->bd_info = on->on_info->oi_orig; + + /* if the subordinate database has overlays, the bi_chk_controls() + * hook is actually over_aux_chk_controls(); in case it actually + * wraps a missing hok, we need to mimic the behavior + * of the frontend applied to that database */ + if ( op->o_bd->bd_info->bi_chk_controls ) { + rc = ( *op->o_bd->bd_info->bi_chk_controls )( op, rs ); + } + + + if ( rc == SLAP_CB_CONTINUE ) { + rc = backend_check_controls( op, rs ); + } + + op->o_bd = b0; + op->o_bd->bd_info = bi0; + return rc; +} + +/* ITS#4615 - overlays configured above the glue overlay should be + * invoked for the entire glued tree. Overlays configured below the + * glue overlay should only be invoked on the primary backend. + * So, if we're searching on any subordinates, we need to force the + * current overlay chain to stop processing, without stopping the + * overall callback flow. + */ +static int +glue_sub_search( Operation *op, SlapReply *rs, BackendDB *b0, + slap_overinst *on ) +{ + /* Process any overlays on the primary backend */ + if ( op->o_bd == b0 && on->on_next ) { + BackendInfo *bi = op->o_bd->bd_info; + int rc = SLAP_CB_CONTINUE; + for ( on=on->on_next; on; on=on->on_next ) { + op->o_bd->bd_info = (BackendInfo *)on; + if ( on->on_bi.bi_op_search ) { + rc = on->on_bi.bi_op_search( op, rs ); + if ( rc != SLAP_CB_CONTINUE ) + break; + } + } + op->o_bd->bd_info = bi; + if ( rc != SLAP_CB_CONTINUE ) + return rc; + } + return op->o_bd->be_search( op, rs ); +} + +static const ID glueID = NOID; +static const struct berval gluecookie = { sizeof( glueID ), (char *)&glueID }; + +static int +glue_op_search ( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + glueinfo *gi = (glueinfo *)on->on_bi.bi_private; + BackendDB *b0 = op->o_bd; + BackendDB *b1 = NULL, *btmp; + BackendInfo *bi0 = op->o_bd->bd_info; + int i; + long stoptime = 0, starttime; + glue_state gs = {NULL, NULL, NULL, 0, 0, 0, 0}; + slap_callback cb = { NULL, glue_op_response, glue_op_cleanup, NULL }; + int scope0, tlimit0; + struct berval dn, ndn, *pdn; + + cb.sc_private = &gs; + + cb.sc_next = op->o_callback; + + starttime = op->o_time; + stoptime = slap_get_time () + op->ors_tlimit; + + /* reset dummy cookie used to keep paged results going across databases */ + if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED + && bvmatch( &((PagedResultsState *)op->o_pagedresults_state)->ps_cookieval, &gluecookie ) ) + { + PagedResultsState *ps = op->o_pagedresults_state; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + struct berval cookie = BER_BVC(""), value; + int c; + + for (c = 0; op->o_ctrls[c] != NULL; c++) { + if (strcmp(op->o_ctrls[c]->ldctl_oid, LDAP_CONTROL_PAGEDRESULTS) == 0) + break; + } + + assert( op->o_ctrls[c] != NULL ); + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_printf( ber, "{iO}", ps->ps_size, &cookie ); + ber_flatten2( ber, &value, 0 ); + assert( op->o_ctrls[c]->ldctl_value.bv_len >= value.bv_len ); + op->o_ctrls[c]->ldctl_value.bv_len = value.bv_len; + lutil_memcopy( op->o_ctrls[c]->ldctl_value.bv_val, + value.bv_val, value.bv_len ); + ber_free_buf( ber ); + + ps->ps_cookie = (PagedResultsCookie)0; + BER_BVZERO( &ps->ps_cookieval ); + } + + op->o_bd = glue_back_select (b0, &op->o_req_ndn); + b0->bd_info = on->on_info->oi_orig; + + switch (op->ors_scope) { + case LDAP_SCOPE_BASE: + if ( op->o_bd == b0 ) + return SLAP_CB_CONTINUE; + + if (op->o_bd && op->o_bd->be_search) { + rs->sr_err = op->o_bd->be_search( op, rs ); + } else { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + } + return rs->sr_err; + + case LDAP_SCOPE_ONELEVEL: + case LDAP_SCOPE_SUBTREE: + case LDAP_SCOPE_SUBORDINATE: /* FIXME */ + op->o_callback = &cb; + rs->sr_err = gs.err = LDAP_UNWILLING_TO_PERFORM; + scope0 = op->ors_scope; + tlimit0 = op->ors_tlimit; + dn = op->o_req_dn; + ndn = op->o_req_ndn; + b1 = op->o_bd; + + /* + * Execute in reverse order, most specific first + */ + for (i = gi->gi_nodes; i >= 0; i--) { + if ( i == gi->gi_nodes ) { + btmp = b0; + pdn = &gi->gi_pdn; + } else { + btmp = gi->gi_n[i].gn_be; + pdn = &gi->gi_n[i].gn_pdn; + } + if (!btmp || !btmp->be_search) + continue; + if (!dnIsSuffix(&btmp->be_nsuffix[0], &b1->be_nsuffix[0])) + continue; + if (get_no_subordinate_glue(op) && btmp != b1) + continue; + /* If we remembered which backend we were on before, + * skip down to it now + */ + if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED && + op->o_conn->c_pagedresults_state.ps_be && + op->o_conn->c_pagedresults_state.ps_be != btmp ) + continue; + + if (tlimit0 != SLAP_NO_LIMIT) { + op->o_time = slap_get_time(); + op->ors_tlimit = stoptime - op->o_time; + if (op->ors_tlimit <= 0) { + rs->sr_err = gs.err = LDAP_TIMELIMIT_EXCEEDED; + break; + } + } + rs->sr_err = 0; + /* + * check for abandon + */ + if (op->o_abandon) { + goto end_of_loop; + } + op->o_bd = btmp; + + assert( op->o_bd->be_suffix != NULL ); + assert( op->o_bd->be_nsuffix != NULL ); + + if (scope0 == LDAP_SCOPE_ONELEVEL && + dn_match(pdn, &ndn)) + { + struct berval mdn, mndn; + op->ors_scope = LDAP_SCOPE_BASE; + mdn = op->o_req_dn = op->o_bd->be_suffix[0]; + mndn = op->o_req_ndn = op->o_bd->be_nsuffix[0]; + rs->sr_err = op->o_bd->be_search(op, rs); + if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) { + gs.err = LDAP_SUCCESS; + } + op->ors_scope = LDAP_SCOPE_ONELEVEL; + if ( op->o_req_dn.bv_val == mdn.bv_val ) + op->o_req_dn = dn; + if ( op->o_req_ndn.bv_val == mndn.bv_val ) + op->o_req_ndn = ndn; + + } else if (scope0 == LDAP_SCOPE_SUBTREE && + dn_match(&op->o_bd->be_nsuffix[0], &ndn)) + { + rs->sr_err = glue_sub_search( op, rs, b0, on ); + + } else if (scope0 == LDAP_SCOPE_SUBTREE && + dnIsSuffix(&op->o_bd->be_nsuffix[0], &ndn)) + { + struct berval mdn, mndn; + mdn = op->o_req_dn = op->o_bd->be_suffix[0]; + mndn = op->o_req_ndn = op->o_bd->be_nsuffix[0]; + rs->sr_err = glue_sub_search( op, rs, b0, on ); + if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) { + gs.err = LDAP_SUCCESS; + } + if ( op->o_req_dn.bv_val == mdn.bv_val ) + op->o_req_dn = dn; + if ( op->o_req_ndn.bv_val == mndn.bv_val ) + op->o_req_ndn = ndn; + + } else if (dnIsSuffix(&ndn, &op->o_bd->be_nsuffix[0])) { + rs->sr_err = glue_sub_search( op, rs, b0, on ); + } + + switch ( gs.err ) { + + /* + * Add errors that should result in dropping + * the search + */ + case LDAP_SIZELIMIT_EXCEEDED: + case LDAP_TIMELIMIT_EXCEEDED: + case LDAP_ADMINLIMIT_EXCEEDED: + case LDAP_NO_SUCH_OBJECT: +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + case LDAP_X_CANNOT_CHAIN: +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + goto end_of_loop; + + case LDAP_SUCCESS: + if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) { + PagedResultsState *ps = op->o_pagedresults_state; + + /* Assume this backend can be forgotten now */ + op->o_conn->c_pagedresults_state.ps_be = NULL; + + /* If we have a full page, exit the loop. We may + * need to remember this backend so we can continue + * from here on a subsequent request. + */ + if ( rs->sr_nentries >= ps->ps_size ) { + PagedResultsState *cps = &op->o_conn->c_pagedresults_state; + + /* Don't bother to remember the first backend. + * Only remember the last one if there's more state left. + */ + if ( op->o_bd != b0 && + ( cps->ps_cookie != NOID + || !BER_BVISNULL( &cps->ps_cookieval ) + || op->o_bd != gi->gi_n[0].gn_be ) ) + { + op->o_conn->c_pagedresults_state.ps_be = op->o_bd; + } + + /* Check whether the cookie is empty, + * and give remaining databases a chance + */ + if ( op->o_bd != gi->gi_n[0].gn_be || cps->ps_cookie == NOID ) { + int c; + + for ( c = 0; gs.ctrls[c] != NULL; c++ ) { + if ( strcmp( gs.ctrls[c]->ldctl_oid, LDAP_CONTROL_PAGEDRESULTS ) == 0 ) { + break; + } + } + + if ( gs.ctrls[c] != NULL ) { + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_tag_t tag; + ber_int_t size; + struct berval cookie, value; + + ber_init2( ber, &gs.ctrls[c]->ldctl_value, LBER_USE_DER ); + + tag = ber_scanf( ber, "{im}", &size, &cookie ); + assert( tag != LBER_ERROR ); + + if ( BER_BVISEMPTY( &cookie ) && op->o_bd != gi->gi_n[0].gn_be ) { + /* delete old, create new cookie with NOID */ + PagedResultsCookie respcookie = (PagedResultsCookie)NOID; + ber_len_t oidlen = strlen( gs.ctrls[c]->ldctl_oid ); + LDAPControl *newctrl; + + /* it's next database's turn */ + if ( btmp == b0 ) { + op->o_conn->c_pagedresults_state.ps_be = gi->gi_n[gi->gi_nodes - 1].gn_be; + + } else { + op->o_conn->c_pagedresults_state.ps_be = gi->gi_n[(i > 0 ? i - 1: 0)].gn_be; + } + + cookie.bv_val = (char *)&respcookie; + cookie.bv_len = sizeof( PagedResultsCookie ); + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_printf( ber, "{iO}", 0, &cookie ); + ber_flatten2( ber, &value, 0 ); + + newctrl = op->o_tmprealloc( gs.ctrls[c], + sizeof(LDAPControl) + oidlen + 1 + value.bv_len + 1, + op->o_tmpmemctx); + newctrl->ldctl_iscritical = gs.ctrls[c]->ldctl_iscritical; + newctrl->ldctl_oid = (char *)&newctrl[1]; + lutil_strcopy( newctrl->ldctl_oid, gs.ctrls[c]->ldctl_oid ); + newctrl->ldctl_value.bv_len = value.bv_len; + lutil_memcopy( newctrl->ldctl_value.bv_val, + value.bv_val, value.bv_len ); + + gs.ctrls[c] = newctrl; + + ber_free_buf( ber ); + + } else if ( !BER_BVISEMPTY( &cookie ) && op->o_bd != b0 ) { + /* if cookie not empty, it's again this database's turn */ + op->o_conn->c_pagedresults_state.ps_be = op->o_bd; + } + } + } + + goto end_of_loop; + } + + /* This backend has run out of entries, but more responses + * can fit in the page. Fake a reset of the state so the + * next backend will start up properly. Only back-[bh]db + * and back-sql look at this state info. + */ + ps->ps_cookie = (PagedResultsCookie)0; + BER_BVZERO( &ps->ps_cookieval ); + + { + /* change the size of the page in the request + * that will be propagated, and reset the cookie */ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + int size = ps->ps_size - rs->sr_nentries; + struct berval cookie = BER_BVC(""), value; + int c; + + for (c = 0; op->o_ctrls[c] != NULL; c++) { + if (strcmp(op->o_ctrls[c]->ldctl_oid, LDAP_CONTROL_PAGEDRESULTS) == 0) + break; + } + + assert( op->o_ctrls[c] != NULL ); + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_printf( ber, "{iO}", size, &cookie ); + ber_flatten2( ber, &value, 0 ); + assert( op->o_ctrls[c]->ldctl_value.bv_len >= value.bv_len ); + op->o_ctrls[c]->ldctl_value.bv_len = value.bv_len; + lutil_memcopy( op->o_ctrls[c]->ldctl_value.bv_val, + value.bv_val, value.bv_len ); + ber_free_buf( ber ); + } + } + + default: + break; + } + } +end_of_loop:; + op->ors_scope = scope0; + op->ors_tlimit = tlimit0; + op->o_time = starttime; + + break; + } + + op->o_callback = cb.sc_next; + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + } else { + rs->sr_err = gs.err; + rs->sr_matched = gs.matched; + rs->sr_ref = gs.refs; + } + rs->sr_ctrls = gs.ctrls; + + send_ldap_result( op, rs ); + + op->o_bd = b0; + op->o_bd->bd_info = bi0; + if (gs.matched) + free (gs.matched); + if (gs.refs) + ber_bvarray_free(gs.refs); + if (gs.ctrls) { + for (i = gs.nctrls; --i >= 0; ) { + op->o_tmpfree(gs.ctrls[i], op->o_tmpmemctx); + } + op->o_tmpfree(gs.ctrls, op->o_tmpmemctx); + } + return rs->sr_err; +} + +static BackendDB toolDB; + +static int +glue_tool_entry_open ( + BackendDB *b0, + int mode +) +{ + slap_overinfo *oi = (slap_overinfo *)b0->bd_info; + + /* We don't know which backend to talk to yet, so just + * remember the mode and move on... + */ + + glueMode = mode; + glueBack = NULL; + toolDB = *b0; + toolDB.bd_info = oi->oi_orig; + + /* Sanity checks */ + { + slap_overinst *on = glue_tool_inst( b0->bd_info ); + glueinfo *gi = on->on_bi.bi_private; + + int i; + for (i = 0; i < gi->gi_nodes; i++) { + BackendDB *bd; + struct berval pdn; + + dnParent( &gi->gi_n[i].gn_be->be_nsuffix[0], &pdn ); + bd = select_backend( &pdn, 0 ); + if ( bd ) { + ID id; + BackendDB db; + + if ( overlay_is_over( bd ) ) { + slap_overinfo *oi = (slap_overinfo *)bd->bd_info; + db = *bd; + db.bd_info = oi->oi_orig; + bd = &db; + } + + if ( !bd->bd_info->bi_tool_dn2id_get + || !bd->bd_info->bi_tool_entry_open + || !bd->bd_info->bi_tool_entry_close ) + { + continue; + } + + bd->bd_info->bi_tool_entry_open( bd, 0 ); + id = bd->bd_info->bi_tool_dn2id_get( bd, &gi->gi_n[i].gn_be->be_nsuffix[0] ); + bd->bd_info->bi_tool_entry_close( bd ); + if ( id != NOID ) { + Debug( LDAP_DEBUG_ANY, + "glue_tool_entry_open: subordinate database suffix entry DN=\"%s\" also present in superior database rooted at DN=\"%s\"\n", + gi->gi_n[i].gn_be->be_suffix[0].bv_val, bd->be_suffix[0].bv_val, 0 ); + return LDAP_OTHER; + } + } + } + } + + return 0; +} + +static int +glue_tool_entry_close ( + BackendDB *b0 +) +{ + int rc = 0; + + if (glueBack && glueBack != GLUEBACK_DONE) { + if (!glueBack->be_entry_close) + return 0; + rc = glueBack->be_entry_close (glueBack); + } + return rc; +} + +static slap_overinst * +glue_tool_inst( + BackendInfo *bi +) +{ + slap_overinfo *oi = (slap_overinfo *)bi; + slap_overinst *on; + + for ( on = oi->oi_list; on; on=on->on_next ) { + if ( !strcmp( on->on_bi.bi_type, glue.on_bi.bi_type )) + return on; + } + return NULL; +} + +/* This function will only be called in tool mode */ +static int +glue_open ( + BackendInfo *bi +) +{ + slap_overinst *on = glue_tool_inst( bi ); + glueinfo *gi = on->on_bi.bi_private; + static int glueOpened = 0; + int i, j, same, bsame = 0, rc = 0; + ConfigReply cr = {0}; + + if (glueOpened) return 0; + + glueOpened = 1; + + /* If we were invoked in tool mode, open all the underlying backends */ + if (slapMode & SLAP_TOOL_MODE) { + for (i = 0; i<gi->gi_nodes; i++) { + same = 0; + /* Same bi_open as our main backend? */ + if ( gi->gi_n[i].gn_be->bd_info->bi_open == + on->on_info->oi_orig->bi_open ) + bsame = 1; + + /* Loop thru the bd_info's and make sure we only + * invoke their bi_open functions once each. + */ + for ( j = 0; j<i; j++ ) { + if ( gi->gi_n[i].gn_be->bd_info->bi_open == + gi->gi_n[j].gn_be->bd_info->bi_open ) { + same = 1; + break; + } + } + /* OK, it's unique and non-NULL, call it. */ + if ( !same && gi->gi_n[i].gn_be->bd_info->bi_open ) + rc = gi->gi_n[i].gn_be->bd_info->bi_open( + gi->gi_n[i].gn_be->bd_info ); + /* Let backend.c take care of the rest of startup */ + if ( !rc ) + rc = backend_startup_one( gi->gi_n[i].gn_be, &cr ); + if ( rc ) break; + } + if ( !rc && !bsame && on->on_info->oi_orig->bi_open ) + rc = on->on_info->oi_orig->bi_open( on->on_info->oi_orig ); + + } /* other case is impossible */ + return rc; +} + +/* This function will only be called in tool mode */ +static int +glue_close ( + BackendInfo *bi +) +{ + static int glueClosed = 0; + int rc = 0; + + if (glueClosed) return 0; + + glueClosed = 1; + + if (slapMode & SLAP_TOOL_MODE) { + rc = backend_shutdown( NULL ); + } + return rc; +} + +static int +glue_entry_get_rw ( + Operation *op, + struct berval *dn, + ObjectClass *oc, + AttributeDescription *ad, + int rw, + Entry **e ) +{ + int rc; + BackendDB *b0 = op->o_bd; + op->o_bd = glue_back_select( b0, dn ); + + if ( op->o_bd->be_fetch ) { + rc = op->o_bd->be_fetch( op, dn, oc, ad, rw, e ); + } else { + rc = LDAP_UNWILLING_TO_PERFORM; + } + op->o_bd =b0; + return rc; +} + +static int +glue_entry_release_rw ( + Operation *op, + Entry *e, + int rw +) +{ + BackendDB *b0 = op->o_bd; + int rc = -1; + + op->o_bd = glue_back_select (b0, &e->e_nname); + + if ( op->o_bd->be_release ) { + rc = op->o_bd->be_release( op, e, rw ); + + } else { + /* FIXME: mimic be_entry_release_rw + * when no be_release() available */ + /* free entry */ + entry_free( e ); + rc = 0; + } + op->o_bd = b0; + return rc; +} + +static struct berval *glue_base; +static int glue_scope; +static Filter *glue_filter; + +static ID +glue_tool_entry_first ( + BackendDB *b0 +) +{ + slap_overinst *on = glue_tool_inst( b0->bd_info ); + glueinfo *gi = on->on_bi.bi_private; + int i; + ID rc; + + /* If we're starting from scratch, start at the most general */ + if (!glueBack) { + if ( toolDB.be_entry_open && toolDB.be_entry_first ) { + glueBack = &toolDB; + } else { + for (i = gi->gi_nodes-1; i >= 0; i--) { + if (gi->gi_n[i].gn_be->be_entry_open && + gi->gi_n[i].gn_be->be_entry_first) { + glueBack = gi->gi_n[i].gn_be; + break; + } + } + } + } + if (!glueBack || !glueBack->be_entry_open || !glueBack->be_entry_first || + glueBack->be_entry_open (glueBack, glueMode) != 0) + return NOID; + + rc = glueBack->be_entry_first (glueBack); + while ( rc == NOID ) { + if ( glueBack && glueBack->be_entry_close ) + glueBack->be_entry_close (glueBack); + for (i=0; i<gi->gi_nodes; i++) { + if (gi->gi_n[i].gn_be == glueBack) + break; + } + if (i == 0) { + glueBack = GLUEBACK_DONE; + break; + } else { + glueBack = gi->gi_n[i-1].gn_be; + rc = glue_tool_entry_first (b0); + if ( glueBack == GLUEBACK_DONE ) { + break; + } + } + } + return rc; +} + +static ID +glue_tool_entry_first_x ( + BackendDB *b0, + struct berval *base, + int scope, + Filter *f +) +{ + slap_overinst *on = glue_tool_inst( b0->bd_info ); + glueinfo *gi = on->on_bi.bi_private; + int i; + ID rc; + + glue_base = base; + glue_scope = scope; + glue_filter = f; + + /* If we're starting from scratch, start at the most general */ + if (!glueBack) { + if ( toolDB.be_entry_open && toolDB.be_entry_first_x ) { + glueBack = &toolDB; + } else { + for (i = gi->gi_nodes-1; i >= 0; i--) { + if (gi->gi_n[i].gn_be->be_entry_open && + gi->gi_n[i].gn_be->be_entry_first_x) + { + glueBack = gi->gi_n[i].gn_be; + break; + } + } + } + } + if (!glueBack || !glueBack->be_entry_open || !glueBack->be_entry_first_x || + glueBack->be_entry_open (glueBack, glueMode) != 0) + return NOID; + + rc = glueBack->be_entry_first_x (glueBack, + glue_base, glue_scope, glue_filter); + while ( rc == NOID ) { + if ( glueBack && glueBack->be_entry_close ) + glueBack->be_entry_close (glueBack); + for (i=0; i<gi->gi_nodes; i++) { + if (gi->gi_n[i].gn_be == glueBack) + break; + } + if (i == 0) { + glueBack = GLUEBACK_DONE; + break; + } else { + glueBack = gi->gi_n[i-1].gn_be; + rc = glue_tool_entry_first_x (b0, + glue_base, glue_scope, glue_filter); + if ( glueBack == GLUEBACK_DONE ) { + break; + } + } + } + return rc; +} + +static ID +glue_tool_entry_next ( + BackendDB *b0 +) +{ + slap_overinst *on = glue_tool_inst( b0->bd_info ); + glueinfo *gi = on->on_bi.bi_private; + int i; + ID rc; + + if (!glueBack || !glueBack->be_entry_next) + return NOID; + + rc = glueBack->be_entry_next (glueBack); + + /* If we ran out of entries in one database, move on to the next */ + while (rc == NOID) { + if ( glueBack && glueBack->be_entry_close ) + glueBack->be_entry_close (glueBack); + for (i=0; i<gi->gi_nodes; i++) { + if (gi->gi_n[i].gn_be == glueBack) + break; + } + if (i == 0) { + glueBack = GLUEBACK_DONE; + break; + } else { + glueBack = gi->gi_n[i-1].gn_be; + if ( glue_base || glue_filter ) { + /* using entry_first_x() */ + rc = glue_tool_entry_first_x (b0, + glue_base, glue_scope, glue_filter); + + } else { + /* using entry_first() */ + rc = glue_tool_entry_first (b0); + } + if ( glueBack == GLUEBACK_DONE ) { + break; + } + } + } + return rc; +} + +static ID +glue_tool_dn2id_get ( + BackendDB *b0, + struct berval *dn +) +{ + BackendDB *be, b2; + int rc = -1; + + b2 = *b0; + b2.bd_info = (BackendInfo *)glue_tool_inst( b0->bd_info ); + be = glue_back_select (&b2, dn); + if ( be == &b2 ) be = &toolDB; + + if (!be->be_dn2id_get) + return NOID; + + if (!glueBack) { + if ( be->be_entry_open ) { + rc = be->be_entry_open (be, glueMode); + } + if (rc != 0) { + return NOID; + } + } else if (be != glueBack) { + /* If this entry belongs in a different branch than the + * previous one, close the current database and open the + * new one. + */ + if ( glueBack->be_entry_close ) { + glueBack->be_entry_close (glueBack); + } + if ( be->be_entry_open ) { + rc = be->be_entry_open (be, glueMode); + } + if (rc != 0) { + return NOID; + } + } + glueBack = be; + return be->be_dn2id_get (be, dn); +} + +static Entry * +glue_tool_entry_get ( + BackendDB *b0, + ID id +) +{ + if (!glueBack || !glueBack->be_entry_get) + return NULL; + + return glueBack->be_entry_get (glueBack, id); +} + +static ID +glue_tool_entry_put ( + BackendDB *b0, + Entry *e, + struct berval *text +) +{ + BackendDB *be, b2; + int rc = -1; + + b2 = *b0; + b2.bd_info = (BackendInfo *)glue_tool_inst( b0->bd_info ); + be = glue_back_select (&b2, &e->e_nname); + if ( be == &b2 ) be = &toolDB; + + if (!be->be_entry_put) + return NOID; + + if (!glueBack) { + if ( be->be_entry_open ) { + rc = be->be_entry_open (be, glueMode); + } + if (rc != 0) { + return NOID; + } + } else if (be != glueBack) { + /* If this entry belongs in a different branch than the + * previous one, close the current database and open the + * new one. + */ + if ( glueBack->be_entry_close ) { + glueBack->be_entry_close (glueBack); + } + if ( be->be_entry_open ) { + rc = be->be_entry_open (be, glueMode); + } + if (rc != 0) { + return NOID; + } + } + glueBack = be; + return be->be_entry_put (be, e, text); +} + +static ID +glue_tool_entry_modify ( + BackendDB *b0, + Entry *e, + struct berval *text +) +{ + if (!glueBack || !glueBack->be_entry_modify) + return NOID; + + return glueBack->be_entry_modify (glueBack, e, text); +} + +static int +glue_tool_entry_reindex ( + BackendDB *b0, + ID id, + AttributeDescription **adv +) +{ + if (!glueBack || !glueBack->be_entry_reindex) + return -1; + + return glueBack->be_entry_reindex (glueBack, id, adv); +} + +static int +glue_tool_sync ( + BackendDB *b0 +) +{ + slap_overinst *on = glue_tool_inst( b0->bd_info ); + glueinfo *gi = on->on_bi.bi_private; + BackendInfo *bi = b0->bd_info; + int i; + + /* just sync everyone */ + for (i = 0; i<gi->gi_nodes; i++) + if (gi->gi_n[i].gn_be->be_sync) + gi->gi_n[i].gn_be->be_sync (gi->gi_n[i].gn_be); + b0->bd_info = on->on_info->oi_orig; + if ( b0->be_sync ) + b0->be_sync( b0 ); + b0->bd_info = bi; + return 0; +} + +typedef struct glue_Addrec { + struct glue_Addrec *ga_next; + BackendDB *ga_be; +} glue_Addrec; + +/* List of added subordinates */ +static glue_Addrec *ga_list; +static int ga_adding; + +static int +glue_db_init( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + slap_overinfo *oi = on->on_info; + BackendInfo *bi = oi->oi_orig; + glueinfo *gi; + + if ( SLAP_GLUE_SUBORDINATE( be )) { + Debug( LDAP_DEBUG_ANY, "glue: backend %s is already subordinate, " + "cannot have glue overlay!\n", + be->be_suffix[0].bv_val, 0, 0 ); + return LDAP_OTHER; + } + + gi = ch_calloc( 1, sizeof(glueinfo)); + on->on_bi.bi_private = gi; + dnParent( be->be_nsuffix, &gi->gi_pdn ); + + /* Currently the overlay framework doesn't handle these entry points + * but we need them.... + */ + oi->oi_bi.bi_open = glue_open; + oi->oi_bi.bi_close = glue_close; + + /* Only advertise these if the root DB supports them */ + if ( bi->bi_tool_entry_open ) + oi->oi_bi.bi_tool_entry_open = glue_tool_entry_open; + if ( bi->bi_tool_entry_close ) + oi->oi_bi.bi_tool_entry_close = glue_tool_entry_close; + if ( bi->bi_tool_entry_first ) + oi->oi_bi.bi_tool_entry_first = glue_tool_entry_first; + /* FIXME: check whether all support bi_tool_entry_first_x() ? */ + if ( bi->bi_tool_entry_first_x ) + oi->oi_bi.bi_tool_entry_first_x = glue_tool_entry_first_x; + if ( bi->bi_tool_entry_next ) + oi->oi_bi.bi_tool_entry_next = glue_tool_entry_next; + if ( bi->bi_tool_entry_get ) + oi->oi_bi.bi_tool_entry_get = glue_tool_entry_get; + if ( bi->bi_tool_dn2id_get ) + oi->oi_bi.bi_tool_dn2id_get = glue_tool_dn2id_get; + if ( bi->bi_tool_entry_put ) + oi->oi_bi.bi_tool_entry_put = glue_tool_entry_put; + if ( bi->bi_tool_entry_reindex ) + oi->oi_bi.bi_tool_entry_reindex = glue_tool_entry_reindex; + if ( bi->bi_tool_entry_modify ) + oi->oi_bi.bi_tool_entry_modify = glue_tool_entry_modify; + if ( bi->bi_tool_sync ) + oi->oi_bi.bi_tool_sync = glue_tool_sync; + + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_GLUE_INSTANCE; + + if ( ga_list && ( slapMode & SLAP_SERVER_MODE ) ) { + be->bd_info = (BackendInfo *)oi; + glue_sub_attach( 1 ); + } + + return 0; +} + +static int +glue_db_destroy ( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + glueinfo *gi = (glueinfo *)on->on_bi.bi_private; + + free (gi); + return SLAP_CB_CONTINUE; +} + +static int +glue_db_close( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + + on->on_info->oi_bi.bi_db_close = 0; + return 0; +} + +int +glue_sub_del( BackendDB *b0 ) +{ + BackendDB *be; + int rc = 0; + + /* Find the top backend for this subordinate */ + be = b0; + while ( (be=LDAP_STAILQ_NEXT( be, be_next )) != NULL ) { + slap_overinfo *oi; + slap_overinst *on; + glueinfo *gi; + int i; + + if ( SLAP_GLUE_SUBORDINATE( be )) + continue; + if ( !SLAP_GLUE_INSTANCE( be )) + continue; + if ( !dnIsSuffix( &b0->be_nsuffix[0], &be->be_nsuffix[0] )) + continue; + + /* OK, got the right backend, find the overlay */ + oi = (slap_overinfo *)be->bd_info; + for ( on=oi->oi_list; on; on=on->on_next ) { + if ( on->on_bi.bi_type == glue.on_bi.bi_type ) + break; + } + assert( on != NULL ); + gi = on->on_bi.bi_private; + for ( i=0; i < gi->gi_nodes; i++ ) { + if ( gi->gi_n[i].gn_be == b0 ) { + int j; + + for (j=i+1; j < gi->gi_nodes; j++) + gi->gi_n[j-1] = gi->gi_n[j]; + + gi->gi_nodes--; + } + } + } + if ( be == NULL ) + rc = LDAP_NO_SUCH_OBJECT; + + return rc; +} + + +/* Attach all the subordinate backends to their superior */ +int +glue_sub_attach( int online ) +{ + glue_Addrec *ga, *gnext = NULL; + int rc = 0; + + if ( ga_adding ) + return 0; + + ga_adding = 1; + + /* For all the subordinate backends */ + for ( ga=ga_list; ga != NULL; ga = gnext ) { + BackendDB *be; + + gnext = ga->ga_next; + + /* Find the top backend for this subordinate */ + be = ga->ga_be; + while ( (be=LDAP_STAILQ_NEXT( be, be_next )) != NULL ) { + slap_overinfo *oi; + slap_overinst *on; + glueinfo *gi; + + if ( SLAP_GLUE_SUBORDINATE( be )) + continue; + if ( !dnIsSuffix( &ga->ga_be->be_nsuffix[0], &be->be_nsuffix[0] )) + continue; + + /* If it's not already configured, set up the overlay */ + if ( !SLAP_GLUE_INSTANCE( be )) { + rc = overlay_config( be, glue.on_bi.bi_type, -1, NULL, NULL); + if ( rc ) + break; + } + /* Find the overlay instance */ + oi = (slap_overinfo *)be->bd_info; + for ( on=oi->oi_list; on; on=on->on_next ) { + if ( on->on_bi.bi_type == glue.on_bi.bi_type ) + break; + } + assert( on != NULL ); + gi = on->on_bi.bi_private; + gi = (glueinfo *)ch_realloc( gi, sizeof(glueinfo) + + gi->gi_nodes * sizeof(gluenode)); + gi->gi_n[gi->gi_nodes].gn_be = ga->ga_be; + dnParent( &ga->ga_be->be_nsuffix[0], + &gi->gi_n[gi->gi_nodes].gn_pdn ); + gi->gi_nodes++; + on->on_bi.bi_private = gi; + ga->ga_be->be_flags |= SLAP_DBFLAG_GLUE_LINKED; + break; + } + if ( !be ) { + Debug( LDAP_DEBUG_ANY, "glue: no superior found for sub %s!\n", + ga->ga_be->be_suffix[0].bv_val, 0, 0 ); + /* allow this for now, assume a superior will + * be added later + */ + if ( online ) { + rc = 0; + gnext = ga_list; + break; + } + rc = LDAP_NO_SUCH_OBJECT; + } + ch_free( ga ); + if ( rc ) break; + } + + ga_list = gnext; + + ga_adding = 0; + + return rc; +} + +int +glue_sub_add( BackendDB *be, int advert, int online ) +{ + glue_Addrec *ga; + int rc = 0; + + if ( overlay_is_inst( be, "glue" )) { + Debug( LDAP_DEBUG_ANY, "glue: backend %s already has glue overlay, " + "cannot be a subordinate!\n", + be->be_suffix[0].bv_val, 0, 0 ); + return LDAP_OTHER; + } + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_GLUE_SUBORDINATE; + if ( advert ) + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_GLUE_ADVERTISE; + + ga = ch_malloc( sizeof( glue_Addrec )); + ga->ga_next = ga_list; + ga->ga_be = be; + ga_list = ga; + + if ( online ) + rc = glue_sub_attach( online ); + + return rc; +} + +static int +glue_access_allowed( + Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + slap_access_t access, + AccessControlState *state, + slap_mask_t *maskp ) +{ + BackendDB *b0, *be = glue_back_select( op->o_bd, &e->e_nname ); + int rc; + + if ( be == NULL || be == op->o_bd || be->bd_info->bi_access_allowed == NULL ) + return SLAP_CB_CONTINUE; + + b0 = op->o_bd; + op->o_bd = be; + rc = be->bd_info->bi_access_allowed ( op, e, desc, val, access, state, maskp ); + op->o_bd = b0; + return rc; +} + +int +glue_sub_init() +{ + glue.on_bi.bi_type = "glue"; + + glue.on_bi.bi_db_init = glue_db_init; + glue.on_bi.bi_db_close = glue_db_close; + glue.on_bi.bi_db_destroy = glue_db_destroy; + + glue.on_bi.bi_op_search = glue_op_search; + glue.on_bi.bi_op_modify = glue_op_func; + glue.on_bi.bi_op_modrdn = glue_op_func; + glue.on_bi.bi_op_add = glue_op_func; + glue.on_bi.bi_op_delete = glue_op_func; + glue.on_bi.bi_op_abandon = glue_op_abandon; + glue.on_bi.bi_extended = glue_op_func; + + glue.on_bi.bi_chk_referrals = glue_chk_referrals; + glue.on_bi.bi_chk_controls = glue_chk_controls; + glue.on_bi.bi_entry_get_rw = glue_entry_get_rw; + glue.on_bi.bi_entry_release_rw = glue_entry_release_rw; + glue.on_bi.bi_access_allowed = glue_access_allowed; + + glue.on_response = glue_response; + + return overlay_register( &glue ); +} diff --git a/servers/slapd/backover.c b/servers/slapd/backover.c new file mode 100644 index 0000000..f47e64d --- /dev/null +++ b/servers/slapd/backover.c @@ -0,0 +1,1440 @@ +/* backover.c - backend overlay routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +/* Functions to overlay other modules over a backend. */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#define SLAPD_TOOLS +#include "slap.h" +#include "config.h" + +static slap_overinst *overlays; + +static int +over_db_config( + BackendDB *be, + const char *fname, + int lineno, + int argc, + char **argv +) +{ + slap_overinfo *oi = be->bd_info->bi_private; + slap_overinst *on = oi->oi_list; + BackendInfo *bi_orig = be->bd_info; + struct ConfigOCs *be_cf_ocs = be->be_cf_ocs; + ConfigArgs ca = {0}; + int rc = 0; + + if ( oi->oi_orig->bi_db_config ) { + be->bd_info = oi->oi_orig; + be->be_cf_ocs = oi->oi_orig->bi_cf_ocs; + rc = oi->oi_orig->bi_db_config( be, fname, lineno, + argc, argv ); + + if ( be->bd_info != oi->oi_orig ) { + slap_overinfo *oi2; + slap_overinst *on2, **onp; + BackendDB be2 = *be; + int i; + + /* a database added an overlay; + * work it around... */ + assert( overlay_is_over( be ) ); + + oi2 = ( slap_overinfo * )be->bd_info->bi_private; + on2 = oi2->oi_list; + + /* need to put a uniqueness check here as well; + * note that in principle there could be more than + * one overlay as a result of multiple calls to + * overlay_config() */ + be2.bd_info = (BackendInfo *)oi; + + for ( i = 0, onp = &on2; *onp; i++, onp = &(*onp)->on_next ) { + if ( overlay_is_inst( &be2, (*onp)->on_bi.bi_type ) ) { + Debug( LDAP_DEBUG_ANY, "over_db_config(): " + "warning, freshly added " + "overlay #%d \"%s\" is already in list\n", + i, (*onp)->on_bi.bi_type, 0 ); + + /* NOTE: if the overlay already exists, + * there is no way to merge the results + * of the configuration that may have + * occurred during bi_db_config(); we + * just issue a warning, and the + * administrator should deal with this */ + } + } + *onp = oi->oi_list; + + oi->oi_list = on2; + + ch_free( be->bd_info ); + } + + be->bd_info = (BackendInfo *)oi; + if ( rc != SLAP_CONF_UNKNOWN ) return rc; + } + + ca.argv = argv; + ca.argc = argc; + ca.fname = fname; + ca.lineno = lineno; + ca.be = be; + snprintf( ca.log, sizeof( ca.log ), "%s: line %d", + ca.fname, ca.lineno ); + ca.op = SLAP_CONFIG_ADD; + ca.valx = -1; + + for (; on; on=on->on_next) { + rc = SLAP_CONF_UNKNOWN; + if (on->on_bi.bi_cf_ocs) { + ConfigTable *ct; + ca.bi = &on->on_bi; + ct = config_find_keyword( on->on_bi.bi_cf_ocs->co_table, &ca ); + if ( ct ) { + ca.table = on->on_bi.bi_cf_ocs->co_type; + rc = config_add_vals( ct, &ca ); + if ( rc != SLAP_CONF_UNKNOWN ) + break; + } + } + if (on->on_bi.bi_db_config && rc == SLAP_CONF_UNKNOWN) { + be->bd_info = &on->on_bi; + rc = on->on_bi.bi_db_config( be, fname, lineno, + argc, argv ); + if ( rc != SLAP_CONF_UNKNOWN ) break; + } + } + be->bd_info = bi_orig; + be->be_cf_ocs = be_cf_ocs; + + return rc; +} + +static int +over_db_open( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinfo *oi = be->bd_info->bi_private; + slap_overinst *on = oi->oi_list; + BackendDB db = *be; + int rc = 0; + + db.be_flags |= SLAP_DBFLAG_OVERLAY; + db.bd_info = oi->oi_orig; + if ( db.bd_info->bi_db_open ) { + rc = db.bd_info->bi_db_open( &db, cr ); + } + + for (; on && rc == 0; on=on->on_next) { + db.bd_info = &on->on_bi; + if ( db.bd_info->bi_db_open ) { + rc = db.bd_info->bi_db_open( &db, cr ); + } + } + + return rc; +} + +static int +over_db_close( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinfo *oi = be->bd_info->bi_private; + slap_overinst *on = oi->oi_list; + BackendInfo *bi_orig = be->bd_info; + int rc = 0; + + for (; on && rc == 0; on=on->on_next) { + be->bd_info = &on->on_bi; + if ( be->bd_info->bi_db_close ) { + rc = be->bd_info->bi_db_close( be, cr ); + } + } + + if ( oi->oi_orig->bi_db_close ) { + be->bd_info = oi->oi_orig; + rc = be->bd_info->bi_db_close( be, cr ); + } + + be->bd_info = bi_orig; + return rc; +} + +static int +over_db_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinfo *oi = be->bd_info->bi_private; + slap_overinst *on = oi->oi_list, *next; + BackendInfo *bi_orig = be->bd_info; + int rc = 0; + + be->bd_info = oi->oi_orig; + if ( be->bd_info->bi_db_destroy ) { + rc = be->bd_info->bi_db_destroy( be, cr ); + } + + for (; on && rc == 0; on=on->on_next) { + be->bd_info = &on->on_bi; + if ( be->bd_info->bi_db_destroy ) { + rc = be->bd_info->bi_db_destroy( be, cr ); + } + } + + on = oi->oi_list; + if ( on ) { + for (next = on->on_next; on; on=next) { + next = on->on_next; + free( on ); + } + } + be->bd_info = bi_orig; + free( oi ); + return rc; +} + +static int +over_back_response ( Operation *op, SlapReply *rs ) +{ + slap_overinfo *oi = op->o_callback->sc_private; + slap_overinst *on = oi->oi_list; + int rc = SLAP_CB_CONTINUE; + BackendDB *be = op->o_bd, db = *op->o_bd; + + db.be_flags |= SLAP_DBFLAG_OVERLAY; + op->o_bd = &db; + for (; on; on=on->on_next ) { + if ( on->on_response ) { + db.bd_info = (BackendInfo *)on; + rc = on->on_response( op, rs ); + if ( rc != SLAP_CB_CONTINUE ) break; + } + } + /* Bypass the remaining on_response layers, but allow + * normal execution to continue. + */ + if ( rc == SLAP_CB_BYPASS ) + rc = SLAP_CB_CONTINUE; + op->o_bd = be; + return rc; +} + +static int +over_access_allowed( + Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + slap_access_t access, + AccessControlState *state, + slap_mask_t *maskp ) +{ + slap_overinfo *oi; + slap_overinst *on; + BackendInfo *bi; + BackendDB *be = op->o_bd, db; + int rc = SLAP_CB_CONTINUE; + + /* FIXME: used to happen for instance during abandon + * when global overlays are used... */ + assert( op->o_bd != NULL ); + + bi = op->o_bd->bd_info; + /* Were we invoked on the frontend? */ + if ( !bi->bi_access_allowed ) { + oi = frontendDB->bd_info->bi_private; + } else { + oi = op->o_bd->bd_info->bi_private; + } + on = oi->oi_list; + + for ( ; on; on = on->on_next ) { + if ( on->on_bi.bi_access_allowed ) { + /* NOTE: do not copy the structure until required */ + if ( !SLAP_ISOVERLAY( op->o_bd ) ) { + db = *op->o_bd; + db.be_flags |= SLAP_DBFLAG_OVERLAY; + op->o_bd = &db; + } + + op->o_bd->bd_info = (BackendInfo *)on; + rc = on->on_bi.bi_access_allowed( op, e, + desc, val, access, state, maskp ); + if ( rc != SLAP_CB_CONTINUE ) break; + } + } + + if ( rc == SLAP_CB_CONTINUE ) { + BI_access_allowed *bi_access_allowed; + + /* if the database structure was changed, o_bd points to a + * copy of the structure; put the original bd_info in place */ + if ( SLAP_ISOVERLAY( op->o_bd ) ) { + op->o_bd->bd_info = oi->oi_orig; + } + + if ( oi->oi_orig->bi_access_allowed ) { + bi_access_allowed = oi->oi_orig->bi_access_allowed; + } else { + bi_access_allowed = slap_access_allowed; + } + + rc = bi_access_allowed( op, e, + desc, val, access, state, maskp ); + } + /* should not fall thru this far without anything happening... */ + if ( rc == SLAP_CB_CONTINUE ) { + /* access not allowed */ + rc = 0; + } + + op->o_bd = be; + op->o_bd->bd_info = bi; + + return rc; +} + +int +overlay_entry_get_ov( + Operation *op, + struct berval *dn, + ObjectClass *oc, + AttributeDescription *ad, + int rw, + Entry **e, + slap_overinst *on ) +{ + slap_overinfo *oi = on->on_info; + BackendDB *be = op->o_bd, db; + BackendInfo *bi = op->o_bd->bd_info; + int rc = SLAP_CB_CONTINUE; + + for ( ; on; on = on->on_next ) { + if ( on->on_bi.bi_entry_get_rw ) { + /* NOTE: do not copy the structure until required */ + if ( !SLAP_ISOVERLAY( op->o_bd ) ) { + db = *op->o_bd; + db.be_flags |= SLAP_DBFLAG_OVERLAY; + op->o_bd = &db; + } + + op->o_bd->bd_info = (BackendInfo *)on; + rc = on->on_bi.bi_entry_get_rw( op, dn, + oc, ad, rw, e ); + if ( rc != SLAP_CB_CONTINUE ) break; + } + } + + if ( rc == SLAP_CB_CONTINUE ) { + /* if the database structure was changed, o_bd points to a + * copy of the structure; put the original bd_info in place */ + if ( SLAP_ISOVERLAY( op->o_bd ) ) { + op->o_bd->bd_info = oi->oi_orig; + } + + if ( oi->oi_orig->bi_entry_get_rw ) { + rc = oi->oi_orig->bi_entry_get_rw( op, dn, + oc, ad, rw, e ); + } + } + /* should not fall thru this far without anything happening... */ + if ( rc == SLAP_CB_CONTINUE ) { + rc = LDAP_UNWILLING_TO_PERFORM; + } + + op->o_bd = be; + op->o_bd->bd_info = bi; + + return rc; +} + +static int +over_entry_get_rw( + Operation *op, + struct berval *dn, + ObjectClass *oc, + AttributeDescription *ad, + int rw, + Entry **e ) +{ + slap_overinfo *oi; + slap_overinst *on; + + assert( op->o_bd != NULL ); + + oi = op->o_bd->bd_info->bi_private; + on = oi->oi_list; + + return overlay_entry_get_ov( op, dn, oc, ad, rw, e, on ); +} + +int +overlay_entry_release_ov( + Operation *op, + Entry *e, + int rw, + slap_overinst *on ) +{ + slap_overinfo *oi = on->on_info; + BackendDB *be = op->o_bd, db; + BackendInfo *bi = op->o_bd->bd_info; + int rc = SLAP_CB_CONTINUE; + + for ( ; on; on = on->on_next ) { + if ( on->on_bi.bi_entry_release_rw ) { + /* NOTE: do not copy the structure until required */ + if ( !SLAP_ISOVERLAY( op->o_bd ) ) { + db = *op->o_bd; + db.be_flags |= SLAP_DBFLAG_OVERLAY; + op->o_bd = &db; + } + + op->o_bd->bd_info = (BackendInfo *)on; + rc = on->on_bi.bi_entry_release_rw( op, e, rw ); + if ( rc != SLAP_CB_CONTINUE ) break; + } + } + + if ( rc == SLAP_CB_CONTINUE ) { + /* if the database structure was changed, o_bd points to a + * copy of the structure; put the original bd_info in place */ + if ( SLAP_ISOVERLAY( op->o_bd ) ) { + op->o_bd->bd_info = oi->oi_orig; + } + + if ( oi->oi_orig->bi_entry_release_rw ) { + rc = oi->oi_orig->bi_entry_release_rw( op, e, rw ); + } + } + /* should not fall thru this far without anything happening... */ + if ( rc == SLAP_CB_CONTINUE ) { + entry_free( e ); + rc = 0; + } + + op->o_bd = be; + op->o_bd->bd_info = bi; + + return rc; +} + +static int +over_entry_release_rw( + Operation *op, + Entry *e, + int rw ) +{ + slap_overinfo *oi; + slap_overinst *on; + + assert( op->o_bd != NULL ); + + oi = op->o_bd->bd_info->bi_private; + on = oi->oi_list; + + return overlay_entry_release_ov( op, e, rw, on ); +} + +static int +over_acl_group( + Operation *op, + Entry *e, + struct berval *gr_ndn, + struct berval *op_ndn, + ObjectClass *group_oc, + AttributeDescription *group_at ) +{ + slap_overinfo *oi; + slap_overinst *on; + BackendInfo *bi; + BackendDB *be = op->o_bd, db; + int rc = SLAP_CB_CONTINUE; + + /* FIXME: used to happen for instance during abandon + * when global overlays are used... */ + assert( be != NULL ); + + bi = be->bd_info; + oi = bi->bi_private; + on = oi->oi_list; + + for ( ; on; on = on->on_next ) { + if ( on->on_bi.bi_acl_group ) { + /* NOTE: do not copy the structure until required */ + if ( !SLAP_ISOVERLAY( op->o_bd ) ) { + db = *op->o_bd; + db.be_flags |= SLAP_DBFLAG_OVERLAY; + op->o_bd = &db; + } + + op->o_bd->bd_info = (BackendInfo *)on; + rc = on->on_bi.bi_acl_group( op, e, + gr_ndn, op_ndn, group_oc, group_at ); + if ( rc != SLAP_CB_CONTINUE ) break; + } + } + + if ( rc == SLAP_CB_CONTINUE ) { + BI_acl_group *bi_acl_group; + + /* if the database structure was changed, o_bd points to a + * copy of the structure; put the original bd_info in place */ + if ( SLAP_ISOVERLAY( op->o_bd ) ) { + op->o_bd->bd_info = oi->oi_orig; + } + + if ( oi->oi_orig->bi_acl_group ) { + bi_acl_group = oi->oi_orig->bi_acl_group; + } else { + bi_acl_group = backend_group; + } + + rc = bi_acl_group( op, e, + gr_ndn, op_ndn, group_oc, group_at ); + } + /* should not fall thru this far without anything happening... */ + if ( rc == SLAP_CB_CONTINUE ) { + /* access not allowed */ + rc = 0; + } + + op->o_bd = be; + op->o_bd->bd_info = bi; + + return rc; +} + +static int +over_acl_attribute( + Operation *op, + Entry *target, + struct berval *entry_ndn, + AttributeDescription *entry_at, + BerVarray *vals, + slap_access_t access ) +{ + slap_overinfo *oi; + slap_overinst *on; + BackendInfo *bi; + BackendDB *be = op->o_bd, db; + int rc = SLAP_CB_CONTINUE; + + /* FIXME: used to happen for instance during abandon + * when global overlays are used... */ + assert( be != NULL ); + + bi = be->bd_info; + oi = bi->bi_private; + on = oi->oi_list; + + for ( ; on; on = on->on_next ) { + if ( on->on_bi.bi_acl_attribute ) { + /* NOTE: do not copy the structure until required */ + if ( !SLAP_ISOVERLAY( op->o_bd ) ) { + db = *op->o_bd; + db.be_flags |= SLAP_DBFLAG_OVERLAY; + op->o_bd = &db; + } + + op->o_bd->bd_info = (BackendInfo *)on; + rc = on->on_bi.bi_acl_attribute( op, target, + entry_ndn, entry_at, vals, access ); + if ( rc != SLAP_CB_CONTINUE ) break; + } + } + + if ( rc == SLAP_CB_CONTINUE ) { + BI_acl_attribute *bi_acl_attribute; + + /* if the database structure was changed, o_bd points to a + * copy of the structure; put the original bd_info in place */ + if ( SLAP_ISOVERLAY( op->o_bd ) ) { + op->o_bd->bd_info = oi->oi_orig; + } + + if ( oi->oi_orig->bi_acl_attribute ) { + bi_acl_attribute = oi->oi_orig->bi_acl_attribute; + } else { + bi_acl_attribute = backend_attribute; + } + + rc = bi_acl_attribute( op, target, + entry_ndn, entry_at, vals, access ); + } + /* should not fall thru this far without anything happening... */ + if ( rc == SLAP_CB_CONTINUE ) { + /* access not allowed */ + rc = 0; + } + + op->o_bd = be; + op->o_bd->bd_info = bi; + + return rc; +} + +int +overlay_callback_after_backover( Operation *op, slap_callback *sc, int append ) +{ + slap_callback **scp; + + for ( scp = &op->o_callback; *scp != NULL; scp = &(*scp)->sc_next ) { + if ( (*scp)->sc_response == over_back_response ) { + sc->sc_next = (*scp)->sc_next; + (*scp)->sc_next = sc; + return 0; + } + } + + if ( append ) { + *scp = sc; + return 0; + } + + return 1; +} + +/* + * default return code in case of missing backend function + * and overlay stack returning SLAP_CB_CONTINUE + */ +static int op_rc[ op_last ] = { + LDAP_UNWILLING_TO_PERFORM, /* bind */ + LDAP_UNWILLING_TO_PERFORM, /* unbind */ + LDAP_UNWILLING_TO_PERFORM, /* search */ + SLAP_CB_CONTINUE, /* compare; pass to frontend */ + LDAP_UNWILLING_TO_PERFORM, /* modify */ + LDAP_UNWILLING_TO_PERFORM, /* modrdn */ + LDAP_UNWILLING_TO_PERFORM, /* add */ + LDAP_UNWILLING_TO_PERFORM, /* delete */ + LDAP_UNWILLING_TO_PERFORM, /* abandon */ + LDAP_UNWILLING_TO_PERFORM, /* cancel */ + LDAP_UNWILLING_TO_PERFORM, /* extended */ + LDAP_SUCCESS, /* aux_operational */ + LDAP_SUCCESS, /* aux_chk_referrals */ + SLAP_CB_CONTINUE /* aux_chk_controls; pass to frontend */ +}; + +int overlay_op_walk( + Operation *op, + SlapReply *rs, + slap_operation_t which, + slap_overinfo *oi, + slap_overinst *on +) +{ + BI_op_bind **func; + int rc = SLAP_CB_CONTINUE; + + for (; on; on=on->on_next ) { + func = &on->on_bi.bi_op_bind; + if ( func[which] ) { + op->o_bd->bd_info = (BackendInfo *)on; + rc = func[which]( op, rs ); + if ( rc != SLAP_CB_CONTINUE ) break; + } + } + if ( rc == SLAP_CB_BYPASS ) + rc = SLAP_CB_CONTINUE; + + /* if an overlay halted processing, make sure + * any previously set cleanup handlers are run + */ + if ( rc != SLAP_CB_CONTINUE ) + goto cleanup; + + func = &oi->oi_orig->bi_op_bind; + if ( func[which] ) { + op->o_bd->bd_info = oi->oi_orig; + rc = func[which]( op, rs ); + } + /* should not fall thru this far without anything happening... */ + if ( rc == SLAP_CB_CONTINUE ) { + rc = op_rc[ which ]; + } + + /* The underlying backend didn't handle the request, make sure + * overlay cleanup is processed. + */ + if ( rc == LDAP_UNWILLING_TO_PERFORM ) { + slap_callback *sc_next; +cleanup: + for ( ; op->o_callback && op->o_callback->sc_response != + over_back_response; op->o_callback = sc_next ) { + sc_next = op->o_callback->sc_next; + if ( op->o_callback->sc_cleanup ) { + op->o_callback->sc_cleanup( op, rs ); + } + } + } + return rc; +} + +static int +over_op_func( + Operation *op, + SlapReply *rs, + slap_operation_t which +) +{ + slap_overinfo *oi; + slap_overinst *on; + BackendDB *be = op->o_bd, db; + slap_callback cb = {NULL, over_back_response, NULL, NULL}, **sc; + int rc = SLAP_CB_CONTINUE; + + /* FIXME: used to happen for instance during abandon + * when global overlays are used... */ + assert( op->o_bd != NULL ); + + oi = op->o_bd->bd_info->bi_private; + on = oi->oi_list; + + if ( !SLAP_ISOVERLAY( op->o_bd )) { + db = *op->o_bd; + db.be_flags |= SLAP_DBFLAG_OVERLAY; + op->o_bd = &db; + } + cb.sc_next = op->o_callback; + cb.sc_private = oi; + op->o_callback = &cb; + + rc = overlay_op_walk( op, rs, which, oi, on ); + for ( sc = &op->o_callback; *sc; sc = &(*sc)->sc_next ) { + if ( *sc == &cb ) { + *sc = cb.sc_next; + break; + } + } + + op->o_bd = be; + return rc; +} + +static int +over_op_bind( Operation *op, SlapReply *rs ) +{ + return over_op_func( op, rs, op_bind ); +} + +static int +over_op_unbind( Operation *op, SlapReply *rs ) +{ + return over_op_func( op, rs, op_unbind ); +} + +static int +over_op_search( Operation *op, SlapReply *rs ) +{ + return over_op_func( op, rs, op_search ); +} + +static int +over_op_compare( Operation *op, SlapReply *rs ) +{ + return over_op_func( op, rs, op_compare ); +} + +static int +over_op_modify( Operation *op, SlapReply *rs ) +{ + return over_op_func( op, rs, op_modify ); +} + +static int +over_op_modrdn( Operation *op, SlapReply *rs ) +{ + return over_op_func( op, rs, op_modrdn ); +} + +static int +over_op_add( Operation *op, SlapReply *rs ) +{ + return over_op_func( op, rs, op_add ); +} + +static int +over_op_delete( Operation *op, SlapReply *rs ) +{ + return over_op_func( op, rs, op_delete ); +} + +static int +over_op_abandon( Operation *op, SlapReply *rs ) +{ + return over_op_func( op, rs, op_abandon ); +} + +static int +over_op_cancel( Operation *op, SlapReply *rs ) +{ + return over_op_func( op, rs, op_cancel ); +} + +static int +over_op_extended( Operation *op, SlapReply *rs ) +{ + return over_op_func( op, rs, op_extended ); +} + +static int +over_aux_operational( Operation *op, SlapReply *rs ) +{ + return over_op_func( op, rs, op_aux_operational ); +} + +static int +over_aux_chk_referrals( Operation *op, SlapReply *rs ) +{ + return over_op_func( op, rs, op_aux_chk_referrals ); +} + +static int +over_aux_chk_controls( Operation *op, SlapReply *rs ) +{ + return over_op_func( op, rs, op_aux_chk_controls ); +} + +enum conn_which { + conn_init = 0, + conn_destroy, + conn_last +}; + +static int +over_connection_func( + BackendDB *bd, + Connection *conn, + enum conn_which which +) +{ + slap_overinfo *oi; + slap_overinst *on; + BackendDB db; + int rc = SLAP_CB_CONTINUE; + BI_connection_init **func; + + /* FIXME: used to happen for instance during abandon + * when global overlays are used... */ + assert( bd != NULL ); + + oi = bd->bd_info->bi_private; + on = oi->oi_list; + + if ( !SLAP_ISOVERLAY( bd ) ) { + db = *bd; + db.be_flags |= SLAP_DBFLAG_OVERLAY; + bd = &db; + } + + for ( ; on; on = on->on_next ) { + func = &on->on_bi.bi_connection_init; + if ( func[ which ] ) { + bd->bd_info = (BackendInfo *)on; + rc = func[ which ]( bd, conn ); + if ( rc != SLAP_CB_CONTINUE ) break; + } + } + + func = &oi->oi_orig->bi_connection_init; + if ( func[ which ] && rc == SLAP_CB_CONTINUE ) { + bd->bd_info = oi->oi_orig; + rc = func[ which ]( bd, conn ); + } + /* should not fall thru this far without anything happening... */ + if ( rc == SLAP_CB_CONTINUE ) { + rc = LDAP_UNWILLING_TO_PERFORM; + } + + return rc; +} + +static int +over_connection_init( + BackendDB *bd, + Connection *conn +) +{ + return over_connection_func( bd, conn, conn_init ); +} + +static int +over_connection_destroy( + BackendDB *bd, + Connection *conn +) +{ + return over_connection_func( bd, conn, conn_destroy ); +} + +int +overlay_register( + slap_overinst *on +) +{ + slap_overinst *tmp; + + /* FIXME: check for duplicates? */ + for ( tmp = overlays; tmp != NULL; tmp = tmp->on_next ) { + if ( strcmp( on->on_bi.bi_type, tmp->on_bi.bi_type ) == 0 ) { + Debug( LDAP_DEBUG_ANY, + "overlay_register(\"%s\"): " + "name already in use.\n", + on->on_bi.bi_type, 0, 0 ); + return -1; + } + + if ( on->on_bi.bi_obsolete_names != NULL ) { + int i; + + for ( i = 0; on->on_bi.bi_obsolete_names[ i ] != NULL; i++ ) { + if ( strcmp( on->on_bi.bi_obsolete_names[ i ], tmp->on_bi.bi_type ) == 0 ) { + Debug( LDAP_DEBUG_ANY, + "overlay_register(\"%s\"): " + "obsolete name \"%s\" already in use " + "by overlay \"%s\".\n", + on->on_bi.bi_type, + on->on_bi.bi_obsolete_names[ i ], + tmp->on_bi.bi_type ); + return -1; + } + } + } + + if ( tmp->on_bi.bi_obsolete_names != NULL ) { + int i; + + for ( i = 0; tmp->on_bi.bi_obsolete_names[ i ] != NULL; i++ ) { + int j; + + if ( strcmp( on->on_bi.bi_type, tmp->on_bi.bi_obsolete_names[ i ] ) == 0 ) { + Debug( LDAP_DEBUG_ANY, + "overlay_register(\"%s\"): " + "name already in use " + "as obsolete by overlay \"%s\".\n", + on->on_bi.bi_type, + tmp->on_bi.bi_obsolete_names[ i ], 0 ); + return -1; + } + + if ( on->on_bi.bi_obsolete_names != NULL ) { + for ( j = 0; on->on_bi.bi_obsolete_names[ j ] != NULL; j++ ) { + if ( strcmp( on->on_bi.bi_obsolete_names[ j ], tmp->on_bi.bi_obsolete_names[ i ] ) == 0 ) { + Debug( LDAP_DEBUG_ANY, + "overlay_register(\"%s\"): " + "obsolete name \"%s\" already in use " + "as obsolete by overlay \"%s\".\n", + on->on_bi.bi_type, + on->on_bi.bi_obsolete_names[ j ], + tmp->on_bi.bi_type ); + return -1; + } + } + } + } + } + } + + on->on_next = overlays; + overlays = on; + return 0; +} + +/* + * iterator on registered overlays; overlay_next( NULL ) returns the first + * overlay; subsequent calls with the previously returned value allow to + * iterate over the entire list; returns NULL when no more overlays are + * registered. + */ + +slap_overinst * +overlay_next( + slap_overinst *on +) +{ + if ( on == NULL ) { + return overlays; + } + + return on->on_next; +} + +/* + * returns a specific registered overlay based on the type; NULL if not + * registered. + */ + +slap_overinst * +overlay_find( const char *over_type ) +{ + slap_overinst *on = overlays; + + assert( over_type != NULL ); + + for ( ; on; on = on->on_next ) { + if ( strcmp( on->on_bi.bi_type, over_type ) == 0 ) { + goto foundit; + } + + if ( on->on_bi.bi_obsolete_names != NULL ) { + int i; + + for ( i = 0; on->on_bi.bi_obsolete_names[ i ] != NULL; i++ ) { + if ( strcmp( on->on_bi.bi_obsolete_names[ i ], over_type ) == 0 ) { + Debug( LDAP_DEBUG_ANY, + "overlay_find(\"%s\"): " + "obsolete name for \"%s\".\n", + on->on_bi.bi_obsolete_names[ i ], + on->on_bi.bi_type, 0 ); + goto foundit; + } + } + } + } + +foundit:; + return on; +} + +static const char overtype[] = "over"; + +/* + * returns TRUE (1) if the database is actually an overlay instance; + * FALSE (0) otherwise. + */ + +int +overlay_is_over( BackendDB *be ) +{ + return be->bd_info->bi_type == overtype; +} + +/* + * returns TRUE (1) if the given database is actually an overlay + * instance and, somewhere in the list, contains the requested overlay; + * FALSE (0) otherwise. + */ + +int +overlay_is_inst( BackendDB *be, const char *over_type ) +{ + slap_overinst *on; + + assert( be != NULL ); + + if ( !overlay_is_over( be ) ) { + return 0; + } + + on = ((slap_overinfo *)be->bd_info->bi_private)->oi_list; + for ( ; on; on = on->on_next ) { + if ( strcmp( on->on_bi.bi_type, over_type ) == 0 ) { + return 1; + } + } + + return 0; +} + +int +overlay_register_control( BackendDB *be, const char *oid ) +{ + int gotit = 0; + int cid; + + if ( slap_find_control_id( oid, &cid ) == LDAP_CONTROL_NOT_FOUND ) { + return -1; + } + + if ( SLAP_ISGLOBALOVERLAY( be ) ) { + BackendDB *bd; + + /* add to all backends... */ + LDAP_STAILQ_FOREACH( bd, &backendDB, be_next ) { + if ( bd == be->bd_self ) { + gotit = 1; + } + + /* overlays can be instanciated multiple times, use + * be_ctrls[ cid ] as an instance counter, so that the + * overlay's controls are only really disabled after the + * last instance called overlay_register_control() */ + bd->be_ctrls[ cid ]++; + bd->be_ctrls[ SLAP_MAX_CIDS ] = 1; + } + + } + + if ( !gotit ) { + /* overlays can be instanciated multiple times, use + * be_ctrls[ cid ] as an instance counter, so that the + * overlay's controls are only really unregistered after the + * last instance called overlay_register_control() */ + be->bd_self->be_ctrls[ cid ]++; + be->bd_self->be_ctrls[ SLAP_MAX_CIDS ] = 1; + } + + return 0; +} + +#ifdef SLAP_CONFIG_DELETE +void +overlay_unregister_control( BackendDB *be, const char *oid ) +{ + int gotit = 0; + int cid; + + if ( slap_find_control_id( oid, &cid ) == LDAP_CONTROL_NOT_FOUND ) { + return; + } + + if ( SLAP_ISGLOBALOVERLAY( be ) ) { + BackendDB *bd; + + /* remove from all backends... */ + LDAP_STAILQ_FOREACH( bd, &backendDB, be_next ) { + if ( bd == be->bd_self ) { + gotit = 1; + } + + bd->be_ctrls[ cid ]--; + } + } + + if ( !gotit ) { + be->bd_self->be_ctrls[ cid ]--; + } +} +#endif /* SLAP_CONFIG_DELETE */ + +void +overlay_destroy_one( BackendDB *be, slap_overinst *on ) +{ + slap_overinfo *oi = on->on_info; + slap_overinst **oidx; + + for ( oidx = &oi->oi_list; *oidx; oidx = &(*oidx)->on_next ) { + if ( *oidx == on ) { + *oidx = on->on_next; + if ( on->on_bi.bi_db_destroy ) { + BackendInfo *bi_orig = be->bd_info; + be->bd_info = (BackendInfo *)on; + on->on_bi.bi_db_destroy( be, NULL ); + be->bd_info = bi_orig; + } + free( on ); + break; + } + } +} + +#ifdef SLAP_CONFIG_DELETE +typedef struct ov_remove_ctx { + BackendDB *be; + slap_overinst *on; +} ov_remove_ctx; + +int +overlay_remove_cb( Operation *op, SlapReply *rs ) +{ + ov_remove_ctx *rm_ctx = (ov_remove_ctx*) op->o_callback->sc_private; + BackendInfo *bi_orig = rm_ctx->be->bd_info; + + rm_ctx->be->bd_info = (BackendInfo*) rm_ctx->on; + + if ( rm_ctx->on->on_bi.bi_db_close ) { + rm_ctx->on->on_bi.bi_db_close( rm_ctx->be, NULL ); + } + if ( rm_ctx->on->on_bi.bi_db_destroy ) { + rm_ctx->on->on_bi.bi_db_destroy( rm_ctx->be, NULL ); + } + + /* clean up after removing last overlay */ + if ( ! rm_ctx->on->on_info->oi_list ) { + /* reset db flags and bd_info to orig */ + SLAP_DBFLAGS( rm_ctx->be ) &= ~SLAP_DBFLAG_GLOBAL_OVERLAY; + rm_ctx->be->bd_info = rm_ctx->on->on_info->oi_orig; + ch_free(rm_ctx->on->on_info); + } else { + rm_ctx->be->bd_info = bi_orig; + } + free( rm_ctx->on ); + op->o_tmpfree(rm_ctx, op->o_tmpmemctx); + return SLAP_CB_CONTINUE; +} + +void +overlay_remove( BackendDB *be, slap_overinst *on, Operation *op ) +{ + slap_overinfo *oi = on->on_info; + slap_overinst **oidx; + ov_remove_ctx *rm_ctx; + slap_callback *rm_cb, *cb; + + /* remove overlay from oi_list */ + for ( oidx = &oi->oi_list; *oidx; oidx = &(*oidx)->on_next ) { + if ( *oidx == on ) { + *oidx = on->on_next; + break; + } + } + + /* The db_close and db_destroy handlers to cleanup a release + * the overlay's resources are called from the cleanup callback + */ + rm_ctx = op->o_tmpalloc( sizeof( ov_remove_ctx ), op->o_tmpmemctx ); + rm_ctx->be = be; + rm_ctx->on = on; + + rm_cb = op->o_tmpalloc( sizeof( slap_callback ), op->o_tmpmemctx ); + rm_cb->sc_next = NULL; + rm_cb->sc_cleanup = overlay_remove_cb; + rm_cb->sc_response = NULL; + rm_cb->sc_private = (void*) rm_ctx; + rm_cb->sc_writewait = NULL; + + /* Append callback to the end of the list */ + if ( !op->o_callback ) { + op->o_callback = rm_cb; + } else { + for ( cb = op->o_callback; cb->sc_next; cb = cb->sc_next ); + cb->sc_next = rm_cb; + } +} +#endif /* SLAP_CONFIG_DELETE */ + +void +overlay_insert( BackendDB *be, slap_overinst *on2, slap_overinst ***prev, + int idx ) +{ + slap_overinfo *oi = (slap_overinfo *)be->bd_info; + + if ( idx == -1 ) { + on2->on_next = oi->oi_list; + oi->oi_list = on2; + } else { + int i, novs; + slap_overinst *on, **prev; + + /* Since the list is in reverse order and is singly linked, + * we have to count the overlays and then insert backwards. + * Adding on overlay at a specific point should be a pretty + * infrequent occurrence. + */ + novs = 0; + for ( on = oi->oi_list; on; on=on->on_next ) + novs++; + + if (idx > novs) + idx = 0; + else + idx = novs - idx; + + /* advance to insertion point */ + prev = &oi->oi_list; + for ( i=0; i<idx; i++ ) { + on = *prev; + prev = &on->on_next; + } + /* insert */ + on2->on_next = *prev; + *prev = on2; + } +} + +void +overlay_move( BackendDB *be, slap_overinst *on, int idx ) +{ + slap_overinfo *oi = (slap_overinfo *)be->bd_info; + slap_overinst **onp; + + for (onp = &oi->oi_list; *onp; onp= &(*onp)->on_next) { + if ( *onp == on ) { + *onp = on->on_next; + break; + } + } + overlay_insert( be, on, &onp, idx ); +} + +/* add an overlay to a particular backend. */ +int +overlay_config( BackendDB *be, const char *ov, int idx, BackendInfo **res, ConfigReply *cr ) +{ + slap_overinst *on = NULL, *on2 = NULL, **prev; + slap_overinfo *oi = NULL; + BackendInfo *bi = NULL; + + if ( res ) + *res = NULL; + + on = overlay_find( ov ); + if ( !on ) { + Debug( LDAP_DEBUG_ANY, "overlay \"%s\" not found\n", ov, 0, 0 ); + return 1; + } + + /* If this is the first overlay on this backend, set up the + * overlay info structure + */ + if ( !overlay_is_over( be ) ) { + int isglobal = 0; + + /* NOTE: the first time a global overlay is configured, + * frontendDB gets this flag; it is used later by overlays + * to determine if they're stacked on top of the frontendDB */ + if ( be->bd_info == frontendDB->bd_info || SLAP_ISGLOBALOVERLAY( be ) ) { + isglobal = 1; + if ( on->on_bi.bi_flags & SLAPO_BFLAG_DBONLY ) { + snprintf( cr->msg, sizeof( cr->msg ), "overlay_config(): " + "overlay \"%s\" cannot be global.", ov ); + Debug( LDAP_DEBUG_ANY, "%s\n", cr->msg, 0, 0 ); + return 1; + } + + } else if ( on->on_bi.bi_flags & SLAPO_BFLAG_GLOBONLY ) { + snprintf( cr->msg, sizeof( cr->msg ), "overlay_config(): " + "overlay \"%s\" can only be global.", ov ); + Debug( LDAP_DEBUG_ANY, "%s\n", cr->msg, 0, 0 ); + return 1; + } + + oi = ch_malloc( sizeof( slap_overinfo ) ); + oi->oi_orig = be->bd_info; + oi->oi_bi = *be->bd_info; + oi->oi_origdb = be; + + if ( isglobal ) { + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_GLOBAL_OVERLAY; + } + + /* Save a pointer to ourself in bi_private. + */ + oi->oi_bi.bi_private = oi; + oi->oi_list = NULL; + bi = (BackendInfo *)oi; + + bi->bi_type = (char *)overtype; + + bi->bi_db_config = over_db_config; + bi->bi_db_open = over_db_open; + bi->bi_db_close = over_db_close; + bi->bi_db_destroy = over_db_destroy; + + bi->bi_op_bind = over_op_bind; + bi->bi_op_unbind = over_op_unbind; + bi->bi_op_search = over_op_search; + bi->bi_op_compare = over_op_compare; + bi->bi_op_modify = over_op_modify; + bi->bi_op_modrdn = over_op_modrdn; + bi->bi_op_add = over_op_add; + bi->bi_op_delete = over_op_delete; + bi->bi_op_abandon = over_op_abandon; + bi->bi_op_cancel = over_op_cancel; + + bi->bi_extended = over_op_extended; + + /* + * this is fine because it has the same + * args of the operations; we need to rework + * all the hooks to share the same args + * of the operations... + */ + bi->bi_operational = over_aux_operational; + bi->bi_chk_referrals = over_aux_chk_referrals; + bi->bi_chk_controls = over_aux_chk_controls; + + /* these have specific arglists */ + bi->bi_entry_get_rw = over_entry_get_rw; + bi->bi_entry_release_rw = over_entry_release_rw; + bi->bi_access_allowed = over_access_allowed; + bi->bi_acl_group = over_acl_group; + bi->bi_acl_attribute = over_acl_attribute; + + bi->bi_connection_init = over_connection_init; + bi->bi_connection_destroy = over_connection_destroy; + + be->bd_info = bi; + + } else { + if ( overlay_is_inst( be, ov ) ) { + if ( on->on_bi.bi_flags & SLAPO_BFLAG_SINGLE ) { + snprintf( cr->msg, sizeof( cr->msg ), "overlay_config(): " + "overlay \"%s\" already in list", ov ); + Debug( LDAP_DEBUG_ANY, "%s\n", cr->msg, 0, 0 ); + return 1; + } + } + + oi = be->bd_info->bi_private; + } + + /* Insert new overlay into list. By default overlays are + * added to head of list and executed in LIFO order. + */ + on2 = ch_calloc( 1, sizeof(slap_overinst) ); + *on2 = *on; + on2->on_info = oi; + + prev = &oi->oi_list; + /* Do we need to find the insertion point? */ + if ( idx >= 0 ) { + int i; + + /* count current overlays */ + for ( i=0, on=oi->oi_list; on; on=on->on_next, i++ ); + + /* are we just appending a new one? */ + if ( idx >= i ) + idx = -1; + } + overlay_insert( be, on2, &prev, idx ); + + /* Any initialization needed? */ + if ( on2->on_bi.bi_db_init ) { + int rc; + be->bd_info = (BackendInfo *)on2; + rc = on2->on_bi.bi_db_init( be, cr); + be->bd_info = (BackendInfo *)oi; + if ( rc ) { + *prev = on2->on_next; + ch_free( on2 ); + on2 = NULL; + return rc; + } + } + + if ( res ) + *res = &on2->on_bi; + + return 0; +} + diff --git a/servers/slapd/bconfig.c b/servers/slapd/bconfig.c new file mode 100644 index 0000000..3188ccf --- /dev/null +++ b/servers/slapd/bconfig.c @@ -0,0 +1,7511 @@ +/* bconfig.c - the config backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by Howard Chu for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/ctype.h> +#include <ac/errno.h> +#include <sys/stat.h> +#include <ac/unistd.h> + +#include "slap.h" + +#ifdef LDAP_SLAPI +#include "slapi/slapi.h" +#endif + +#include <ldif.h> +#include <lutil.h> + +#include "config.h" + +#define CONFIG_RDN "cn=config" +#define SCHEMA_RDN "cn=schema" + +static struct berval config_rdn = BER_BVC(CONFIG_RDN); +static struct berval schema_rdn = BER_BVC(SCHEMA_RDN); + +extern int slap_DN_strict; /* dn.c */ + +#ifdef SLAPD_MODULES +typedef struct modpath_s { + struct modpath_s *mp_next; + struct berval mp_path; + BerVarray mp_loads; +} ModPaths; + +static ModPaths modpaths, *modlast = &modpaths, *modcur = &modpaths; +#endif + +typedef struct ConfigFile { + struct ConfigFile *c_sibs; + struct ConfigFile *c_kids; + struct berval c_file; + AttributeType *c_at_head, *c_at_tail; + ContentRule *c_cr_head, *c_cr_tail; + ObjectClass *c_oc_head, *c_oc_tail; + OidMacro *c_om_head, *c_om_tail; + Syntax *c_syn_head, *c_syn_tail; + BerVarray c_dseFiles; +} ConfigFile; + +typedef struct { + ConfigFile *cb_config; + CfEntryInfo *cb_root; + BackendDB cb_db; /* underlying database */ + int cb_got_ldif; + int cb_use_ldif; +} CfBackInfo; + +static CfBackInfo cfBackInfo; + +static char *passwd_salt; +static FILE *logfile; +static char *logfileName; +#ifdef SLAP_AUTH_REWRITE +static BerVarray authz_rewrites; +#endif +static AccessControl *defacl_parsed = NULL; + +static struct berval cfdir; + +/* Private state */ +static AttributeDescription *cfAd_backend, *cfAd_database, *cfAd_overlay, + *cfAd_include, *cfAd_attr, *cfAd_oc, *cfAd_om, *cfAd_syntax; + +static ConfigFile *cfn; + +static Avlnode *CfOcTree; + +/* System schema state */ +extern AttributeType *at_sys_tail; /* at.c */ +extern ObjectClass *oc_sys_tail; /* oc.c */ +extern OidMacro *om_sys_tail; /* oidm.c */ +extern Syntax *syn_sys_tail; /* syntax.c */ +static AttributeType *cf_at_tail; +static ObjectClass *cf_oc_tail; +static OidMacro *cf_om_tail; +static Syntax *cf_syn_tail; + +static int config_add_internal( CfBackInfo *cfb, Entry *e, ConfigArgs *ca, + SlapReply *rs, int *renumber, Operation *op ); + +static int config_check_schema( Operation *op, CfBackInfo *cfb ); + +static ConfigDriver config_fname; +static ConfigDriver config_cfdir; +static ConfigDriver config_generic; +static ConfigDriver config_search_base; +static ConfigDriver config_passwd_hash; +static ConfigDriver config_schema_dn; +static ConfigDriver config_sizelimit; +static ConfigDriver config_timelimit; +static ConfigDriver config_overlay; +static ConfigDriver config_subordinate; +static ConfigDriver config_suffix; +#ifdef LDAP_TCP_BUFFER +static ConfigDriver config_tcp_buffer; +#endif /* LDAP_TCP_BUFFER */ +static ConfigDriver config_rootdn; +static ConfigDriver config_rootpw; +static ConfigDriver config_restrict; +static ConfigDriver config_allows; +static ConfigDriver config_disallows; +static ConfigDriver config_requires; +static ConfigDriver config_security; +static ConfigDriver config_referral; +static ConfigDriver config_loglevel; +static ConfigDriver config_updatedn; +static ConfigDriver config_updateref; +static ConfigDriver config_extra_attrs; +static ConfigDriver config_include; +static ConfigDriver config_obsolete; +#ifdef HAVE_TLS +static ConfigDriver config_tls_option; +static ConfigDriver config_tls_config; +#endif +extern ConfigDriver syncrepl_config; + +enum { + CFG_ACL = 1, + CFG_BACKEND, + CFG_DATABASE, + CFG_TLS_RAND, + CFG_TLS_CIPHER, + CFG_TLS_PROTOCOL_MIN, + CFG_TLS_CERT_FILE, + CFG_TLS_CERT_KEY, + CFG_TLS_CA_PATH, + CFG_TLS_CA_FILE, + CFG_TLS_DH_FILE, + CFG_TLS_VERIFY, + CFG_TLS_CRLCHECK, + CFG_TLS_CRL_FILE, + CFG_CONCUR, + CFG_THREADS, + CFG_SALT, + CFG_LIMITS, + CFG_RO, + CFG_REWRITE, + CFG_DEPTH, + CFG_OID, + CFG_OC, + CFG_DIT, + CFG_ATTR, + CFG_ATOPT, + CFG_ROOTDSE, + CFG_LOGFILE, + CFG_PLUGIN, + CFG_MODLOAD, + CFG_MODPATH, + CFG_LASTMOD, + CFG_AZPOLICY, + CFG_AZREGEXP, + CFG_SASLSECP, + CFG_SSTR_IF_MAX, + CFG_SSTR_IF_MIN, + CFG_TTHREADS, + CFG_MIRRORMODE, + CFG_HIDDEN, + CFG_MONITORING, + CFG_SERVERID, + CFG_SORTVALS, + CFG_IX_INTLEN, + CFG_SYNTAX, + CFG_ACL_ADD, + CFG_SYNC_SUBENTRY, + CFG_LTHREADS, + CFG_TLS_ECNAME, + CFG_LAST +}; + +typedef struct { + char *name, *oid; +} OidRec; + +static OidRec OidMacros[] = { + /* OpenLDAProot:1.12.2 */ + { "OLcfg", "1.3.6.1.4.1.4203.1.12.2" }, + { "OLcfgAt", "OLcfg:3" }, + { "OLcfgGlAt", "OLcfgAt:0" }, + { "OLcfgBkAt", "OLcfgAt:1" }, + { "OLcfgDbAt", "OLcfgAt:2" }, + { "OLcfgOvAt", "OLcfgAt:3" }, + { "OLcfgCtAt", "OLcfgAt:4" }, /* contrib modules */ + { "OLcfgOc", "OLcfg:4" }, + { "OLcfgGlOc", "OLcfgOc:0" }, + { "OLcfgBkOc", "OLcfgOc:1" }, + { "OLcfgDbOc", "OLcfgOc:2" }, + { "OLcfgOvOc", "OLcfgOc:3" }, + { "OLcfgCtOc", "OLcfgOc:4" }, /* contrib modules */ + + /* Syntaxes. We should just start using the standard names and + * document that they are predefined and available for users + * to reference in their own schema. Defining schema without + * OID macros is for masochists... + */ + { "OMsyn", "1.3.6.1.4.1.1466.115.121.1" }, + { "OMsBoolean", "OMsyn:7" }, + { "OMsDN", "OMsyn:12" }, + { "OMsDirectoryString", "OMsyn:15" }, + { "OMsIA5String", "OMsyn:26" }, + { "OMsInteger", "OMsyn:27" }, + { "OMsOID", "OMsyn:38" }, + { "OMsOctetString", "OMsyn:40" }, + { NULL, NULL } +}; + +/* + * Backend/Database registry + * + * OLcfg{Bk|Db}{Oc|At}:0 -> common + * OLcfg{Bk|Db}{Oc|At}:1 -> back-bdb(/back-hdb) + * OLcfg{Bk|Db}{Oc|At}:2 -> back-ldif + * OLcfg{Bk|Db}{Oc|At}:3 -> back-ldap/meta + * OLcfg{Bk|Db}{Oc|At}:4 -> back-monitor + * OLcfg{Bk|Db}{Oc|At}:5 -> back-relay + * OLcfg{Bk|Db}{Oc|At}:6 -> back-sql(/back-ndb) + * OLcfg{Bk|Db}{Oc|At}:7 -> back-sock + * OLcfg{Bk|Db}{Oc|At}:8 -> back-null + * OLcfg{Bk|Db}{Oc|At}:9 -> back-passwd + * OLcfg{Bk|Db}{Oc|At}:10 -> back-shell + * OLcfg{Bk|Db}{Oc|At}:11 -> back-perl + * OLcfg{Bk|Db}{Oc|At}:12 -> back-mdb + */ + +/* + * Overlay registry + * + * OLcfgOv{Oc|At}:1 -> syncprov + * OLcfgOv{Oc|At}:2 -> pcache + * OLcfgOv{Oc|At}:3 -> chain + * OLcfgOv{Oc|At}:4 -> accesslog + * OLcfgOv{Oc|At}:5 -> valsort + * OLcfgOv{Oc|At}:7 -> distproc + * OLcfgOv{Oc|At}:8 -> dynlist + * OLcfgOv{Oc|At}:9 -> dds + * OLcfgOv{Oc|At}:10 -> unique + * OLcfgOv{Oc|At}:11 -> refint + * OLcfgOv{Oc|At}:12 -> ppolicy + * OLcfgOv{Oc|At}:13 -> constraint + * OLcfgOv{Oc|At}:14 -> translucent + * OLcfgOv{Oc|At}:15 -> auditlog + * OLcfgOv{Oc|At}:16 -> rwm + * OLcfgOv{Oc|At}:17 -> dyngroup + * OLcfgOv{Oc|At}:18 -> memberof + * OLcfgOv{Oc|At}:19 -> collect + * OLcfgOv{Oc|At}:20 -> retcode + * OLcfgOv{Oc|At}:21 -> sssvlv + */ + +/* alphabetical ordering */ + +static ConfigTable config_back_cf_table[] = { + /* This attr is read-only */ + { "", "", 0, 0, 0, ARG_MAGIC, + &config_fname, "( OLcfgGlAt:78 NAME 'olcConfigFile' " + "DESC 'File for slapd configuration directives' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "", "", 0, 0, 0, ARG_MAGIC, + &config_cfdir, "( OLcfgGlAt:79 NAME 'olcConfigDir' " + "DESC 'Directory for slapd configuration backend' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "access", NULL, 0, 0, 0, ARG_MAY_DB|ARG_MAGIC|CFG_ACL, + &config_generic, "( OLcfgGlAt:1 NAME 'olcAccess' " + "DESC 'Access Control List' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )", NULL, NULL }, + { "add_content_acl", NULL, 0, 0, 0, ARG_MAY_DB|ARG_ON_OFF|ARG_MAGIC|CFG_ACL_ADD, + &config_generic, "( OLcfgGlAt:86 NAME 'olcAddContentAcl' " + "DESC 'Check ACLs against content of Add ops' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "allows", "features", 2, 0, 5, ARG_PRE_DB|ARG_MAGIC, + &config_allows, "( OLcfgGlAt:2 NAME 'olcAllows' " + "DESC 'Allowed set of deprecated features' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "argsfile", "file", 2, 2, 0, ARG_STRING, + &slapd_args_file, "( OLcfgGlAt:3 NAME 'olcArgsFile' " + "DESC 'File for slapd command line options' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "attributeoptions", NULL, 0, 0, 0, ARG_MAGIC|CFG_ATOPT, + &config_generic, "( OLcfgGlAt:5 NAME 'olcAttributeOptions' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "attribute", "attribute", 2, 0, STRLENOF( "attribute" ), + ARG_PAREN|ARG_MAGIC|CFG_ATTR, + &config_generic, "( OLcfgGlAt:4 NAME 'olcAttributeTypes' " + "DESC 'OpenLDAP attributeTypes' " + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )", + NULL, NULL }, + { "authid-rewrite", NULL, 2, 0, STRLENOF( "authid-rewrite" ), +#ifdef SLAP_AUTH_REWRITE + ARG_MAGIC|CFG_REWRITE|ARG_NO_INSERT, &config_generic, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:6 NAME 'olcAuthIDRewrite' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )", NULL, NULL }, + { "authz-policy", "policy", 2, 2, 0, ARG_STRING|ARG_MAGIC|CFG_AZPOLICY, + &config_generic, "( OLcfgGlAt:7 NAME 'olcAuthzPolicy' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "authz-regexp", "regexp> <DN", 3, 3, 0, ARG_MAGIC|CFG_AZREGEXP|ARG_NO_INSERT, + &config_generic, "( OLcfgGlAt:8 NAME 'olcAuthzRegexp' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )", NULL, NULL }, + { "backend", "type", 2, 2, 0, ARG_PRE_DB|ARG_MAGIC|CFG_BACKEND, + &config_generic, "( OLcfgGlAt:9 NAME 'olcBackend' " + "DESC 'A type of backend' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE X-ORDERED 'SIBLINGS' )", + NULL, NULL }, + { "concurrency", "level", 2, 2, 0, ARG_INT|ARG_MAGIC|CFG_CONCUR, + &config_generic, "( OLcfgGlAt:10 NAME 'olcConcurrency' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "conn_max_pending", "max", 2, 2, 0, ARG_INT, + &slap_conn_max_pending, "( OLcfgGlAt:11 NAME 'olcConnMaxPending' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "conn_max_pending_auth", "max", 2, 2, 0, ARG_INT, + &slap_conn_max_pending_auth, "( OLcfgGlAt:12 NAME 'olcConnMaxPendingAuth' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "database", "type", 2, 2, 0, ARG_MAGIC|CFG_DATABASE, + &config_generic, "( OLcfgGlAt:13 NAME 'olcDatabase' " + "DESC 'The backend type for a database instance' " + "SUP olcBackend SINGLE-VALUE X-ORDERED 'SIBLINGS' )", NULL, NULL }, + { "defaultSearchBase", "dn", 2, 2, 0, ARG_PRE_BI|ARG_PRE_DB|ARG_DN|ARG_QUOTE|ARG_MAGIC, + &config_search_base, "( OLcfgGlAt:14 NAME 'olcDefaultSearchBase' " + "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL }, + { "disallows", "features", 2, 0, 8, ARG_PRE_DB|ARG_MAGIC, + &config_disallows, "( OLcfgGlAt:15 NAME 'olcDisallows' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "ditcontentrule", NULL, 0, 0, 0, ARG_MAGIC|CFG_DIT|ARG_NO_DELETE|ARG_NO_INSERT, + &config_generic, "( OLcfgGlAt:16 NAME 'olcDitContentRules' " + "DESC 'OpenLDAP DIT content rules' " + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )", + NULL, NULL }, + { "extra_attrs", "attrlist", 2, 2, 0, ARG_DB|ARG_MAGIC, + &config_extra_attrs, "( OLcfgDbAt:0.20 NAME 'olcExtraAttrs' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "gentlehup", "on|off", 2, 2, 0, +#ifdef SIGHUP + ARG_ON_OFF, &global_gentlehup, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:17 NAME 'olcGentleHUP' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "hidden", "on|off", 2, 2, 0, ARG_DB|ARG_ON_OFF|ARG_MAGIC|CFG_HIDDEN, + &config_generic, "( OLcfgDbAt:0.17 NAME 'olcHidden' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "idletimeout", "timeout", 2, 2, 0, ARG_INT, + &global_idletimeout, "( OLcfgGlAt:18 NAME 'olcIdleTimeout' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "include", "file", 2, 2, 0, ARG_MAGIC, + &config_include, "( OLcfgGlAt:19 NAME 'olcInclude' " + "SUP labeledURI )", NULL, NULL }, + { "index_substr_if_minlen", "min", 2, 2, 0, ARG_UINT|ARG_NONZERO|ARG_MAGIC|CFG_SSTR_IF_MIN, + &config_generic, "( OLcfgGlAt:20 NAME 'olcIndexSubstrIfMinLen' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "index_substr_if_maxlen", "max", 2, 2, 0, ARG_UINT|ARG_NONZERO|ARG_MAGIC|CFG_SSTR_IF_MAX, + &config_generic, "( OLcfgGlAt:21 NAME 'olcIndexSubstrIfMaxLen' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "index_substr_any_len", "len", 2, 2, 0, ARG_UINT|ARG_NONZERO, + &index_substr_any_len, "( OLcfgGlAt:22 NAME 'olcIndexSubstrAnyLen' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "index_substr_any_step", "step", 2, 2, 0, ARG_UINT|ARG_NONZERO, + &index_substr_any_step, "( OLcfgGlAt:23 NAME 'olcIndexSubstrAnyStep' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "index_intlen", "len", 2, 2, 0, ARG_UINT|ARG_MAGIC|CFG_IX_INTLEN, + &config_generic, "( OLcfgGlAt:84 NAME 'olcIndexIntLen' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "lastmod", "on|off", 2, 2, 0, ARG_DB|ARG_ON_OFF|ARG_MAGIC|CFG_LASTMOD, + &config_generic, "( OLcfgDbAt:0.4 NAME 'olcLastMod' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "ldapsyntax", "syntax", 2, 0, 0, + ARG_PAREN|ARG_MAGIC|CFG_SYNTAX, + &config_generic, "( OLcfgGlAt:85 NAME 'olcLdapSyntaxes' " + "DESC 'OpenLDAP ldapSyntax' " + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )", + NULL, NULL }, + { "limits", "limits", 2, 0, 0, ARG_DB|ARG_MAGIC|CFG_LIMITS, + &config_generic, "( OLcfgDbAt:0.5 NAME 'olcLimits' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )", NULL, NULL }, + { "listener-threads", "count", 2, 0, 0, +#ifdef NO_THREADS + ARG_IGNORED, NULL, +#else + ARG_UINT|ARG_MAGIC|CFG_LTHREADS, &config_generic, +#endif + "( OLcfgGlAt:93 NAME 'olcListenerThreads' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "localSSF", "ssf", 2, 2, 0, ARG_INT, + &local_ssf, "( OLcfgGlAt:26 NAME 'olcLocalSSF' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "logfile", "file", 2, 2, 0, ARG_STRING|ARG_MAGIC|CFG_LOGFILE, + &config_generic, "( OLcfgGlAt:27 NAME 'olcLogFile' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "loglevel", "level", 2, 0, 0, ARG_MAGIC, + &config_loglevel, "( OLcfgGlAt:28 NAME 'olcLogLevel' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "maxDerefDepth", "depth", 2, 2, 0, ARG_DB|ARG_INT|ARG_MAGIC|CFG_DEPTH, + &config_generic, "( OLcfgDbAt:0.6 NAME 'olcMaxDerefDepth' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "mirrormode", "on|off", 2, 2, 0, ARG_DB|ARG_ON_OFF|ARG_MAGIC|CFG_MIRRORMODE, + &config_generic, "( OLcfgDbAt:0.16 NAME 'olcMirrorMode' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "moduleload", "file", 2, 0, 0, +#ifdef SLAPD_MODULES + ARG_MAGIC|CFG_MODLOAD|ARG_NO_DELETE, &config_generic, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:30 NAME 'olcModuleLoad' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )", NULL, NULL }, + { "modulepath", "path", 2, 2, 0, +#ifdef SLAPD_MODULES + ARG_MAGIC|CFG_MODPATH|ARG_NO_DELETE|ARG_NO_INSERT, &config_generic, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:31 NAME 'olcModulePath' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "monitoring", "TRUE|FALSE", 2, 2, 0, + ARG_MAGIC|CFG_MONITORING|ARG_DB|ARG_ON_OFF, &config_generic, + "( OLcfgDbAt:0.18 NAME 'olcMonitoring' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "objectclass", "objectclass", 2, 0, 0, ARG_PAREN|ARG_MAGIC|CFG_OC, + &config_generic, "( OLcfgGlAt:32 NAME 'olcObjectClasses' " + "DESC 'OpenLDAP object classes' " + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )", + NULL, NULL }, + { "objectidentifier", "name> <oid", 3, 3, 0, ARG_MAGIC|CFG_OID, + &config_generic, "( OLcfgGlAt:33 NAME 'olcObjectIdentifier' " + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )", NULL, NULL }, + { "overlay", "overlay", 2, 2, 0, ARG_MAGIC, + &config_overlay, "( OLcfgGlAt:34 NAME 'olcOverlay' " + "SUP olcDatabase SINGLE-VALUE X-ORDERED 'SIBLINGS' )", NULL, NULL }, + { "password-crypt-salt-format", "salt", 2, 2, 0, ARG_STRING|ARG_MAGIC|CFG_SALT, + &config_generic, "( OLcfgGlAt:35 NAME 'olcPasswordCryptSaltFormat' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "password-hash", "hash", 2, 0, 0, ARG_MAGIC, + &config_passwd_hash, "( OLcfgGlAt:36 NAME 'olcPasswordHash' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "pidfile", "file", 2, 2, 0, ARG_STRING, + &slapd_pid_file, "( OLcfgGlAt:37 NAME 'olcPidFile' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "plugin", NULL, 0, 0, 0, +#ifdef LDAP_SLAPI + ARG_MAGIC|CFG_PLUGIN, &config_generic, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:38 NAME 'olcPlugin' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "pluginlog", "filename", 2, 2, 0, +#ifdef LDAP_SLAPI + ARG_STRING, &slapi_log_file, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:39 NAME 'olcPluginLogFile' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "readonly", "on|off", 2, 2, 0, ARG_MAY_DB|ARG_ON_OFF|ARG_MAGIC|CFG_RO, + &config_generic, "( OLcfgGlAt:40 NAME 'olcReadOnly' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "referral", "url", 2, 2, 0, ARG_MAGIC, + &config_referral, "( OLcfgGlAt:41 NAME 'olcReferral' " + "SUP labeledURI SINGLE-VALUE )", NULL, NULL }, + { "replica", "host or uri", 2, 0, 0, ARG_DB|ARG_MAGIC, + &config_obsolete, "( OLcfgDbAt:0.7 NAME 'olcReplica' " + "EQUALITY caseIgnoreMatch " + "SUP labeledURI X-ORDERED 'VALUES' )", NULL, NULL }, + { "replica-argsfile", NULL, 0, 0, 0, ARG_MAY_DB|ARG_MAGIC, + &config_obsolete, "( OLcfgGlAt:43 NAME 'olcReplicaArgsFile' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "replica-pidfile", NULL, 0, 0, 0, ARG_MAY_DB|ARG_MAGIC, + &config_obsolete, "( OLcfgGlAt:44 NAME 'olcReplicaPidFile' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "replicationInterval", NULL, 0, 0, 0, ARG_MAY_DB|ARG_MAGIC, + &config_obsolete, "( OLcfgGlAt:45 NAME 'olcReplicationInterval' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "replogfile", "filename", 2, 2, 0, ARG_MAY_DB|ARG_MAGIC, + &config_obsolete, "( OLcfgGlAt:46 NAME 'olcReplogFile' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "require", "features", 2, 0, 7, ARG_MAY_DB|ARG_MAGIC, + &config_requires, "( OLcfgGlAt:47 NAME 'olcRequires' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "restrict", "op_list", 2, 0, 0, ARG_MAY_DB|ARG_MAGIC, + &config_restrict, "( OLcfgGlAt:48 NAME 'olcRestrict' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "reverse-lookup", "on|off", 2, 2, 0, +#ifdef SLAPD_RLOOKUPS + ARG_ON_OFF, &use_reverse_lookup, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:49 NAME 'olcReverseLookup' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "rootdn", "dn", 2, 2, 0, ARG_DB|ARG_DN|ARG_QUOTE|ARG_MAGIC, + &config_rootdn, "( OLcfgDbAt:0.8 NAME 'olcRootDN' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL }, + { "rootDSE", "file", 2, 2, 0, ARG_MAGIC|CFG_ROOTDSE, + &config_generic, "( OLcfgGlAt:51 NAME 'olcRootDSE' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "rootpw", "password", 2, 2, 0, ARG_BERVAL|ARG_DB|ARG_MAGIC, + &config_rootpw, "( OLcfgDbAt:0.9 NAME 'olcRootPW' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "sasl-authz-policy", NULL, 2, 2, 0, ARG_MAGIC|CFG_AZPOLICY, + &config_generic, NULL, NULL, NULL }, + { "sasl-auxprops", NULL, 2, 0, 0, +#ifdef HAVE_CYRUS_SASL + ARG_STRING|ARG_UNIQUE, &slap_sasl_auxprops, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:89 NAME 'olcSaslAuxprops' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "sasl-host", "host", 2, 2, 0, +#ifdef HAVE_CYRUS_SASL + ARG_STRING|ARG_UNIQUE, &sasl_host, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:53 NAME 'olcSaslHost' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "sasl-realm", "realm", 2, 2, 0, +#ifdef HAVE_CYRUS_SASL + ARG_STRING|ARG_UNIQUE, &global_realm, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:54 NAME 'olcSaslRealm' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "sasl-regexp", NULL, 3, 3, 0, ARG_MAGIC|CFG_AZREGEXP, + &config_generic, NULL, NULL, NULL }, + { "sasl-secprops", "properties", 2, 2, 0, +#ifdef HAVE_CYRUS_SASL + ARG_MAGIC|CFG_SASLSECP, &config_generic, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:56 NAME 'olcSaslSecProps' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "saslRegexp", NULL, 3, 3, 0, ARG_MAGIC|CFG_AZREGEXP, + &config_generic, NULL, NULL, NULL }, + { "schemadn", "dn", 2, 2, 0, ARG_MAY_DB|ARG_DN|ARG_QUOTE|ARG_MAGIC, + &config_schema_dn, "( OLcfgGlAt:58 NAME 'olcSchemaDN' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL }, + { "security", "factors", 2, 0, 0, ARG_MAY_DB|ARG_MAGIC, + &config_security, "( OLcfgGlAt:59 NAME 'olcSecurity' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "serverID", "number> <[URI]", 2, 3, 0, ARG_MAGIC|CFG_SERVERID, + &config_generic, "( OLcfgGlAt:81 NAME 'olcServerID' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "sizelimit", "limit", 2, 0, 0, ARG_MAY_DB|ARG_MAGIC, + &config_sizelimit, "( OLcfgGlAt:60 NAME 'olcSizeLimit' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "sockbuf_max_incoming", "max", 2, 2, 0, ARG_BER_LEN_T, + &sockbuf_max_incoming, "( OLcfgGlAt:61 NAME 'olcSockbufMaxIncoming' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "sockbuf_max_incoming_auth", "max", 2, 2, 0, ARG_BER_LEN_T, + &sockbuf_max_incoming_auth, "( OLcfgGlAt:62 NAME 'olcSockbufMaxIncomingAuth' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "sortvals", "attr", 2, 0, 0, ARG_MAGIC|CFG_SORTVALS, + &config_generic, "( OLcfgGlAt:83 NAME 'olcSortVals' " + "DESC 'Attributes whose values will always be sorted' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "subordinate", "[advertise]", 1, 2, 0, ARG_DB|ARG_MAGIC, + &config_subordinate, "( OLcfgDbAt:0.15 NAME 'olcSubordinate' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "suffix", "suffix", 2, 2, 0, ARG_DB|ARG_DN|ARG_QUOTE|ARG_MAGIC, + &config_suffix, "( OLcfgDbAt:0.10 NAME 'olcSuffix' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN )", NULL, NULL }, + { "sync_use_subentry", NULL, 0, 0, 0, ARG_ON_OFF|ARG_DB|ARG_MAGIC|CFG_SYNC_SUBENTRY, + &config_generic, "( OLcfgDbAt:0.19 NAME 'olcSyncUseSubentry' " + "DESC 'Store sync context in a subentry' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "syncrepl", NULL, 0, 0, 0, ARG_DB|ARG_MAGIC, + &syncrepl_config, "( OLcfgDbAt:0.11 NAME 'olcSyncrepl' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )", NULL, NULL }, + { "tcp-buffer", "[listener=<listener>] [{read|write}=]size", 0, 0, 0, +#ifndef LDAP_TCP_BUFFER + ARG_IGNORED, NULL, +#else /* LDAP_TCP_BUFFER */ + ARG_MAGIC, &config_tcp_buffer, +#endif /* LDAP_TCP_BUFFER */ + "( OLcfgGlAt:90 NAME 'olcTCPBuffer' " + "DESC 'Custom TCP buffer size' " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "threads", "count", 2, 2, 0, +#ifdef NO_THREADS + ARG_IGNORED, NULL, +#else + ARG_INT|ARG_MAGIC|CFG_THREADS, &config_generic, +#endif + "( OLcfgGlAt:66 NAME 'olcThreads' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "timelimit", "limit", 2, 0, 0, ARG_MAY_DB|ARG_MAGIC, + &config_timelimit, "( OLcfgGlAt:67 NAME 'olcTimeLimit' " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "TLSCACertificateFile", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_CA_FILE|ARG_STRING|ARG_MAGIC, &config_tls_option, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:68 NAME 'olcTLSCACertificateFile' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "TLSCACertificatePath", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_CA_PATH|ARG_STRING|ARG_MAGIC, &config_tls_option, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:69 NAME 'olcTLSCACertificatePath' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "TLSCertificateFile", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_CERT_FILE|ARG_STRING|ARG_MAGIC, &config_tls_option, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:70 NAME 'olcTLSCertificateFile' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "TLSCertificateKeyFile", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_CERT_KEY|ARG_STRING|ARG_MAGIC, &config_tls_option, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:71 NAME 'olcTLSCertificateKeyFile' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "TLSCipherSuite", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_CIPHER|ARG_STRING|ARG_MAGIC, &config_tls_option, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:72 NAME 'olcTLSCipherSuite' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "TLSCRLCheck", NULL, 2, 2, 0, +#if defined(HAVE_TLS) && defined(HAVE_OPENSSL_CRL) + CFG_TLS_CRLCHECK|ARG_STRING|ARG_MAGIC, &config_tls_config, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:73 NAME 'olcTLSCRLCheck' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "TLSCRLFile", NULL, 2, 2, 0, +#if defined(HAVE_GNUTLS) + CFG_TLS_CRL_FILE|ARG_STRING|ARG_MAGIC, &config_tls_option, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:82 NAME 'olcTLSCRLFile' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "TLSRandFile", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_RAND|ARG_STRING|ARG_MAGIC, &config_tls_option, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:74 NAME 'olcTLSRandFile' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "TLSVerifyClient", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_VERIFY|ARG_STRING|ARG_MAGIC, &config_tls_config, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:75 NAME 'olcTLSVerifyClient' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "TLSDHParamFile", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_DH_FILE|ARG_STRING|ARG_MAGIC, &config_tls_option, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:77 NAME 'olcTLSDHParamFile' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "TLSECName", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_ECNAME|ARG_STRING|ARG_MAGIC, &config_tls_option, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:96 NAME 'olcTLSECName' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "TLSProtocolMin", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_PROTOCOL_MIN|ARG_STRING|ARG_MAGIC, &config_tls_config, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgGlAt:87 NAME 'olcTLSProtocolMin' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "tool-threads", "count", 2, 2, 0, ARG_INT|ARG_MAGIC|CFG_TTHREADS, + &config_generic, "( OLcfgGlAt:80 NAME 'olcToolThreads' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "ucdata-path", "path", 2, 2, 0, ARG_IGNORED, + NULL, NULL, NULL, NULL }, + { "updatedn", "dn", 2, 2, 0, ARG_DB|ARG_DN|ARG_QUOTE|ARG_MAGIC, + &config_updatedn, "( OLcfgDbAt:0.12 NAME 'olcUpdateDN' " + "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL }, + { "updateref", "url", 2, 2, 0, ARG_DB|ARG_MAGIC, + &config_updateref, "( OLcfgDbAt:0.13 NAME 'olcUpdateRef' " + "EQUALITY caseIgnoreMatch " + "SUP labeledURI )", NULL, NULL }, + { "writetimeout", "timeout", 2, 2, 0, ARG_INT, + &global_writetimeout, "( OLcfgGlAt:88 NAME 'olcWriteTimeout' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED, + NULL, NULL, NULL, NULL } +}; + +/* Need to no-op this keyword for dynamic config */ +ConfigTable olcDatabaseDummy[] = { + { "", "", 0, 0, 0, ARG_IGNORED, + NULL, "( OLcfgGlAt:13 NAME 'olcDatabase' " + "DESC 'The backend type for a database instance' " + "SUP olcBackend SINGLE-VALUE X-ORDERED 'SIBLINGS' )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +/* Routines to check if a child can be added to this type */ +static ConfigLDAPadd cfAddSchema, cfAddInclude, cfAddDatabase, + cfAddBackend, cfAddModule, cfAddOverlay; + +/* NOTE: be careful when defining array members + * that can be conditionally compiled */ +#define CFOC_GLOBAL cf_ocs[1] +#define CFOC_SCHEMA cf_ocs[2] +#define CFOC_BACKEND cf_ocs[3] +#define CFOC_DATABASE cf_ocs[4] +#define CFOC_OVERLAY cf_ocs[5] +#define CFOC_INCLUDE cf_ocs[6] +#define CFOC_FRONTEND cf_ocs[7] +#ifdef SLAPD_MODULES +#define CFOC_MODULE cf_ocs[8] +#endif /* SLAPD_MODULES */ + +static ConfigOCs cf_ocs[] = { + { "( OLcfgGlOc:0 " + "NAME 'olcConfig' " + "DESC 'OpenLDAP configuration object' " + "ABSTRACT SUP top )", Cft_Abstract, NULL }, + { "( OLcfgGlOc:1 " + "NAME 'olcGlobal' " + "DESC 'OpenLDAP Global configuration options' " + "SUP olcConfig STRUCTURAL " + "MAY ( cn $ olcConfigFile $ olcConfigDir $ olcAllows $ olcArgsFile $ " + "olcAttributeOptions $ olcAuthIDRewrite $ " + "olcAuthzPolicy $ olcAuthzRegexp $ olcConcurrency $ " + "olcConnMaxPending $ olcConnMaxPendingAuth $ " + "olcDisallows $ olcGentleHUP $ olcIdleTimeout $ " + "olcIndexSubstrIfMaxLen $ olcIndexSubstrIfMinLen $ " + "olcIndexSubstrAnyLen $ olcIndexSubstrAnyStep $ olcIndexIntLen $ " + "olcListenerThreads $ olcLocalSSF $ olcLogFile $ olcLogLevel $ " + "olcPasswordCryptSaltFormat $ olcPasswordHash $ olcPidFile $ " + "olcPluginLogFile $ olcReadOnly $ olcReferral $ " + "olcReplogFile $ olcRequires $ olcRestrict $ olcReverseLookup $ " + "olcRootDSE $ " + "olcSaslAuxprops $ olcSaslHost $ olcSaslRealm $ olcSaslSecProps $ " + "olcSecurity $ olcServerID $ olcSizeLimit $ " + "olcSockbufMaxIncoming $ olcSockbufMaxIncomingAuth $ " + "olcTCPBuffer $ " + "olcThreads $ olcTimeLimit $ olcTLSCACertificateFile $ " + "olcTLSCACertificatePath $ olcTLSCertificateFile $ " + "olcTLSCertificateKeyFile $ olcTLSCipherSuite $ olcTLSCRLCheck $ " + "olcTLSRandFile $ olcTLSVerifyClient $ olcTLSDHParamFile $ olcTLSECName $ " + "olcTLSCRLFile $ olcTLSProtocolMin $ olcToolThreads $ olcWriteTimeout $ " + "olcObjectIdentifier $ olcAttributeTypes $ olcObjectClasses $ " + "olcDitContentRules $ olcLdapSyntaxes ) )", Cft_Global }, + { "( OLcfgGlOc:2 " + "NAME 'olcSchemaConfig' " + "DESC 'OpenLDAP schema object' " + "SUP olcConfig STRUCTURAL " + "MAY ( cn $ olcObjectIdentifier $ olcLdapSyntaxes $ " + "olcAttributeTypes $ olcObjectClasses $ olcDitContentRules ) )", + Cft_Schema, NULL, cfAddSchema }, + { "( OLcfgGlOc:3 " + "NAME 'olcBackendConfig' " + "DESC 'OpenLDAP Backend-specific options' " + "SUP olcConfig STRUCTURAL " + "MUST olcBackend )", Cft_Backend, NULL, cfAddBackend }, + { "( OLcfgGlOc:4 " + "NAME 'olcDatabaseConfig' " + "DESC 'OpenLDAP Database-specific options' " + "SUP olcConfig STRUCTURAL " + "MUST olcDatabase " + "MAY ( olcHidden $ olcSuffix $ olcSubordinate $ olcAccess $ " + "olcAddContentAcl $ olcLastMod $ olcLimits $ " + "olcMaxDerefDepth $ olcPlugin $ olcReadOnly $ olcReplica $ " + "olcReplicaArgsFile $ olcReplicaPidFile $ olcReplicationInterval $ " + "olcReplogFile $ olcRequires $ olcRestrict $ olcRootDN $ olcRootPW $ " + "olcSchemaDN $ olcSecurity $ olcSizeLimit $ olcSyncUseSubentry $ olcSyncrepl $ " + "olcTimeLimit $ olcUpdateDN $ olcUpdateRef $ olcMirrorMode $ " + "olcMonitoring $ olcExtraAttrs ) )", + Cft_Database, NULL, cfAddDatabase }, + { "( OLcfgGlOc:5 " + "NAME 'olcOverlayConfig' " + "DESC 'OpenLDAP Overlay-specific options' " + "SUP olcConfig STRUCTURAL " + "MUST olcOverlay )", Cft_Overlay, NULL, cfAddOverlay }, + { "( OLcfgGlOc:6 " + "NAME 'olcIncludeFile' " + "DESC 'OpenLDAP configuration include file' " + "SUP olcConfig STRUCTURAL " + "MUST olcInclude " + "MAY ( cn $ olcRootDSE ) )", + /* Used to be Cft_Include, that def has been removed */ + Cft_Abstract, NULL, cfAddInclude }, + /* This should be STRUCTURAL like all the other database classes, but + * that would mean inheriting all of the olcDatabaseConfig attributes, + * which causes them to be merged twice in config_build_entry. + */ + { "( OLcfgGlOc:7 " + "NAME 'olcFrontendConfig' " + "DESC 'OpenLDAP frontend configuration' " + "AUXILIARY " + "MAY ( olcDefaultSearchBase $ olcPasswordHash $ olcSortVals ) )", + Cft_Database, NULL, NULL }, +#ifdef SLAPD_MODULES + { "( OLcfgGlOc:8 " + "NAME 'olcModuleList' " + "DESC 'OpenLDAP dynamic module info' " + "SUP olcConfig STRUCTURAL " + "MAY ( cn $ olcModulePath $ olcModuleLoad ) )", + Cft_Module, NULL, cfAddModule }, +#endif + { NULL, 0, NULL } +}; + +typedef struct ServerID { + struct ServerID *si_next; + struct berval si_url; + int si_num; +} ServerID; + +static ServerID *sid_list; +static ServerID *sid_set; + +typedef struct voidList { + struct voidList *vl_next; + void *vl_ptr; +} voidList; + +typedef struct ADlist { + struct ADlist *al_next; + AttributeDescription *al_desc; +} ADlist; + +static ADlist *sortVals; + +static int +config_generic(ConfigArgs *c) { + int i; + + if ( c->op == SLAP_CONFIG_EMIT ) { + int rc = 0; + switch(c->type) { + case CFG_CONCUR: + c->value_int = ldap_pvt_thread_get_concurrency(); + break; + case CFG_THREADS: + c->value_int = connection_pool_max; + break; + case CFG_TTHREADS: + c->value_int = slap_tool_thread_max; + break; + case CFG_LTHREADS: + c->value_uint = slapd_daemon_threads; + break; + case CFG_SALT: + if ( passwd_salt ) + c->value_string = ch_strdup( passwd_salt ); + else + rc = 1; + break; + case CFG_LIMITS: + if ( c->be->be_limits ) { + char buf[4096*3]; + struct berval bv; + + for ( i=0; c->be->be_limits[i]; i++ ) { + bv.bv_len = snprintf( buf, sizeof( buf ), SLAP_X_ORDERED_FMT, i ); + if ( bv.bv_len >= sizeof( buf ) ) { + ber_bvarray_free_x( c->rvalue_vals, NULL ); + c->rvalue_vals = NULL; + rc = 1; + break; + } + bv.bv_val = buf + bv.bv_len; + limits_unparse( c->be->be_limits[i], &bv, + sizeof( buf ) - ( bv.bv_val - buf ) ); + bv.bv_len += bv.bv_val - buf; + bv.bv_val = buf; + value_add_one( &c->rvalue_vals, &bv ); + } + } + if ( !c->rvalue_vals ) rc = 1; + break; + case CFG_RO: + c->value_int = (c->be->be_restrictops & SLAP_RESTRICT_READONLY); + break; + case CFG_AZPOLICY: + c->value_string = ch_strdup( slap_sasl_getpolicy()); + break; + case CFG_AZREGEXP: + slap_sasl_regexp_unparse( &c->rvalue_vals ); + if ( !c->rvalue_vals ) rc = 1; + break; +#ifdef HAVE_CYRUS_SASL + case CFG_SASLSECP: { + struct berval bv = BER_BVNULL; + slap_sasl_secprops_unparse( &bv ); + if ( !BER_BVISNULL( &bv )) { + ber_bvarray_add( &c->rvalue_vals, &bv ); + } else { + rc = 1; + } + } + break; +#endif + case CFG_DEPTH: + c->value_int = c->be->be_max_deref_depth; + break; + case CFG_HIDDEN: + if ( SLAP_DBHIDDEN( c->be )) { + c->value_int = 1; + } else { + rc = 1; + } + break; + case CFG_OID: { + ConfigFile *cf = c->ca_private; + if ( !cf ) + oidm_unparse( &c->rvalue_vals, NULL, NULL, 1 ); + else if ( cf->c_om_head ) + oidm_unparse( &c->rvalue_vals, cf->c_om_head, + cf->c_om_tail, 0 ); + if ( !c->rvalue_vals ) + rc = 1; + } + break; + case CFG_ATOPT: + ad_unparse_options( &c->rvalue_vals ); + break; + case CFG_OC: { + ConfigFile *cf = c->ca_private; + if ( !cf ) + oc_unparse( &c->rvalue_vals, NULL, NULL, 1 ); + else if ( cf->c_oc_head ) + oc_unparse( &c->rvalue_vals, cf->c_oc_head, + cf->c_oc_tail, 0 ); + if ( !c->rvalue_vals ) + rc = 1; + } + break; + case CFG_ATTR: { + ConfigFile *cf = c->ca_private; + if ( !cf ) + at_unparse( &c->rvalue_vals, NULL, NULL, 1 ); + else if ( cf->c_at_head ) + at_unparse( &c->rvalue_vals, cf->c_at_head, + cf->c_at_tail, 0 ); + if ( !c->rvalue_vals ) + rc = 1; + } + break; + case CFG_SYNTAX: { + ConfigFile *cf = c->ca_private; + if ( !cf ) + syn_unparse( &c->rvalue_vals, NULL, NULL, 1 ); + else if ( cf->c_syn_head ) + syn_unparse( &c->rvalue_vals, cf->c_syn_head, + cf->c_syn_tail, 0 ); + if ( !c->rvalue_vals ) + rc = 1; + } + break; + case CFG_DIT: { + ConfigFile *cf = c->ca_private; + if ( !cf ) + cr_unparse( &c->rvalue_vals, NULL, NULL, 1 ); + else if ( cf->c_cr_head ) + cr_unparse( &c->rvalue_vals, cf->c_cr_head, + cf->c_cr_tail, 0 ); + if ( !c->rvalue_vals ) + rc = 1; + } + break; + + case CFG_ACL: { + AccessControl *a; + char *src, *dst, ibuf[11]; + struct berval bv, abv; + for (i=0, a=c->be->be_acl; a; i++,a=a->acl_next) { + abv.bv_len = snprintf( ibuf, sizeof( ibuf ), SLAP_X_ORDERED_FMT, i ); + if ( abv.bv_len >= sizeof( ibuf ) ) { + ber_bvarray_free_x( c->rvalue_vals, NULL ); + c->rvalue_vals = NULL; + i = 0; + break; + } + acl_unparse( a, &bv ); + abv.bv_val = ch_malloc( abv.bv_len + bv.bv_len + 1 ); + AC_MEMCPY( abv.bv_val, ibuf, abv.bv_len ); + /* Turn TAB / EOL into plain space */ + for (src=bv.bv_val,dst=abv.bv_val+abv.bv_len; *src; src++) { + if (isspace((unsigned char)*src)) *dst++ = ' '; + else *dst++ = *src; + } + *dst = '\0'; + if (dst[-1] == ' ') { + dst--; + *dst = '\0'; + } + abv.bv_len = dst - abv.bv_val; + ber_bvarray_add( &c->rvalue_vals, &abv ); + } + rc = (!i); + break; + } + case CFG_ACL_ADD: + c->value_int = (SLAP_DBACL_ADD(c->be) != 0); + break; + case CFG_ROOTDSE: { + ConfigFile *cf = c->ca_private; + if ( cf->c_dseFiles ) { + value_add( &c->rvalue_vals, cf->c_dseFiles ); + } else { + rc = 1; + } + } + break; + case CFG_SERVERID: + if ( sid_list ) { + ServerID *si; + struct berval bv; + + for ( si = sid_list; si; si=si->si_next ) { + assert( si->si_num >= 0 && si->si_num <= SLAP_SYNC_SID_MAX ); + if ( !BER_BVISEMPTY( &si->si_url )) { + bv.bv_len = si->si_url.bv_len + 6; + bv.bv_val = ch_malloc( bv.bv_len ); + bv.bv_len = sprintf( bv.bv_val, "%d %s", si->si_num, + si->si_url.bv_val ); + ber_bvarray_add( &c->rvalue_vals, &bv ); + } else { + char buf[5]; + bv.bv_val = buf; + bv.bv_len = sprintf( buf, "%d", si->si_num ); + value_add_one( &c->rvalue_vals, &bv ); + } + } + } else { + rc = 1; + } + break; + case CFG_LOGFILE: + if ( logfileName ) + c->value_string = ch_strdup( logfileName ); + else + rc = 1; + break; + case CFG_LASTMOD: + c->value_int = (SLAP_NOLASTMOD(c->be) == 0); + break; + case CFG_SYNC_SUBENTRY: + c->value_int = (SLAP_SYNC_SUBENTRY(c->be) != 0); + break; + case CFG_MIRRORMODE: + if ( SLAP_SHADOW(c->be)) + c->value_int = (SLAP_MULTIMASTER(c->be) != 0); + else + rc = 1; + break; + case CFG_MONITORING: + c->value_int = (SLAP_DBMONITORING(c->be) != 0); + break; + case CFG_SSTR_IF_MAX: + c->value_uint = index_substr_if_maxlen; + break; + case CFG_SSTR_IF_MIN: + c->value_uint = index_substr_if_minlen; + break; + case CFG_IX_INTLEN: + c->value_int = index_intlen; + break; + case CFG_SORTVALS: { + ADlist *sv; + rc = 1; + for ( sv = sortVals; sv; sv = sv->al_next ) { + value_add_one( &c->rvalue_vals, &sv->al_desc->ad_cname ); + rc = 0; + } + } break; +#ifdef SLAPD_MODULES + case CFG_MODLOAD: { + ModPaths *mp = c->ca_private; + if (mp->mp_loads) { + int i; + for (i=0; !BER_BVISNULL(&mp->mp_loads[i]); i++) { + struct berval bv; + bv.bv_val = c->log; + bv.bv_len = snprintf( bv.bv_val, sizeof( c->log ), + SLAP_X_ORDERED_FMT "%s", i, + mp->mp_loads[i].bv_val ); + if ( bv.bv_len >= sizeof( c->log ) ) { + ber_bvarray_free_x( c->rvalue_vals, NULL ); + c->rvalue_vals = NULL; + break; + } + value_add_one( &c->rvalue_vals, &bv ); + } + } + + rc = c->rvalue_vals ? 0 : 1; + } + break; + case CFG_MODPATH: { + ModPaths *mp = c->ca_private; + if ( !BER_BVISNULL( &mp->mp_path )) + value_add_one( &c->rvalue_vals, &mp->mp_path ); + + rc = c->rvalue_vals ? 0 : 1; + } + break; +#endif +#ifdef LDAP_SLAPI + case CFG_PLUGIN: + slapi_int_plugin_unparse( c->be, &c->rvalue_vals ); + if ( !c->rvalue_vals ) rc = 1; + break; +#endif +#ifdef SLAP_AUTH_REWRITE + case CFG_REWRITE: + if ( authz_rewrites ) { + struct berval bv, idx; + char ibuf[32]; + int i; + + idx.bv_val = ibuf; + for ( i=0; !BER_BVISNULL( &authz_rewrites[i] ); i++ ) { + idx.bv_len = snprintf( idx.bv_val, sizeof( ibuf ), SLAP_X_ORDERED_FMT, i ); + if ( idx.bv_len >= sizeof( ibuf ) ) { + ber_bvarray_free_x( c->rvalue_vals, NULL ); + c->rvalue_vals = NULL; + break; + } + bv.bv_len = idx.bv_len + authz_rewrites[i].bv_len; + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + AC_MEMCPY( bv.bv_val, idx.bv_val, idx.bv_len ); + AC_MEMCPY( &bv.bv_val[ idx.bv_len ], + authz_rewrites[i].bv_val, + authz_rewrites[i].bv_len + 1 ); + ber_bvarray_add( &c->rvalue_vals, &bv ); + } + } + if ( !c->rvalue_vals ) rc = 1; + break; +#endif + default: + rc = 1; + } + return rc; + } else if ( c->op == LDAP_MOD_DELETE ) { + int rc = 0; + switch(c->type) { + /* single-valued attrs, no-ops */ + case CFG_CONCUR: + case CFG_THREADS: + case CFG_TTHREADS: + case CFG_LTHREADS: + case CFG_RO: + case CFG_AZPOLICY: + case CFG_DEPTH: + case CFG_LASTMOD: + case CFG_MONITORING: + case CFG_SASLSECP: + case CFG_SSTR_IF_MAX: + case CFG_SSTR_IF_MIN: + case CFG_ACL_ADD: + case CFG_SYNC_SUBENTRY: + break; + + /* no-ops, requires slapd restart */ + case CFG_PLUGIN: + case CFG_MODLOAD: + case CFG_AZREGEXP: + case CFG_REWRITE: + snprintf(c->log, sizeof( c->log ), "change requires slapd restart"); + break; + + case CFG_MIRRORMODE: + SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_MULTI_SHADOW; + if(SLAP_SHADOW(c->be)) + SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_SINGLE_SHADOW; + break; + + case CFG_SALT: + ch_free( passwd_salt ); + passwd_salt = NULL; + break; + + case CFG_LOGFILE: + ch_free( logfileName ); + logfileName = NULL; + if ( logfile ) { + fclose( logfile ); + logfile = NULL; + } + break; + + case CFG_SERVERID: { + ServerID *si, **sip; + + for ( i=0, si = sid_list, sip = &sid_list; + si; si = *sip, i++ ) { + if ( c->valx == -1 || i == c->valx ) { + *sip = si->si_next; + if ( sid_set == si ) + sid_set = NULL; + ch_free( si ); + if ( c->valx >= 0 ) + break; + } else { + sip = &si->si_next; + } + } + } + break; + case CFG_HIDDEN: + c->be->be_flags &= ~SLAP_DBFLAG_HIDDEN; + break; + + case CFG_IX_INTLEN: + index_intlen = SLAP_INDEX_INTLEN_DEFAULT; + index_intlen_strlen = SLAP_INDEX_INTLEN_STRLEN( + SLAP_INDEX_INTLEN_DEFAULT ); + break; + + case CFG_ACL: + if ( c->valx < 0 ) { + acl_destroy( c->be->be_acl ); + c->be->be_acl = NULL; + + } else { + AccessControl **prev, *a; + int i; + for (i=0, prev = &c->be->be_acl; i < c->valx; + i++ ) { + a = *prev; + prev = &a->acl_next; + } + a = *prev; + *prev = a->acl_next; + acl_free( a ); + } + if ( SLAP_CONFIG( c->be ) && !c->be->be_acl ) { + Debug( LDAP_DEBUG_CONFIG, "config_generic (CFG_ACL): " + "Last explicit ACL for back-config removed. " + "Using hardcoded default\n", 0, 0, 0 ); + c->be->be_acl = defacl_parsed; + } + break; + + case CFG_OC: { + CfEntryInfo *ce; + /* Can be NULL when undoing a failed add */ + if ( c->ca_entry ) { + ce = c->ca_entry->e_private; + /* can't modify the hardcoded schema */ + if ( ce->ce_parent->ce_type == Cft_Global ) + return 1; + } + } + cfn = c->ca_private; + if ( c->valx < 0 ) { + ObjectClass *oc; + + for( oc = cfn->c_oc_head; oc; oc_next( &oc )) { + oc_delete( oc ); + if ( oc == cfn->c_oc_tail ) + break; + } + cfn->c_oc_head = cfn->c_oc_tail = NULL; + } else { + ObjectClass *oc, *prev = NULL; + + for ( i=0, oc=cfn->c_oc_head; i<c->valx; i++) { + prev = oc; + oc_next( &oc ); + } + oc_delete( oc ); + if ( cfn->c_oc_tail == oc ) { + cfn->c_oc_tail = prev; + } + if ( cfn->c_oc_head == oc ) { + oc_next( &oc ); + cfn->c_oc_head = oc; + } + } + break; + + case CFG_ATTR: { + CfEntryInfo *ce; + /* Can be NULL when undoing a failed add */ + if ( c->ca_entry ) { + ce = c->ca_entry->e_private; + /* can't modify the hardcoded schema */ + if ( ce->ce_parent->ce_type == Cft_Global ) + return 1; + } + } + cfn = c->ca_private; + if ( c->valx < 0 ) { + AttributeType *at; + + for( at = cfn->c_at_head; at; at_next( &at )) { + at_delete( at ); + if ( at == cfn->c_at_tail ) + break; + } + cfn->c_at_head = cfn->c_at_tail = NULL; + } else { + AttributeType *at, *prev = NULL; + + for ( i=0, at=cfn->c_at_head; i<c->valx; i++) { + prev = at; + at_next( &at ); + } + at_delete( at ); + if ( cfn->c_at_tail == at ) { + cfn->c_at_tail = prev; + } + if ( cfn->c_at_head == at ) { + at_next( &at ); + cfn->c_at_head = at; + } + } + break; + + case CFG_SYNTAX: { + CfEntryInfo *ce; + /* Can be NULL when undoing a failed add */ + if ( c->ca_entry ) { + ce = c->ca_entry->e_private; + /* can't modify the hardcoded schema */ + if ( ce->ce_parent->ce_type == Cft_Global ) + return 1; + } + } + cfn = c->ca_private; + if ( c->valx < 0 ) { + Syntax *syn; + + for( syn = cfn->c_syn_head; syn; syn_next( &syn )) { + syn_delete( syn ); + if ( syn == cfn->c_syn_tail ) + break; + } + cfn->c_syn_head = cfn->c_syn_tail = NULL; + } else { + Syntax *syn, *prev = NULL; + + for ( i = 0, syn = cfn->c_syn_head; i < c->valx; i++) { + prev = syn; + syn_next( &syn ); + } + syn_delete( syn ); + if ( cfn->c_syn_tail == syn ) { + cfn->c_syn_tail = prev; + } + if ( cfn->c_syn_head == syn ) { + syn_next( &syn ); + cfn->c_syn_head = syn; + } + } + break; + case CFG_SORTVALS: + if ( c->valx < 0 ) { + ADlist *sv; + for ( sv = sortVals; sv; sv = sortVals ) { + sortVals = sv->al_next; + sv->al_desc->ad_type->sat_flags &= ~SLAP_AT_SORTED_VAL; + ch_free( sv ); + } + } else { + ADlist *sv, **prev; + int i = 0; + + for ( prev = &sortVals, sv = sortVals; i < c->valx; i++ ) { + prev = &sv->al_next; + sv = sv->al_next; + } + sv->al_desc->ad_type->sat_flags &= ~SLAP_AT_SORTED_VAL; + *prev = sv->al_next; + ch_free( sv ); + } + break; + + case CFG_LIMITS: + /* FIXME: there is no limits_free function */ + if ( c->valx < 0 ) { + limits_destroy( c->be->be_limits ); + c->be->be_limits = NULL; + + } else { + int cnt, num = -1; + + if ( c->be->be_limits ) { + for ( num = 0; c->be->be_limits[ num ]; num++ ) + /* just count */ ; + } + + if ( c->valx >= num ) { + return 1; + } + + if ( num == 1 ) { + limits_destroy( c->be->be_limits ); + c->be->be_limits = NULL; + + } else { + limits_free_one( c->be->be_limits[ c->valx ] ); + + for ( cnt = c->valx; cnt < num; cnt++ ) { + c->be->be_limits[ cnt ] = c->be->be_limits[ cnt + 1 ]; + } + } + } + break; + + case CFG_ATOPT: + /* FIXME: there is no ad_option_free function */ + case CFG_ROOTDSE: + /* FIXME: there is no way to remove attributes added by + a DSE file */ + case CFG_OID: + case CFG_DIT: + case CFG_MODPATH: + default: + rc = 1; + break; + } + return rc; + } + + switch(c->type) { + case CFG_BACKEND: + if(!(c->bi = backend_info(c->argv[1]))) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> failed init", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s (%s)!\n", + c->log, c->cr_msg, c->argv[1] ); + return(1); + } + break; + + case CFG_DATABASE: + c->bi = NULL; + /* NOTE: config is always the first backend! + */ + if ( !strcasecmp( c->argv[1], "config" )) { + c->be = LDAP_STAILQ_FIRST(&backendDB); + } else if ( !strcasecmp( c->argv[1], "frontend" )) { + c->be = frontendDB; + } else { + c->be = backend_db_init(c->argv[1], NULL, c->valx, &c->reply); + if ( !c->be ) { + if ( c->cr_msg[0] == 0 ) + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> failed init", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s (%s)\n", c->log, c->cr_msg, c->argv[1] ); + return(1); + } + } + break; + + case CFG_CONCUR: + ldap_pvt_thread_set_concurrency(c->value_int); + break; + + case CFG_THREADS: + if ( c->value_int < 2 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "threads=%d smaller than minimum value 2", + c->value_int ); + Debug(LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + return 1; + + } else if ( c->value_int > 2 * SLAP_MAX_WORKER_THREADS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "warning, threads=%d larger than twice the default (2*%d=%d); YMMV", + c->value_int, SLAP_MAX_WORKER_THREADS, 2 * SLAP_MAX_WORKER_THREADS ); + Debug(LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + } + if ( slapMode & SLAP_SERVER_MODE ) + ldap_pvt_thread_pool_maxthreads(&connection_pool, c->value_int); + connection_pool_max = c->value_int; /* save for reference */ + break; + + case CFG_TTHREADS: + if ( slapMode & SLAP_TOOL_MODE ) + ldap_pvt_thread_pool_maxthreads(&connection_pool, c->value_int); + slap_tool_thread_max = c->value_int; /* save for reference */ + break; + + case CFG_LTHREADS: + { int mask = 0; + /* use a power of two */ + while (c->value_uint > 1) { + c->value_uint >>= 1; + mask <<= 1; + mask |= 1; + } + slapd_daemon_mask = mask; + slapd_daemon_threads = mask+1; + } + break; + + case CFG_SALT: + if ( passwd_salt ) ch_free( passwd_salt ); + passwd_salt = c->value_string; + lutil_salt_format(passwd_salt); + break; + + case CFG_LIMITS: + if(limits_parse(c->be, c->fname, c->lineno, c->argc, c->argv)) + return(1); + break; + + case CFG_RO: + if(c->value_int) + c->be->be_restrictops |= SLAP_RESTRICT_READONLY; + else + c->be->be_restrictops &= ~SLAP_RESTRICT_READONLY; + break; + + case CFG_AZPOLICY: + ch_free(c->value_string); + if (slap_sasl_setpolicy( c->argv[1] )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unable to parse value", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", + c->log, c->cr_msg, c->argv[1] ); + return(1); + } + break; + + case CFG_AZREGEXP: + if (slap_sasl_regexp_config( c->argv[1], c->argv[2] )) + return(1); + break; + +#ifdef HAVE_CYRUS_SASL + case CFG_SASLSECP: + { + char *txt = slap_sasl_secprops( c->argv[1] ); + if ( txt ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), "<%s> %s", + c->argv[0], txt ); + Debug(LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg, 0 ); + return(1); + } + break; + } +#endif + + case CFG_DEPTH: + c->be->be_max_deref_depth = c->value_int; + break; + + case CFG_OID: { + OidMacro *om; + + if ( c->op == LDAP_MOD_ADD && c->ca_private && cfn != c->ca_private ) + cfn = c->ca_private; + if(parse_oidm(c, 1, &om)) + return(1); + if (!cfn->c_om_head) cfn->c_om_head = om; + cfn->c_om_tail = om; + } + break; + + case CFG_OC: { + ObjectClass *oc, *prev; + + if ( c->op == LDAP_MOD_ADD && c->ca_private && cfn != c->ca_private ) + cfn = c->ca_private; + if ( c->valx < 0 ) { + prev = cfn->c_oc_tail; + } else { + prev = NULL; + /* If adding anything after the first, prev is easy */ + if ( c->valx ) { + int i; + for (i=0, oc = cfn->c_oc_head; i<c->valx; i++) { + prev = oc; + if ( !oc_next( &oc )) + break; + } + } else + /* If adding the first, and head exists, find its prev */ + if (cfn->c_oc_head) { + for ( oc_start( &oc ); oc != cfn->c_oc_head; ) { + prev = oc; + oc_next( &oc ); + } + } + /* else prev is NULL, append to end of global list */ + } + if(parse_oc(c, &oc, prev)) return(1); + if (!cfn->c_oc_head || !c->valx) cfn->c_oc_head = oc; + if (cfn->c_oc_tail == prev) cfn->c_oc_tail = oc; + } + break; + + case CFG_ATTR: { + AttributeType *at, *prev; + + if ( c->op == LDAP_MOD_ADD && c->ca_private && cfn != c->ca_private ) + cfn = c->ca_private; + if ( c->valx < 0 ) { + prev = cfn->c_at_tail; + } else { + prev = NULL; + /* If adding anything after the first, prev is easy */ + if ( c->valx ) { + int i; + for (i=0, at = cfn->c_at_head; i<c->valx; i++) { + prev = at; + if ( !at_next( &at )) + break; + } + } else + /* If adding the first, and head exists, find its prev */ + if (cfn->c_at_head) { + for ( at_start( &at ); at != cfn->c_at_head; ) { + prev = at; + at_next( &at ); + } + } + /* else prev is NULL, append to end of global list */ + } + if(parse_at(c, &at, prev)) return(1); + if (!cfn->c_at_head || !c->valx) cfn->c_at_head = at; + if (cfn->c_at_tail == prev) cfn->c_at_tail = at; + } + break; + + case CFG_SYNTAX: { + Syntax *syn, *prev; + + if ( c->op == LDAP_MOD_ADD && c->ca_private && cfn != c->ca_private ) + cfn = c->ca_private; + if ( c->valx < 0 ) { + prev = cfn->c_syn_tail; + } else { + prev = NULL; + /* If adding anything after the first, prev is easy */ + if ( c->valx ) { + int i; + for ( i = 0, syn = cfn->c_syn_head; i < c->valx; i++ ) { + prev = syn; + if ( !syn_next( &syn )) + break; + } + } else + /* If adding the first, and head exists, find its prev */ + if (cfn->c_syn_head) { + for ( syn_start( &syn ); syn != cfn->c_syn_head; ) { + prev = syn; + syn_next( &syn ); + } + } + /* else prev is NULL, append to end of global list */ + } + if ( parse_syn( c, &syn, prev ) ) return(1); + if ( !cfn->c_syn_head || !c->valx ) cfn->c_syn_head = syn; + if ( cfn->c_syn_tail == prev ) cfn->c_syn_tail = syn; + } + break; + + case CFG_DIT: { + ContentRule *cr; + + if ( c->op == LDAP_MOD_ADD && c->ca_private && cfn != c->ca_private ) + cfn = c->ca_private; + if(parse_cr(c, &cr)) return(1); + if (!cfn->c_cr_head) cfn->c_cr_head = cr; + cfn->c_cr_tail = cr; + } + break; + + case CFG_ATOPT: + ad_define_option(NULL, NULL, 0); + for(i = 1; i < c->argc; i++) + if(ad_define_option(c->argv[i], c->fname, c->lineno)) + return(1); + break; + + case CFG_IX_INTLEN: + if ( c->value_int < SLAP_INDEX_INTLEN_DEFAULT ) + c->value_int = SLAP_INDEX_INTLEN_DEFAULT; + else if ( c->value_int > 255 ) + c->value_int = 255; + index_intlen = c->value_int; + index_intlen_strlen = SLAP_INDEX_INTLEN_STRLEN( + index_intlen ); + break; + + case CFG_SORTVALS: { + ADlist *svnew = NULL, *svtail, *sv; + + for ( i = 1; i < c->argc; i++ ) { + AttributeDescription *ad = NULL; + const char *text; + int rc; + + rc = slap_str2ad( c->argv[i], &ad, &text ); + if ( rc ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unknown attribute type #%d", + c->argv[0], i ); +sortval_reject: + Debug(LDAP_DEBUG_ANY, "%s: %s %s\n", + c->log, c->cr_msg, c->argv[i] ); + for ( sv = svnew; sv; sv = svnew ) { + svnew = sv->al_next; + ch_free( sv ); + } + return 1; + } + if (( ad->ad_type->sat_flags & SLAP_AT_ORDERED ) || + ad->ad_type->sat_single_value ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> inappropriate attribute type #%d", + c->argv[0], i ); + goto sortval_reject; + } + sv = ch_malloc( sizeof( ADlist )); + sv->al_desc = ad; + if ( !svnew ) { + svnew = sv; + } else { + svtail->al_next = sv; + } + svtail = sv; + } + sv->al_next = NULL; + for ( sv = svnew; sv; sv = sv->al_next ) + sv->al_desc->ad_type->sat_flags |= SLAP_AT_SORTED_VAL; + for ( sv = sortVals; sv && sv->al_next; sv = sv->al_next ); + if ( sv ) + sv->al_next = svnew; + else + sortVals = svnew; + } + break; + + case CFG_ACL: + if ( SLAP_CONFIG( c->be ) && c->be->be_acl == defacl_parsed) { + c->be->be_acl = NULL; + } + /* Don't append to the global ACL if we're on a specific DB */ + i = c->valx; + if ( c->valx == -1 ) { + AccessControl *a; + i = 0; + for ( a=c->be->be_acl; a; a = a->acl_next ) + i++; + } + if ( parse_acl(c->be, c->fname, c->lineno, c->argc, c->argv, i ) ) { + if ( SLAP_CONFIG( c->be ) && !c->be->be_acl) { + c->be->be_acl = defacl_parsed; + } + return 1; + } + break; + + case CFG_ACL_ADD: + if(c->value_int) + SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_ACL_ADD; + else + SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_ACL_ADD; + break; + + case CFG_ROOTDSE: + if(root_dse_read_file(c->argv[1])) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> could not read file", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s %s\n", + c->log, c->cr_msg, c->argv[1] ); + return(1); + } + { + struct berval bv; + ber_str2bv( c->argv[1], 0, 1, &bv ); + if ( c->op == LDAP_MOD_ADD && c->ca_private && cfn != c->ca_private ) + cfn = c->ca_private; + ber_bvarray_add( &cfn->c_dseFiles, &bv ); + } + break; + + case CFG_SERVERID: + { + ServerID *si, **sip; + LDAPURLDesc *lud; + int num; + if (( lutil_atoi( &num, c->argv[1] ) && + lutil_atoix( &num, c->argv[1], 16 )) || + num < 0 || num > SLAP_SYNC_SID_MAX ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> illegal server ID", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s %s\n", + c->log, c->cr_msg, c->argv[1] ); + return 1; + } + /* only one value allowed if no URL is given */ + if ( c->argc > 2 ) { + int len; + + if ( sid_list && BER_BVISEMPTY( &sid_list->si_url )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> only one server ID allowed now", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s %s\n", + c->log, c->cr_msg, c->argv[1] ); + return 1; + } + + if ( ldap_url_parse( c->argv[2], &lud )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> invalid URL", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s %s\n", + c->log, c->cr_msg, c->argv[2] ); + return 1; + } + len = strlen( c->argv[2] ); + si = ch_malloc( sizeof(ServerID) + len + 1 ); + si->si_url.bv_val = (char *)(si+1); + si->si_url.bv_len = len; + strcpy( si->si_url.bv_val, c->argv[2] ); + } else { + if ( sid_list ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> unqualified server ID not allowed now", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s %s\n", + c->log, c->cr_msg, c->argv[1] ); + return 1; + } + si = ch_malloc( sizeof(ServerID) ); + BER_BVZERO( &si->si_url ); + slap_serverID = num; + Debug( LDAP_DEBUG_CONFIG, + "%s: SID=0x%03x\n", + c->log, slap_serverID, 0 ); + sid_set = si; + } + si->si_next = NULL; + si->si_num = num; + for ( sip = &sid_list; *sip; sip = &(*sip)->si_next ); + *sip = si; + + if (( slapMode & SLAP_SERVER_MODE ) && c->argc > 2 ) { + Listener *l = config_check_my_url( c->argv[2], lud ); + if ( l ) { + if ( sid_set ) { + ldap_free_urldesc( lud ); + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> multiple server ID URLs matched, only one is allowed", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s %s\n", + c->log, c->cr_msg, c->argv[1] ); + return 1; + } + slap_serverID = si->si_num; + Debug( LDAP_DEBUG_CONFIG, + "%s: SID=0x%03x (listener=%s)\n", + c->log, slap_serverID, + l->sl_url.bv_val ); + sid_set = si; + } + } + if ( c->argc > 2 ) + ldap_free_urldesc( lud ); + } + break; + case CFG_LOGFILE: { + if ( logfileName ) ch_free( logfileName ); + logfileName = c->value_string; + logfile = fopen(logfileName, "w"); + if(logfile) lutil_debug_file(logfile); + } break; + + case CFG_LASTMOD: + if(SLAP_NOLASTMODCMD(c->be)) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> not available for %s database", + c->argv[0], c->be->bd_info->bi_type ); + Debug(LDAP_DEBUG_ANY, "%s: %s\n", + c->log, c->cr_msg, 0 ); + return(1); + } + if(c->value_int) + SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_NOLASTMOD; + else + SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_NOLASTMOD; + break; + + case CFG_MIRRORMODE: + if(c->value_int && !SLAP_SHADOW(c->be)) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> database is not a shadow", + c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s\n", + c->log, c->cr_msg, 0 ); + return(1); + } + if(c->value_int) { + SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_SINGLE_SHADOW; + SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_MULTI_SHADOW; + } else { + SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_SINGLE_SHADOW; + SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_MULTI_SHADOW; + } + break; + + case CFG_MONITORING: + if(c->value_int) + SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_MONITORING; + else + SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_MONITORING; + break; + + case CFG_HIDDEN: + if (c->value_int) + SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_HIDDEN; + else + SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_HIDDEN; + break; + + case CFG_SYNC_SUBENTRY: + if (c->value_int) + SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_SYNC_SUBENTRY; + else + SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_SYNC_SUBENTRY; + break; + + case CFG_SSTR_IF_MAX: + if (c->value_uint < index_substr_if_minlen) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> invalid value", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s (%d)\n", + c->log, c->cr_msg, c->value_int ); + return(1); + } + index_substr_if_maxlen = c->value_uint; + break; + + case CFG_SSTR_IF_MIN: + if (c->value_uint > index_substr_if_maxlen) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> invalid value", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s (%d)\n", + c->log, c->cr_msg, c->value_int ); + return(1); + } + index_substr_if_minlen = c->value_uint; + break; + +#ifdef SLAPD_MODULES + case CFG_MODLOAD: + /* If we're just adding a module on an existing modpath, + * make sure we've selected the current path. + */ + if ( c->op == LDAP_MOD_ADD && c->ca_private && modcur != c->ca_private ) { + modcur = c->ca_private; + /* This should never fail */ + if ( module_path( modcur->mp_path.bv_val )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> module path no longer valid", + c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s (%s)\n", + c->log, c->cr_msg, modcur->mp_path.bv_val ); + return(1); + } + } + if(module_load(c->argv[1], c->argc - 2, (c->argc > 2) ? c->argv + 2 : NULL)) + return(1); + /* Record this load on the current path */ + { + struct berval bv; + char *ptr; + if ( c->op == SLAP_CONFIG_ADD ) { + ptr = c->line + STRLENOF("moduleload"); + while (!isspace((unsigned char) *ptr)) ptr++; + while (isspace((unsigned char) *ptr)) ptr++; + } else { + ptr = c->line; + } + ber_str2bv(ptr, 0, 1, &bv); + ber_bvarray_add( &modcur->mp_loads, &bv ); + } + /* Check for any new hardcoded schema */ + if ( c->op == LDAP_MOD_ADD && CONFIG_ONLINE_ADD( c )) { + config_check_schema( NULL, &cfBackInfo ); + } + break; + + case CFG_MODPATH: + if(module_path(c->argv[1])) return(1); + /* Record which path was used with each module */ + { + ModPaths *mp; + + if (!modpaths.mp_loads) { + mp = &modpaths; + } else { + mp = ch_malloc( sizeof( ModPaths )); + modlast->mp_next = mp; + } + ber_str2bv(c->argv[1], 0, 1, &mp->mp_path); + mp->mp_next = NULL; + mp->mp_loads = NULL; + modlast = mp; + c->ca_private = mp; + modcur = mp; + } + + break; +#endif + +#ifdef LDAP_SLAPI + case CFG_PLUGIN: + if(slapi_int_read_config(c->be, c->fname, c->lineno, c->argc, c->argv) != LDAP_SUCCESS) + return(1); + slapi_plugins_used++; + break; +#endif + +#ifdef SLAP_AUTH_REWRITE + case CFG_REWRITE: { + struct berval bv; + char *line; + int rc = 0; + + if ( c->op == LDAP_MOD_ADD ) { + c->argv++; + c->argc--; + } + if(slap_sasl_rewrite_config(c->fname, c->lineno, c->argc, c->argv)) + rc = 1; + if ( rc == 0 ) { + + if ( c->argc > 1 ) { + char *s; + + /* quote all args but the first */ + line = ldap_charray2str( c->argv, "\" \"" ); + ber_str2bv( line, 0, 0, &bv ); + s = ber_bvchr( &bv, '"' ); + assert( s != NULL ); + /* move the trailing quote of argv[0] to the end */ + AC_MEMCPY( s, s + 1, bv.bv_len - ( s - bv.bv_val ) ); + bv.bv_val[ bv.bv_len - 1 ] = '"'; + + } else { + ber_str2bv( c->argv[ 0 ], 0, 1, &bv ); + } + + ber_bvarray_add( &authz_rewrites, &bv ); + } + if ( c->op == LDAP_MOD_ADD ) { + c->argv--; + c->argc++; + } + return rc; + } +#endif + + + default: + Debug( LDAP_DEBUG_ANY, + "%s: unknown CFG_TYPE %d.\n", + c->log, c->type, 0 ); + return 1; + + } + return(0); +} + + +static int +config_fname(ConfigArgs *c) { + if(c->op == SLAP_CONFIG_EMIT) { + if (c->ca_private) { + ConfigFile *cf = c->ca_private; + value_add_one( &c->rvalue_vals, &cf->c_file ); + return 0; + } + return 1; + } + return(0); +} + +static int +config_cfdir(ConfigArgs *c) { + if(c->op == SLAP_CONFIG_EMIT) { + if ( !BER_BVISEMPTY( &cfdir )) { + value_add_one( &c->rvalue_vals, &cfdir ); + return 0; + } + return 1; + } + return(0); +} + +static int +config_search_base(ConfigArgs *c) { + if(c->op == SLAP_CONFIG_EMIT) { + int rc = 1; + if (!BER_BVISEMPTY(&default_search_base)) { + value_add_one(&c->rvalue_vals, &default_search_base); + value_add_one(&c->rvalue_nvals, &default_search_nbase); + rc = 0; + } + return rc; + } else if( c->op == LDAP_MOD_DELETE ) { + ch_free( default_search_base.bv_val ); + ch_free( default_search_nbase.bv_val ); + BER_BVZERO( &default_search_base ); + BER_BVZERO( &default_search_nbase ); + return 0; + } + + if(c->bi || c->be != frontendDB) { + Debug(LDAP_DEBUG_ANY, "%s: defaultSearchBase line must appear " + "prior to any backend or database definition\n", + c->log, 0, 0); + return(1); + } + + if(default_search_nbase.bv_len) { + free(default_search_base.bv_val); + free(default_search_nbase.bv_val); + } + + default_search_base = c->value_dn; + default_search_nbase = c->value_ndn; + return(0); +} + +/* For RE23 compatibility we allow this in the global entry + * but we now defer it to the frontend entry to allow modules + * to load new hash types. + */ +static int +config_passwd_hash(ConfigArgs *c) { + int i; + if (c->op == SLAP_CONFIG_EMIT) { + struct berval bv; + /* Don't generate it in the global entry */ + if ( c->table == Cft_Global ) + return 1; + for (i=0; default_passwd_hash && default_passwd_hash[i]; i++) { + ber_str2bv(default_passwd_hash[i], 0, 0, &bv); + value_add_one(&c->rvalue_vals, &bv); + } + return i ? 0 : 1; + } else if ( c->op == LDAP_MOD_DELETE ) { + /* Deleting from global is a no-op, only the frontendDB entry matters */ + if ( c->table == Cft_Global ) + return 0; + if ( c->valx < 0 ) { + ldap_charray_free( default_passwd_hash ); + default_passwd_hash = NULL; + } else { + i = c->valx; + ch_free( default_passwd_hash[i] ); + for (; default_passwd_hash[i]; i++ ) + default_passwd_hash[i] = default_passwd_hash[i+1]; + } + return 0; + } + for(i = 1; i < c->argc; i++) { + if(!lutil_passwd_scheme(c->argv[i])) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> scheme not available", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s (%s)\n", + c->log, c->cr_msg, c->argv[i]); + } else { + ldap_charray_add(&default_passwd_hash, c->argv[i]); + } + } + if(!default_passwd_hash) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> no valid hashes found", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s\n", + c->log, c->cr_msg, 0 ); + return(1); + } + return(0); +} + +static int +config_schema_dn(ConfigArgs *c) { + if ( c->op == SLAP_CONFIG_EMIT ) { + int rc = 1; + if ( !BER_BVISEMPTY( &c->be->be_schemadn )) { + value_add_one(&c->rvalue_vals, &c->be->be_schemadn); + value_add_one(&c->rvalue_nvals, &c->be->be_schemandn); + rc = 0; + } + return rc; + } else if ( c->op == LDAP_MOD_DELETE ) { + ch_free( c->be->be_schemadn.bv_val ); + ch_free( c->be->be_schemandn.bv_val ); + BER_BVZERO( &c->be->be_schemadn ); + BER_BVZERO( &c->be->be_schemandn ); + return 0; + } + ch_free( c->be->be_schemadn.bv_val ); + ch_free( c->be->be_schemandn.bv_val ); + c->be->be_schemadn = c->value_dn; + c->be->be_schemandn = c->value_ndn; + return(0); +} + +static int +config_sizelimit(ConfigArgs *c) { + int i, rc = 0; + struct slap_limits_set *lim = &c->be->be_def_limit; + if (c->op == SLAP_CONFIG_EMIT) { + char buf[8192]; + struct berval bv; + bv.bv_val = buf; + bv.bv_len = 0; + limits_unparse_one( lim, SLAP_LIMIT_SIZE, &bv, sizeof( buf ) ); + if ( !BER_BVISEMPTY( &bv )) + value_add_one( &c->rvalue_vals, &bv ); + else + rc = 1; + return rc; + } else if ( c->op == LDAP_MOD_DELETE ) { + /* Reset to defaults or values from frontend */ + if ( c->be == frontendDB ) { + lim->lms_s_soft = SLAPD_DEFAULT_SIZELIMIT; + lim->lms_s_hard = 0; + lim->lms_s_unchecked = -1; + lim->lms_s_pr = 0; + lim->lms_s_pr_hide = 0; + lim->lms_s_pr_total = 0; + } else { + lim->lms_s_soft = frontendDB->be_def_limit.lms_s_soft; + lim->lms_s_hard = frontendDB->be_def_limit.lms_s_hard; + lim->lms_s_unchecked = frontendDB->be_def_limit.lms_s_unchecked; + lim->lms_s_pr = frontendDB->be_def_limit.lms_s_pr; + lim->lms_s_pr_hide = frontendDB->be_def_limit.lms_s_pr_hide; + lim->lms_s_pr_total = frontendDB->be_def_limit.lms_s_pr_total; + } + goto ok; + } + for(i = 1; i < c->argc; i++) { + if(!strncasecmp(c->argv[i], "size", 4)) { + rc = limits_parse_one(c->argv[i], lim); + if ( rc ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unable to parse value", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", + c->log, c->cr_msg, c->argv[i]); + return(1); + } + } else { + if(!strcasecmp(c->argv[i], "unlimited")) { + lim->lms_s_soft = -1; + } else { + if ( lutil_atoix( &lim->lms_s_soft, c->argv[i], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unable to parse limit", c->argv[0]); + Debug(LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", + c->log, c->cr_msg, c->argv[i]); + return(1); + } + } + lim->lms_s_hard = 0; + } + } + +ok: + if ( ( c->be == frontendDB ) && ( c->ca_entry ) ) { + /* This is a modification to the global limits apply it to + * the other databases as needed */ + AttributeDescription *ad=NULL; + const char *text = NULL; + CfEntryInfo *ce = c->ca_entry->e_private; + + slap_str2ad(c->argv[0], &ad, &text); + /* if we got here... */ + assert( ad != NULL ); + + if ( ce->ce_type == Cft_Global ){ + ce = ce->ce_kids; + } + for (; ce; ce=ce->ce_sibs) { + Entry *dbe = ce->ce_entry; + if ( (ce->ce_type == Cft_Database) && (ce->ce_be != frontendDB) + && (!attr_find(dbe->e_attrs, ad)) ) { + ce->ce_be->be_def_limit.lms_s_soft = lim->lms_s_soft; + ce->ce_be->be_def_limit.lms_s_hard = lim->lms_s_hard; + ce->ce_be->be_def_limit.lms_s_unchecked =lim->lms_s_unchecked; + ce->ce_be->be_def_limit.lms_s_pr =lim->lms_s_pr; + ce->ce_be->be_def_limit.lms_s_pr_hide =lim->lms_s_pr_hide; + ce->ce_be->be_def_limit.lms_s_pr_total =lim->lms_s_pr_total; + } + } + } + return(0); +} + +static int +config_timelimit(ConfigArgs *c) { + int i, rc = 0; + struct slap_limits_set *lim = &c->be->be_def_limit; + if (c->op == SLAP_CONFIG_EMIT) { + char buf[8192]; + struct berval bv; + bv.bv_val = buf; + bv.bv_len = 0; + limits_unparse_one( lim, SLAP_LIMIT_TIME, &bv, sizeof( buf ) ); + if ( !BER_BVISEMPTY( &bv )) + value_add_one( &c->rvalue_vals, &bv ); + else + rc = 1; + return rc; + } else if ( c->op == LDAP_MOD_DELETE ) { + /* Reset to defaults or values from frontend */ + if ( c->be == frontendDB ) { + lim->lms_t_soft = SLAPD_DEFAULT_TIMELIMIT; + lim->lms_t_hard = 0; + } else { + lim->lms_t_soft = frontendDB->be_def_limit.lms_t_soft; + lim->lms_t_hard = frontendDB->be_def_limit.lms_t_hard; + } + goto ok; + } + for(i = 1; i < c->argc; i++) { + if(!strncasecmp(c->argv[i], "time", 4)) { + rc = limits_parse_one(c->argv[i], lim); + if ( rc ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unable to parse value", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", + c->log, c->cr_msg, c->argv[i]); + return(1); + } + } else { + if(!strcasecmp(c->argv[i], "unlimited")) { + lim->lms_t_soft = -1; + } else { + if ( lutil_atoix( &lim->lms_t_soft, c->argv[i], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unable to parse limit", c->argv[0]); + Debug(LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", + c->log, c->cr_msg, c->argv[i]); + return(1); + } + } + lim->lms_t_hard = 0; + } + } + +ok: + if ( ( c->be == frontendDB ) && ( c->ca_entry ) ) { + /* This is a modification to the global limits apply it to + * the other databases as needed */ + AttributeDescription *ad=NULL; + const char *text = NULL; + CfEntryInfo *ce = c->ca_entry->e_private; + + slap_str2ad(c->argv[0], &ad, &text); + /* if we got here... */ + assert( ad != NULL ); + + if ( ce->ce_type == Cft_Global ){ + ce = ce->ce_kids; + } + for (; ce; ce=ce->ce_sibs) { + Entry *dbe = ce->ce_entry; + if ( (ce->ce_type == Cft_Database) && (ce->ce_be != frontendDB) + && (!attr_find(dbe->e_attrs, ad)) ) { + ce->ce_be->be_def_limit.lms_t_soft = lim->lms_t_soft; + ce->ce_be->be_def_limit.lms_t_hard = lim->lms_t_hard; + } + } + } + return(0); +} + +static int +config_overlay(ConfigArgs *c) { + if (c->op == SLAP_CONFIG_EMIT) { + return 1; + } else if ( c->op == LDAP_MOD_DELETE ) { + assert(0); + } + if(c->argv[1][0] == '-' && overlay_config(c->be, &c->argv[1][1], + c->valx, &c->bi, &c->reply)) { + /* log error */ + Debug( LDAP_DEBUG_ANY, + "%s: (optional) %s overlay \"%s\" configuration failed.\n", + c->log, c->be == frontendDB ? "global " : "", &c->argv[1][1]); + return 1; + } else if(overlay_config(c->be, c->argv[1], c->valx, &c->bi, &c->reply)) { + return(1); + } + return(0); +} + +static int +config_subordinate(ConfigArgs *c) +{ + int rc = 1; + int advertise = 0; + + switch( c->op ) { + case SLAP_CONFIG_EMIT: + if ( SLAP_GLUE_SUBORDINATE( c->be )) { + struct berval bv; + + bv.bv_val = SLAP_GLUE_ADVERTISE( c->be ) ? "advertise" : "TRUE"; + bv.bv_len = SLAP_GLUE_ADVERTISE( c->be ) ? STRLENOF("advertise") : + STRLENOF("TRUE"); + + value_add_one( &c->rvalue_vals, &bv ); + rc = 0; + } + break; + case LDAP_MOD_DELETE: + if ( !c->line || strcasecmp( c->line, "advertise" )) { + glue_sub_del( c->be ); + } else { + SLAP_DBFLAGS( c->be ) &= ~SLAP_DBFLAG_GLUE_ADVERTISE; + } + rc = 0; + break; + case LDAP_MOD_ADD: + case SLAP_CONFIG_ADD: + if ( c->be->be_nsuffix == NULL ) { + /* log error */ + snprintf( c->cr_msg, sizeof( c->cr_msg), + "subordinate configuration needs a suffix" ); + Debug( LDAP_DEBUG_ANY, + "%s: %s.\n", + c->log, c->cr_msg, 0 ); + rc = 1; + break; + } + + if ( c->argc == 2 ) { + if ( strcasecmp( c->argv[1], "advertise" ) == 0 ) { + advertise = 1; + + } else if ( strcasecmp( c->argv[1], "TRUE" ) != 0 ) { + /* log error */ + snprintf( c->cr_msg, sizeof( c->cr_msg), + "subordinate must be \"TRUE\" or \"advertise\"" ); + Debug( LDAP_DEBUG_ANY, + "%s: suffix \"%s\": %s.\n", + c->log, c->be->be_suffix[0].bv_val, c->cr_msg ); + rc = 1; + break; + } + } + + rc = glue_sub_add( c->be, advertise, CONFIG_ONLINE_ADD( c )); + break; + } + + return rc; +} + +/* + * [listener=<listener>] [{read|write}=]<size> + */ + +#ifdef LDAP_TCP_BUFFER +static BerVarray tcp_buffer; +int tcp_buffer_num; + +#define SLAP_TCP_RMEM (0x1U) +#define SLAP_TCP_WMEM (0x2U) + +static int +tcp_buffer_parse( struct berval *val, int argc, char **argv, + int *size, int *rw, Listener **l ) +{ + int i, rc = LDAP_SUCCESS; + LDAPURLDesc *lud = NULL; + char *ptr; + + if ( val != NULL && argv == NULL ) { + char *s = val->bv_val; + + argv = ldap_str2charray( s, " \t" ); + if ( argv == NULL ) { + return LDAP_OTHER; + } + } + + i = 0; + if ( strncasecmp( argv[ i ], "listener=", STRLENOF( "listener=" ) ) + == 0 ) + { + char *url = argv[ i ] + STRLENOF( "listener=" ); + + if ( ldap_url_parse( url, &lud ) ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + + *l = config_check_my_url( url, lud ); + if ( *l == NULL ) { + rc = LDAP_NO_SUCH_ATTRIBUTE; + goto done; + } + + i++; + } + + ptr = argv[ i ]; + if ( strncasecmp( ptr, "read=", STRLENOF( "read=" ) ) == 0 ) { + *rw |= SLAP_TCP_RMEM; + ptr += STRLENOF( "read=" ); + + } else if ( strncasecmp( ptr, "write=", STRLENOF( "write=" ) ) == 0 ) { + *rw |= SLAP_TCP_WMEM; + ptr += STRLENOF( "write=" ); + + } else { + *rw |= ( SLAP_TCP_RMEM | SLAP_TCP_WMEM ); + } + + /* accept any base */ + if ( lutil_atoix( size, ptr, 0 ) ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + +done:; + if ( val != NULL && argv != NULL ) { + ldap_charray_free( argv ); + } + + if ( lud != NULL ) { + ldap_free_urldesc( lud ); + } + + return rc; +} + +static int +tcp_buffer_delete_one( struct berval *val ) +{ + int rc = 0; + int size = -1, rw = 0; + Listener *l = NULL; + + rc = tcp_buffer_parse( val, 0, NULL, &size, &rw, &l ); + if ( rc != 0 ) { + return rc; + } + + if ( l != NULL ) { + int i; + Listener **ll = slapd_get_listeners(); + + for ( i = 0; ll[ i ] != NULL; i++ ) { + if ( ll[ i ] == l ) break; + } + + if ( ll[ i ] == NULL ) { + return LDAP_NO_SUCH_ATTRIBUTE; + } + + if ( rw & SLAP_TCP_RMEM ) l->sl_tcp_rmem = -1; + if ( rw & SLAP_TCP_WMEM ) l->sl_tcp_wmem = -1; + + for ( i++ ; ll[ i ] != NULL && bvmatch( &l->sl_url, &ll[ i ]->sl_url ); i++ ) { + if ( rw & SLAP_TCP_RMEM ) ll[ i ]->sl_tcp_rmem = -1; + if ( rw & SLAP_TCP_WMEM ) ll[ i ]->sl_tcp_wmem = -1; + } + + } else { + /* NOTE: this affects listeners without a specific setting, + * does not reset all listeners. If a listener without + * specific settings was assigned a buffer because of + * a global setting, it will not be reset. In any case, + * buffer changes will only take place at restart. */ + if ( rw & SLAP_TCP_RMEM ) slapd_tcp_rmem = -1; + if ( rw & SLAP_TCP_WMEM ) slapd_tcp_wmem = -1; + } + + return rc; +} + +static int +tcp_buffer_delete( BerVarray vals ) +{ + int i; + + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + tcp_buffer_delete_one( &vals[ i ] ); + } + + return 0; +} + +static int +tcp_buffer_unparse( int size, int rw, Listener *l, struct berval *val ) +{ + char buf[sizeof("2147483648")], *ptr; + + /* unparse for later use */ + val->bv_len = snprintf( buf, sizeof( buf ), "%d", size ); + if ( l != NULL ) { + val->bv_len += STRLENOF( "listener=" " " ) + l->sl_url.bv_len; + } + + if ( rw != ( SLAP_TCP_RMEM | SLAP_TCP_WMEM ) ) { + if ( rw & SLAP_TCP_RMEM ) { + val->bv_len += STRLENOF( "read=" ); + } else if ( rw & SLAP_TCP_WMEM ) { + val->bv_len += STRLENOF( "write=" ); + } + } + + val->bv_val = SLAP_MALLOC( val->bv_len + 1 ); + + ptr = val->bv_val; + + if ( l != NULL ) { + ptr = lutil_strcopy( ptr, "listener=" ); + ptr = lutil_strncopy( ptr, l->sl_url.bv_val, l->sl_url.bv_len ); + *ptr++ = ' '; + } + + if ( rw != ( SLAP_TCP_RMEM | SLAP_TCP_WMEM ) ) { + if ( rw & SLAP_TCP_RMEM ) { + ptr = lutil_strcopy( ptr, "read=" ); + } else if ( rw & SLAP_TCP_WMEM ) { + ptr = lutil_strcopy( ptr, "write=" ); + } + } + + ptr = lutil_strcopy( ptr, buf ); + *ptr = '\0'; + + assert( val->bv_val + val->bv_len == ptr ); + + return LDAP_SUCCESS; +} + +static int +tcp_buffer_add_one( int argc, char **argv ) +{ + int rc = 0; + int size = -1, rw = 0; + Listener *l = NULL; + + struct berval val; + + /* parse */ + rc = tcp_buffer_parse( NULL, argc, argv, &size, &rw, &l ); + if ( rc != 0 ) { + return rc; + } + + /* unparse for later use */ + rc = tcp_buffer_unparse( size, rw, l, &val ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + /* use parsed values */ + if ( l != NULL ) { + int i; + Listener **ll = slapd_get_listeners(); + + for ( i = 0; ll[ i ] != NULL; i++ ) { + if ( ll[ i ] == l ) break; + } + + if ( ll[ i ] == NULL ) { + return LDAP_NO_SUCH_ATTRIBUTE; + } + + /* buffer only applies to TCP listeners; + * we do not do any check here, and delegate them + * to setsockopt(2) */ + if ( rw & SLAP_TCP_RMEM ) l->sl_tcp_rmem = size; + if ( rw & SLAP_TCP_WMEM ) l->sl_tcp_wmem = size; + + for ( i++ ; ll[ i ] != NULL && bvmatch( &l->sl_url, &ll[ i ]->sl_url ); i++ ) { + if ( rw & SLAP_TCP_RMEM ) ll[ i ]->sl_tcp_rmem = size; + if ( rw & SLAP_TCP_WMEM ) ll[ i ]->sl_tcp_wmem = size; + } + + } else { + /* NOTE: this affects listeners without a specific setting, + * does not set all listeners */ + if ( rw & SLAP_TCP_RMEM ) slapd_tcp_rmem = size; + if ( rw & SLAP_TCP_WMEM ) slapd_tcp_wmem = size; + } + + tcp_buffer = SLAP_REALLOC( tcp_buffer, sizeof( struct berval ) * ( tcp_buffer_num + 2 ) ); + /* append */ + tcp_buffer[ tcp_buffer_num ] = val; + + tcp_buffer_num++; + BER_BVZERO( &tcp_buffer[ tcp_buffer_num ] ); + + return rc; +} + +static int +config_tcp_buffer( ConfigArgs *c ) +{ + if ( c->op == SLAP_CONFIG_EMIT ) { + if ( tcp_buffer == NULL || BER_BVISNULL( &tcp_buffer[ 0 ] ) ) { + return 1; + } + value_add( &c->rvalue_vals, tcp_buffer ); + value_add( &c->rvalue_nvals, tcp_buffer ); + + } else if ( c->op == LDAP_MOD_DELETE ) { + if ( !c->line ) { + tcp_buffer_delete( tcp_buffer ); + ber_bvarray_free( tcp_buffer ); + tcp_buffer = NULL; + tcp_buffer_num = 0; + + } else { + int rc = 0; + int size = -1, rw = 0; + Listener *l = NULL; + + struct berval val = BER_BVNULL; + + int i; + + if ( tcp_buffer_num == 0 ) { + return 1; + } + + /* parse */ + rc = tcp_buffer_parse( NULL, c->argc - 1, &c->argv[ 1 ], &size, &rw, &l ); + if ( rc != 0 ) { + return 1; + } + + /* unparse for later use */ + rc = tcp_buffer_unparse( size, rw, l, &val ); + if ( rc != LDAP_SUCCESS ) { + return 1; + } + + for ( i = 0; !BER_BVISNULL( &tcp_buffer[ i ] ); i++ ) { + if ( bvmatch( &tcp_buffer[ i ], &val ) ) { + break; + } + } + + if ( BER_BVISNULL( &tcp_buffer[ i ] ) ) { + /* not found */ + rc = 1; + goto done; + } + + tcp_buffer_delete_one( &tcp_buffer[ i ] ); + ber_memfree( tcp_buffer[ i ].bv_val ); + for ( ; i < tcp_buffer_num; i++ ) { + tcp_buffer[ i ] = tcp_buffer[ i + 1 ]; + } + tcp_buffer_num--; + +done:; + if ( !BER_BVISNULL( &val ) ) { + SLAP_FREE( val.bv_val ); + } + + } + + } else { + int rc; + + rc = tcp_buffer_add_one( c->argc - 1, &c->argv[ 1 ] ); + if ( rc ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> unable to add value #%d", + c->argv[0], tcp_buffer_num ); + Debug( LDAP_DEBUG_ANY, "%s: %s\n", + c->log, c->cr_msg, 0 ); + return 1; + } + } + + return 0; +} +#endif /* LDAP_TCP_BUFFER */ + +static int +config_suffix(ConfigArgs *c) +{ + Backend *tbe; + struct berval pdn, ndn; + char *notallowed = NULL; + + if ( c->be == frontendDB ) { + notallowed = "frontend"; + + } else if ( SLAP_MONITOR(c->be) ) { + notallowed = "monitor"; + + } else if ( SLAP_CONFIG(c->be) ) { + notallowed = "config"; + } + + if ( notallowed != NULL ) { + char buf[ SLAP_TEXT_BUFLEN ] = { '\0' }; + + switch ( c->op ) { + case LDAP_MOD_ADD: + case LDAP_MOD_DELETE: + case LDAP_MOD_REPLACE: + case LDAP_MOD_INCREMENT: + case SLAP_CONFIG_ADD: + if ( !BER_BVISNULL( &c->value_dn ) ) { + snprintf( buf, sizeof( buf ), "<%s> ", + c->value_dn.bv_val ); + } + + Debug(LDAP_DEBUG_ANY, + "%s: suffix %snot allowed in %s database.\n", + c->log, buf, notallowed ); + break; + + case SLAP_CONFIG_EMIT: + /* don't complain when emitting... */ + break; + + default: + /* FIXME: don't know what values may be valid; + * please remove assertion, or add legal values + * to either block */ + assert( 0 ); + break; + } + + return 1; + } + + if (c->op == SLAP_CONFIG_EMIT) { + if ( c->be->be_suffix == NULL + || BER_BVISNULL( &c->be->be_suffix[0] ) ) + { + return 1; + } else { + value_add( &c->rvalue_vals, c->be->be_suffix ); + value_add( &c->rvalue_nvals, c->be->be_nsuffix ); + return 0; + } + } else if ( c->op == LDAP_MOD_DELETE ) { + if ( c->valx < 0 ) { + ber_bvarray_free( c->be->be_suffix ); + ber_bvarray_free( c->be->be_nsuffix ); + c->be->be_suffix = NULL; + c->be->be_nsuffix = NULL; + } else { + int i = c->valx; + ch_free( c->be->be_suffix[i].bv_val ); + ch_free( c->be->be_nsuffix[i].bv_val ); + do { + c->be->be_suffix[i] = c->be->be_suffix[i+1]; + c->be->be_nsuffix[i] = c->be->be_nsuffix[i+1]; + i++; + } while ( !BER_BVISNULL( &c->be->be_suffix[i] ) ); + } + return 0; + } + +#ifdef SLAPD_MONITOR_DN + if(!strcasecmp(c->argv[1], SLAPD_MONITOR_DN)) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> DN is reserved for monitoring slapd", + c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s (%s)\n", + c->log, c->cr_msg, SLAPD_MONITOR_DN); + return(1); + } +#endif + + if (SLAP_DB_ONE_SUFFIX( c->be ) && c->be->be_suffix && + !BER_BVISNULL( &c->be->be_suffix[0] )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> Only one suffix is allowed on this %s backend", + c->argv[0], c->be->bd_info->bi_type ); + Debug(LDAP_DEBUG_ANY, "%s: %s\n", + c->log, c->cr_msg, 0); + return(1); + } + + pdn = c->value_dn; + ndn = c->value_ndn; + + if (SLAP_DBHIDDEN( c->be )) + tbe = NULL; + else + tbe = select_backend(&ndn, 0); + if(tbe == c->be) { + Debug( LDAP_DEBUG_ANY, "%s: suffix already served by this backend!.\n", + c->log, 0, 0); + free(pdn.bv_val); + free(ndn.bv_val); + return 1; + } else if(tbe) { + BackendDB *b2 = tbe; + + /* Does tbe precede be? */ + while (( b2 = LDAP_STAILQ_NEXT(b2, be_next )) && b2 && b2 != c->be ); + + if ( b2 ) { + char *type = tbe->bd_info->bi_type; + + if ( overlay_is_over( tbe ) ) { + slap_overinfo *oi = (slap_overinfo *)tbe->bd_info->bi_private; + type = oi->oi_orig->bi_type; + } + + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> namingContext \"%s\" " + "already served by a preceding %s database", + c->argv[0], pdn.bv_val, type ); + Debug(LDAP_DEBUG_ANY, "%s: %s serving namingContext \"%s\"\n", + c->log, c->cr_msg, tbe->be_suffix[0].bv_val); + free(pdn.bv_val); + free(ndn.bv_val); + return(1); + } + } + if(pdn.bv_len == 0 && default_search_nbase.bv_len) { + Debug(LDAP_DEBUG_ANY, "%s: suffix DN empty and default search " + "base provided \"%s\" (assuming okay)\n", + c->log, default_search_base.bv_val, 0); + } + ber_bvarray_add(&c->be->be_suffix, &pdn); + ber_bvarray_add(&c->be->be_nsuffix, &ndn); + return(0); +} + +static int +config_rootdn(ConfigArgs *c) { + if (c->op == SLAP_CONFIG_EMIT) { + if ( !BER_BVISNULL( &c->be->be_rootdn )) { + value_add_one(&c->rvalue_vals, &c->be->be_rootdn); + value_add_one(&c->rvalue_nvals, &c->be->be_rootndn); + return 0; + } else { + return 1; + } + } else if ( c->op == LDAP_MOD_DELETE ) { + ch_free( c->be->be_rootdn.bv_val ); + ch_free( c->be->be_rootndn.bv_val ); + BER_BVZERO( &c->be->be_rootdn ); + BER_BVZERO( &c->be->be_rootndn ); + return 0; + } + if ( !BER_BVISNULL( &c->be->be_rootdn )) { + ch_free( c->be->be_rootdn.bv_val ); + ch_free( c->be->be_rootndn.bv_val ); + } + c->be->be_rootdn = c->value_dn; + c->be->be_rootndn = c->value_ndn; + return(0); +} + +static int +config_rootpw(ConfigArgs *c) { + Backend *tbe; + + if (c->op == SLAP_CONFIG_EMIT) { + if (!BER_BVISEMPTY(&c->be->be_rootpw)) { + /* don't copy, because "rootpw" is marked + * as CFG_BERVAL */ + c->value_bv = c->be->be_rootpw; + return 0; + } + return 1; + } else if ( c->op == LDAP_MOD_DELETE ) { + ch_free( c->be->be_rootpw.bv_val ); + BER_BVZERO( &c->be->be_rootpw ); + return 0; + } + + tbe = select_backend(&c->be->be_rootndn, 0); + if(tbe != c->be && !SLAP_DBHIDDEN( c->be )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> can only be set when rootdn is under suffix", + c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s\n", + c->log, c->cr_msg, 0); + return(1); + } + if ( !BER_BVISNULL( &c->be->be_rootpw )) + ch_free( c->be->be_rootpw.bv_val ); + c->be->be_rootpw = c->value_bv; + return(0); +} + +static int +config_restrict(ConfigArgs *c) { + slap_mask_t restrictops = 0; + int i; + slap_verbmasks restrictable_ops[] = { + { BER_BVC("bind"), SLAP_RESTRICT_OP_BIND }, + { BER_BVC("add"), SLAP_RESTRICT_OP_ADD }, + { BER_BVC("modify"), SLAP_RESTRICT_OP_MODIFY }, + { BER_BVC("rename"), SLAP_RESTRICT_OP_RENAME }, + { BER_BVC("modrdn"), 0 }, + { BER_BVC("delete"), SLAP_RESTRICT_OP_DELETE }, + { BER_BVC("search"), SLAP_RESTRICT_OP_SEARCH }, + { BER_BVC("compare"), SLAP_RESTRICT_OP_COMPARE }, + { BER_BVC("read"), SLAP_RESTRICT_OP_READS }, + { BER_BVC("write"), SLAP_RESTRICT_OP_WRITES }, + { BER_BVC("extended"), SLAP_RESTRICT_OP_EXTENDED }, + { BER_BVC("extended=" LDAP_EXOP_START_TLS ), SLAP_RESTRICT_EXOP_START_TLS }, + { BER_BVC("extended=" LDAP_EXOP_MODIFY_PASSWD ), SLAP_RESTRICT_EXOP_MODIFY_PASSWD }, + { BER_BVC("extended=" LDAP_EXOP_X_WHO_AM_I ), SLAP_RESTRICT_EXOP_WHOAMI }, + { BER_BVC("extended=" LDAP_EXOP_X_CANCEL ), SLAP_RESTRICT_EXOP_CANCEL }, + { BER_BVC("all"), SLAP_RESTRICT_OP_ALL }, + { BER_BVNULL, 0 } + }; + + if (c->op == SLAP_CONFIG_EMIT) { + return mask_to_verbs( restrictable_ops, c->be->be_restrictops, + &c->rvalue_vals ); + } else if ( c->op == LDAP_MOD_DELETE ) { + if ( !c->line ) { + c->be->be_restrictops = 0; + } else { + i = verb_to_mask( c->line, restrictable_ops ); + c->be->be_restrictops &= ~restrictable_ops[i].mask; + } + return 0; + } + i = verbs_to_mask( c->argc, c->argv, restrictable_ops, &restrictops ); + if ( i ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unknown operation", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s %s\n", + c->log, c->cr_msg, c->argv[i]); + return(1); + } + if ( restrictops & SLAP_RESTRICT_OP_EXTENDED ) + restrictops &= ~SLAP_RESTRICT_EXOP_MASK; + c->be->be_restrictops |= restrictops; + return(0); +} + +static int +config_allows(ConfigArgs *c) { + slap_mask_t allows = 0; + int i; + slap_verbmasks allowable_ops[] = { + { BER_BVC("bind_v2"), SLAP_ALLOW_BIND_V2 }, + { BER_BVC("bind_anon_cred"), SLAP_ALLOW_BIND_ANON_CRED }, + { BER_BVC("bind_anon_dn"), SLAP_ALLOW_BIND_ANON_DN }, + { BER_BVC("update_anon"), SLAP_ALLOW_UPDATE_ANON }, + { BER_BVC("proxy_authz_anon"), SLAP_ALLOW_PROXY_AUTHZ_ANON }, + { BER_BVNULL, 0 } + }; + if (c->op == SLAP_CONFIG_EMIT) { + return mask_to_verbs( allowable_ops, global_allows, &c->rvalue_vals ); + } else if ( c->op == LDAP_MOD_DELETE ) { + if ( !c->line ) { + global_allows = 0; + } else { + i = verb_to_mask( c->line, allowable_ops ); + global_allows &= ~allowable_ops[i].mask; + } + return 0; + } + i = verbs_to_mask(c->argc, c->argv, allowable_ops, &allows); + if ( i ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unknown feature", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s %s\n", + c->log, c->cr_msg, c->argv[i]); + return(1); + } + global_allows |= allows; + return(0); +} + +static int +config_disallows(ConfigArgs *c) { + slap_mask_t disallows = 0; + int i; + slap_verbmasks disallowable_ops[] = { + { BER_BVC("bind_anon"), SLAP_DISALLOW_BIND_ANON }, + { BER_BVC("bind_simple"), SLAP_DISALLOW_BIND_SIMPLE }, + { BER_BVC("tls_2_anon"), SLAP_DISALLOW_TLS_2_ANON }, + { BER_BVC("tls_authc"), SLAP_DISALLOW_TLS_AUTHC }, + { BER_BVC("proxy_authz_non_critical"), SLAP_DISALLOW_PROXY_AUTHZ_N_CRIT }, + { BER_BVC("dontusecopy_non_critical"), SLAP_DISALLOW_DONTUSECOPY_N_CRIT }, + { BER_BVNULL, 0 } + }; + if (c->op == SLAP_CONFIG_EMIT) { + return mask_to_verbs( disallowable_ops, global_disallows, &c->rvalue_vals ); + } else if ( c->op == LDAP_MOD_DELETE ) { + if ( !c->line ) { + global_disallows = 0; + } else { + i = verb_to_mask( c->line, disallowable_ops ); + global_disallows &= ~disallowable_ops[i].mask; + } + return 0; + } + i = verbs_to_mask(c->argc, c->argv, disallowable_ops, &disallows); + if ( i ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unknown feature", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s %s\n", + c->log, c->cr_msg, c->argv[i]); + return(1); + } + global_disallows |= disallows; + return(0); +} + +static int +config_requires(ConfigArgs *c) { + slap_mask_t requires = frontendDB->be_requires; + int i, argc = c->argc; + char **argv = c->argv; + + slap_verbmasks requires_ops[] = { + { BER_BVC("bind"), SLAP_REQUIRE_BIND }, + { BER_BVC("LDAPv3"), SLAP_REQUIRE_LDAP_V3 }, + { BER_BVC("authc"), SLAP_REQUIRE_AUTHC }, + { BER_BVC("sasl"), SLAP_REQUIRE_SASL }, + { BER_BVC("strong"), SLAP_REQUIRE_STRONG }, + { BER_BVNULL, 0 } + }; + if (c->op == SLAP_CONFIG_EMIT) { + return mask_to_verbs( requires_ops, c->be->be_requires, &c->rvalue_vals ); + } else if ( c->op == LDAP_MOD_DELETE ) { + if ( !c->line ) { + c->be->be_requires = 0; + } else { + i = verb_to_mask( c->line, requires_ops ); + c->be->be_requires &= ~requires_ops[i].mask; + } + return 0; + } + /* "none" can only be first, to wipe out default/global values */ + if ( strcasecmp( c->argv[ 1 ], "none" ) == 0 ) { + argv++; + argc--; + requires = 0; + } + i = verbs_to_mask(argc, argv, requires_ops, &requires); + if ( i ) { + if (strcasecmp( c->argv[ i ], "none" ) == 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> \"none\" (#%d) must be listed first", c->argv[0], i - 1 ); + Debug(LDAP_DEBUG_ANY, "%s: %s\n", + c->log, c->cr_msg, 0); + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unknown feature #%d", c->argv[0], i - 1 ); + Debug(LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", + c->log, c->cr_msg, c->argv[i]); + } + return(1); + } + c->be->be_requires = requires; + return(0); +} + +static int +config_extra_attrs(ConfigArgs *c) +{ + assert( c->be != NULL ); + + if ( c->op == SLAP_CONFIG_EMIT ) { + int i; + + if ( c->be->be_extra_anlist == NULL ) { + return 1; + } + + for ( i = 0; !BER_BVISNULL( &c->be->be_extra_anlist[i].an_name ); i++ ) { + value_add_one( &c->rvalue_vals, &c->be->be_extra_anlist[i].an_name ); + } + + } else if ( c->op == LDAP_MOD_DELETE ) { + if ( c->be->be_extra_anlist == NULL ) { + return 1; + } + + if ( c->valx < 0 ) { + anlist_free( c->be->be_extra_anlist, 1, NULL ); + c->be->be_extra_anlist = NULL; + + } else { + int i; + + for ( i = 0; i < c->valx && !BER_BVISNULL( &c->be->be_extra_anlist[i + 1].an_name ); i++ ) + ; + + if ( BER_BVISNULL( &c->be->be_extra_anlist[i].an_name ) ) { + return 1; + } + + ch_free( c->be->be_extra_anlist[i].an_name.bv_val ); + + for ( ; !BER_BVISNULL( &c->be->be_extra_anlist[i].an_name ); i++ ) { + c->be->be_extra_anlist[i] = c->be->be_extra_anlist[i + 1]; + } + } + + } else { + c->be->be_extra_anlist = str2anlist( c->be->be_extra_anlist, c->argv[1], " ,\t" ); + if ( c->be->be_extra_anlist == NULL ) { + return 1; + } + } + + return 0; +} + +static slap_verbmasks *loglevel_ops; + +static int +loglevel_init( void ) +{ + slap_verbmasks lo[] = { + { BER_BVC("Any"), (slap_mask_t) LDAP_DEBUG_ANY }, + { BER_BVC("Trace"), LDAP_DEBUG_TRACE }, + { BER_BVC("Packets"), LDAP_DEBUG_PACKETS }, + { BER_BVC("Args"), LDAP_DEBUG_ARGS }, + { BER_BVC("Conns"), LDAP_DEBUG_CONNS }, + { BER_BVC("BER"), LDAP_DEBUG_BER }, + { BER_BVC("Filter"), LDAP_DEBUG_FILTER }, + { BER_BVC("Config"), LDAP_DEBUG_CONFIG }, + { BER_BVC("ACL"), LDAP_DEBUG_ACL }, + { BER_BVC("Stats"), LDAP_DEBUG_STATS }, + { BER_BVC("Stats2"), LDAP_DEBUG_STATS2 }, + { BER_BVC("Shell"), LDAP_DEBUG_SHELL }, + { BER_BVC("Parse"), LDAP_DEBUG_PARSE }, +#if 0 /* no longer used (nor supported) */ + { BER_BVC("Cache"), LDAP_DEBUG_CACHE }, + { BER_BVC("Index"), LDAP_DEBUG_INDEX }, +#endif + { BER_BVC("Sync"), LDAP_DEBUG_SYNC }, + { BER_BVC("None"), LDAP_DEBUG_NONE }, + { BER_BVNULL, 0 } + }; + + return slap_verbmasks_init( &loglevel_ops, lo ); +} + +static void +loglevel_destroy( void ) +{ + if ( loglevel_ops ) { + (void)slap_verbmasks_destroy( loglevel_ops ); + } + loglevel_ops = NULL; +} + +static slap_mask_t loglevel_ignore[] = { -1, 0 }; + +int +slap_loglevel_register( slap_mask_t m, struct berval *s ) +{ + int rc; + + if ( loglevel_ops == NULL ) { + loglevel_init(); + } + + rc = slap_verbmasks_append( &loglevel_ops, m, s, loglevel_ignore ); + + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "slap_loglevel_register(%lu, \"%s\") failed\n", + m, s->bv_val, 0 ); + } + + return rc; +} + +int +slap_loglevel_get( struct berval *s, int *l ) +{ + int rc; + slap_mask_t m, i; + + if ( loglevel_ops == NULL ) { + loglevel_init(); + } + + for ( m = 0, i = 1; !BER_BVISNULL( &loglevel_ops[ i ].word ); i++ ) { + m |= loglevel_ops[ i ].mask; + } + + for ( i = 1; m & i; i <<= 1 ) + ; + + if ( i == 0 ) { + return -1; + } + + rc = slap_verbmasks_append( &loglevel_ops, i, s, loglevel_ignore ); + + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "slap_loglevel_get(%lu, \"%s\") failed\n", + i, s->bv_val, 0 ); + + } else { + *l = i; + } + + return rc; +} + +int +str2loglevel( const char *s, int *l ) +{ + int i; + + if ( loglevel_ops == NULL ) { + loglevel_init(); + } + + i = verb_to_mask( s, loglevel_ops ); + + if ( BER_BVISNULL( &loglevel_ops[ i ].word ) ) { + return -1; + } + + *l = loglevel_ops[ i ].mask; + + return 0; +} + +const char * +loglevel2str( int l ) +{ + struct berval bv = BER_BVNULL; + + loglevel2bv( l, &bv ); + + return bv.bv_val; +} + +int +loglevel2bv( int l, struct berval *bv ) +{ + if ( loglevel_ops == NULL ) { + loglevel_init(); + } + + BER_BVZERO( bv ); + + return enum_to_verb( loglevel_ops, l, bv ) == -1; +} + +int +loglevel2bvarray( int l, BerVarray *bva ) +{ + if ( loglevel_ops == NULL ) { + loglevel_init(); + } + + if ( l == 0 ) { + struct berval bv = BER_BVC("0"); + return value_add_one( bva, &bv ); + } + + return mask_to_verbs( loglevel_ops, l, bva ); +} + +int +loglevel_print( FILE *out ) +{ + int i; + + if ( loglevel_ops == NULL ) { + loglevel_init(); + } + + fprintf( out, "Installed log subsystems:\n\n" ); + for ( i = 0; !BER_BVISNULL( &loglevel_ops[ i ].word ); i++ ) { + unsigned mask = loglevel_ops[ i ].mask & 0xffffffffUL; + fprintf( out, + (mask == ((slap_mask_t) -1 & 0xffffffffUL) + ? "\t%-30s (-1, 0xffffffff)\n" : "\t%-30s (%u, 0x%x)\n"), + loglevel_ops[ i ].word.bv_val, mask, mask ); + } + + fprintf( out, "\nNOTE: custom log subsystems may be later installed " + "by specific code\n\n" ); + + return 0; +} + +static int config_syslog; + +static int +config_loglevel(ConfigArgs *c) { + int i; + + if ( loglevel_ops == NULL ) { + loglevel_init(); + } + + if (c->op == SLAP_CONFIG_EMIT) { + /* Get default or commandline slapd setting */ + if ( ldap_syslog && !config_syslog ) + config_syslog = ldap_syslog; + return loglevel2bvarray( config_syslog, &c->rvalue_vals ); + + } else if ( c->op == LDAP_MOD_DELETE ) { + if ( !c->line ) { + config_syslog = 0; + } else { + i = verb_to_mask( c->line, loglevel_ops ); + config_syslog &= ~loglevel_ops[i].mask; + } + if ( slapMode & SLAP_SERVER_MODE ) { + ldap_syslog = config_syslog; + } + return 0; + } + + for( i=1; i < c->argc; i++ ) { + int level; + + if ( isdigit((unsigned char)c->argv[i][0]) || c->argv[i][0] == '-' ) { + if( lutil_atoix( &level, c->argv[i], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unable to parse level", c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", + c->log, c->cr_msg, c->argv[i]); + return( 1 ); + } + } else { + if ( str2loglevel( c->argv[i], &level ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unknown level", c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", + c->log, c->cr_msg, c->argv[i]); + return( 1 ); + } + } + /* Explicitly setting a zero clears all the levels */ + if ( level ) + config_syslog |= level; + else + config_syslog = 0; + } + if ( slapMode & SLAP_SERVER_MODE ) { + ldap_syslog = config_syslog; + } + return(0); +} + +static int +config_referral(ConfigArgs *c) { + struct berval val; + if (c->op == SLAP_CONFIG_EMIT) { + if ( default_referral ) { + value_add( &c->rvalue_vals, default_referral ); + return 0; + } else { + return 1; + } + } else if ( c->op == LDAP_MOD_DELETE ) { + if ( c->valx < 0 ) { + ber_bvarray_free( default_referral ); + default_referral = NULL; + } else { + int i = c->valx; + ch_free( default_referral[i].bv_val ); + for (; default_referral[i].bv_val; i++ ) + default_referral[i] = default_referral[i+1]; + } + return 0; + } + if(validate_global_referral(c->argv[1])) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> invalid URL", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s (%s)\n", + c->log, c->cr_msg, c->argv[1]); + return(1); + } + + ber_str2bv(c->argv[1], 0, 0, &val); + if(value_add_one(&default_referral, &val)) return(LDAP_OTHER); + return(0); +} + +static struct { + struct berval key; + int off; +} sec_keys[] = { + { BER_BVC("ssf="), offsetof(slap_ssf_set_t, sss_ssf) }, + { BER_BVC("transport="), offsetof(slap_ssf_set_t, sss_transport) }, + { BER_BVC("tls="), offsetof(slap_ssf_set_t, sss_tls) }, + { BER_BVC("sasl="), offsetof(slap_ssf_set_t, sss_sasl) }, + { BER_BVC("update_ssf="), offsetof(slap_ssf_set_t, sss_update_ssf) }, + { BER_BVC("update_transport="), offsetof(slap_ssf_set_t, sss_update_transport) }, + { BER_BVC("update_tls="), offsetof(slap_ssf_set_t, sss_update_tls) }, + { BER_BVC("update_sasl="), offsetof(slap_ssf_set_t, sss_update_sasl) }, + { BER_BVC("simple_bind="), offsetof(slap_ssf_set_t, sss_simple_bind) }, + { BER_BVNULL, 0 } +}; + +static int +config_security(ConfigArgs *c) { + slap_ssf_set_t *set = &c->be->be_ssf_set; + char *next; + int i, j; + if (c->op == SLAP_CONFIG_EMIT) { + char numbuf[32]; + struct berval bv; + slap_ssf_t *tgt; + int rc = 1; + + for (i=0; !BER_BVISNULL( &sec_keys[i].key ); i++) { + tgt = (slap_ssf_t *)((char *)set + sec_keys[i].off); + if ( *tgt ) { + rc = 0; + bv.bv_len = snprintf( numbuf, sizeof( numbuf ), "%u", *tgt ); + if ( bv.bv_len >= sizeof( numbuf ) ) { + ber_bvarray_free_x( c->rvalue_vals, NULL ); + c->rvalue_vals = NULL; + rc = 1; + break; + } + bv.bv_len += sec_keys[i].key.bv_len; + bv.bv_val = ch_malloc( bv.bv_len + 1); + next = lutil_strcopy( bv.bv_val, sec_keys[i].key.bv_val ); + strcpy( next, numbuf ); + ber_bvarray_add( &c->rvalue_vals, &bv ); + } + } + return rc; + } + for(i = 1; i < c->argc; i++) { + slap_ssf_t *tgt = NULL; + char *src = NULL; + for ( j=0; !BER_BVISNULL( &sec_keys[j].key ); j++ ) { + if(!strncasecmp(c->argv[i], sec_keys[j].key.bv_val, + sec_keys[j].key.bv_len)) { + src = c->argv[i] + sec_keys[j].key.bv_len; + tgt = (slap_ssf_t *)((char *)set + sec_keys[j].off); + break; + } + } + if ( !tgt ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unknown factor", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s %s\n", + c->log, c->cr_msg, c->argv[i]); + return(1); + } + + if ( lutil_atou( tgt, src ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unable to parse factor", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", + c->log, c->cr_msg, c->argv[i]); + return(1); + } + } + return(0); +} + +char * +anlist_unparse( AttributeName *an, char *ptr, ber_len_t buflen ) { + int comma = 0; + char *start = ptr; + + for (; !BER_BVISNULL( &an->an_name ); an++) { + /* if buflen == 0, assume the buffer size has been + * already checked otherwise */ + if ( buflen > 0 && buflen - ( ptr - start ) < comma + an->an_name.bv_len ) return NULL; + if ( comma ) *ptr++ = ','; + ptr = lutil_strcopy( ptr, an->an_name.bv_val ); + comma = 1; + } + return ptr; +} + +static int +config_updatedn(ConfigArgs *c) { + if (c->op == SLAP_CONFIG_EMIT) { + if (!BER_BVISEMPTY(&c->be->be_update_ndn)) { + value_add_one(&c->rvalue_vals, &c->be->be_update_ndn); + value_add_one(&c->rvalue_nvals, &c->be->be_update_ndn); + return 0; + } + return 1; + } else if ( c->op == LDAP_MOD_DELETE ) { + ch_free( c->be->be_update_ndn.bv_val ); + BER_BVZERO( &c->be->be_update_ndn ); + SLAP_DBFLAGS(c->be) ^= (SLAP_DBFLAG_SHADOW | SLAP_DBFLAG_SLURP_SHADOW); + return 0; + } + if(SLAP_SHADOW(c->be)) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> database already shadowed", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s\n", + c->log, c->cr_msg, 0); + return(1); + } + + ber_memfree_x( c->value_dn.bv_val, NULL ); + if ( !BER_BVISNULL( &c->be->be_update_ndn ) ) { + ber_memfree_x( c->be->be_update_ndn.bv_val, NULL ); + } + c->be->be_update_ndn = c->value_ndn; + BER_BVZERO( &c->value_dn ); + BER_BVZERO( &c->value_ndn ); + + return config_slurp_shadow( c ); +} + +int +config_shadow( ConfigArgs *c, slap_mask_t flag ) +{ + char *notallowed = NULL; + + if ( c->be == frontendDB ) { + notallowed = "frontend"; + + } else if ( SLAP_MONITOR(c->be) ) { + notallowed = "monitor"; + } + + if ( notallowed != NULL ) { + Debug( LDAP_DEBUG_ANY, "%s: %s database cannot be shadow.\n", c->log, notallowed, 0 ); + return 1; + } + + if ( SLAP_SHADOW(c->be) ) { + /* if already shadow, only check consistency */ + if ( ( SLAP_DBFLAGS(c->be) & flag ) != flag ) { + Debug( LDAP_DEBUG_ANY, "%s: inconsistent shadow flag 0x%lx.\n", + c->log, flag, 0 ); + return 1; + } + + } else { + SLAP_DBFLAGS(c->be) |= (SLAP_DBFLAG_SHADOW | flag); + if ( !SLAP_MULTIMASTER( c->be )) + SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_SINGLE_SHADOW; + } + + return 0; +} + +static int +config_updateref(ConfigArgs *c) { + struct berval val; + if (c->op == SLAP_CONFIG_EMIT) { + if ( c->be->be_update_refs ) { + value_add( &c->rvalue_vals, c->be->be_update_refs ); + return 0; + } else { + return 1; + } + } else if ( c->op == LDAP_MOD_DELETE ) { + if ( c->valx < 0 ) { + ber_bvarray_free( c->be->be_update_refs ); + c->be->be_update_refs = NULL; + } else { + int i = c->valx; + ch_free( c->be->be_update_refs[i].bv_val ); + for (; c->be->be_update_refs[i].bv_val; i++) + c->be->be_update_refs[i] = c->be->be_update_refs[i+1]; + } + return 0; + } + if(!SLAP_SHADOW(c->be) && !c->be->be_syncinfo) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> must appear after syncrepl or updatedn", + c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s\n", + c->log, c->cr_msg, 0); + return(1); + } + + if(validate_global_referral(c->argv[1])) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> invalid URL", c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s (%s)\n", + c->log, c->cr_msg, c->argv[1]); + return(1); + } + ber_str2bv(c->argv[1], 0, 0, &val); + if(value_add_one(&c->be->be_update_refs, &val)) return(LDAP_OTHER); + return(0); +} + +static int +config_obsolete(ConfigArgs *c) { + if (c->op == SLAP_CONFIG_EMIT) + return 1; + + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> keyword is obsolete (ignored)", + c->argv[0] ); + Debug(LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg, 0); + return(0); +} + +static int +config_include(ConfigArgs *c) { + int savelineno = c->lineno; + int rc; + ConfigFile *cf; + ConfigFile *cfsave = cfn; + ConfigFile *cf2 = NULL; + + /* Leftover from RE23. No dynamic config for include files */ + if ( c->op == SLAP_CONFIG_EMIT || c->op == LDAP_MOD_DELETE ) + return 1; + + cf = ch_calloc( 1, sizeof(ConfigFile)); + if ( cfn->c_kids ) { + for (cf2=cfn->c_kids; cf2 && cf2->c_sibs; cf2=cf2->c_sibs) ; + cf2->c_sibs = cf; + } else { + cfn->c_kids = cf; + } + cfn = cf; + ber_str2bv( c->argv[1], 0, 1, &cf->c_file ); + rc = read_config_file(c->argv[1], c->depth + 1, c, config_back_cf_table); + c->lineno = savelineno - 1; + cfn = cfsave; + if ( rc ) { + if ( cf2 ) cf2->c_sibs = NULL; + else cfn->c_kids = NULL; + ch_free( cf->c_file.bv_val ); + ch_free( cf ); + } else { + c->ca_private = cf; + } + return(rc); +} + +#ifdef HAVE_TLS +static int +config_tls_cleanup(ConfigArgs *c) { + int rc = 0; + + if ( slap_tls_ld ) { + int opt = 1; + + ldap_pvt_tls_ctx_free( slap_tls_ctx ); + slap_tls_ctx = NULL; + + /* Force new ctx to be created */ + rc = ldap_pvt_tls_set_option( slap_tls_ld, LDAP_OPT_X_TLS_NEWCTX, &opt ); + if( rc == 0 ) { + /* The ctx's refcount is bumped up here */ + ldap_pvt_tls_get_option( slap_tls_ld, LDAP_OPT_X_TLS_CTX, &slap_tls_ctx ); + /* This is a no-op if it's already loaded */ + load_extop( &slap_EXOP_START_TLS, 0, starttls_extop ); + } else { + if ( rc == LDAP_NOT_SUPPORTED ) + rc = LDAP_UNWILLING_TO_PERFORM; + else + rc = LDAP_OTHER; + } + } + return rc; +} + +static int +config_tls_option(ConfigArgs *c) { + int flag; + LDAP *ld = slap_tls_ld; + switch(c->type) { + case CFG_TLS_RAND: flag = LDAP_OPT_X_TLS_RANDOM_FILE; ld = NULL; break; + case CFG_TLS_CIPHER: flag = LDAP_OPT_X_TLS_CIPHER_SUITE; break; + case CFG_TLS_CERT_FILE: flag = LDAP_OPT_X_TLS_CERTFILE; break; + case CFG_TLS_CERT_KEY: flag = LDAP_OPT_X_TLS_KEYFILE; break; + case CFG_TLS_CA_PATH: flag = LDAP_OPT_X_TLS_CACERTDIR; break; + case CFG_TLS_CA_FILE: flag = LDAP_OPT_X_TLS_CACERTFILE; break; + case CFG_TLS_DH_FILE: flag = LDAP_OPT_X_TLS_DHFILE; break; + case CFG_TLS_ECNAME: flag = LDAP_OPT_X_TLS_ECNAME; break; +#ifdef HAVE_GNUTLS + case CFG_TLS_CRL_FILE: flag = LDAP_OPT_X_TLS_CRLFILE; break; +#endif + default: Debug(LDAP_DEBUG_ANY, "%s: " + "unknown tls_option <0x%x>\n", + c->log, c->type, 0); + return 1; + } + if (c->op == SLAP_CONFIG_EMIT) { + return ldap_pvt_tls_get_option( ld, flag, &c->value_string ); + } else if ( c->op == LDAP_MOD_DELETE ) { + c->cleanup = config_tls_cleanup; + return ldap_pvt_tls_set_option( ld, flag, NULL ); + } + ch_free(c->value_string); + c->cleanup = config_tls_cleanup; + return(ldap_pvt_tls_set_option(ld, flag, c->argv[1])); +} + +/* FIXME: this ought to be provided by libldap */ +static int +config_tls_config(ConfigArgs *c) { + int i, flag; + switch(c->type) { + case CFG_TLS_CRLCHECK: flag = LDAP_OPT_X_TLS_CRLCHECK; break; + case CFG_TLS_VERIFY: flag = LDAP_OPT_X_TLS_REQUIRE_CERT; break; + case CFG_TLS_PROTOCOL_MIN: flag = LDAP_OPT_X_TLS_PROTOCOL_MIN; break; + default: + Debug(LDAP_DEBUG_ANY, "%s: " + "unknown tls_option <0x%x>\n", + c->log, c->type, 0); + return 1; + } + if (c->op == SLAP_CONFIG_EMIT) { + return slap_tls_get_config( slap_tls_ld, flag, &c->value_string ); + } else if ( c->op == LDAP_MOD_DELETE ) { + int i = 0; + c->cleanup = config_tls_cleanup; + return ldap_pvt_tls_set_option( slap_tls_ld, flag, &i ); + } + ch_free( c->value_string ); + c->cleanup = config_tls_cleanup; + if ( isdigit( (unsigned char)c->argv[1][0] ) && c->type != CFG_TLS_PROTOCOL_MIN ) { + if ( lutil_atoi( &i, c->argv[1] ) != 0 ) { + Debug(LDAP_DEBUG_ANY, "%s: " + "unable to parse %s \"%s\"\n", + c->log, c->argv[0], c->argv[1] ); + return 1; + } + return(ldap_pvt_tls_set_option(slap_tls_ld, flag, &i)); + } else { + return(ldap_int_tls_config(slap_tls_ld, flag, c->argv[1])); + } +} +#endif + +static CfEntryInfo * +config_find_base( CfEntryInfo *root, struct berval *dn, CfEntryInfo **last ) +{ + struct berval cdn; + char *c; + + if ( !root ) { + *last = NULL; + return NULL; + } + + if ( dn_match( &root->ce_entry->e_nname, dn )) + return root; + + c = dn->bv_val+dn->bv_len; + for (;*c != ',';c--); + + while(root) { + *last = root; + for (--c;c>dn->bv_val && *c != ',';c--); + cdn.bv_val = c; + if ( *c == ',' ) + cdn.bv_val++; + cdn.bv_len = dn->bv_len - (cdn.bv_val - dn->bv_val); + + root = root->ce_kids; + + for (;root;root=root->ce_sibs) { + if ( dn_match( &root->ce_entry->e_nname, &cdn )) { + if ( cdn.bv_val == dn->bv_val ) { + return root; + } + break; + } + } + } + return root; +} + +typedef struct setup_cookie { + CfBackInfo *cfb; + ConfigArgs *ca; + Entry *frontend; + Entry *config; + int got_frontend; + int got_config; +} setup_cookie; + +static int +config_ldif_resp( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH ) { + setup_cookie *sc = op->o_callback->sc_private; + struct berval pdn; + + sc->cfb->cb_got_ldif = 1; + /* Does the frontend exist? */ + if ( !sc->got_frontend ) { + if ( !strncmp( rs->sr_entry->e_nname.bv_val, + "olcDatabase", STRLENOF( "olcDatabase" ))) + { + if ( strncmp( rs->sr_entry->e_nname.bv_val + + STRLENOF( "olcDatabase" ), "={-1}frontend", + STRLENOF( "={-1}frontend" ))) + { + struct berval rdn; + int i = op->o_noop; + sc->ca->be = frontendDB; + sc->ca->bi = frontendDB->bd_info; + frontendDB->be_cf_ocs = &CFOC_FRONTEND; + rdn.bv_val = sc->ca->log; + rdn.bv_len = snprintf(rdn.bv_val, sizeof( sc->ca->log ), + "%s=" SLAP_X_ORDERED_FMT "%s", + cfAd_database->ad_cname.bv_val, -1, + sc->ca->bi->bi_type); + op->o_noop = 1; + sc->frontend = config_build_entry( op, rs, + sc->cfb->cb_root, sc->ca, &rdn, &CFOC_DATABASE, + sc->ca->be->be_cf_ocs ); + op->o_noop = i; + sc->got_frontend++; + } else { + sc->got_frontend++; + goto ok; + } + } + } + + dnParent( &rs->sr_entry->e_nname, &pdn ); + + /* Does the configDB exist? */ + if ( sc->got_frontend && !sc->got_config && + !strncmp( rs->sr_entry->e_nname.bv_val, + "olcDatabase", STRLENOF( "olcDatabase" )) && + dn_match( &config_rdn, &pdn ) ) + { + if ( strncmp( rs->sr_entry->e_nname.bv_val + + STRLENOF( "olcDatabase" ), "={0}config", + STRLENOF( "={0}config" ))) + { + struct berval rdn; + int i = op->o_noop; + sc->ca->be = LDAP_STAILQ_FIRST( &backendDB ); + sc->ca->bi = sc->ca->be->bd_info; + rdn.bv_val = sc->ca->log; + rdn.bv_len = snprintf(rdn.bv_val, sizeof( sc->ca->log ), + "%s=" SLAP_X_ORDERED_FMT "%s", + cfAd_database->ad_cname.bv_val, 0, + sc->ca->bi->bi_type); + op->o_noop = 1; + sc->config = config_build_entry( op, rs, sc->cfb->cb_root, + sc->ca, &rdn, &CFOC_DATABASE, sc->ca->be->be_cf_ocs ); + op->o_noop = i; + } + sc->got_config++; + } + +ok: + rs->sr_err = config_add_internal( sc->cfb, rs->sr_entry, sc->ca, NULL, NULL, NULL ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "config error processing %s: %s\n", + rs->sr_entry->e_name.bv_val, sc->ca->cr_msg, 0 ); + } + } + return rs->sr_err; +} + +/* Configure and read the underlying back-ldif store */ +static int +config_setup_ldif( BackendDB *be, const char *dir, int readit ) { + CfBackInfo *cfb = be->be_private; + ConfigArgs c = {0}; + ConfigTable *ct; + char *argv[3]; + int rc = 0; + setup_cookie sc; + slap_callback cb = { NULL, config_ldif_resp, NULL, NULL }; + Connection conn = {0}; + OperationBuffer opbuf; + Operation *op; + SlapReply rs = {REP_RESULT}; + Filter filter = { LDAP_FILTER_PRESENT }; + struct berval filterstr = BER_BVC("(objectclass=*)"); + struct stat st; + + /* Is the config directory available? */ + if ( stat( dir, &st ) < 0 ) { + /* No, so don't bother using the backing store. + * All changes will be in-memory only. + */ + return 0; + } + + cfb->cb_db.bd_info = backend_info( "ldif" ); + if ( !cfb->cb_db.bd_info ) + return 0; /* FIXME: eventually this will be a fatal error */ + + if ( backend_db_init( "ldif", &cfb->cb_db, -1, NULL ) == NULL ) + return 1; + + cfb->cb_db.be_suffix = be->be_suffix; + cfb->cb_db.be_nsuffix = be->be_nsuffix; + + /* The suffix is always "cn=config". The underlying DB's rootdn + * is always the same as the suffix. + */ + cfb->cb_db.be_rootdn = be->be_suffix[0]; + cfb->cb_db.be_rootndn = be->be_nsuffix[0]; + + ber_str2bv( dir, 0, 1, &cfdir ); + + c.be = &cfb->cb_db; + c.fname = "slapd"; + c.argc = 2; + argv[0] = "directory"; + argv[1] = (char *)dir; + argv[2] = NULL; + c.argv = argv; + c.reply.err = 0; + c.reply.msg[0] = 0; + c.table = Cft_Database; + + ct = config_find_keyword( c.be->be_cf_ocs->co_table, &c ); + if ( !ct ) + return 1; + + if ( config_add_vals( ct, &c )) + return 1; + + if ( backend_startup_one( &cfb->cb_db, &c.reply )) + return 1; + + if ( readit ) { + void *thrctx = ldap_pvt_thread_pool_context(); + int prev_DN_strict; + + connection_fake_init( &conn, &opbuf, thrctx ); + op = &opbuf.ob_op; + + filter.f_desc = slap_schema.si_ad_objectClass; + + op->o_tag = LDAP_REQ_SEARCH; + + op->ors_filter = &filter; + op->ors_filterstr = filterstr; + op->ors_scope = LDAP_SCOPE_SUBTREE; + + op->o_dn = c.be->be_rootdn; + op->o_ndn = c.be->be_rootndn; + + op->o_req_dn = be->be_suffix[0]; + op->o_req_ndn = be->be_nsuffix[0]; + + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_slimit = SLAP_NO_LIMIT; + + op->ors_attrs = slap_anlist_all_attributes; + op->ors_attrsonly = 0; + + op->o_callback = &cb; + sc.cfb = cfb; + sc.ca = &c; + cb.sc_private = ≻ + sc.got_frontend = 0; + sc.got_config = 0; + sc.frontend = NULL; + sc.config = NULL; + + op->o_bd = &cfb->cb_db; + + /* Allow unknown attrs in DNs */ + prev_DN_strict = slap_DN_strict; + slap_DN_strict = 0; + + rc = op->o_bd->be_search( op, &rs ); + + /* Restore normal DN validation */ + slap_DN_strict = prev_DN_strict; + + op->o_tag = LDAP_REQ_ADD; + if ( rc == LDAP_SUCCESS && sc.frontend ) { + rs_reinit( &rs, REP_RESULT ); + op->ora_e = sc.frontend; + rc = op->o_bd->be_add( op, &rs ); + } + if ( rc == LDAP_SUCCESS && sc.config ) { + rs_reinit( &rs, REP_RESULT ); + op->ora_e = sc.config; + rc = op->o_bd->be_add( op, &rs ); + } + ldap_pvt_thread_pool_context_reset( thrctx ); + } + + /* ITS#4194 - only use if it's present, or we're converting. */ + if ( !readit || rc == LDAP_SUCCESS ) + cfb->cb_use_ldif = 1; + + return rc; +} + +static int +CfOc_cmp( const void *c1, const void *c2 ) { + const ConfigOCs *co1 = c1; + const ConfigOCs *co2 = c2; + + return ber_bvcmp( co1->co_name, co2->co_name ); +} + +int +config_register_schema(ConfigTable *ct, ConfigOCs *ocs) { + int i; + + i = init_config_attrs( ct ); + if ( i ) return i; + + /* set up the objectclasses */ + i = init_config_ocs( ocs ); + if ( i ) return i; + + for (i=0; ocs[i].co_def; i++) { + if ( ocs[i].co_oc ) { + ocs[i].co_name = &ocs[i].co_oc->soc_cname; + if ( !ocs[i].co_table ) + ocs[i].co_table = ct; + avl_insert( &CfOcTree, &ocs[i], CfOc_cmp, avl_dup_error ); + } + } + return 0; +} + +int +read_config(const char *fname, const char *dir) { + BackendDB *be; + CfBackInfo *cfb; + const char *cfdir, *cfname; + int rc; + + /* Setup the config backend */ + be = backend_db_init( "config", NULL, 0, NULL ); + if ( !be ) + return 1; + + cfb = be->be_private; + be->be_dfltaccess = ACL_NONE; + + /* If no .conf, or a dir was specified, setup the dir */ + if ( !fname || dir ) { + if ( dir ) { + /* If explicitly given, check for existence */ + struct stat st; + + if ( stat( dir, &st ) < 0 ) { + Debug( LDAP_DEBUG_ANY, + "invalid config directory %s, error %d\n", + dir, errno, 0 ); + return 1; + } + cfdir = dir; + } else { + cfdir = SLAPD_DEFAULT_CONFIGDIR; + } + /* if fname is defaulted, try reading .d */ + rc = config_setup_ldif( be, cfdir, !fname ); + + if ( rc ) { + /* It may be OK if the base object doesn't exist yet. */ + if ( rc != LDAP_NO_SUCH_OBJECT ) + return 1; + /* ITS#4194: But if dir was specified and no fname, + * then we were supposed to read the dir. Unless we're + * trying to slapadd the dir... + */ + if ( dir && !fname ) { + if ( slapMode & (SLAP_SERVER_MODE|SLAP_TOOL_READMAIN|SLAP_TOOL_READONLY)) + return 1; + /* Assume it's slapadd with a config dir, let it continue */ + rc = 0; + cfb->cb_got_ldif = 1; + cfb->cb_use_ldif = 1; + goto done; + } + } + + /* If we read the config from back-ldif, nothing to do here */ + if ( cfb->cb_got_ldif ) { + rc = 0; + goto done; + } + } + + if ( fname ) + cfname = fname; + else + cfname = SLAPD_DEFAULT_CONFIGFILE; + + rc = read_config_file(cfname, 0, NULL, config_back_cf_table); + + if ( rc == 0 ) + ber_str2bv( cfname, 0, 1, &cfb->cb_config->c_file ); + +done: + if ( rc == 0 && BER_BVISNULL( &frontendDB->be_schemadn ) ) { + ber_str2bv( SLAPD_SCHEMA_DN, STRLENOF( SLAPD_SCHEMA_DN ), 1, + &frontendDB->be_schemadn ); + rc = dnNormalize( 0, NULL, NULL, &frontendDB->be_schemadn, &frontendDB->be_schemandn, NULL ); + if ( rc != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ANY, "read_config: " + "unable to normalize default schema DN \"%s\"\n", + frontendDB->be_schemadn.bv_val, 0, 0 ); + /* must not happen */ + assert( 0 ); + } + } + if ( rc == 0 && ( slapMode & SLAP_SERVER_MODE ) && sid_list ) { + if ( !BER_BVISEMPTY( &sid_list->si_url ) && !sid_set ) { + Debug(LDAP_DEBUG_ANY, "read_config: no serverID / URL match found. " + "Check slapd -h arguments.\n", 0,0,0 ); + rc = LDAP_OTHER; + } + } + return rc; +} + +static int +config_back_bind( Operation *op, SlapReply *rs ) +{ + if ( be_isroot_pw( op ) ) { + ber_dupbv( &op->orb_edn, be_root_dn( op->o_bd )); + /* frontend sends result */ + return LDAP_SUCCESS; + } + + rs->sr_err = LDAP_INVALID_CREDENTIALS; + send_ldap_result( op, rs ); + + return rs->sr_err; +} + +static int +config_send( Operation *op, SlapReply *rs, CfEntryInfo *ce, int depth ) +{ + int rc = 0; + + if ( test_filter( op, ce->ce_entry, op->ors_filter ) == LDAP_COMPARE_TRUE ) + { + rs->sr_attrs = op->ors_attrs; + rs->sr_entry = ce->ce_entry; + rs->sr_flags = 0; + rc = send_search_entry( op, rs ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + } + if ( op->ors_scope == LDAP_SCOPE_SUBTREE ) { + if ( ce->ce_kids ) { + rc = config_send( op, rs, ce->ce_kids, 1 ); + if ( rc ) return rc; + } + if ( depth ) { + for (ce=ce->ce_sibs; ce; ce=ce->ce_sibs) { + rc = config_send( op, rs, ce, 0 ); + if ( rc ) break; + } + } + } + return rc; +} + +static ConfigTable * +config_find_table( ConfigOCs **colst, int nocs, AttributeDescription *ad, + ConfigArgs *ca ) +{ + int i, j; + + for (j=0; j<nocs; j++) { + for (i=0; colst[j]->co_table[i].name; i++) + if ( colst[j]->co_table[i].ad == ad ) { + ca->table = colst[j]->co_type; + return &colst[j]->co_table[i]; + } + } + return NULL; +} + +/* Sort the attributes of the entry according to the order defined + * in the objectclass, with required attributes occurring before + * allowed attributes. For any attributes with sequencing dependencies + * (e.g., rootDN must be defined after suffix) the objectclass must + * list the attributes in the desired sequence. + */ +static void +sort_attrs( Entry *e, ConfigOCs **colst, int nocs ) +{ + Attribute *a, *head = NULL, *tail = NULL, **prev; + int i, j; + + for (i=0; i<nocs; i++) { + if ( colst[i]->co_oc->soc_required ) { + AttributeType **at = colst[i]->co_oc->soc_required; + for (j=0; at[j]; j++) { + for (a=e->e_attrs, prev=&e->e_attrs; a; + prev = &(*prev)->a_next, a=a->a_next) { + if ( a->a_desc == at[j]->sat_ad ) { + *prev = a->a_next; + if (!head) { + head = a; + tail = a; + } else { + tail->a_next = a; + tail = a; + } + break; + } + } + } + } + if ( colst[i]->co_oc->soc_allowed ) { + AttributeType **at = colst[i]->co_oc->soc_allowed; + for (j=0; at[j]; j++) { + for (a=e->e_attrs, prev=&e->e_attrs; a; + prev = &(*prev)->a_next, a=a->a_next) { + if ( a->a_desc == at[j]->sat_ad ) { + *prev = a->a_next; + if (!head) { + head = a; + tail = a; + } else { + tail->a_next = a; + tail = a; + } + break; + } + } + } + } + } + if ( tail ) { + tail->a_next = e->e_attrs; + e->e_attrs = head; + } +} + +static int +check_vals( ConfigTable *ct, ConfigArgs *ca, void *ptr, int isAttr ) +{ + Attribute *a = NULL; + AttributeDescription *ad; + BerVarray vals; + + int i, rc = 0; + + if ( isAttr ) { + a = ptr; + ad = a->a_desc; + vals = a->a_vals; + } else { + Modifications *ml = ptr; + ad = ml->sml_desc; + vals = ml->sml_values; + } + + if ( a && ( ad->ad_type->sat_flags & SLAP_AT_ORDERED_VAL )) { + rc = ordered_value_sort( a, 1 ); + if ( rc ) { + snprintf(ca->cr_msg, sizeof( ca->cr_msg ), "ordered_value_sort failed on attr %s\n", + ad->ad_cname.bv_val ); + return rc; + } + } + for ( i=0; vals[i].bv_val; i++ ) { + ca->line = vals[i].bv_val; + if (( ad->ad_type->sat_flags & SLAP_AT_ORDERED_VAL ) && + ca->line[0] == '{' ) { + char *idx = strchr( ca->line, '}' ); + if ( idx ) ca->line = idx+1; + } + rc = config_parse_vals( ct, ca, i ); + if ( rc ) { + break; + } + } + return rc; +} + +static int +config_rename_attr( SlapReply *rs, Entry *e, struct berval *rdn, + Attribute **at ) +{ + struct berval rtype, rval; + Attribute *a; + AttributeDescription *ad = NULL; + + dnRdn( &e->e_name, rdn ); + rval.bv_val = strchr(rdn->bv_val, '=' ) + 1; + rval.bv_len = rdn->bv_len - (rval.bv_val - rdn->bv_val); + rtype.bv_val = rdn->bv_val; + rtype.bv_len = rval.bv_val - rtype.bv_val - 1; + + /* Find attr */ + slap_bv2ad( &rtype, &ad, &rs->sr_text ); + a = attr_find( e->e_attrs, ad ); + if (!a ) return LDAP_NAMING_VIOLATION; + *at = a; + + return 0; +} + +static void +config_rename_kids( CfEntryInfo *ce ) +{ + CfEntryInfo *ce2; + struct berval rdn, nrdn; + + for (ce2 = ce->ce_kids; ce2; ce2 = ce2->ce_sibs) { + struct berval newdn, newndn; + dnRdn ( &ce2->ce_entry->e_name, &rdn ); + dnRdn ( &ce2->ce_entry->e_nname, &nrdn ); + build_new_dn( &newdn, &ce->ce_entry->e_name, &rdn, NULL ); + build_new_dn( &newndn, &ce->ce_entry->e_nname, &nrdn, NULL ); + free( ce2->ce_entry->e_name.bv_val ); + free( ce2->ce_entry->e_nname.bv_val ); + ce2->ce_entry->e_name = newdn; + ce2->ce_entry->e_nname = newndn; + config_rename_kids( ce2 ); + } +} + +static int +config_rename_one( Operation *op, SlapReply *rs, Entry *e, + CfEntryInfo *parent, Attribute *a, struct berval *newrdn, + struct berval *nnewrdn, int use_ldif ) +{ + char *ptr1; + int cnt, rc = 0; + struct berval odn, ondn; + const char *text = ""; + LDAPRDN rDN; + + odn = e->e_name; + ondn = e->e_nname; + build_new_dn( &e->e_name, &parent->ce_entry->e_name, newrdn, NULL ); + build_new_dn( &e->e_nname, &parent->ce_entry->e_nname, nnewrdn, NULL ); + + /* Replace attr */ + rc = ldap_bv2rdn( &e->e_name, &rDN, &text, LDAP_DN_FORMAT_LDAP ); + if ( rc ) { + return rc; + } + for ( cnt = 0; rDN[cnt]; cnt++ ) { + AttributeDescription *ad = NULL; + LDAPAVA *ava = rDN[cnt]; + + rc = slap_bv2ad( &ava->la_attr, &ad, &text ); + if ( rc ) { + break; + } + + if ( ad != a->a_desc ) continue; + + free( a->a_vals[0].bv_val ); + ber_dupbv( &a->a_vals[0], &ava->la_value ); + if ( a->a_nvals != a->a_vals ) { + free( a->a_nvals[0].bv_val ); + rc = attr_normalize_one( ad, &ava->la_value, &a->a_nvals[0], NULL ); + if ( rc ) { + break; + } + } + + /* attributes with X-ORDERED 'SIBLINGS' are single-valued, we're done */ + break; + } + /* the attribute must be present in rDN */ + assert( rDN[cnt] ); + ldap_rdnfree( rDN ); + if ( rc ) { + return rc; + } + + if ( use_ldif ) { + CfBackInfo *cfb = (CfBackInfo *)op->o_bd->be_private; + BackendDB *be = op->o_bd; + slap_callback sc = { NULL, slap_null_cb, NULL, NULL }, *scp; + struct berval dn, ndn, xdn, xndn; + + op->o_bd = &cfb->cb_db; + + /* Save current rootdn; use the underlying DB's rootdn */ + dn = op->o_dn; + ndn = op->o_ndn; + xdn = op->o_req_dn; + xndn = op->o_req_ndn; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + op->o_req_dn = odn; + op->o_req_ndn = ondn; + + scp = op->o_callback; + op->o_callback = ≻ + op->orr_newrdn = *newrdn; + op->orr_nnewrdn = *nnewrdn; + op->orr_newSup = NULL; + op->orr_nnewSup = NULL; + op->orr_deleteoldrdn = 1; + op->orr_modlist = NULL; + slap_modrdn2mods( op, rs ); + slap_mods_opattrs( op, &op->orr_modlist, 1 ); + rc = op->o_bd->be_modrdn( op, rs ); + slap_mods_free( op->orr_modlist, 1 ); + + op->o_bd = be; + op->o_callback = scp; + op->o_dn = dn; + op->o_ndn = ndn; + op->o_req_dn = xdn; + op->o_req_ndn = xndn; + } + free( odn.bv_val ); + free( ondn.bv_val ); + if ( e->e_private ) + config_rename_kids( e->e_private ); + return rc; +} + +static int +config_renumber_one( Operation *op, SlapReply *rs, CfEntryInfo *parent, + Entry *e, int idx, int tailindex, int use_ldif ) +{ + struct berval ival, newrdn, nnewrdn; + struct berval rdn; + Attribute *a; + char ibuf[32], *ptr1, *ptr2 = NULL; + int rc = 0; + + rc = config_rename_attr( rs, e, &rdn, &a ); + if ( rc ) return rc; + + ival.bv_val = ibuf; + ival.bv_len = snprintf( ibuf, sizeof( ibuf ), SLAP_X_ORDERED_FMT, idx ); + if ( ival.bv_len >= sizeof( ibuf ) ) { + return LDAP_NAMING_VIOLATION; + } + + newrdn.bv_len = rdn.bv_len + ival.bv_len; + newrdn.bv_val = ch_malloc( newrdn.bv_len+1 ); + + if ( tailindex ) { + ptr1 = lutil_strncopy( newrdn.bv_val, rdn.bv_val, rdn.bv_len ); + ptr1 = lutil_strcopy( ptr1, ival.bv_val ); + } else { + int xlen; + ptr2 = ber_bvchr( &rdn, '}' ); + if ( ptr2 ) { + ptr2++; + } else { + ptr2 = rdn.bv_val + a->a_desc->ad_cname.bv_len + 1; + } + xlen = rdn.bv_len - (ptr2 - rdn.bv_val); + ptr1 = lutil_strncopy( newrdn.bv_val, a->a_desc->ad_cname.bv_val, + a->a_desc->ad_cname.bv_len ); + *ptr1++ = '='; + ptr1 = lutil_strcopy( ptr1, ival.bv_val ); + ptr1 = lutil_strncopy( ptr1, ptr2, xlen ); + *ptr1 = '\0'; + } + + /* Do the equivalent of ModRDN */ + /* Replace DN / NDN */ + newrdn.bv_len = ptr1 - newrdn.bv_val; + rc = rdnNormalize( 0, NULL, NULL, &newrdn, &nnewrdn, NULL ); + if ( rc ) { + free( newrdn.bv_val ); + return LDAP_NAMING_VIOLATION; + } + rc = config_rename_one( op, rs, e, parent, a, &newrdn, &nnewrdn, use_ldif ); + + free( nnewrdn.bv_val ); + free( newrdn.bv_val ); + return rc; +} + +static int +check_name_index( CfEntryInfo *parent, ConfigType ce_type, Entry *e, + SlapReply *rs, int *renum, int *ibase ) +{ + CfEntryInfo *ce; + int index = -1, gotindex = 0, nsibs, rc = 0; + int renumber = 0, tailindex = 0, isfrontend = 0, isconfig = 0; + char *ptr1, *ptr2 = NULL; + struct berval rdn; + + if ( renum ) *renum = 0; + + /* These entries don't get indexed/renumbered */ + if ( ce_type == Cft_Global ) return 0; + if ( ce_type == Cft_Schema && parent->ce_type == Cft_Global ) return 0; + + if ( ce_type == Cft_Module ) + tailindex = 1; + + /* See if the rdn has an index already */ + dnRdn( &e->e_name, &rdn ); + if ( ce_type == Cft_Database ) { + if ( !strncmp( rdn.bv_val + rdn.bv_len - STRLENOF("frontend"), + "frontend", STRLENOF("frontend") )) + isfrontend = 1; + else if ( !strncmp( rdn.bv_val + rdn.bv_len - STRLENOF("config"), + "config", STRLENOF("config") )) + isconfig = 1; + } + ptr1 = ber_bvchr( &e->e_name, '{' ); + if ( ptr1 && ptr1 < &e->e_name.bv_val[ rdn.bv_len ] ) { + char *next; + ptr2 = strchr( ptr1, '}' ); + if ( !ptr2 || ptr2 > &e->e_name.bv_val[ rdn.bv_len ] ) + return LDAP_NAMING_VIOLATION; + if ( ptr2-ptr1 == 1) + return LDAP_NAMING_VIOLATION; + gotindex = 1; + index = strtol( ptr1 + 1, &next, 10 ); + if ( next == ptr1 + 1 || next[ 0 ] != '}' ) { + return LDAP_NAMING_VIOLATION; + } + if ( index < 0 ) { + /* Special case, we allow -1 for the frontendDB */ + if ( index != -1 || !isfrontend ) + return LDAP_NAMING_VIOLATION; + } + if ( isconfig && index != 0 ){ + return LDAP_NAMING_VIOLATION; + } + } + + /* count related kids. + * For entries of type Cft_Misc, only count siblings with same RDN type + */ + if ( ce_type == Cft_Misc ) { + rdn.bv_val = e->e_nname.bv_val; + ptr1 = strchr( rdn.bv_val, '=' ); + assert( ptr1 != NULL ); + + rdn.bv_len = ptr1 - rdn.bv_val; + + for (nsibs=0, ce=parent->ce_kids; ce; ce=ce->ce_sibs) { + struct berval rdn2; + if ( ce->ce_type != ce_type ) + continue; + + dnRdn( &ce->ce_entry->e_nname, &rdn2 ); + + ptr1 = strchr( rdn2.bv_val, '=' ); + assert( ptr1 != NULL ); + + rdn2.bv_len = ptr1 - rdn2.bv_val; + if ( bvmatch( &rdn, &rdn2 )) + nsibs++; + } + } else { + for (nsibs=0, ce=parent->ce_kids; ce; ce=ce->ce_sibs) { + if ( ce->ce_type == ce_type ) nsibs++; + } + } + + /* account for -1 frontend */ + if ( ce_type == Cft_Database ) + nsibs--; + + if ( index != nsibs || isfrontend ) { + if ( gotindex ) { + if ( index < nsibs ) { + if ( tailindex ) return LDAP_NAMING_VIOLATION; + /* Siblings need to be renumbered */ + if ( index != -1 || !isfrontend ) + renumber = 1; + } + } + /* config DB is always "0" */ + if ( isconfig && index == -1 ) { + index = 0; + } + if (( !isfrontend && index == -1 ) || ( index > nsibs ) ){ + index = nsibs; + } + + /* just make index = nsibs */ + if ( !renumber ) { + rc = config_renumber_one( NULL, rs, parent, e, index, tailindex, 0 ); + } + } + if ( ibase ) *ibase = index; + if ( renum ) *renum = renumber; + return rc; +} + +/* Insert all superior classes of the given class */ +static int +count_oc( ObjectClass *oc, ConfigOCs ***copp, int *nocs ) +{ + ConfigOCs co, *cop; + ObjectClass **sups; + + for ( sups = oc->soc_sups; sups && *sups; sups++ ) { + if ( count_oc( *sups, copp, nocs ) ) { + return -1; + } + } + + co.co_name = &oc->soc_cname; + cop = avl_find( CfOcTree, &co, CfOc_cmp ); + if ( cop ) { + int i; + + /* check for duplicates */ + for ( i = 0; i < *nocs; i++ ) { + if ( *copp && (*copp)[i] == cop ) { + break; + } + } + + if ( i == *nocs ) { + ConfigOCs **tmp = ch_realloc( *copp, (*nocs + 1)*sizeof( ConfigOCs * ) ); + if ( tmp == NULL ) { + return -1; + } + *copp = tmp; + (*copp)[*nocs] = cop; + (*nocs)++; + } + } + + return 0; +} + +/* Find all superior classes of the given objectclasses, + * return list in order of most-subordinate first. + * + * Special / auxiliary / Cft_Misc classes always take precedence. + */ +static ConfigOCs ** +count_ocs( Attribute *oc_at, int *nocs ) +{ + int i, j, misc = -1; + ConfigOCs **colst = NULL; + + *nocs = 0; + + for ( i = oc_at->a_numvals; i--; ) { + ObjectClass *oc = oc_bvfind( &oc_at->a_nvals[i] ); + + assert( oc != NULL ); + if ( count_oc( oc, &colst, nocs ) ) { + ch_free( colst ); + return NULL; + } + } + + /* invert order */ + i = 0; + j = *nocs - 1; + while ( i < j ) { + ConfigOCs *tmp = colst[i]; + colst[i] = colst[j]; + colst[j] = tmp; + if (tmp->co_type == Cft_Misc) + misc = j; + i++; j--; + } + /* Move misc class to front of list */ + if (misc > 0) { + ConfigOCs *tmp = colst[misc]; + for (i=misc; i>0; i--) + colst[i] = colst[i-1]; + colst[0] = tmp; + } + + return colst; +} + +static int +cfAddInclude( CfEntryInfo *p, Entry *e, ConfigArgs *ca ) +{ + /* Leftover from RE23. Never parse this entry */ + return LDAP_COMPARE_TRUE; +} + +static int +cfAddSchema( CfEntryInfo *p, Entry *e, ConfigArgs *ca ) +{ + ConfigFile *cfo; + + /* This entry is hardcoded, don't re-parse it */ + if ( p->ce_type == Cft_Global ) { + cfn = p->ce_private; + ca->ca_private = cfn; + return LDAP_COMPARE_TRUE; + } + if ( p->ce_type != Cft_Schema ) + return LDAP_CONSTRAINT_VIOLATION; + + cfn = ch_calloc( 1, sizeof(ConfigFile) ); + ca->ca_private = cfn; + cfo = p->ce_private; + cfn->c_sibs = cfo->c_kids; + cfo->c_kids = cfn; + return LDAP_SUCCESS; +} + +static int +cfAddDatabase( CfEntryInfo *p, Entry *e, struct config_args_s *ca ) +{ + if ( p->ce_type != Cft_Global ) { + return LDAP_CONSTRAINT_VIOLATION; + } + /* config must be {0}, nothing else allowed */ + if ( !strncmp( e->e_nname.bv_val, "olcDatabase={0}", STRLENOF("olcDatabase={0}")) && + strncmp( e->e_nname.bv_val + STRLENOF("olcDatabase={0}"), "config,", STRLENOF("config,") )) { + return LDAP_CONSTRAINT_VIOLATION; + } + ca->be = frontendDB; /* just to get past check_vals */ + return LDAP_SUCCESS; +} + +static int +cfAddBackend( CfEntryInfo *p, Entry *e, struct config_args_s *ca ) +{ + if ( p->ce_type != Cft_Global ) { + return LDAP_CONSTRAINT_VIOLATION; + } + return LDAP_SUCCESS; +} + +static int +cfAddModule( CfEntryInfo *p, Entry *e, struct config_args_s *ca ) +{ + if ( p->ce_type != Cft_Global ) { + return LDAP_CONSTRAINT_VIOLATION; + } + return LDAP_SUCCESS; +} + +static int +cfAddOverlay( CfEntryInfo *p, Entry *e, struct config_args_s *ca ) +{ + if ( p->ce_type != Cft_Database ) { + return LDAP_CONSTRAINT_VIOLATION; + } + ca->be = p->ce_be; + return LDAP_SUCCESS; +} + +static void +schema_destroy_one( ConfigArgs *ca, ConfigOCs **colst, int nocs, + CfEntryInfo *p ) +{ + ConfigTable *ct; + ConfigFile *cfo; + AttributeDescription *ad; + const char *text; + + ca->valx = -1; + ca->line = NULL; + ca->argc = 1; + if ( cfn->c_cr_head ) { + struct berval bv = BER_BVC("olcDitContentRules"); + ad = NULL; + slap_bv2ad( &bv, &ad, &text ); + ct = config_find_table( colst, nocs, ad, ca ); + config_del_vals( ct, ca ); + } + if ( cfn->c_oc_head ) { + struct berval bv = BER_BVC("olcObjectClasses"); + ad = NULL; + slap_bv2ad( &bv, &ad, &text ); + ct = config_find_table( colst, nocs, ad, ca ); + config_del_vals( ct, ca ); + } + if ( cfn->c_at_head ) { + struct berval bv = BER_BVC("olcAttributeTypes"); + ad = NULL; + slap_bv2ad( &bv, &ad, &text ); + ct = config_find_table( colst, nocs, ad, ca ); + config_del_vals( ct, ca ); + } + if ( cfn->c_syn_head ) { + struct berval bv = BER_BVC("olcLdapSyntaxes"); + ad = NULL; + slap_bv2ad( &bv, &ad, &text ); + ct = config_find_table( colst, nocs, ad, ca ); + config_del_vals( ct, ca ); + } + if ( cfn->c_om_head ) { + struct berval bv = BER_BVC("olcObjectIdentifier"); + ad = NULL; + slap_bv2ad( &bv, &ad, &text ); + ct = config_find_table( colst, nocs, ad, ca ); + config_del_vals( ct, ca ); + } + cfo = p->ce_private; + cfo->c_kids = cfn->c_sibs; + ch_free( cfn ); +} + +static int +config_add_oc( ConfigOCs **cop, CfEntryInfo *last, Entry *e, ConfigArgs *ca ) +{ + int rc = LDAP_CONSTRAINT_VIOLATION; + ObjectClass **ocp; + + if ( (*cop)->co_ldadd ) { + rc = (*cop)->co_ldadd( last, e, ca ); + if ( rc != LDAP_CONSTRAINT_VIOLATION ) { + return rc; + } + } + + for ( ocp = (*cop)->co_oc->soc_sups; ocp && *ocp; ocp++ ) { + ConfigOCs co = { 0 }; + + co.co_name = &(*ocp)->soc_cname; + *cop = avl_find( CfOcTree, &co, CfOc_cmp ); + if ( *cop == NULL ) { + return rc; + } + + rc = config_add_oc( cop, last, e, ca ); + if ( rc != LDAP_CONSTRAINT_VIOLATION ) { + return rc; + } + } + + return rc; +} + +/* Parse an LDAP entry into config directives */ +static int +config_add_internal( CfBackInfo *cfb, Entry *e, ConfigArgs *ca, SlapReply *rs, + int *renum, Operation *op ) +{ + CfEntryInfo *ce, *last = NULL; + ConfigOCs co, *coptr, **colst; + Attribute *a, *oc_at, *soc_at; + int i, ibase = -1, nocs, rc = 0; + struct berval pdn; + ConfigTable *ct; + char *ptr, *log_prefix = op ? op->o_log_prefix : ""; + + memset( ca, 0, sizeof(ConfigArgs)); + + /* Make sure parent exists and entry does not. But allow + * Databases and Overlays to be inserted. Don't do any + * auto-renumbering if manageDSAit control is present. + */ + ce = config_find_base( cfb->cb_root, &e->e_nname, &last ); + if ( ce ) { + if ( ( op && op->o_managedsait ) || + ( ce->ce_type != Cft_Database && ce->ce_type != Cft_Overlay && + ce->ce_type != Cft_Module ) ) + { + Debug( LDAP_DEBUG_TRACE, "%s: config_add_internal: " + "DN=\"%s\" already exists\n", + log_prefix, e->e_name.bv_val, 0 ); + /* global schema ignores all writes */ + if ( ce->ce_type == Cft_Schema && ce->ce_parent->ce_type == Cft_Global ) + return LDAP_COMPARE_TRUE; + return LDAP_ALREADY_EXISTS; + } + } + + dnParent( &e->e_nname, &pdn ); + + /* If last is NULL, the new entry is the root/suffix entry, + * otherwise last should be the parent. + */ + if ( last && !dn_match( &last->ce_entry->e_nname, &pdn ) ) { + if ( rs ) { + rs->sr_matched = last->ce_entry->e_name.bv_val; + } + Debug( LDAP_DEBUG_TRACE, "%s: config_add_internal: " + "DN=\"%s\" not child of DN=\"%s\"\n", + log_prefix, e->e_name.bv_val, + last->ce_entry->e_name.bv_val ); + return LDAP_NO_SUCH_OBJECT; + } + + if ( op ) { + /* No parent, must be root. This will never happen... */ + if ( !last && !be_isroot( op ) && !be_shadow_update( op ) ) { + return LDAP_NO_SUCH_OBJECT; + } + + if ( last && !access_allowed( op, last->ce_entry, + slap_schema.si_ad_children, NULL, ACL_WADD, NULL ) ) + { + Debug( LDAP_DEBUG_TRACE, "%s: config_add_internal: " + "DN=\"%s\" no write access to \"children\" of parent\n", + log_prefix, e->e_name.bv_val, 0 ); + return LDAP_INSUFFICIENT_ACCESS; + } + } + + oc_at = attr_find( e->e_attrs, slap_schema.si_ad_objectClass ); + if ( !oc_at ) { + Debug( LDAP_DEBUG_TRACE, "%s: config_add_internal: " + "DN=\"%s\" no objectClass\n", + log_prefix, e->e_name.bv_val, 0 ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + soc_at = attr_find( e->e_attrs, slap_schema.si_ad_structuralObjectClass ); + if ( !soc_at ) { + ObjectClass *soc = NULL; + char textbuf[ SLAP_TEXT_BUFLEN ]; + const char *text = textbuf; + + /* FIXME: check result */ + rc = structural_class( oc_at->a_nvals, &soc, NULL, + &text, textbuf, sizeof(textbuf), NULL ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "%s: config_add_internal: " + "DN=\"%s\" no structural objectClass (%s)\n", + log_prefix, e->e_name.bv_val, text ); + return rc; + } + attr_merge_one( e, slap_schema.si_ad_structuralObjectClass, &soc->soc_cname, NULL ); + soc_at = attr_find( e->e_attrs, slap_schema.si_ad_structuralObjectClass ); + if ( soc_at == NULL ) { + Debug( LDAP_DEBUG_TRACE, "%s: config_add_internal: " + "DN=\"%s\" no structural objectClass; " + "unable to merge computed class %s\n", + log_prefix, e->e_name.bv_val, + soc->soc_cname.bv_val ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + Debug( LDAP_DEBUG_TRACE, "%s: config_add_internal: " + "DN=\"%s\" no structural objectClass; " + "computed objectClass %s merged\n", + log_prefix, e->e_name.bv_val, + soc->soc_cname.bv_val ); + } + + /* Fake the coordinates based on whether we're part of an + * LDAP Add or if reading the config dir + */ + if ( rs ) { + ca->fname = "slapd"; + ca->lineno = 0; + } else { + ca->fname = cfdir.bv_val; + ca->lineno = 1; + } + ca->ca_op = op; + + co.co_name = &soc_at->a_nvals[0]; + coptr = avl_find( CfOcTree, &co, CfOc_cmp ); + if ( coptr == NULL ) { + Debug( LDAP_DEBUG_TRACE, "%s: config_add_internal: " + "DN=\"%s\" no structural objectClass in configuration table\n", + log_prefix, e->e_name.bv_val, 0 ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + /* Only the root can be Cft_Global, everything else must + * have a parent. Only limited nesting arrangements are allowed. + */ + rc = LDAP_CONSTRAINT_VIOLATION; + if ( coptr->co_type == Cft_Global && !last ) { + cfn = cfb->cb_config; + ca->ca_private = cfn; + ca->be = frontendDB; /* just to get past check_vals */ + rc = LDAP_SUCCESS; + } + + colst = count_ocs( oc_at, &nocs ); + + /* Check whether the Add is allowed by its parent, and do + * any necessary arg setup + */ + if ( last ) { + rc = config_add_oc( &coptr, last, e, ca ); + if ( rc == LDAP_CONSTRAINT_VIOLATION ) { + for ( i = 0; i<nocs; i++ ) { + /* Already checked these */ + if ( colst[i]->co_oc->soc_kind == LDAP_SCHEMA_STRUCTURAL ) + continue; + if ( colst[i]->co_ldadd && + ( rc = colst[i]->co_ldadd( last, e, ca )) + != LDAP_CONSTRAINT_VIOLATION ) { + coptr = colst[i]; + break; + } + } + } + if ( rc == LDAP_CONSTRAINT_VIOLATION ) { + Debug( LDAP_DEBUG_TRACE, "%s: config_add_internal: " + "DN=\"%s\" no structural objectClass add function\n", + log_prefix, e->e_name.bv_val, 0 ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + } + + /* Add the entry but don't parse it, we already have its contents */ + if ( rc == LDAP_COMPARE_TRUE ) { + rc = LDAP_SUCCESS; + goto ok; + } + + if ( rc != LDAP_SUCCESS ) + goto done_noop; + + /* Parse all the values and check for simple syntax errors before + * performing any set actions. + * + * If doing an LDAPadd, check for indexed names and any necessary + * renaming/renumbering. Entries that don't need indexed names are + * ignored. Entries that need an indexed name and arrive without one + * are assigned to the end. Entries that arrive with an index may + * cause the following entries to be renumbered/bumped down. + * + * Note that "pseudo-indexed" entries (cn=Include{xx}, cn=Module{xx}) + * don't allow Adding an entry with an index that's already in use. + * This is flagged as an error (LDAP_ALREADY_EXISTS) up above. + * + * These entries can have auto-assigned indexes (appended to the end) + * but only the other types support auto-renumbering of siblings. + */ + { + rc = check_name_index( last, coptr->co_type, e, rs, renum, + &ibase ); + if ( rc ) { + goto done_noop; + } + if ( renum && *renum && coptr->co_type != Cft_Database && + coptr->co_type != Cft_Overlay ) + { + snprintf( ca->cr_msg, sizeof( ca->cr_msg ), + "operation requires sibling renumbering" ); + rc = LDAP_UNWILLING_TO_PERFORM; + goto done_noop; + } + } + + init_config_argv( ca ); + + /* Make sure we process attrs in the required order */ + sort_attrs( e, colst, nocs ); + + for ( a = e->e_attrs; a; a = a->a_next ) { + if ( a == oc_at ) continue; + ct = config_find_table( colst, nocs, a->a_desc, ca ); + if ( !ct ) continue; /* user data? */ + rc = check_vals( ct, ca, a, 1 ); + if ( rc ) goto done_noop; + } + + /* Basic syntax checks are OK. Do the actual settings. */ + for ( a=e->e_attrs; a; a=a->a_next ) { + if ( a == oc_at ) continue; + ct = config_find_table( colst, nocs, a->a_desc, ca ); + if ( !ct ) continue; /* user data? */ + for (i=0; a->a_vals[i].bv_val; i++) { + char *iptr = NULL; + ca->valx = -1; + ca->line = a->a_vals[i].bv_val; + if ( a->a_desc->ad_type->sat_flags & SLAP_AT_ORDERED ) { + ptr = strchr( ca->line, '}' ); + if ( ptr ) { + iptr = strchr( ca->line, '{' ); + ca->line = ptr+1; + } + } + if ( a->a_desc->ad_type->sat_flags & SLAP_AT_ORDERED_SIB ) { + if ( iptr ) { + ca->valx = strtol( iptr+1, NULL, 0 ); + } + } else { + ca->valx = i; + } + rc = config_parse_add( ct, ca, i ); + if ( rc ) { + rc = LDAP_OTHER; + goto done; + } + } + } +ok: + /* Newly added databases and overlays need to be started up */ + if ( CONFIG_ONLINE_ADD( ca )) { + if ( coptr->co_type == Cft_Database ) { + rc = backend_startup_one( ca->be, &ca->reply ); + + } else if ( coptr->co_type == Cft_Overlay ) { + if ( ca->bi->bi_db_open ) { + BackendInfo *bi_orig = ca->be->bd_info; + ca->be->bd_info = ca->bi; + rc = ca->bi->bi_db_open( ca->be, &ca->reply ); + ca->be->bd_info = bi_orig; + } + } else if ( ca->cleanup ) { + rc = ca->cleanup( ca ); + } + if ( rc ) { + if (ca->cr_msg[0] == '\0') + snprintf( ca->cr_msg, sizeof( ca->cr_msg ), "<%s> failed startup", ca->argv[0] ); + + Debug(LDAP_DEBUG_ANY, "%s: %s (%s)!\n", + ca->log, ca->cr_msg, ca->argv[1] ); + rc = LDAP_OTHER; + goto done; + } + } + + ca->valx = ibase; + ce = ch_calloc( 1, sizeof(CfEntryInfo) ); + ce->ce_parent = last; + ce->ce_entry = entry_dup( e ); + ce->ce_entry->e_private = ce; + ce->ce_type = coptr->co_type; + ce->ce_be = ca->be; + ce->ce_bi = ca->bi; + ce->ce_private = ca->ca_private; + ca->ca_entry = ce->ce_entry; + if ( !last ) { + cfb->cb_root = ce; + } else if ( last->ce_kids ) { + CfEntryInfo *c2, **cprev; + + /* Advance to first of this type */ + cprev = &last->ce_kids; + for ( c2 = *cprev; c2 && c2->ce_type < ce->ce_type; ) { + cprev = &c2->ce_sibs; + c2 = c2->ce_sibs; + } + /* Account for the (-1) frontendDB entry */ + if ( ce->ce_type == Cft_Database ) { + if ( ca->be == frontendDB ) + ibase = 0; + else if ( ibase != -1 ) + ibase++; + } + /* Append */ + if ( ibase < 0 ) { + for (c2 = *cprev; c2 && c2->ce_type == ce->ce_type;) { + cprev = &c2->ce_sibs; + c2 = c2->ce_sibs; + } + } else { + /* Insert */ + int i; + for ( i=0; i<ibase; i++ ) { + c2 = *cprev; + cprev = &c2->ce_sibs; + } + } + ce->ce_sibs = *cprev; + *cprev = ce; + } else { + last->ce_kids = ce; + } + +done: + if ( rc ) { + if ( (coptr->co_type == Cft_Database) && ca->be ) { + if ( ca->be != frontendDB ) + backend_destroy_one( ca->be, 1 ); + } else if ( (coptr->co_type == Cft_Overlay) && ca->bi ) { + overlay_destroy_one( ca->be, (slap_overinst *)ca->bi ); + } else if ( coptr->co_type == Cft_Schema ) { + schema_destroy_one( ca, colst, nocs, last ); + } + } +done_noop: + + ch_free( ca->argv ); + if ( colst ) ch_free( colst ); + return rc; +} + +#define BIGTMP 10000 +static int +config_rename_add( Operation *op, SlapReply *rs, CfEntryInfo *ce, + int base, int rebase, int max, int use_ldif ) +{ + CfEntryInfo *ce2, *ce3, *cetmp = NULL, *cerem = NULL; + ConfigType etype = ce->ce_type; + int count = 0, rc = 0; + + /* Reverse ce list */ + for (ce2 = ce->ce_sibs;ce2;ce2 = ce3) { + if (ce2->ce_type != etype) { + cerem = ce2; + break; + } + ce3 = ce2->ce_sibs; + ce2->ce_sibs = cetmp; + cetmp = ce2; + count++; + if ( max && count >= max ) { + cerem = ce3; + break; + } + } + + /* Move original to a temp name until increments are done */ + if ( rebase ) { + ce->ce_entry->e_private = NULL; + rc = config_renumber_one( op, rs, ce->ce_parent, ce->ce_entry, + base+BIGTMP, 0, use_ldif ); + ce->ce_entry->e_private = ce; + } + /* start incrementing */ + for (ce2=cetmp; ce2; ce2=ce3) { + ce3 = ce2->ce_sibs; + ce2->ce_sibs = cerem; + cerem = ce2; + if ( rc == 0 ) + rc = config_renumber_one( op, rs, ce2->ce_parent, ce2->ce_entry, + count+base, 0, use_ldif ); + count--; + } + if ( rebase ) + rc = config_renumber_one( op, rs, ce->ce_parent, ce->ce_entry, + base, 0, use_ldif ); + return rc; +} + +static int +config_rename_del( Operation *op, SlapReply *rs, CfEntryInfo *ce, + CfEntryInfo *ce2, int old, int use_ldif ) +{ + int count = 0; + + /* Renumber original to a temp value */ + ce->ce_entry->e_private = NULL; + config_renumber_one( op, rs, ce->ce_parent, ce->ce_entry, + old+BIGTMP, 0, use_ldif ); + ce->ce_entry->e_private = ce; + + /* start decrementing */ + for (; ce2 != ce; ce2=ce2->ce_sibs) { + config_renumber_one( op, rs, ce2->ce_parent, ce2->ce_entry, + count+old, 0, use_ldif ); + count++; + } + return config_renumber_one( op, rs, ce->ce_parent, ce->ce_entry, + count+old, 0, use_ldif ); +} + +/* Parse an LDAP entry into config directives, then store in underlying + * database. + */ +static int +config_back_add( Operation *op, SlapReply *rs ) +{ + CfBackInfo *cfb; + int renumber; + ConfigArgs ca; + + if ( !access_allowed( op, op->ora_e, slap_schema.si_ad_entry, + NULL, ACL_WADD, NULL )) { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto out; + } + + /* + * Check for attribute ACL + */ + if ( !acl_check_modlist( op, op->ora_e, op->orm_modlist )) { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to attribute"; + goto out; + } + + cfb = (CfBackInfo *)op->o_bd->be_private; + + /* add opattrs for syncprov */ + { + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof textbuf; + rs->sr_err = entry_schema_check(op, op->ora_e, NULL, 0, 1, NULL, + &rs->sr_text, textbuf, sizeof( textbuf ) ); + if ( rs->sr_err != LDAP_SUCCESS ) + goto out; + rs->sr_err = slap_add_opattrs( op, &rs->sr_text, textbuf, textlen, 1 ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(config_back_add) ": entry failed op attrs add: " + "%s (%d)\n", rs->sr_text, rs->sr_err, 0 ); + goto out; + } + } + + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto out; + } + ldap_pvt_thread_pool_pause( &connection_pool ); + + /* Strategy: + * 1) check for existence of entry + * 2) check for sibling renumbering + * 3) perform internal add + * 4) perform any necessary renumbering + * 5) store entry in underlying database + */ + rs->sr_err = config_add_internal( cfb, op->ora_e, &ca, rs, &renumber, op ); + if ( rs->sr_err != LDAP_SUCCESS ) { + rs->sr_text = ca.cr_msg; + goto out2; + } + + if ( renumber ) { + CfEntryInfo *ce = ca.ca_entry->e_private; + req_add_s addr = op->oq_add; + op->o_tag = LDAP_REQ_MODRDN; + rs->sr_err = config_rename_add( op, rs, ce, ca.valx, 0, 0, cfb->cb_use_ldif ); + op->o_tag = LDAP_REQ_ADD; + op->oq_add = addr; + if ( rs->sr_err != LDAP_SUCCESS ) { + goto out2; + } + } + + if ( cfb->cb_use_ldif ) { + BackendDB *be = op->o_bd; + slap_callback sc = { NULL, slap_null_cb, NULL, NULL }, *scp; + struct berval dn, ndn; + + op->o_bd = &cfb->cb_db; + + /* Save current rootdn; use the underlying DB's rootdn */ + dn = op->o_dn; + ndn = op->o_ndn; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + + scp = op->o_callback; + op->o_callback = ≻ + op->o_bd->be_add( op, rs ); + op->o_bd = be; + op->o_callback = scp; + op->o_dn = dn; + op->o_ndn = ndn; + } + +out2:; + ldap_pvt_thread_pool_resume( &connection_pool ); + +out:; + { int repl = op->o_dont_replicate; + if ( rs->sr_err == LDAP_COMPARE_TRUE ) { + rs->sr_text = NULL; /* Set after config_add_internal */ + rs->sr_err = LDAP_SUCCESS; + op->o_dont_replicate = 1; + } + send_ldap_result( op, rs ); + op->o_dont_replicate = repl; + } + slap_graduate_commit_csn( op ); + return rs->sr_err; +} + +typedef struct delrec { + struct delrec *next; + int nidx; + int idx[1]; +} delrec; + +static int +config_modify_add( ConfigTable *ct, ConfigArgs *ca, AttributeDescription *ad, + int i ) +{ + int rc; + + ca->valx = -1; + if (ad->ad_type->sat_flags & SLAP_AT_ORDERED && + ca->line[0] == '{' ) + { + char *ptr = strchr( ca->line + 1, '}' ); + if ( ptr ) { + char *next; + + ca->valx = strtol( ca->line + 1, &next, 0 ); + if ( next == ca->line + 1 || next[ 0 ] != '}' ) { + return LDAP_OTHER; + } + ca->line = ptr+1; + } + } + rc = config_parse_add( ct, ca, i ); + if ( rc ) { + rc = LDAP_OTHER; + } + return rc; +} + +static int +config_modify_internal( CfEntryInfo *ce, Operation *op, SlapReply *rs, + ConfigArgs *ca ) +{ + int rc = LDAP_UNWILLING_TO_PERFORM; + Modifications *ml; + Entry *e = ce->ce_entry; + Attribute *save_attrs = e->e_attrs, *oc_at, *s, *a; + ConfigTable *ct; + ConfigOCs **colst; + int i, nocs; + char *ptr; + delrec *dels = NULL, *deltail = NULL; + + oc_at = attr_find( e->e_attrs, slap_schema.si_ad_objectClass ); + if ( !oc_at ) return LDAP_OBJECT_CLASS_VIOLATION; + + for (ml = op->orm_modlist; ml; ml=ml->sml_next) { + if (ml->sml_desc == slap_schema.si_ad_objectClass) + return rc; + } + + colst = count_ocs( oc_at, &nocs ); + + /* make sure add/del flags are clear; should always be true */ + for ( s = save_attrs; s; s = s->a_next ) { + s->a_flags &= ~(SLAP_ATTR_IXADD|SLAP_ATTR_IXDEL); + } + + e->e_attrs = attrs_dup( e->e_attrs ); + + init_config_argv( ca ); + ca->be = ce->ce_be; + ca->bi = ce->ce_bi; + ca->ca_private = ce->ce_private; + ca->ca_entry = e; + ca->fname = "slapd"; + ca->ca_op = op; + strcpy( ca->log, "back-config" ); + + for (ml = op->orm_modlist; ml; ml=ml->sml_next) { + ct = config_find_table( colst, nocs, ml->sml_desc, ca ); + switch (ml->sml_op) { + case LDAP_MOD_DELETE: + case LDAP_MOD_REPLACE: + case SLAP_MOD_SOFTDEL: + { + BerVarray vals = NULL, nvals = NULL; + int *idx = NULL; + if ( ct && ( ct->arg_type & ARG_NO_DELETE )) { + rc = LDAP_OTHER; + snprintf(ca->cr_msg, sizeof(ca->cr_msg), "cannot delete %s", + ml->sml_desc->ad_cname.bv_val ); + goto out_noop; + } + if ( ml->sml_op == LDAP_MOD_REPLACE ) { + vals = ml->sml_values; + nvals = ml->sml_nvalues; + ml->sml_values = NULL; + ml->sml_nvalues = NULL; + } + /* If we're deleting by values, remember the indexes of the + * values we deleted. + */ + if ( ct && ml->sml_values ) { + delrec *d; + i = ml->sml_numvals; + d = ch_malloc( sizeof(delrec) + (i - 1)* sizeof(int)); + d->nidx = i; + d->next = NULL; + if ( dels ) { + deltail->next = d; + } else { + dels = d; + } + deltail = d; + idx = d->idx; + } + rc = modify_delete_vindex(e, &ml->sml_mod, + get_permissiveModify(op), + &rs->sr_text, ca->cr_msg, sizeof(ca->cr_msg), idx ); + if ( ml->sml_op == LDAP_MOD_REPLACE ) { + ml->sml_values = vals; + ml->sml_nvalues = nvals; + } + if ( rc == LDAP_NO_SUCH_ATTRIBUTE && ml->sml_op == SLAP_MOD_SOFTDEL ) + { + rc = LDAP_SUCCESS; + } + /* FIXME: check rc before fallthru? */ + if ( !vals ) + break; + } + /* FALLTHRU: LDAP_MOD_REPLACE && vals */ + + case SLAP_MOD_ADD_IF_NOT_PRESENT: + if ( ml->sml_op == SLAP_MOD_ADD_IF_NOT_PRESENT + && attr_find( e->e_attrs, ml->sml_desc ) ) + { + rc = LDAP_SUCCESS; + break; + } + + case LDAP_MOD_ADD: + case SLAP_MOD_SOFTADD: { + int mop = ml->sml_op; + int navals = -1; + ml->sml_op = LDAP_MOD_ADD; + if ( ct ) { + if ( ct->arg_type & ARG_NO_INSERT ) { + Attribute *a = attr_find( e->e_attrs, ml->sml_desc ); + if ( a ) { + navals = a->a_numvals; + } + } + for ( i=0; !BER_BVISNULL( &ml->sml_values[i] ); i++ ) { + if ( ml->sml_values[i].bv_val[0] == '{' && + navals >= 0 ) + { + char *next, *val = ml->sml_values[i].bv_val + 1; + int j; + + j = strtol( val, &next, 0 ); + if ( next == val || next[ 0 ] != '}' || j < navals ) { + rc = LDAP_OTHER; + snprintf(ca->cr_msg, sizeof(ca->cr_msg), "cannot insert %s", + ml->sml_desc->ad_cname.bv_val ); + goto out_noop; + } + } + rc = check_vals( ct, ca, ml, 0 ); + if ( rc ) goto out_noop; + } + } + rc = modify_add_values(e, &ml->sml_mod, + get_permissiveModify(op), + &rs->sr_text, ca->cr_msg, sizeof(ca->cr_msg) ); + + /* If value already exists, show success here + * and ignore this operation down below. + */ + if ( mop == SLAP_MOD_SOFTADD ) { + if ( rc == LDAP_TYPE_OR_VALUE_EXISTS ) + rc = LDAP_SUCCESS; + else + mop = LDAP_MOD_ADD; + } + ml->sml_op = mop; + break; + } + + break; + case LDAP_MOD_INCREMENT: /* FIXME */ + break; + default: + break; + } + if(rc != LDAP_SUCCESS) break; + } + + if ( rc == LDAP_SUCCESS) { + /* check that the entry still obeys the schema */ + rc = entry_schema_check(op, e, NULL, 0, 0, NULL, + &rs->sr_text, ca->cr_msg, sizeof(ca->cr_msg) ); + } + if ( rc ) goto out_noop; + + /* Basic syntax checks are OK. Do the actual settings. */ + for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) { + ct = config_find_table( colst, nocs, ml->sml_desc, ca ); + if ( !ct ) continue; + + s = attr_find( save_attrs, ml->sml_desc ); + a = attr_find( e->e_attrs, ml->sml_desc ); + + switch (ml->sml_op) { + case LDAP_MOD_DELETE: + case LDAP_MOD_REPLACE: { + BerVarray vals = NULL, nvals = NULL; + delrec *d = NULL; + + if ( ml->sml_op == LDAP_MOD_REPLACE ) { + vals = ml->sml_values; + nvals = ml->sml_nvalues; + ml->sml_values = NULL; + ml->sml_nvalues = NULL; + } + + if ( ml->sml_values ) + d = dels; + + /* If we didn't delete the whole attribute */ + if ( ml->sml_values && a ) { + struct berval *mvals; + int j; + + if ( ml->sml_nvalues ) + mvals = ml->sml_nvalues; + else + mvals = ml->sml_values; + + /* use the indexes we saved up above */ + for (i=0; i < d->nidx; i++) { + struct berval bv = *mvals++; + if ( a->a_desc->ad_type->sat_flags & SLAP_AT_ORDERED && + bv.bv_val[0] == '{' ) { + ptr = strchr( bv.bv_val, '}' ) + 1; + bv.bv_len -= ptr - bv.bv_val; + bv.bv_val = ptr; + } + ca->line = bv.bv_val; + ca->valx = d->idx[i]; + config_parse_vals(ct, ca, d->idx[i] ); + rc = config_del_vals( ct, ca ); + if ( rc != LDAP_SUCCESS ) break; + if ( s ) + s->a_flags |= SLAP_ATTR_IXDEL; + for (j=i+1; j < d->nidx; j++) + if ( d->idx[j] >d->idx[i] ) + d->idx[j]--; + } + } else { + ca->valx = -1; + ca->line = NULL; + ca->argc = 1; + rc = config_del_vals( ct, ca ); + if ( rc ) rc = LDAP_OTHER; + if ( s ) + s->a_flags |= SLAP_ATTR_IXDEL; + } + if ( ml->sml_values ) { + d = d->next; + ch_free( dels ); + dels = d; + } + if ( ml->sml_op == LDAP_MOD_REPLACE ) { + ml->sml_values = vals; + ml->sml_nvalues = nvals; + } + if ( !vals || rc != LDAP_SUCCESS ) + break; + } + /* FALLTHRU: LDAP_MOD_REPLACE && vals */ + + case LDAP_MOD_ADD: + if ( !a ) + break; + for (i=0; ml->sml_values[i].bv_val; i++) { + ca->line = ml->sml_values[i].bv_val; + ca->valx = -1; + rc = config_modify_add( ct, ca, ml->sml_desc, i ); + if ( rc ) + goto out; + a->a_flags |= SLAP_ATTR_IXADD; + } + break; + } + } + +out: + /* Undo for a failed operation */ + if ( rc != LDAP_SUCCESS ) { + ConfigReply msg = ca->reply; + for ( s = save_attrs; s; s = s->a_next ) { + if ( s->a_flags & SLAP_ATTR_IXDEL ) { + s->a_flags &= ~(SLAP_ATTR_IXDEL|SLAP_ATTR_IXADD); + ct = config_find_table( colst, nocs, s->a_desc, ca ); + a = attr_find( e->e_attrs, s->a_desc ); + if ( a ) { + /* clear the flag so the add check below will skip it */ + a->a_flags &= ~(SLAP_ATTR_IXDEL|SLAP_ATTR_IXADD); + ca->valx = -1; + ca->line = NULL; + ca->argc = 1; + config_del_vals( ct, ca ); + } + for ( i=0; !BER_BVISNULL( &s->a_vals[i] ); i++ ) { + ca->line = s->a_vals[i].bv_val; + ca->valx = -1; + config_modify_add( ct, ca, s->a_desc, i ); + } + } + } + for ( a = e->e_attrs; a; a = a->a_next ) { + if ( a->a_flags & SLAP_ATTR_IXADD ) { + ct = config_find_table( colst, nocs, a->a_desc, ca ); + ca->valx = -1; + ca->line = NULL; + ca->argc = 1; + config_del_vals( ct, ca ); + s = attr_find( save_attrs, a->a_desc ); + if ( s ) { + s->a_flags &= ~(SLAP_ATTR_IXDEL|SLAP_ATTR_IXADD); + for ( i=0; !BER_BVISNULL( &s->a_vals[i] ); i++ ) { + ca->line = s->a_vals[i].bv_val; + ca->valx = -1; + config_modify_add( ct, ca, s->a_desc, i ); + } + } + } + } + ca->reply = msg; + } + + if ( ca->cleanup ) { + i = ca->cleanup( ca ); + if (rc == LDAP_SUCCESS) + rc = i; + } +out_noop: + if ( rc == LDAP_SUCCESS ) { + attrs_free( save_attrs ); + rs->sr_text = NULL; + } else { + attrs_free( e->e_attrs ); + e->e_attrs = save_attrs; + } + ch_free( ca->argv ); + if ( colst ) ch_free( colst ); + while( dels ) { + deltail = dels->next; + ch_free( dels ); + dels = deltail; + } + + return rc; +} + +static int +config_back_modify( Operation *op, SlapReply *rs ) +{ + CfBackInfo *cfb; + CfEntryInfo *ce, *last; + Modifications *ml; + ConfigArgs ca = {0}; + struct berval rdn; + char *ptr; + AttributeDescription *rad = NULL; + int do_pause = 1; + + cfb = (CfBackInfo *)op->o_bd->be_private; + + ce = config_find_base( cfb->cb_root, &op->o_req_ndn, &last ); + if ( !ce ) { + if ( last ) + rs->sr_matched = last->ce_entry->e_name.bv_val; + rs->sr_err = LDAP_NO_SUCH_OBJECT; + goto out; + } + + if ( !acl_check_modlist( op, ce->ce_entry, op->orm_modlist )) { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto out; + } + + /* Get type of RDN */ + rdn = ce->ce_entry->e_nname; + ptr = strchr( rdn.bv_val, '=' ); + rdn.bv_len = ptr - rdn.bv_val; + rs->sr_err = slap_bv2ad( &rdn, &rad, &rs->sr_text ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto out; + } + + /* Some basic validation... */ + for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) { + /* Don't allow Modify of RDN; must use ModRdn for that. */ + if ( ml->sml_desc == rad ) { + rs->sr_err = LDAP_NOT_ALLOWED_ON_RDN; + rs->sr_text = "Use modrdn to change the entry name"; + goto out; + } + /* Internal update of contextCSN? */ + if ( ml->sml_desc == slap_schema.si_ad_contextCSN && op->o_conn->c_conn_idx == -1 ) { + do_pause = 0; + break; + } + } + + slap_mods_opattrs( op, &op->orm_modlist, 1 ); + + if ( do_pause ) { + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto out; + } + ldap_pvt_thread_pool_pause( &connection_pool ); + } + + /* Strategy: + * 1) perform the Modify on the cached Entry. + * 2) verify that the Entry still satisfies the schema. + * 3) perform the individual config operations. + * 4) store Modified entry in underlying LDIF backend. + */ + rs->sr_err = config_modify_internal( ce, op, rs, &ca ); + if ( rs->sr_err ) { + rs->sr_text = ca.cr_msg; + } else if ( cfb->cb_use_ldif ) { + BackendDB *be = op->o_bd; + slap_callback sc = { NULL, slap_null_cb, NULL, NULL }, *scp; + struct berval dn, ndn; + + op->o_bd = &cfb->cb_db; + + dn = op->o_dn; + ndn = op->o_ndn; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + + scp = op->o_callback; + op->o_callback = ≻ + op->o_bd->be_modify( op, rs ); + op->o_bd = be; + op->o_callback = scp; + op->o_dn = dn; + op->o_ndn = ndn; + } + + if ( do_pause ) + ldap_pvt_thread_pool_resume( &connection_pool ); +out: + send_ldap_result( op, rs ); + slap_graduate_commit_csn( op ); + return rs->sr_err; +} + +static int +config_back_modrdn( Operation *op, SlapReply *rs ) +{ + CfBackInfo *cfb; + CfEntryInfo *ce, *last; + struct berval rdn; + int ixold, ixnew; + + cfb = (CfBackInfo *)op->o_bd->be_private; + + ce = config_find_base( cfb->cb_root, &op->o_req_ndn, &last ); + if ( !ce ) { + if ( last ) + rs->sr_matched = last->ce_entry->e_name.bv_val; + rs->sr_err = LDAP_NO_SUCH_OBJECT; + goto out; + } + if ( !access_allowed( op, ce->ce_entry, slap_schema.si_ad_entry, + NULL, ACL_WRITE, NULL )) { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto out; + } + { Entry *parent; + if ( ce->ce_parent ) + parent = ce->ce_parent->ce_entry; + else + parent = (Entry *)&slap_entry_root; + if ( !access_allowed( op, parent, slap_schema.si_ad_children, + NULL, ACL_WRITE, NULL )) { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto out; + } + } + + /* We don't allow moving objects to new parents. + * Generally we only allow reordering a set of ordered entries. + */ + if ( op->orr_newSup ) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + goto out; + } + + /* If newRDN == oldRDN, quietly succeed */ + dnRdn( &op->o_req_ndn, &rdn ); + if ( dn_match( &rdn, &op->orr_nnewrdn )) { + rs->sr_err = LDAP_SUCCESS; + goto out; + } + + /* Current behavior, subject to change as needed: + * + * For backends and overlays, we only allow renumbering. + * For schema, we allow renaming with the same number. + * Otherwise, the op is not allowed. + */ + + if ( ce->ce_type == Cft_Schema ) { + char *ptr1, *ptr2; + int len; + + /* Can't alter the main cn=schema entry */ + if ( ce->ce_parent->ce_type == Cft_Global ) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "renaming not allowed for this entry"; + goto out; + } + + /* We could support this later if desired */ + ptr1 = ber_bvchr( &rdn, '}' ); + ptr2 = ber_bvchr( &op->orr_newrdn, '}' ); + len = ptr1 - rdn.bv_val; + if ( len != ptr2 - op->orr_newrdn.bv_val || + strncmp( rdn.bv_val, op->orr_newrdn.bv_val, len )) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "schema reordering not supported"; + goto out; + } + } else if ( ce->ce_type == Cft_Database || + ce->ce_type == Cft_Overlay ) { + char *ptr1, *ptr2, *iptr1, *iptr2; + int len1, len2; + + iptr2 = ber_bvchr( &op->orr_newrdn, '=' ) + 1; + if ( *iptr2 != '{' ) { + rs->sr_err = LDAP_NAMING_VIOLATION; + rs->sr_text = "new ordering index is required"; + goto out; + } + iptr2++; + iptr1 = ber_bvchr( &rdn, '{' ) + 1; + ptr1 = ber_bvchr( &rdn, '}' ); + ptr2 = ber_bvchr( &op->orr_newrdn, '}' ); + if ( !ptr2 ) { + rs->sr_err = LDAP_NAMING_VIOLATION; + rs->sr_text = "new ordering index is required"; + goto out; + } + + len1 = ptr1 - rdn.bv_val; + len2 = ptr2 - op->orr_newrdn.bv_val; + + if ( rdn.bv_len - len1 != op->orr_newrdn.bv_len - len2 || + strncmp( ptr1, ptr2, rdn.bv_len - len1 )) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "changing database/overlay type not allowed"; + goto out; + } + ixold = strtol( iptr1, NULL, 0 ); + ixnew = strtol( iptr2, &ptr1, 0 ); + if ( ptr1 != ptr2 || ixold < 0 || ixnew < 0 ) { + rs->sr_err = LDAP_NAMING_VIOLATION; + goto out; + } + /* config DB is always 0, cannot be changed */ + if ( ce->ce_type == Cft_Database && ( ixold == 0 || ixnew == 0 )) { + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + goto out; + } + } else { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "renaming not supported for this entry"; + goto out; + } + + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto out; + } + ldap_pvt_thread_pool_pause( &connection_pool ); + + if ( ce->ce_type == Cft_Schema ) { + req_modrdn_s modr = op->oq_modrdn; + struct berval rdn; + Attribute *a; + rs->sr_err = config_rename_attr( rs, ce->ce_entry, &rdn, &a ); + if ( rs->sr_err == LDAP_SUCCESS ) { + rs->sr_err = config_rename_one( op, rs, ce->ce_entry, + ce->ce_parent, a, &op->orr_newrdn, &op->orr_nnewrdn, + cfb->cb_use_ldif ); + } + op->oq_modrdn = modr; + } else { + CfEntryInfo *ce2, *cebase, **cprev, **cbprev, *ceold; + req_modrdn_s modr = op->oq_modrdn; + int i; + + /* Advance to first of this type */ + cprev = &ce->ce_parent->ce_kids; + for ( ce2 = *cprev; ce2 && ce2->ce_type != ce->ce_type; ) { + cprev = &ce2->ce_sibs; + ce2 = ce2->ce_sibs; + } + /* Skip the -1 entry */ + if ( ce->ce_type == Cft_Database ) { + cprev = &ce2->ce_sibs; + ce2 = ce2->ce_sibs; + } + cebase = ce2; + cbprev = cprev; + + /* Remove from old slot */ + for ( ce2 = *cprev; ce2 && ce2 != ce; ce2 = ce2->ce_sibs ) + cprev = &ce2->ce_sibs; + *cprev = ce->ce_sibs; + ceold = ce->ce_sibs; + + /* Insert into new slot */ + cprev = cbprev; + for ( i=0; i<ixnew; i++ ) { + ce2 = *cprev; + if ( !ce2 ) + break; + cprev = &ce2->ce_sibs; + } + ce->ce_sibs = *cprev; + *cprev = ce; + + ixnew = i; + + /* NOTE: These should be encoded in the OC tables, not inline here */ + if ( ce->ce_type == Cft_Database ) + backend_db_move( ce->ce_be, ixnew ); + else if ( ce->ce_type == Cft_Overlay ) + overlay_move( ce->ce_be, (slap_overinst *)ce->ce_bi, ixnew ); + + if ( ixold < ixnew ) { + rs->sr_err = config_rename_del( op, rs, ce, ceold, ixold, + cfb->cb_use_ldif ); + } else { + rs->sr_err = config_rename_add( op, rs, ce, ixnew, 1, + ixold - ixnew, cfb->cb_use_ldif ); + } + op->oq_modrdn = modr; + } + + ldap_pvt_thread_pool_resume( &connection_pool ); +out: + send_ldap_result( op, rs ); + return rs->sr_err; +} + +static int +config_back_delete( Operation *op, SlapReply *rs ) +{ +#ifdef SLAP_CONFIG_DELETE + CfBackInfo *cfb; + CfEntryInfo *ce, *last, *ce2; + + cfb = (CfBackInfo *)op->o_bd->be_private; + + ce = config_find_base( cfb->cb_root, &op->o_req_ndn, &last ); + if ( !ce ) { + if ( last ) + rs->sr_matched = last->ce_entry->e_name.bv_val; + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } else if ( ce->ce_kids ) { + rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF; + } else if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + } else if ( ce->ce_type == Cft_Overlay || + ce->ce_type == Cft_Database || + ce->ce_type == Cft_Misc ){ + char *iptr; + int count, ixold; + + ldap_pvt_thread_pool_pause( &connection_pool ); + + if ( ce->ce_type == Cft_Overlay ){ + overlay_remove( ce->ce_be, (slap_overinst *)ce->ce_bi, op ); + } else if ( ce->ce_type == Cft_Misc ) { + /* + * only Cft_Misc objects that have a co_lddel handler set in + * the ConfigOCs struct can be deleted. This code also + * assumes that the entry can be only have one objectclass + * with co_type == Cft_Misc + */ + ConfigOCs co, *coptr; + Attribute *oc_at; + int i; + + oc_at = attr_find( ce->ce_entry->e_attrs, + slap_schema.si_ad_objectClass ); + if ( !oc_at ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "objectclass not found"; + ldap_pvt_thread_pool_resume( &connection_pool ); + goto out; + } + for ( i=0; !BER_BVISNULL(&oc_at->a_nvals[i]); i++ ) { + co.co_name = &oc_at->a_nvals[i]; + coptr = avl_find( CfOcTree, &co, CfOc_cmp ); + if ( coptr == NULL || coptr->co_type != Cft_Misc ) { + continue; + } + if ( ! coptr->co_lddel || coptr->co_lddel( ce, op ) ){ + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + if ( ! coptr->co_lddel ) { + rs->sr_text = "No delete handler found"; + } else { + rs->sr_err = LDAP_OTHER; + /* FIXME: We should return a helpful error message + * here */ + } + ldap_pvt_thread_pool_resume( &connection_pool ); + goto out; + } + break; + } + } else if (ce->ce_type == Cft_Database ) { + if ( ce->ce_be == frontendDB || ce->ce_be == op->o_bd ){ + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "Cannot delete config or frontend database"; + ldap_pvt_thread_pool_resume( &connection_pool ); + goto out; + } + if ( ce->ce_be->bd_info->bi_db_close ) { + ce->ce_be->bd_info->bi_db_close( ce->ce_be, NULL ); + } + backend_destroy_one( ce->ce_be, 1); + } + + /* remove CfEntryInfo from the siblings list */ + if ( ce->ce_parent->ce_kids == ce ) { + ce->ce_parent->ce_kids = ce->ce_sibs; + } else { + for ( ce2 = ce->ce_parent->ce_kids ; ce2; ce2 = ce2->ce_sibs ) { + if ( ce2->ce_sibs == ce ) { + ce2->ce_sibs = ce->ce_sibs; + break; + } + } + } + + /* remove from underlying database */ + if ( cfb->cb_use_ldif ) { + BackendDB *be = op->o_bd; + slap_callback sc = { NULL, slap_null_cb, NULL, NULL }, *scp; + struct berval dn, ndn, req_dn, req_ndn; + + op->o_bd = &cfb->cb_db; + + dn = op->o_dn; + ndn = op->o_ndn; + req_dn = op->o_req_dn; + req_ndn = op->o_req_ndn; + + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + op->o_req_dn = ce->ce_entry->e_name; + op->o_req_ndn = ce->ce_entry->e_nname; + + scp = op->o_callback; + op->o_callback = ≻ + op->o_bd->be_delete( op, rs ); + op->o_bd = be; + op->o_callback = scp; + op->o_dn = dn; + op->o_ndn = ndn; + op->o_req_dn = req_dn; + op->o_req_ndn = req_ndn; + } + + /* renumber siblings */ + iptr = ber_bvchr( &op->o_req_ndn, '{' ) + 1; + ixold = strtol( iptr, NULL, 0 ); + for (ce2 = ce->ce_sibs, count=0; ce2; ce2=ce2->ce_sibs) { + config_renumber_one( op, rs, ce2->ce_parent, ce2->ce_entry, + count+ixold, 0, cfb->cb_use_ldif ); + count++; + } + + ce->ce_entry->e_private=NULL; + entry_free(ce->ce_entry); + ch_free(ce); + ldap_pvt_thread_pool_resume( &connection_pool ); + } else { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + } +out: +#else + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; +#endif /* SLAP_CONFIG_DELETE */ + send_ldap_result( op, rs ); + return rs->sr_err; +} + +static int +config_back_search( Operation *op, SlapReply *rs ) +{ + CfBackInfo *cfb; + CfEntryInfo *ce, *last; + slap_mask_t mask; + + cfb = (CfBackInfo *)op->o_bd->be_private; + + ce = config_find_base( cfb->cb_root, &op->o_req_ndn, &last ); + if ( !ce ) { + if ( last ) + rs->sr_matched = last->ce_entry->e_name.bv_val; + rs->sr_err = LDAP_NO_SUCH_OBJECT; + goto out; + } + if ( !access_allowed_mask( op, ce->ce_entry, slap_schema.si_ad_entry, NULL, + ACL_SEARCH, NULL, &mask )) + { + if ( !ACL_GRANT( mask, ACL_DISCLOSE )) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } else { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + } + goto out; + } + switch ( op->ors_scope ) { + case LDAP_SCOPE_BASE: + case LDAP_SCOPE_SUBTREE: + rs->sr_err = config_send( op, rs, ce, 0 ); + break; + + case LDAP_SCOPE_ONELEVEL: + for (ce = ce->ce_kids; ce; ce=ce->ce_sibs) { + rs->sr_err = config_send( op, rs, ce, 1 ); + if ( rs->sr_err ) { + break; + } + } + break; + } + +out: + send_ldap_result( op, rs ); + return rs->sr_err; +} + +/* no-op, we never free entries */ +int config_entry_release( + Operation *op, + Entry *e, + int rw ) +{ + if ( !e->e_private ) { + entry_free( e ); + } + return LDAP_SUCCESS; +} + +/* return LDAP_SUCCESS IFF we can retrieve the specified entry. + */ +int config_back_entry_get( + Operation *op, + struct berval *ndn, + ObjectClass *oc, + AttributeDescription *at, + int rw, + Entry **ent ) +{ + CfBackInfo *cfb; + CfEntryInfo *ce, *last; + int rc = LDAP_NO_SUCH_OBJECT; + + cfb = (CfBackInfo *)op->o_bd->be_private; + + ce = config_find_base( cfb->cb_root, ndn, &last ); + if ( ce ) { + *ent = ce->ce_entry; + if ( *ent ) { + rc = LDAP_SUCCESS; + if ( oc && !is_entry_objectclass_or_sub( *ent, oc ) ) { + rc = LDAP_NO_SUCH_ATTRIBUTE; + *ent = NULL; + } + } + } + + return rc; +} + +static int +config_build_attrs( Entry *e, AttributeType **at, AttributeDescription *ad, + ConfigTable *ct, ConfigArgs *c ) +{ + int i, rc; + + for (; at && *at; at++) { + /* Skip the naming attr */ + if ((*at)->sat_ad == ad || (*at)->sat_ad == slap_schema.si_ad_cn ) + continue; + for (i=0;ct[i].name;i++) { + if (ct[i].ad == (*at)->sat_ad) { + rc = config_get_vals(&ct[i], c); + /* NOTE: tolerate that config_get_vals() + * returns success with no values */ + if (rc == LDAP_SUCCESS && c->rvalue_vals != NULL ) { + if ( c->rvalue_nvals ) + rc = attr_merge(e, ct[i].ad, c->rvalue_vals, + c->rvalue_nvals); + else { + slap_syntax_validate_func *validate = + ct[i].ad->ad_type->sat_syntax->ssyn_validate; + if ( validate ) { + int j; + for ( j=0; c->rvalue_vals[j].bv_val; j++ ) { + rc = ordered_value_validate( ct[i].ad, + &c->rvalue_vals[j], LDAP_MOD_ADD ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "config_build_attrs: error %d on %s value #%d\n", + rc, ct[i].ad->ad_cname.bv_val, j ); + return rc; + } + } + } + + rc = attr_merge_normalize(e, ct[i].ad, + c->rvalue_vals, NULL); + } + ber_bvarray_free( c->rvalue_nvals ); + ber_bvarray_free( c->rvalue_vals ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "config_build_attrs: error %d on %s\n", + rc, ct[i].ad->ad_cname.bv_val, 0 ); + return rc; + } + } + break; + } + } + } + return 0; +} + +/* currently (2010) does not access rs except possibly writing rs->sr_err */ + +Entry * +config_build_entry( Operation *op, SlapReply *rs, CfEntryInfo *parent, + ConfigArgs *c, struct berval *rdn, ConfigOCs *main, ConfigOCs *extra ) +{ + Entry *e = entry_alloc(); + CfEntryInfo *ce = ch_calloc( 1, sizeof(CfEntryInfo) ); + struct berval val; + struct berval ad_name; + AttributeDescription *ad = NULL; + int cnt, rc; + char *ptr; + const char *text = ""; + Attribute *oc_at; + struct berval pdn; + ObjectClass *oc; + CfEntryInfo *ceprev = NULL; + LDAPRDN rDN; + + Debug( LDAP_DEBUG_TRACE, "config_build_entry: \"%s\"\n", rdn->bv_val, 0, 0); + e->e_private = ce; + ce->ce_entry = e; + ce->ce_type = main->co_type; + ce->ce_parent = parent; + if ( parent ) { + pdn = parent->ce_entry->e_nname; + if ( parent->ce_kids && parent->ce_kids->ce_type <= ce->ce_type ) + for ( ceprev = parent->ce_kids; ceprev->ce_sibs && + ceprev->ce_type <= ce->ce_type; + ceprev = ceprev->ce_sibs ); + } else { + BER_BVZERO( &pdn ); + } + + ce->ce_private = c->ca_private; + ce->ce_be = c->be; + ce->ce_bi = c->bi; + + build_new_dn( &e->e_name, &pdn, rdn, NULL ); + ber_dupbv( &e->e_nname, &e->e_name ); + + attr_merge_normalize_one(e, slap_schema.si_ad_objectClass, + main->co_name, NULL ); + if ( extra ) + attr_merge_normalize_one(e, slap_schema.si_ad_objectClass, + extra->co_name, NULL ); + + rc = ldap_bv2rdn( rdn, &rDN, &text, LDAP_DN_FORMAT_LDAP ); + if ( rc ) { + goto fail; + } + for ( cnt = 0; rDN[cnt]; cnt++ ) { + LDAPAVA *ava = rDN[cnt]; + + ad = NULL; + rc = slap_bv2ad( &ava->la_attr, &ad, &text ); + if ( rc ) { + break; + } + if ( !ad->ad_type->sat_equality ) { + rc = LDAP_CONSTRAINT_VIOLATION; + text = "attribute has no equality matching rule"; + break; + } + if ( !ad->ad_type->sat_equality->smr_match ) { + rc = LDAP_CONSTRAINT_VIOLATION; + text = "attribute has unsupported equality matching rule"; + break; + } + attr_merge_normalize_one(e, ad, &ava->la_value, NULL ); + } + ldap_rdnfree( rDN ); + if ( rc ) { + goto fail; + } + + oc = main->co_oc; + c->table = main->co_type; + if ( oc->soc_required ) { + rc = config_build_attrs( e, oc->soc_required, ad, main->co_table, c ); + if ( rc ) goto fail; + } + + if ( oc->soc_allowed ) { + rc = config_build_attrs( e, oc->soc_allowed, ad, main->co_table, c ); + if ( rc ) goto fail; + } + + if ( extra ) { + oc = extra->co_oc; + c->table = extra->co_type; + if ( oc->soc_required ) { + rc = config_build_attrs( e, oc->soc_required, ad, extra->co_table, c ); + if ( rc ) goto fail; + } + + if ( oc->soc_allowed ) { + rc = config_build_attrs( e, oc->soc_allowed, ad, extra->co_table, c ); + if ( rc ) goto fail; + } + } + + oc_at = attr_find( e->e_attrs, slap_schema.si_ad_objectClass ); + rc = structural_class(oc_at->a_vals, &oc, NULL, &text, c->cr_msg, + sizeof(c->cr_msg), op ? op->o_tmpmemctx : NULL ); + if ( rc != LDAP_SUCCESS ) { +fail: + Debug( LDAP_DEBUG_ANY, + "config_build_entry: build \"%s\" failed: \"%s\"\n", + rdn->bv_val, text, 0); + return NULL; + } + attr_merge_normalize_one(e, slap_schema.si_ad_structuralObjectClass, &oc->soc_cname, NULL ); + if ( op ) { + op->ora_e = e; + op->ora_modlist = NULL; + slap_add_opattrs( op, NULL, NULL, 0, 0 ); + if ( !op->o_noop ) { + SlapReply rs2 = {REP_RESULT}; + op->o_bd->be_add( op, &rs2 ); + rs->sr_err = rs2.sr_err; + rs_assert_done( &rs2 ); + if ( ( rs2.sr_err != LDAP_SUCCESS ) + && (rs2.sr_err != LDAP_ALREADY_EXISTS) ) { + goto fail; + } + } + } + if ( ceprev ) { + ce->ce_sibs = ceprev->ce_sibs; + ceprev->ce_sibs = ce; + } else if ( parent ) { + ce->ce_sibs = parent->ce_kids; + parent->ce_kids = ce; + } + + return e; +} + +static int +config_build_schema_inc( ConfigArgs *c, CfEntryInfo *ceparent, + Operation *op, SlapReply *rs ) +{ + Entry *e; + ConfigFile *cf = c->ca_private; + char *ptr; + struct berval bv, rdn; + + for (; cf; cf=cf->c_sibs, c->depth++) { + if ( !cf->c_at_head && !cf->c_cr_head && !cf->c_oc_head && + !cf->c_om_head && !cf->c_syn_head && !cf->c_kids ) continue; + c->value_dn.bv_val = c->log; + LUTIL_SLASHPATH( cf->c_file.bv_val ); + bv.bv_val = strrchr(cf->c_file.bv_val, LDAP_DIRSEP[0]); + if ( !bv.bv_val ) { + bv = cf->c_file; + } else { + bv.bv_val++; + bv.bv_len = cf->c_file.bv_len - (bv.bv_val - cf->c_file.bv_val); + } + ptr = strchr( bv.bv_val, '.' ); + if ( ptr ) + bv.bv_len = ptr - bv.bv_val; + c->value_dn.bv_len = snprintf(c->value_dn.bv_val, sizeof( c->log ), "cn=" SLAP_X_ORDERED_FMT, c->depth); + if ( c->value_dn.bv_len >= sizeof( c->log ) ) { + /* FIXME: how can indicate error? */ + return -1; + } + strncpy( c->value_dn.bv_val + c->value_dn.bv_len, bv.bv_val, + bv.bv_len ); + c->value_dn.bv_len += bv.bv_len; + c->value_dn.bv_val[c->value_dn.bv_len] ='\0'; + if ( rdnNormalize( 0, NULL, NULL, &c->value_dn, &rdn, NULL )) { + Debug( LDAP_DEBUG_ANY, + "config_build_schema_inc: invalid schema name \"%s\"\n", + bv.bv_val, 0, 0 ); + return -1; + } + + c->ca_private = cf; + e = config_build_entry( op, rs, ceparent, c, &rdn, + &CFOC_SCHEMA, NULL ); + ch_free( rdn.bv_val ); + if ( !e ) { + return -1; + } else if ( e && cf->c_kids ) { + c->ca_private = cf->c_kids; + config_build_schema_inc( c, e->e_private, op, rs ); + } + } + return 0; +} + +#ifdef SLAPD_MODULES + +static int +config_build_modules( ConfigArgs *c, CfEntryInfo *ceparent, + Operation *op, SlapReply *rs ) +{ + int i; + ModPaths *mp; + + for (i=0, mp=&modpaths; mp; mp=mp->mp_next, i++) { + if ( BER_BVISNULL( &mp->mp_path ) && !mp->mp_loads ) + continue; + c->value_dn.bv_val = c->log; + c->value_dn.bv_len = snprintf(c->value_dn.bv_val, sizeof( c->log ), "cn=module" SLAP_X_ORDERED_FMT, i); + if ( c->value_dn.bv_len >= sizeof( c->log ) ) { + /* FIXME: how can indicate error? */ + return -1; + } + c->ca_private = mp; + if ( ! config_build_entry( op, rs, ceparent, c, &c->value_dn, &CFOC_MODULE, NULL )) { + return -1; + } + } + return 0; +} +#endif + +static int +config_check_schema(Operation *op, CfBackInfo *cfb) +{ + struct berval schema_dn = BER_BVC(SCHEMA_RDN "," CONFIG_RDN); + ConfigArgs c = {0}; + CfEntryInfo *ce, *last; + Entry *e; + + /* If there's no root entry, we must be in the midst of converting */ + if ( !cfb->cb_root ) + return 0; + + /* Make sure the main schema entry exists */ + ce = config_find_base( cfb->cb_root, &schema_dn, &last ); + if ( ce ) { + Attribute *a; + struct berval *bv; + + e = ce->ce_entry; + + /* Make sure it's up to date */ + if ( cf_om_tail != om_sys_tail ) { + a = attr_find( e->e_attrs, cfAd_om ); + if ( a ) { + if ( a->a_nvals != a->a_vals ) + ber_bvarray_free( a->a_nvals ); + ber_bvarray_free( a->a_vals ); + a->a_vals = NULL; + a->a_nvals = NULL; + a->a_numvals = 0; + } + oidm_unparse( &bv, NULL, NULL, 1 ); + attr_merge_normalize( e, cfAd_om, bv, NULL ); + ber_bvarray_free( bv ); + cf_om_tail = om_sys_tail; + } + if ( cf_at_tail != at_sys_tail ) { + a = attr_find( e->e_attrs, cfAd_attr ); + if ( a ) { + if ( a->a_nvals != a->a_vals ) + ber_bvarray_free( a->a_nvals ); + ber_bvarray_free( a->a_vals ); + a->a_vals = NULL; + a->a_nvals = NULL; + a->a_numvals = 0; + } + at_unparse( &bv, NULL, NULL, 1 ); + attr_merge_normalize( e, cfAd_attr, bv, NULL ); + ber_bvarray_free( bv ); + cf_at_tail = at_sys_tail; + } + if ( cf_oc_tail != oc_sys_tail ) { + a = attr_find( e->e_attrs, cfAd_oc ); + if ( a ) { + if ( a->a_nvals != a->a_vals ) + ber_bvarray_free( a->a_nvals ); + ber_bvarray_free( a->a_vals ); + a->a_vals = NULL; + a->a_nvals = NULL; + a->a_numvals = 0; + } + oc_unparse( &bv, NULL, NULL, 1 ); + attr_merge_normalize( e, cfAd_oc, bv, NULL ); + ber_bvarray_free( bv ); + cf_oc_tail = oc_sys_tail; + } + if ( cf_syn_tail != syn_sys_tail ) { + a = attr_find( e->e_attrs, cfAd_syntax ); + if ( a ) { + if ( a->a_nvals != a->a_vals ) + ber_bvarray_free( a->a_nvals ); + ber_bvarray_free( a->a_vals ); + a->a_vals = NULL; + a->a_nvals = NULL; + a->a_numvals = 0; + } + syn_unparse( &bv, NULL, NULL, 1 ); + attr_merge_normalize( e, cfAd_syntax, bv, NULL ); + ber_bvarray_free( bv ); + cf_syn_tail = syn_sys_tail; + } + } else { + SlapReply rs = {REP_RESULT}; + c.ca_private = NULL; + e = config_build_entry( op, &rs, cfb->cb_root, &c, &schema_rdn, + &CFOC_SCHEMA, NULL ); + if ( !e ) { + return -1; + } + ce = e->e_private; + ce->ce_private = cfb->cb_config; + cf_at_tail = at_sys_tail; + cf_oc_tail = oc_sys_tail; + cf_om_tail = om_sys_tail; + cf_syn_tail = syn_sys_tail; + } + return 0; +} + +static const char *defacl[] = { + NULL, "to", "*", "by", "*", "none", NULL +}; + +static int +config_back_db_open( BackendDB *be, ConfigReply *cr ) +{ + CfBackInfo *cfb = be->be_private; + struct berval rdn; + Entry *e, *parent; + CfEntryInfo *ce, *ceparent; + int i, unsupp = 0; + BackendInfo *bi; + ConfigArgs c; + Connection conn = {0}; + OperationBuffer opbuf; + Operation *op; + slap_callback cb = { NULL, slap_null_cb, NULL, NULL }; + SlapReply rs = {REP_RESULT}; + void *thrctx = NULL; + AccessControl *save_access; + + Debug( LDAP_DEBUG_TRACE, "config_back_db_open\n", 0, 0, 0); + + /* If we have no explicitly configured ACLs, don't just use + * the global ACLs. Explicitly deny access to everything. + */ + save_access = be->bd_self->be_acl; + be->bd_self->be_acl = NULL; + parse_acl(be->bd_self, "config_back_db_open", 0, 6, (char **)defacl, 0 ); + defacl_parsed = be->bd_self->be_acl; + if ( save_access ) { + be->bd_self->be_acl = save_access; + } else { + Debug( LDAP_DEBUG_CONFIG, "config_back_db_open: " + "No explicit ACL for back-config configured. " + "Using hardcoded default\n", 0, 0, 0 ); + } + + thrctx = ldap_pvt_thread_pool_context(); + connection_fake_init( &conn, &opbuf, thrctx ); + op = &opbuf.ob_op; + + op->o_tag = LDAP_REQ_ADD; + op->o_callback = &cb; + op->o_bd = &cfb->cb_db; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + + if ( !cfb->cb_use_ldif ) { + op->o_noop = 1; + } + + /* If we read the config from back-ldif, do some quick sanity checks */ + if ( cfb->cb_got_ldif ) { + return config_check_schema( op, cfb ); + } + + /* create root of tree */ + rdn = config_rdn; + c.ca_private = cfb->cb_config; + c.be = frontendDB; + e = config_build_entry( op, &rs, NULL, &c, &rdn, &CFOC_GLOBAL, NULL ); + if ( !e ) { + return -1; + } + ce = e->e_private; + cfb->cb_root = ce; + + parent = e; + ceparent = ce; + +#ifdef SLAPD_MODULES + /* Create Module nodes... */ + if ( modpaths.mp_loads ) { + if ( config_build_modules( &c, ceparent, op, &rs ) ){ + return -1; + } + } +#endif + + /* Create schema nodes... cn=schema will contain the hardcoded core + * schema, read-only. Child objects will contain runtime loaded schema + * files. + */ + rdn = schema_rdn; + c.ca_private = NULL; + e = config_build_entry( op, &rs, ceparent, &c, &rdn, &CFOC_SCHEMA, NULL ); + if ( !e ) { + return -1; + } + ce = e->e_private; + ce->ce_private = cfb->cb_config; + cf_at_tail = at_sys_tail; + cf_oc_tail = oc_sys_tail; + cf_om_tail = om_sys_tail; + cf_syn_tail = syn_sys_tail; + + /* Create schema nodes for included schema... */ + if ( cfb->cb_config->c_kids ) { + int rc; + c.depth = 0; + c.ca_private = cfb->cb_config->c_kids; + rc = config_build_schema_inc( &c, ce, op, &rs ); + if ( rc ) { + return -1; + } + } + + /* Create backend nodes. Skip if they don't provide a cf_table. + * There usually aren't any of these. + */ + + c.line = 0; + LDAP_STAILQ_FOREACH( bi, &backendInfo, bi_next) { + if (!bi->bi_cf_ocs) { + /* If it only supports the old config mech, complain. */ + if ( bi->bi_config ) { + Debug( LDAP_DEBUG_ANY, + "WARNING: No dynamic config support for backend %s.\n", + bi->bi_type, 0, 0 ); + unsupp++; + } + continue; + } + if (!bi->bi_private) continue; + + rdn.bv_val = c.log; + rdn.bv_len = snprintf(rdn.bv_val, sizeof( c.log ), + "%s=%s", cfAd_backend->ad_cname.bv_val, bi->bi_type); + if ( rdn.bv_len >= sizeof( c.log ) ) { + /* FIXME: holler ... */ ; + } + c.bi = bi; + e = config_build_entry( op, &rs, ceparent, &c, &rdn, &CFOC_BACKEND, + bi->bi_cf_ocs ); + if ( !e ) { + return -1; + } + } + + /* Create database nodes... */ + frontendDB->be_cf_ocs = &CFOC_FRONTEND; + LDAP_STAILQ_NEXT(frontendDB, be_next) = LDAP_STAILQ_FIRST(&backendDB); + for ( i = -1, be = frontendDB ; be; + i++, be = LDAP_STAILQ_NEXT( be, be_next )) { + slap_overinfo *oi = NULL; + + if ( overlay_is_over( be )) { + oi = be->bd_info->bi_private; + bi = oi->oi_orig; + } else { + bi = be->bd_info; + } + + /* If this backend supports the old config mechanism, but not + * the new mech, complain. + */ + if ( !be->be_cf_ocs && bi->bi_db_config ) { + Debug( LDAP_DEBUG_ANY, + "WARNING: No dynamic config support for database %s.\n", + bi->bi_type, 0, 0 ); + unsupp++; + } + rdn.bv_val = c.log; + rdn.bv_len = snprintf(rdn.bv_val, sizeof( c.log ), + "%s=" SLAP_X_ORDERED_FMT "%s", cfAd_database->ad_cname.bv_val, + i, bi->bi_type); + if ( rdn.bv_len >= sizeof( c.log ) ) { + /* FIXME: holler ... */ ; + } + c.be = be; + c.bi = bi; + e = config_build_entry( op, &rs, ceparent, &c, &rdn, &CFOC_DATABASE, + be->be_cf_ocs ); + if ( !e ) { + return -1; + } + ce = e->e_private; + if ( be->be_cf_ocs && be->be_cf_ocs->co_cfadd ) { + rs_reinit( &rs, REP_RESULT ); + be->be_cf_ocs->co_cfadd( op, &rs, e, &c ); + } + /* Iterate through overlays */ + if ( oi ) { + slap_overinst *on; + Entry *oe; + int j; + voidList *vl, *v0 = NULL; + + /* overlays are in LIFO order, must reverse stack */ + for (on=oi->oi_list; on; on=on->on_next) { + vl = ch_malloc( sizeof( voidList )); + vl->vl_next = v0; + v0 = vl; + vl->vl_ptr = on; + } + for (j=0; vl; j++,vl=v0) { + on = vl->vl_ptr; + v0 = vl->vl_next; + ch_free( vl ); + if ( on->on_bi.bi_db_config && !on->on_bi.bi_cf_ocs ) { + Debug( LDAP_DEBUG_ANY, + "WARNING: No dynamic config support for overlay %s.\n", + on->on_bi.bi_type, 0, 0 ); + unsupp++; + } + rdn.bv_val = c.log; + rdn.bv_len = snprintf(rdn.bv_val, sizeof( c.log ), + "%s=" SLAP_X_ORDERED_FMT "%s", + cfAd_overlay->ad_cname.bv_val, j, on->on_bi.bi_type ); + if ( rdn.bv_len >= sizeof( c.log ) ) { + /* FIXME: holler ... */ ; + } + c.be = be; + c.bi = &on->on_bi; + oe = config_build_entry( op, &rs, ce, &c, &rdn, + &CFOC_OVERLAY, c.bi->bi_cf_ocs ); + if ( !oe ) { + return -1; + } + if ( c.bi->bi_cf_ocs && c.bi->bi_cf_ocs->co_cfadd ) { + rs_reinit( &rs, REP_RESULT ); + c.bi->bi_cf_ocs->co_cfadd( op, &rs, oe, &c ); + } + } + } + } + if ( thrctx ) + ldap_pvt_thread_pool_context_reset( thrctx ); + + if ( unsupp && cfb->cb_use_ldif ) { + Debug( LDAP_DEBUG_ANY, "\nWARNING: The converted cn=config " + "directory is incomplete and may not work.\n\n", 0, 0, 0 ); + } + + return 0; +} + +static void +cfb_free_cffile( ConfigFile *cf ) +{ + ConfigFile *next; + + for (; cf; cf=next) { + next = cf->c_sibs; + if ( cf->c_kids ) + cfb_free_cffile( cf->c_kids ); + ch_free( cf->c_file.bv_val ); + ber_bvarray_free( cf->c_dseFiles ); + ch_free( cf ); + } +} + +static void +cfb_free_entries( CfEntryInfo *ce ) +{ + CfEntryInfo *next; + + for (; ce; ce=next) { + next = ce->ce_sibs; + if ( ce->ce_kids ) + cfb_free_entries( ce->ce_kids ); + ce->ce_entry->e_private = NULL; + entry_free( ce->ce_entry ); + ch_free( ce ); + } +} + +static int +config_back_db_close( BackendDB *be, ConfigReply *cr ) +{ + CfBackInfo *cfb = be->be_private; + + cfb_free_entries( cfb->cb_root ); + cfb->cb_root = NULL; + + if ( cfb->cb_db.bd_info ) { + backend_shutdown( &cfb->cb_db ); + } + + if ( defacl_parsed && be->be_acl != defacl_parsed ) { + acl_free( defacl_parsed ); + defacl_parsed = NULL; + } + + return 0; +} + +static int +config_back_db_destroy( BackendDB *be, ConfigReply *cr ) +{ + CfBackInfo *cfb = be->be_private; + + cfb_free_cffile( cfb->cb_config ); + + ch_free( cfdir.bv_val ); + + avl_free( CfOcTree, NULL ); + + if ( cfb->cb_db.bd_info ) { + cfb->cb_db.be_suffix = NULL; + cfb->cb_db.be_nsuffix = NULL; + BER_BVZERO( &cfb->cb_db.be_rootdn ); + BER_BVZERO( &cfb->cb_db.be_rootndn ); + + backend_destroy_one( &cfb->cb_db, 0 ); + } + + loglevel_destroy(); + + return 0; +} + +static int +config_back_db_init( BackendDB *be, ConfigReply* cr ) +{ + struct berval dn; + CfBackInfo *cfb; + + cfb = &cfBackInfo; + cfb->cb_config = ch_calloc( 1, sizeof(ConfigFile)); + cfn = cfb->cb_config; + be->be_private = cfb; + + ber_dupbv( &be->be_rootdn, &config_rdn ); + ber_dupbv( &be->be_rootndn, &be->be_rootdn ); + ber_dupbv( &dn, &be->be_rootdn ); + ber_bvarray_add( &be->be_suffix, &dn ); + ber_dupbv( &dn, &be->be_rootdn ); + ber_bvarray_add( &be->be_nsuffix, &dn ); + + /* Hide from namingContexts */ + SLAP_BFLAGS(be) |= SLAP_BFLAG_CONFIG; + + /* Check ACLs on content of Adds by default */ + SLAP_DBFLAGS(be) |= SLAP_DBFLAG_ACL_ADD; + + return 0; +} + +static int +config_back_destroy( BackendInfo *bi ) +{ + ldif_must_b64_encode_release(); + return 0; +} + +static int +config_tool_entry_open( BackendDB *be, int mode ) +{ + CfBackInfo *cfb = be->be_private; + BackendInfo *bi = cfb->cb_db.bd_info; + + if ( bi && bi->bi_tool_entry_open ) + return bi->bi_tool_entry_open( &cfb->cb_db, mode ); + else + return -1; + +} + +static int +config_tool_entry_close( BackendDB *be ) +{ + CfBackInfo *cfb = be->be_private; + BackendInfo *bi = cfb->cb_db.bd_info; + + if ( bi && bi->bi_tool_entry_close ) + return bi->bi_tool_entry_close( &cfb->cb_db ); + else + return -1; +} + +static ID +config_tool_entry_first( BackendDB *be ) +{ + CfBackInfo *cfb = be->be_private; + BackendInfo *bi = cfb->cb_db.bd_info; + + if ( bi && bi->bi_tool_entry_first ) { + return bi->bi_tool_entry_first( &cfb->cb_db ); + } + if ( bi && bi->bi_tool_entry_first_x ) { + return bi->bi_tool_entry_first_x( &cfb->cb_db, + NULL, LDAP_SCOPE_DEFAULT, NULL ); + } + return NOID; +} + +static ID +config_tool_entry_first_x( + BackendDB *be, + struct berval *base, + int scope, + Filter *f ) +{ + CfBackInfo *cfb = be->be_private; + BackendInfo *bi = cfb->cb_db.bd_info; + + if ( bi && bi->bi_tool_entry_first_x ) { + return bi->bi_tool_entry_first_x( &cfb->cb_db, base, scope, f ); + } + return NOID; +} + +static ID +config_tool_entry_next( BackendDB *be ) +{ + CfBackInfo *cfb = be->be_private; + BackendInfo *bi = cfb->cb_db.bd_info; + + if ( bi && bi->bi_tool_entry_next ) + return bi->bi_tool_entry_next( &cfb->cb_db ); + else + return NOID; +} + +static Entry * +config_tool_entry_get( BackendDB *be, ID id ) +{ + CfBackInfo *cfb = be->be_private; + BackendInfo *bi = cfb->cb_db.bd_info; + + if ( bi && bi->bi_tool_entry_get ) + return bi->bi_tool_entry_get( &cfb->cb_db, id ); + else + return NULL; +} + +static int entry_put_got_frontend=0; +static int entry_put_got_config=0; +static ID +config_tool_entry_put( BackendDB *be, Entry *e, struct berval *text ) +{ + CfBackInfo *cfb = be->be_private; + BackendInfo *bi = cfb->cb_db.bd_info; + int rc; + struct berval rdn, vals[ 2 ]; + ConfigArgs ca; + OperationBuffer opbuf; + Entry *ce; + Connection conn = {0}; + Operation *op = NULL; + void *thrctx; + int isFrontend = 0; + int isFrontendChild = 0; + + /* Create entry for frontend database if it does not exist already */ + if ( !entry_put_got_frontend ) { + if ( !strncmp( e->e_nname.bv_val, "olcDatabase", + STRLENOF( "olcDatabase" ))) { + if ( strncmp( e->e_nname.bv_val + + STRLENOF( "olcDatabase" ), "={-1}frontend", + STRLENOF( "={-1}frontend" )) && + strncmp( e->e_nname.bv_val + + STRLENOF( "olcDatabase" ), "=frontend", + STRLENOF( "=frontend" ))) { + vals[1].bv_len = 0; + vals[1].bv_val = NULL; + memset( &ca, 0, sizeof(ConfigArgs)); + ca.be = frontendDB; + ca.bi = frontendDB->bd_info; + ca.be->be_cf_ocs = &CFOC_FRONTEND; + rdn.bv_val = ca.log; + rdn.bv_len = snprintf(rdn.bv_val, sizeof( ca.log ), + "%s=" SLAP_X_ORDERED_FMT "%s", + cfAd_database->ad_cname.bv_val, -1, + ca.bi->bi_type); + ce = config_build_entry( NULL, NULL, cfb->cb_root, &ca, &rdn, + &CFOC_DATABASE, ca.be->be_cf_ocs ); + thrctx = ldap_pvt_thread_pool_context(); + connection_fake_init2( &conn, &opbuf, thrctx,0 ); + op = &opbuf.ob_op; + op->o_bd = &cfb->cb_db; + op->o_tag = LDAP_REQ_ADD; + op->ora_e = ce; + op->o_dn = be->be_rootdn; + op->o_ndn = be->be_rootndn; + rc = slap_add_opattrs(op, NULL, NULL, 0, 0); + if ( rc != LDAP_SUCCESS ) { + text->bv_val = "autocreation of \"olcDatabase={-1}frontend\" failed"; + text->bv_len = STRLENOF("autocreation of \"olcDatabase={-1}frontend\" failed"); + return NOID; + } + + if ( ce && bi && bi->bi_tool_entry_put && + bi->bi_tool_entry_put( &cfb->cb_db, ce, text ) != NOID ) { + entry_put_got_frontend++; + } else { + text->bv_val = "autocreation of \"olcDatabase={-1}frontend\" failed"; + text->bv_len = STRLENOF("autocreation of \"olcDatabase={-1}frontend\" failed"); + return NOID; + } + } else { + entry_put_got_frontend++; + isFrontend = 1; + } + } + } + + /* Child entries of the frontend database, e.g. slapo-chain's back-ldap + * instances, may appear before the config database entry in the ldif, skip + * auto-creation of olcDatabase={0}config in such a case */ + if ( !entry_put_got_config && + !strncmp( e->e_nname.bv_val, "olcDatabase", STRLENOF( "olcDatabase" ))) { + struct berval pdn; + dnParent( &e->e_nname, &pdn ); + while ( pdn.bv_len ) { + if ( !strncmp( pdn.bv_val, "olcDatabase", + STRLENOF( "olcDatabase" ))) { + if ( !strncmp( pdn.bv_val + + STRLENOF( "olcDatabase" ), "={-1}frontend", + STRLENOF( "={-1}frontend" )) || + !strncmp( pdn.bv_val + + STRLENOF( "olcDatabase" ), "=frontend", + STRLENOF( "=frontend" ))) { + + isFrontendChild = 1; + break; + } + } + dnParent( &pdn, &pdn ); + } + } + + /* Create entry for config database if it does not exist already */ + if ( !entry_put_got_config && !isFrontend && !isFrontendChild ) { + if ( !strncmp( e->e_nname.bv_val, "olcDatabase", + STRLENOF( "olcDatabase" ))) { + if ( strncmp( e->e_nname.bv_val + + STRLENOF( "olcDatabase" ), "={0}config", + STRLENOF( "={0}config" )) && + strncmp( e->e_nname.bv_val + + STRLENOF( "olcDatabase" ), "=config", + STRLENOF( "=config" )) ) { + vals[1].bv_len = 0; + vals[1].bv_val = NULL; + memset( &ca, 0, sizeof(ConfigArgs)); + ca.be = LDAP_STAILQ_FIRST( &backendDB ); + ca.bi = ca.be->bd_info; + rdn.bv_val = ca.log; + rdn.bv_len = snprintf(rdn.bv_val, sizeof( ca.log ), + "%s=" SLAP_X_ORDERED_FMT "%s", + cfAd_database->ad_cname.bv_val, 0, + ca.bi->bi_type); + ce = config_build_entry( NULL, NULL, cfb->cb_root, &ca, &rdn, &CFOC_DATABASE, + ca.be->be_cf_ocs ); + if ( ! op ) { + thrctx = ldap_pvt_thread_pool_context(); + connection_fake_init2( &conn, &opbuf, thrctx,0 ); + op = &opbuf.ob_op; + op->o_bd = &cfb->cb_db; + op->o_tag = LDAP_REQ_ADD; + op->o_dn = be->be_rootdn; + op->o_ndn = be->be_rootndn; + } + op->ora_e = ce; + rc = slap_add_opattrs(op, NULL, NULL, 0, 0); + if ( rc != LDAP_SUCCESS ) { + text->bv_val = "autocreation of \"olcDatabase={0}config\" failed"; + text->bv_len = STRLENOF("autocreation of \"olcDatabase={0}config\" failed"); + return NOID; + } + if (ce && bi && bi->bi_tool_entry_put && + bi->bi_tool_entry_put( &cfb->cb_db, ce, text ) != NOID ) { + entry_put_got_config++; + } else { + text->bv_val = "autocreation of \"olcDatabase={0}config\" failed"; + text->bv_len = STRLENOF("autocreation of \"olcDatabase={0}config\" failed"); + return NOID; + } + } else { + entry_put_got_config++; + } + } + } + if ( bi && bi->bi_tool_entry_put && + config_add_internal( cfb, e, &ca, NULL, NULL, NULL ) == 0 ) + return bi->bi_tool_entry_put( &cfb->cb_db, e, text ); + else + return NOID; +} + +static struct { + char *name; + AttributeDescription **desc; +} ads[] = { + { "attribute", &cfAd_attr }, + { "backend", &cfAd_backend }, + { "database", &cfAd_database }, + { "include", &cfAd_include }, + { "ldapsyntax", &cfAd_syntax }, + { "objectclass", &cfAd_oc }, + { "objectidentifier", &cfAd_om }, + { "overlay", &cfAd_overlay }, + { NULL, NULL } +}; + +/* Notes: + * add / delete: all types that may be added or deleted must use an + * X-ORDERED attributeType for their RDN. Adding and deleting entries + * should automatically renumber the index of any siblings as needed, + * so that no gaps in the numbering sequence exist after the add/delete + * is completed. + * What can be added: + * schema objects + * backend objects for backend-specific config directives + * database objects + * overlay objects + * + * delete: probably no support this time around. + * + * modrdn: generally not done. Will be invoked automatically by add/ + * delete to update numbering sequence. Perform as an explicit operation + * so that the renumbering effect may be replicated. Subtree rename must + * be supported, since renumbering a database will affect all its child + * overlays. + * + * modify: must be fully supported. + */ + +int +config_back_initialize( BackendInfo *bi ) +{ + ConfigTable *ct = config_back_cf_table; + ConfigArgs ca; + char *argv[4]; + int i; + AttributeDescription *ad = NULL; + const char *text; + static char *controls[] = { + LDAP_CONTROL_MANAGEDSAIT, + NULL + }; + + /* Make sure we don't exceed the bits reserved for userland */ + config_check_userland( CFG_LAST ); + + bi->bi_controls = controls; + + bi->bi_open = 0; + bi->bi_close = 0; + bi->bi_config = 0; + bi->bi_destroy = config_back_destroy; + + bi->bi_db_init = config_back_db_init; + bi->bi_db_config = 0; + bi->bi_db_open = config_back_db_open; + bi->bi_db_close = config_back_db_close; + bi->bi_db_destroy = config_back_db_destroy; + + bi->bi_op_bind = config_back_bind; + bi->bi_op_unbind = 0; + bi->bi_op_search = config_back_search; + bi->bi_op_compare = 0; + bi->bi_op_modify = config_back_modify; + bi->bi_op_modrdn = config_back_modrdn; + bi->bi_op_add = config_back_add; + bi->bi_op_delete = config_back_delete; + bi->bi_op_abandon = 0; + + bi->bi_extended = 0; + + bi->bi_chk_referrals = 0; + + bi->bi_access_allowed = slap_access_allowed; + + bi->bi_connection_init = 0; + bi->bi_connection_destroy = 0; + + bi->bi_entry_release_rw = config_entry_release; + bi->bi_entry_get_rw = config_back_entry_get; + + bi->bi_tool_entry_open = config_tool_entry_open; + bi->bi_tool_entry_close = config_tool_entry_close; + bi->bi_tool_entry_first = config_tool_entry_first; + bi->bi_tool_entry_first_x = config_tool_entry_first_x; + bi->bi_tool_entry_next = config_tool_entry_next; + bi->bi_tool_entry_get = config_tool_entry_get; + bi->bi_tool_entry_put = config_tool_entry_put; + + ca.argv = argv; + argv[ 0 ] = "slapd"; + ca.argv = argv; + ca.argc = 3; + ca.fname = argv[0]; + + argv[3] = NULL; + for (i=0; OidMacros[i].name; i++ ) { + argv[1] = OidMacros[i].name; + argv[2] = OidMacros[i].oid; + parse_oidm( &ca, 0, NULL ); + } + + bi->bi_cf_ocs = cf_ocs; + + i = config_register_schema( ct, cf_ocs ); + if ( i ) return i; + + i = slap_str2ad( "olcDatabase", &olcDatabaseDummy[0].ad, &text ); + if ( i ) return i; + + /* setup olcRootPW to be base64-encoded when written in LDIF form; + * basically, we don't care if it fails */ + i = slap_str2ad( "olcRootPW", &ad, &text ); + if ( i ) { + Debug( LDAP_DEBUG_ANY, "config_back_initialize: " + "warning, unable to get \"olcRootPW\" " + "attribute description: %d: %s\n", + i, text, 0 ); + } else { + (void)ldif_must_b64_encode_register( ad->ad_cname.bv_val, + ad->ad_type->sat_oid ); + } + + /* set up the notable AttributeDescriptions */ + i = 0; + for (;ct->name;ct++) { + if (strcmp(ct->name, ads[i].name)) continue; + *ads[i].desc = ct->ad; + i++; + if (!ads[i].name) break; + } + + return 0; +} diff --git a/servers/slapd/bind.c b/servers/slapd/bind.c new file mode 100644 index 0000000..254f3d6 --- /dev/null +++ b/servers/slapd/bind.c @@ -0,0 +1,444 @@ +/* bind.c - decode an ldap bind operation and pass it to a backend db */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" + +int +do_bind( + Operation *op, + SlapReply *rs ) +{ + BerElement *ber = op->o_ber; + ber_int_t version; + ber_tag_t method; + struct berval mech = BER_BVNULL; + struct berval dn = BER_BVNULL; + ber_tag_t tag; + Backend *be = NULL; + + Debug( LDAP_DEBUG_TRACE, "%s do_bind\n", + op->o_log_prefix, 0, 0 ); + + /* + * Force the connection to "anonymous" until bind succeeds. + */ + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + if ( op->o_conn->c_sasl_bind_in_progress ) { + be = op->o_conn->c_authz_backend; + } + if ( !BER_BVISEMPTY( &op->o_conn->c_dn ) ) { + /* log authorization identity demotion */ + Statslog( LDAP_DEBUG_STATS, + "%s BIND anonymous mech=implicit ssf=0\n", + op->o_log_prefix, 0, 0, 0, 0 ); + } + connection2anonymous( op->o_conn ); + if ( op->o_conn->c_sasl_bind_in_progress ) { + op->o_conn->c_authz_backend = be; + } + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + if ( !BER_BVISNULL( &op->o_dn ) ) { + /* NOTE: temporarily wasting few bytes + * (until bind is completed), but saving + * a couple of ch_free() and ch_strdup("") */ + op->o_dn.bv_val[0] = '\0'; + op->o_dn.bv_len = 0; + } + if ( !BER_BVISNULL( &op->o_ndn ) ) { + op->o_ndn.bv_val[0] = '\0'; + op->o_ndn.bv_len = 0; + } + + /* + * Parse the bind request. It looks like this: + * + * BindRequest ::= SEQUENCE { + * version INTEGER, -- version + * name DistinguishedName, -- dn + * authentication CHOICE { + * simple [0] OCTET STRING -- passwd + * krbv42ldap [1] OCTET STRING -- OBSOLETE + * krbv42dsa [2] OCTET STRING -- OBSOLETE + * SASL [3] SaslCredentials + * } + * } + * + * SaslCredentials ::= SEQUENCE { + * mechanism LDAPString, + * credentials OCTET STRING OPTIONAL + * } + */ + + tag = ber_scanf( ber, "{imt" /*}*/, &version, &dn, &method ); + + if ( tag == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, "%s do_bind: ber_scanf failed\n", + op->o_log_prefix, 0, 0 ); + send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" ); + rs->sr_err = SLAPD_DISCONNECT; + goto cleanup; + } + + op->o_protocol = version; + op->orb_method = method; + + if( op->orb_method != LDAP_AUTH_SASL ) { + tag = ber_scanf( ber, /*{*/ "m}", &op->orb_cred ); + + } else { + tag = ber_scanf( ber, "{m" /*}*/, &mech ); + + if ( tag != LBER_ERROR ) { + ber_len_t len; + tag = ber_peek_tag( ber, &len ); + + if ( tag == LDAP_TAG_LDAPCRED ) { + tag = ber_scanf( ber, "m", &op->orb_cred ); + } else { + tag = LDAP_TAG_LDAPCRED; + BER_BVZERO( &op->orb_cred ); + } + + if ( tag != LBER_ERROR ) { + tag = ber_scanf( ber, /*{{*/ "}}" ); + } + } + } + + if ( tag == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, "%s do_bind: ber_scanf failed\n", + op->o_log_prefix, 0, 0 ); + send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" ); + rs->sr_err = SLAPD_DISCONNECT; + goto cleanup; + } + + if( get_ctrls( op, rs, 1 ) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s do_bind: get_ctrls failed\n", + op->o_log_prefix, 0, 0 ); + goto cleanup; + } + + /* We use the tmpmemctx here because it speeds up normalization. + * However, we must dup with regular malloc when storing any + * resulting DNs in the op or conn structures. + */ + rs->sr_err = dnPrettyNormal( NULL, &dn, &op->o_req_dn, &op->o_req_ndn, + op->o_tmpmemctx ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s do_bind: invalid dn (%s)\n", + op->o_log_prefix, dn.bv_val, 0 ); + send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid DN" ); + goto cleanup; + } + + Statslog( LDAP_DEBUG_STATS, "%s BIND dn=\"%s\" method=%ld\n", + op->o_log_prefix, op->o_req_dn.bv_val, + (unsigned long) op->orb_method, 0, 0 ); + + if( op->orb_method == LDAP_AUTH_SASL ) { + Debug( LDAP_DEBUG_TRACE, "do_bind: dn (%s) SASL mech %s\n", + op->o_req_dn.bv_val, mech.bv_val, NULL ); + + } else { + Debug( LDAP_DEBUG_TRACE, + "do_bind: version=%ld dn=\"%s\" method=%ld\n", + (unsigned long) version, op->o_req_dn.bv_val, + (unsigned long) op->orb_method ); + } + + if ( version < LDAP_VERSION_MIN || version > LDAP_VERSION_MAX ) { + Debug( LDAP_DEBUG_ANY, "%s do_bind: unknown version=%ld\n", + op->o_log_prefix, (unsigned long) version, 0 ); + send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR, + "requested protocol version not supported" ); + goto cleanup; + + } else if (!( global_allows & SLAP_ALLOW_BIND_V2 ) && + version < LDAP_VERSION3 ) + { + send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR, + "historical protocol version requested, use LDAPv3 instead" ); + goto cleanup; + } + + /* + * we set connection version regardless of whether bind succeeds or not. + */ + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + op->o_conn->c_protocol = version; + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + + op->orb_mech = mech; + + op->o_bd = frontendDB; + rs->sr_err = frontendDB->be_bind( op, rs ); + +cleanup: + if ( rs->sr_err == LDAP_SUCCESS ) { + if ( op->orb_method != LDAP_AUTH_SASL ) { + ber_dupbv( &op->o_conn->c_authmech, &mech ); + } + op->o_conn->c_authtype = op->orb_method; + } + + if( !BER_BVISNULL( &op->o_req_dn ) ) { + slap_sl_free( op->o_req_dn.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &op->o_req_dn ); + } + if( !BER_BVISNULL( &op->o_req_ndn ) ) { + slap_sl_free( op->o_req_ndn.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &op->o_req_ndn ); + } + + return rs->sr_err; +} + +int +fe_op_bind( Operation *op, SlapReply *rs ) +{ + BackendDB *bd = op->o_bd; + + /* check for inappropriate controls */ + if( get_manageDSAit( op ) == SLAP_CONTROL_CRITICAL ) { + send_ldap_error( op, rs, + LDAP_UNAVAILABLE_CRITICAL_EXTENSION, + "manageDSAit control inappropriate" ); + goto cleanup; + } + + if ( op->orb_method == LDAP_AUTH_SASL ) { + if ( op->o_protocol < LDAP_VERSION3 ) { + Debug( LDAP_DEBUG_ANY, "do_bind: sasl with LDAPv%ld\n", + (unsigned long)op->o_protocol, 0, 0 ); + send_ldap_discon( op, rs, + LDAP_PROTOCOL_ERROR, "SASL bind requires LDAPv3" ); + rs->sr_err = SLAPD_DISCONNECT; + goto cleanup; + } + + if( BER_BVISNULL( &op->orb_mech ) || BER_BVISEMPTY( &op->orb_mech ) ) { + Debug( LDAP_DEBUG_ANY, + "do_bind: no sasl mechanism provided\n", + 0, 0, 0 ); + send_ldap_error( op, rs, LDAP_AUTH_METHOD_NOT_SUPPORTED, + "no SASL mechanism provided" ); + goto cleanup; + } + + /* check restrictions */ + if( backend_check_restrictions( op, rs, &op->orb_mech ) != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + if ( op->o_conn->c_sasl_bind_in_progress ) { + if( !bvmatch( &op->o_conn->c_sasl_bind_mech, &op->orb_mech ) ) { + /* mechanism changed between bind steps */ + slap_sasl_reset(op->o_conn); + } + } else { + ber_dupbv(&op->o_conn->c_sasl_bind_mech, &op->orb_mech); + } + + /* Set the bindop for the benefit of in-directory SASL lookups */ + op->o_conn->c_sasl_bindop = op; + + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + + rs->sr_err = slap_sasl_bind( op, rs ); + + goto cleanup; + + } else { + /* Not SASL, cancel any in-progress bind */ + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + + if ( !BER_BVISNULL( &op->o_conn->c_sasl_bind_mech ) ) { + free( op->o_conn->c_sasl_bind_mech.bv_val ); + BER_BVZERO( &op->o_conn->c_sasl_bind_mech ); + } + op->o_conn->c_sasl_bind_in_progress = 0; + + slap_sasl_reset( op->o_conn ); + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + } + + if ( op->orb_method == LDAP_AUTH_SIMPLE ) { + BER_BVSTR( &op->orb_mech, "SIMPLE" ); + /* accept "anonymous" binds */ + if ( BER_BVISEMPTY( &op->orb_cred ) || BER_BVISEMPTY( &op->o_req_ndn ) ) { + rs->sr_err = LDAP_SUCCESS; + + if( !BER_BVISEMPTY( &op->orb_cred ) && + !( global_allows & SLAP_ALLOW_BIND_ANON_CRED )) + { + /* cred is not empty, disallow */ + rs->sr_err = LDAP_INVALID_CREDENTIALS; + + } else if ( !BER_BVISEMPTY( &op->o_req_ndn ) && + !( global_allows & SLAP_ALLOW_BIND_ANON_DN )) + { + /* DN is not empty, disallow */ + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = + "unauthenticated bind (DN with no password) disallowed"; + + } else if ( global_disallows & SLAP_DISALLOW_BIND_ANON ) { + /* disallow */ + rs->sr_err = LDAP_INAPPROPRIATE_AUTH; + rs->sr_text = "anonymous bind disallowed"; + + } else { + backend_check_restrictions( op, rs, &op->orb_mech ); + } + + /* + * we already forced connection to "anonymous", + * just need to send success + */ + send_ldap_result( op, rs ); + Debug( LDAP_DEBUG_TRACE, "do_bind: v%d anonymous bind\n", + op->o_protocol, 0, 0 ); + goto cleanup; + + } else if ( global_disallows & SLAP_DISALLOW_BIND_SIMPLE ) { + /* disallow simple authentication */ + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "unwilling to perform simple authentication"; + + send_ldap_result( op, rs ); + Debug( LDAP_DEBUG_TRACE, + "do_bind: v%d simple bind(%s) disallowed\n", + op->o_protocol, op->o_req_ndn.bv_val, 0 ); + goto cleanup; + } + + } else { + rs->sr_err = LDAP_AUTH_METHOD_NOT_SUPPORTED; + rs->sr_text = "unknown authentication method"; + + send_ldap_result( op, rs ); + Debug( LDAP_DEBUG_TRACE, + "do_bind: v%d unknown authentication method (%d)\n", + op->o_protocol, op->orb_method, 0 ); + goto cleanup; + } + + /* + * We could be serving multiple database backends. Select the + * appropriate one, or send a referral to our "referral server" + * if we don't hold it. + */ + + if ( (op->o_bd = select_backend( &op->o_req_ndn, 0 )) == NULL ) { + /* don't return referral for bind requests */ + /* noSuchObject is not allowed to be returned by bind */ + rs->sr_err = LDAP_INVALID_CREDENTIALS; + op->o_bd = bd; + send_ldap_result( op, rs ); + goto cleanup; + } + + /* check restrictions */ + if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + if( op->o_bd->be_bind ) { + op->o_conn->c_authz_cookie = NULL; + + rs->sr_err = (op->o_bd->be_bind)( op, rs ); + + if ( rs->sr_err == 0 ) { + (void)fe_op_bind_success( op, rs ); + + } else if ( !BER_BVISNULL( &op->orb_edn ) ) { + free( op->orb_edn.bv_val ); + BER_BVZERO( &op->orb_edn ); + } + + } else { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "operation not supported within naming context" ); + } + +cleanup:; + op->o_bd = bd; + return rs->sr_err; +} + +int +fe_op_bind_success( Operation *op, SlapReply *rs ) +{ + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + + if( op->o_conn->c_authz_backend == NULL ) { + op->o_conn->c_authz_backend = op->o_bd; + } + + /* be_bind returns regular/global edn */ + if( !BER_BVISEMPTY( &op->orb_edn ) ) { + op->o_conn->c_dn = op->orb_edn; + } else { + ber_dupbv(&op->o_conn->c_dn, &op->o_req_dn); + } + + ber_dupbv( &op->o_conn->c_ndn, &op->o_req_ndn ); + + /* op->o_conn->c_sb may be 0 for internal operations */ + if( !BER_BVISEMPTY( &op->o_conn->c_dn ) && op->o_conn->c_sb != 0 ) { + ber_len_t max = sockbuf_max_incoming_auth; + ber_sockbuf_ctrl( op->o_conn->c_sb, + LBER_SB_OPT_SET_MAX_INCOMING, &max ); + } + + /* log authorization identity */ + Statslog( LDAP_DEBUG_STATS, + "%s BIND dn=\"%s\" mech=%s ssf=0\n", + op->o_log_prefix, + op->o_conn->c_dn.bv_val, op->orb_mech.bv_val, 0, 0 ); + + Debug( LDAP_DEBUG_TRACE, + "do_bind: v%d bind: \"%s\" to \"%s\"\n", + op->o_protocol, op->o_req_dn.bv_val, op->o_conn->c_dn.bv_val ); + + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + + /* send this here to avoid a race condition */ + send_ldap_result( op, rs ); + + return LDAP_SUCCESS; +} diff --git a/servers/slapd/cancel.c b/servers/slapd/cancel.c new file mode 100644 index 0000000..22b5232 --- /dev/null +++ b/servers/slapd/cancel.c @@ -0,0 +1,162 @@ +/* cancel.c - LDAP cancel extended operation */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/unistd.h> + +#include "slap.h" + +#include <lber_pvt.h> +#include <lutil.h> + +const struct berval slap_EXOP_CANCEL = BER_BVC(LDAP_EXOP_CANCEL); + +int cancel_extop( Operation *op, SlapReply *rs ) +{ + Operation *o; + int rc; + int opid; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + + assert( ber_bvcmp( &slap_EXOP_CANCEL, &op->ore_reqoid ) == 0 ); + + if ( op->ore_reqdata == NULL ) { + rs->sr_text = "no message ID supplied"; + return LDAP_PROTOCOL_ERROR; + } + + if ( op->ore_reqdata->bv_len == 0 ) { + rs->sr_text = "empty request data field"; + return LDAP_PROTOCOL_ERROR; + } + + /* ber_init2 uses reqdata directly, doesn't allocate new buffers */ + ber_init2( ber, op->ore_reqdata, 0 ); + + if ( ber_scanf( ber, "{i}", &opid ) == LBER_ERROR ) { + rs->sr_text = "message ID parse failed"; + return LDAP_PROTOCOL_ERROR; + } + + Statslog( LDAP_DEBUG_STATS, "%s CANCEL msg=%d\n", + op->o_log_prefix, opid, 0, 0, 0 ); + + if ( opid < 0 ) { + rs->sr_text = "message ID invalid"; + return LDAP_PROTOCOL_ERROR; + } + + if ( opid == op->o_msgid ) { + op->o_cancel = SLAP_CANCEL_DONE; + return LDAP_SUCCESS; + } + + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + + if ( op->o_abandon ) { + /* FIXME: Should instead reject the cancel/abandon of this op, but + * it seems unsafe to reset op->o_abandon once it is set. ITS#6138. + */ + rc = LDAP_OPERATIONS_ERROR; + rs->sr_text = "tried to abandon or cancel this operation"; + goto out; + } + + LDAP_STAILQ_FOREACH( o, &op->o_conn->c_pending_ops, o_next ) { + if ( o->o_msgid == opid ) { + /* TODO: We could instead remove the cancelled operation + * from c_pending_ops like Abandon does, and send its + * response here. Not if it is pending because of a + * congested connection though. + */ + rc = LDAP_CANNOT_CANCEL; + rs->sr_text = "too busy for Cancel, try Abandon instead"; + goto out; + } + } + + LDAP_STAILQ_FOREACH( o, &op->o_conn->c_ops, o_next ) { + if ( o->o_msgid == opid ) { + break; + } + } + + if ( o == NULL ) { + rc = LDAP_NO_SUCH_OPERATION; + rs->sr_text = "message ID not found"; + + } else if ( o->o_tag == LDAP_REQ_BIND + || o->o_tag == LDAP_REQ_UNBIND + || o->o_tag == LDAP_REQ_ABANDON ) { + rc = LDAP_CANNOT_CANCEL; + + } else if ( o->o_cancel != SLAP_CANCEL_NONE ) { + rc = LDAP_OPERATIONS_ERROR; + rs->sr_text = "message ID already being cancelled"; + +#if 0 + } else if ( o->o_abandon ) { + /* TODO: Would this break something when + * o_abandon="suppress response"? (ITS#6138) + */ + rc = LDAP_TOO_LATE; +#endif + + } else { + rc = LDAP_SUCCESS; + o->o_cancel = SLAP_CANCEL_REQ; + o->o_abandon = 1; + } + + out: + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + + if ( rc == LDAP_SUCCESS ) { + LDAP_STAILQ_FOREACH( op->o_bd, &backendDB, be_next ) { + if( !op->o_bd->be_cancel ) continue; + + op->oq_cancel.rs_msgid = opid; + if ( op->o_bd->be_cancel( op, rs ) == LDAP_SUCCESS ) { + return LDAP_SUCCESS; + } + } + + do { + /* Fake a cond_wait with thread_yield, then + * verify the result properly mutex-protected. + */ + while ( o->o_cancel == SLAP_CANCEL_REQ ) + ldap_pvt_thread_yield(); + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + rc = o->o_cancel; + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + } while ( rc == SLAP_CANCEL_REQ ); + + if ( rc == SLAP_CANCEL_ACK ) { + rc = LDAP_SUCCESS; + } + + o->o_cancel = SLAP_CANCEL_DONE; + } + + return rc; +} diff --git a/servers/slapd/ch_malloc.c b/servers/slapd/ch_malloc.c new file mode 100644 index 0000000..4fda4dd --- /dev/null +++ b/servers/slapd/ch_malloc.c @@ -0,0 +1,142 @@ +/* ch_malloc.c - malloc routines that test returns from malloc and friends */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#define CH_FREE 1 + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" + +BerMemoryFunctions ch_mfuncs = { + (BER_MEMALLOC_FN *)ch_malloc, + (BER_MEMCALLOC_FN *)ch_calloc, + (BER_MEMREALLOC_FN *)ch_realloc, + (BER_MEMFREE_FN *)ch_free +}; + +void * +ch_malloc( + ber_len_t size +) +{ + void *new; + + if ( (new = (void *) ber_memalloc_x( size, NULL )) == NULL ) { + Debug( LDAP_DEBUG_ANY, "ch_malloc of %lu bytes failed\n", + (long) size, 0, 0 ); + assert( 0 ); + exit( EXIT_FAILURE ); + } + + return( new ); +} + +void * +ch_realloc( + void *block, + ber_len_t size +) +{ + void *new, *ctx; + + if ( block == NULL ) { + return( ch_malloc( size ) ); + } + + if( size == 0 ) { + ch_free( block ); + return NULL; + } + + ctx = slap_sl_context( block ); + if ( ctx ) { + return slap_sl_realloc( block, size, ctx ); + } + + if ( (new = (void *) ber_memrealloc_x( block, size, NULL )) == NULL ) { + Debug( LDAP_DEBUG_ANY, "ch_realloc of %lu bytes failed\n", + (long) size, 0, 0 ); + assert( 0 ); + exit( EXIT_FAILURE ); + } + + return( new ); +} + +void * +ch_calloc( + ber_len_t nelem, + ber_len_t size +) +{ + void *new; + + if ( (new = (void *) ber_memcalloc_x( nelem, size, NULL )) == NULL ) { + Debug( LDAP_DEBUG_ANY, "ch_calloc of %lu elems of %lu bytes failed\n", + (long) nelem, (long) size, 0 ); + assert( 0 ); + exit( EXIT_FAILURE ); + } + + return( new ); +} + +char * +ch_strdup( + const char *string +) +{ + char *new; + + if ( (new = ber_strdup_x( string, NULL )) == NULL ) { + Debug( LDAP_DEBUG_ANY, "ch_strdup(%s) failed\n", string, 0, 0 ); + assert( 0 ); + exit( EXIT_FAILURE ); + } + + return( new ); +} + +void +ch_free( void *ptr ) +{ + void *ctx; + + ctx = slap_sl_context( ptr ); + if (ctx) { + slap_sl_free( ptr, ctx ); + } else { + ber_memfree_x( ptr, NULL ); + } +} + diff --git a/servers/slapd/compare.c b/servers/slapd/compare.c new file mode 100644 index 0000000..32824ed --- /dev/null +++ b/servers/slapd/compare.c @@ -0,0 +1,409 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" + +int +do_compare( + Operation *op, + SlapReply *rs ) +{ + struct berval dn = BER_BVNULL; + struct berval desc = BER_BVNULL; + struct berval value = BER_BVNULL; + AttributeAssertion ava = ATTRIBUTEASSERTION_INIT; + + Debug( LDAP_DEBUG_TRACE, "%s do_compare\n", + op->o_log_prefix, 0, 0 ); + /* + * Parse the compare request. It looks like this: + * + * CompareRequest := [APPLICATION 14] SEQUENCE { + * entry DistinguishedName, + * ava SEQUENCE { + * type AttributeType, + * value AttributeValue + * } + * } + */ + + if ( ber_scanf( op->o_ber, "{m" /*}*/, &dn ) == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, "%s do_compare: ber_scanf failed\n", + op->o_log_prefix, 0, 0 ); + send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" ); + return SLAPD_DISCONNECT; + } + + if ( ber_scanf( op->o_ber, "{mm}", &desc, &value ) == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, "%s do_compare: get ava failed\n", + op->o_log_prefix, 0, 0 ); + send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" ); + return SLAPD_DISCONNECT; + } + + if ( ber_scanf( op->o_ber, /*{*/ "}" ) == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, "%s do_compare: ber_scanf failed\n", + op->o_log_prefix, 0, 0 ); + send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" ); + return SLAPD_DISCONNECT; + } + + if( get_ctrls( op, rs, 1 ) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s do_compare: get_ctrls failed\n", + op->o_log_prefix, 0, 0 ); + goto cleanup; + } + + rs->sr_err = dnPrettyNormal( NULL, &dn, &op->o_req_dn, &op->o_req_ndn, + op->o_tmpmemctx ); + if( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s do_compare: invalid dn (%s)\n", + op->o_log_prefix, dn.bv_val, 0 ); + send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid DN" ); + goto cleanup; + } + + Statslog( LDAP_DEBUG_STATS, + "%s CMP dn=\"%s\" attr=\"%s\"\n", + op->o_log_prefix, op->o_req_dn.bv_val, + desc.bv_val, 0, 0 ); + + rs->sr_err = slap_bv2ad( &desc, &ava.aa_desc, &rs->sr_text ); + if( rs->sr_err != LDAP_SUCCESS ) { + rs->sr_err = slap_bv2undef_ad( &desc, &ava.aa_desc, + &rs->sr_text, + SLAP_AD_PROXIED|SLAP_AD_NOINSERT ); + if( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + } + + rs->sr_err = asserted_value_validate_normalize( ava.aa_desc, + ava.aa_desc->ad_type->sat_equality, + SLAP_MR_EQUALITY|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX, + &value, &ava.aa_value, &rs->sr_text, op->o_tmpmemctx ); + if( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + op->orc_ava = &ava; + + Debug( LDAP_DEBUG_ARGS, + "do_compare: dn (%s) attr (%s) value (%s)\n", + op->o_req_dn.bv_val, + ava.aa_desc->ad_cname.bv_val, ava.aa_value.bv_val ); + + op->o_bd = frontendDB; + rs->sr_err = frontendDB->be_compare( op, rs ); + +cleanup:; + op->o_tmpfree( op->o_req_dn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx ); + if ( !BER_BVISNULL( &ava.aa_value ) ) { + op->o_tmpfree( ava.aa_value.bv_val, op->o_tmpmemctx ); + } + + return rs->sr_err; +} + +int +fe_op_compare( Operation *op, SlapReply *rs ) +{ + Entry *entry = NULL; + AttributeAssertion *ava = op->orc_ava; + BackendDB *bd = op->o_bd; + + if( strcasecmp( op->o_req_ndn.bv_val, LDAP_ROOT_DSE ) == 0 ) { + if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = root_dse_info( op->o_conn, &entry, &rs->sr_text ); + if( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + } else if ( bvmatch( &op->o_req_ndn, &frontendDB->be_schemandn ) ) { + if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + rs->sr_err = 0; + goto cleanup; + } + + rs->sr_err = schema_info( &entry, &rs->sr_text ); + if( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + rs->sr_err = 0; + goto cleanup; + } + } + + if( entry ) { + rs->sr_err = slap_compare_entry( op, entry, ava ); + entry_free( entry ); + + send_ldap_result( op, rs ); + + if( rs->sr_err == LDAP_COMPARE_TRUE || + rs->sr_err == LDAP_COMPARE_FALSE ) + { + rs->sr_err = LDAP_SUCCESS; + } + + goto cleanup; + } + + /* + * We could be serving multiple database backends. Select the + * appropriate one, or send a referral to our "referral server" + * if we don't hold it. + */ + op->o_bd = select_backend( &op->o_req_ndn, 0 ); + if ( op->o_bd == NULL ) { + rs->sr_ref = referral_rewrite( default_referral, + NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + + rs->sr_err = LDAP_REFERRAL; + if (!rs->sr_ref) rs->sr_ref = default_referral; + op->o_bd = bd; + send_ldap_result( op, rs ); + + if (rs->sr_ref != default_referral) ber_bvarray_free( rs->sr_ref ); + rs->sr_err = 0; + goto cleanup; + } + + /* check restrictions */ + if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + /* check for referrals */ + if( backend_check_referrals( op, rs ) != LDAP_SUCCESS ) { + goto cleanup; + } + + if ( SLAP_SHADOW(op->o_bd) && get_dontUseCopy(op) ) { + /* don't use shadow copy */ + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "copy not used" ); + + } else if ( ava->aa_desc == slap_schema.si_ad_entryDN ) { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "entryDN compare not supported" ); + + } else if ( ava->aa_desc == slap_schema.si_ad_subschemaSubentry ) { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "subschemaSubentry compare not supported" ); + +#ifndef SLAP_COMPARE_IN_FRONTEND + } else if ( ava->aa_desc == slap_schema.si_ad_hasSubordinates + && op->o_bd->be_has_subordinates ) + { + int rc, hasSubordinates = LDAP_SUCCESS; + + rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &entry ); + if ( rc == 0 && entry ) { + if ( ! access_allowed( op, entry, + ava->aa_desc, &ava->aa_value, ACL_COMPARE, NULL ) ) + { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + + } else { + rc = rs->sr_err = op->o_bd->be_has_subordinates( op, + entry, &hasSubordinates ); + be_entry_release_r( op, entry ); + } + } + + if ( rc == 0 ) { + int asserted; + + asserted = bvmatch( &ava->aa_value, &slap_true_bv ) + ? LDAP_COMPARE_TRUE : LDAP_COMPARE_FALSE; + if ( hasSubordinates == asserted ) { + rs->sr_err = LDAP_COMPARE_TRUE; + + } else { + rs->sr_err = LDAP_COMPARE_FALSE; + } + + } else { + /* return error only if "disclose" + * is granted on the object */ + if ( backend_access( op, NULL, &op->o_req_ndn, + slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) == LDAP_INSUFFICIENT_ACCESS ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } + } + + send_ldap_result( op, rs ); + + if ( rc == 0 ) { + rs->sr_err = LDAP_SUCCESS; + } + + } else if ( op->o_bd->be_compare ) { + rs->sr_err = op->o_bd->be_compare( op, rs ); + +#endif /* ! SLAP_COMPARE_IN_FRONTEND */ + } else { + rs->sr_err = SLAP_CB_CONTINUE; + } + + if ( rs->sr_err == SLAP_CB_CONTINUE ) { + /* do our best to compare that AVA + * + * NOTE: this code is used only + * if SLAP_COMPARE_IN_FRONTEND + * is #define'd (it's not by default) + * or if op->o_bd->be_compare is NULL. + * + * FIXME: one potential issue is that + * if SLAP_COMPARE_IN_FRONTEND overlays + * are not executed for compare. */ + BerVarray vals = NULL; + int rc = LDAP_OTHER; + + rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn, + ava->aa_desc, &vals, ACL_COMPARE ); + switch ( rs->sr_err ) { + default: + /* return error only if "disclose" + * is granted on the object */ + if ( backend_access( op, NULL, &op->o_req_ndn, + slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) + == LDAP_INSUFFICIENT_ACCESS ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } + break; + + case LDAP_SUCCESS: + if ( value_find_ex( op->oq_compare.rs_ava->aa_desc, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + vals, &ava->aa_value, op->o_tmpmemctx ) == 0 ) + { + rs->sr_err = LDAP_COMPARE_TRUE; + break; + + } else { + rs->sr_err = LDAP_COMPARE_FALSE; + } + rc = LDAP_SUCCESS; + break; + } + + send_ldap_result( op, rs ); + + if ( rc == 0 ) { + rs->sr_err = LDAP_SUCCESS; + } + + if ( vals ) { + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + } + +cleanup:; + op->o_bd = bd; + return rs->sr_err; +} + +int slap_compare_entry( + Operation *op, + Entry *e, + AttributeAssertion *ava ) +{ + int rc = LDAP_COMPARE_FALSE; + Attribute *a; + + if ( ! access_allowed( op, e, + ava->aa_desc, &ava->aa_value, ACL_COMPARE, NULL ) ) + { + rc = LDAP_INSUFFICIENT_ACCESS; + goto done; + } + + if ( get_assert( op ) && + ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rc = LDAP_ASSERTION_FAILED; + goto done; + } + + a = attrs_find( e->e_attrs, ava->aa_desc ); + if( a == NULL ) { + rc = LDAP_NO_SUCH_ATTRIBUTE; + goto done; + } + + for(; + a != NULL; + a = attrs_find( a->a_next, ava->aa_desc )) + { + if (( ava->aa_desc != a->a_desc ) && ! access_allowed( op, + e, a->a_desc, &ava->aa_value, ACL_COMPARE, NULL ) ) + { + rc = LDAP_INSUFFICIENT_ACCESS; + break; + } + + if ( attr_valfind( a, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + &ava->aa_value, NULL, op->o_tmpmemctx ) == 0 ) + { + rc = LDAP_COMPARE_TRUE; + break; + } + } + +done: + if( rc != LDAP_COMPARE_TRUE && rc != LDAP_COMPARE_FALSE ) { + if ( ! access_allowed( op, e, + slap_schema.si_ad_entry, NULL, ACL_DISCLOSE, NULL ) ) + { + rc = LDAP_NO_SUCH_OBJECT; + } + } + + return rc; +} diff --git a/servers/slapd/component.c b/servers/slapd/component.c new file mode 100644 index 0000000..1968f99 --- /dev/null +++ b/servers/slapd/component.c @@ -0,0 +1,1401 @@ +/* component.c -- Component Filter Match Routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * Portions Copyright 2004 by IBM Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <ac/string.h> +#include <ac/socket.h> + +#include "lutil.h" +#include <ldap.h> +#include "slap.h" + +#ifdef LDAP_COMP_MATCH + +#include "component.h" + +/* + * Following function pointers are initialized + * when a component module is loaded + */ +alloc_nibble_func* nibble_mem_allocator = NULL; +free_nibble_func* nibble_mem_free = NULL; +convert_attr_to_comp_func* attr_converter = NULL; +convert_assert_to_comp_func* assert_converter = NULL ; +free_component_func* component_destructor = NULL ; +test_component_func* test_components = NULL; +test_membership_func* is_aliased_attribute = NULL; +component_encoder_func* component_encoder = NULL; +get_component_info_func* get_component_description = NULL; +#define OID_ALL_COMP_MATCH "1.2.36.79672281.1.13.6" +#define OID_COMP_FILTER_MATCH "1.2.36.79672281.1.13.2" +#define MAX_LDAP_STR_LEN 128 + +static int +peek_componentId_type( ComponentAssertionValue* cav ); + +static int +strip_cav_str( ComponentAssertionValue* cav, char* str); + +static int +peek_cav_str( ComponentAssertionValue* cav, char* str ); + +static int +parse_comp_filter( Operation* op, ComponentAssertionValue* cav, + ComponentFilter** filt, const char** text ); + +static void +free_comp_filter( ComponentFilter* f ); + +static int +test_comp_filter( Syntax *syn, ComponentSyntaxInfo *a, ComponentFilter *f ); + +int +componentCertificateValidate( + Syntax *syntax, + struct berval *val ) +{ + return LDAP_SUCCESS; +} + +int +componentFilterValidate( + Syntax *syntax, + struct berval *val ) +{ + return LDAP_SUCCESS; +} + +int +allComponentsValidate( + Syntax *syntax, + struct berval *val ) +{ + return LDAP_SUCCESS; +} + +int +componentFilterMatch ( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + ComponentSyntaxInfo *csi_attr = (ComponentSyntaxInfo*)value; + MatchingRuleAssertion * ma = (MatchingRuleAssertion*)assertedValue; + int rc; + + if ( !mr || !ma->ma_cf ) return LDAP_INAPPROPRIATE_MATCHING; + + /* Check if the component module is loaded */ + if ( !attr_converter || !nibble_mem_allocator ) { + return LDAP_OTHER; + } + + rc = test_comp_filter( syntax, csi_attr, ma->ma_cf ); + + if ( rc == LDAP_COMPARE_TRUE ) { + *matchp = 0; + return LDAP_SUCCESS; + } + else if ( rc == LDAP_COMPARE_FALSE ) { + *matchp = 1; + return LDAP_SUCCESS; + } + else { + return LDAP_INAPPROPRIATE_MATCHING; + } +} + +int +directoryComponentsMatch( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + /* Only for registration */ + *matchp = 0; + return LDAP_SUCCESS; +} + +int +allComponentsMatch( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + /* Only for registration */ + *matchp = 0; + return LDAP_SUCCESS; +} + +static int +slapd_ber2cav( struct berval* bv, ComponentAssertionValue* cav ) +{ + cav->cav_ptr = cav->cav_buf = bv->bv_val; + cav->cav_end = bv->bv_val + bv->bv_len; + + return LDAP_SUCCESS; +} + +ComponentReference* +dup_comp_ref ( Operation* op, ComponentReference* cr ) +{ + ComponentReference* dup_cr; + ComponentId* ci_curr; + ComponentId** ci_temp; + + dup_cr = op->o_tmpalloc( sizeof( ComponentReference ), op->o_tmpmemctx ); + + dup_cr->cr_len = cr->cr_len; + dup_cr->cr_string = cr->cr_string; + + ci_temp = &dup_cr->cr_list; + ci_curr = cr->cr_list; + + for ( ; ci_curr != NULL ; + ci_curr = ci_curr->ci_next, ci_temp = &(*ci_temp)->ci_next ) + { + *ci_temp = op->o_tmpalloc( sizeof( ComponentId ), op->o_tmpmemctx ); + if ( !*ci_temp ) return NULL; + **ci_temp = *ci_curr; + } + + dup_cr->cr_curr = dup_cr->cr_list; + + return dup_cr; +} + +static int +dup_comp_filter_list ( + Operation *op, + struct berval *bv, + ComponentFilter* in_f, + ComponentFilter** out_f ) +{ + ComponentFilter **new, *f; + int rc; + + new = out_f; + for ( f = in_f; f != NULL; f = f->cf_next ) { + rc = dup_comp_filter( op, bv, f, new ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + new = &(*new)->cf_next; + } + return LDAP_SUCCESS; +} + +int +get_len_of_next_assert_value ( struct berval* bv, char separator ) +{ + ber_len_t i = 0; + while (1) { + if ( (bv->bv_val[ i ] == separator) || ( i >= bv->bv_len) ) + break; + i++; + } + bv->bv_val += (i + 1); + bv->bv_len -= (i + 1); + return i; +} + +int +dup_comp_filter_item ( + Operation *op, + struct berval* assert_bv, + ComponentAssertion* in_ca, + ComponentAssertion** out_ca ) +{ + int len; + + if ( !in_ca->ca_comp_ref ) return SLAPD_DISCONNECT; + + *out_ca = op->o_tmpalloc( sizeof( ComponentAssertion ), op->o_tmpmemctx ); + if ( !(*out_ca) ) return LDAP_NO_MEMORY; + + (*out_ca)->ca_comp_data.cd_tree = NULL; + (*out_ca)->ca_comp_data.cd_mem_op = NULL; + + (*out_ca)->ca_comp_ref = dup_comp_ref ( op, in_ca->ca_comp_ref ); + (*out_ca)->ca_use_def = 0; + (*out_ca)->ca_ma_rule = in_ca->ca_ma_rule; + + (*out_ca)->ca_ma_value.bv_val = assert_bv->bv_val; + len = get_len_of_next_assert_value ( assert_bv, '$' ); + if ( len <= 0 ) return SLAPD_DISCONNECT; + (*out_ca)->ca_ma_value.bv_len = len; + + return LDAP_SUCCESS; +} + +int +dup_comp_filter ( + Operation* op, + struct berval *bv, + ComponentFilter *in_f, + ComponentFilter **out_f ) +{ + int rc; + ComponentFilter dup_f = {0}; + + if ( !in_f ) return LDAP_PROTOCOL_ERROR; + + switch ( in_f->cf_choice ) { + case LDAP_COMP_FILTER_AND: + rc = dup_comp_filter_list( op, bv, in_f->cf_and, &dup_f.cf_and); + dup_f.cf_choice = LDAP_COMP_FILTER_AND; + break; + case LDAP_COMP_FILTER_OR: + rc = dup_comp_filter_list( op, bv, in_f->cf_or, &dup_f.cf_or); + dup_f.cf_choice = LDAP_COMP_FILTER_OR; + break; + case LDAP_COMP_FILTER_NOT: + rc = dup_comp_filter( op, bv, in_f->cf_not, &dup_f.cf_not); + dup_f.cf_choice = LDAP_COMP_FILTER_NOT; + break; + case LDAP_COMP_FILTER_ITEM: + rc = dup_comp_filter_item( op, bv, in_f->cf_ca ,&dup_f.cf_ca ); + dup_f.cf_choice = LDAP_COMP_FILTER_ITEM; + break; + default: + rc = LDAP_PROTOCOL_ERROR; + } + + if ( rc == LDAP_SUCCESS ) { + *out_f = op->o_tmpalloc( sizeof(dup_f), op->o_tmpmemctx ); + **out_f = dup_f; + } + + return( rc ); +} + +int +get_aliased_filter_aa ( Operation* op, AttributeAssertion* a_assert, AttributeAliasing* aa, const char** text ) +{ + struct berval assert_bv; + + Debug( LDAP_DEBUG_FILTER, "get_aliased_filter\n", 0, 0, 0 ); + + if ( !aa->aa_cf ) + return LDAP_PROTOCOL_ERROR; + + assert_bv = a_assert->aa_value; + /* + * Duplicate aa->aa_cf to ma->ma_cf by replacing the + * the component assertion value in assert_bv + * Multiple values may be separated with '$' + */ + return dup_comp_filter ( op, &assert_bv, aa->aa_cf, &a_assert->aa_cf ); +} + +int +get_aliased_filter( Operation* op, + MatchingRuleAssertion* ma, AttributeAliasing* aa, + const char** text ) +{ + struct berval assert_bv; + + Debug( LDAP_DEBUG_FILTER, "get_aliased_filter\n", 0, 0, 0 ); + + if ( !aa->aa_cf ) return LDAP_PROTOCOL_ERROR; + + assert_bv = ma->ma_value; + /* Attribute Description is replaced with aliased one */ + ma->ma_desc = aa->aa_aliased_ad; + ma->ma_rule = aa->aa_mr; + /* + * Duplicate aa->aa_cf to ma->ma_cf by replacing the + * the component assertion value in assert_bv + * Multiple values may be separated with '$' + */ + return dup_comp_filter ( op, &assert_bv, aa->aa_cf, &ma->ma_cf ); +} + +int +get_comp_filter( Operation* op, struct berval* bv, + ComponentFilter** filt, const char **text ) +{ + ComponentAssertionValue cav; + int rc; + + Debug( LDAP_DEBUG_FILTER, "get_comp_filter\n", 0, 0, 0 ); + if ( (rc = slapd_ber2cav(bv, &cav) ) != LDAP_SUCCESS ) { + return rc; + } + rc = parse_comp_filter( op, &cav, filt, text ); + /* bv->bv_val = cav.cav_ptr; */ + + return rc; +} + +static void +eat_whsp( ComponentAssertionValue* cav ) +{ + for ( ; ( *cav->cav_ptr == ' ' ) && ( cav->cav_ptr < cav->cav_end ) ; ) { + cav->cav_ptr++; + } +} + +static int +cav_cur_len( ComponentAssertionValue* cav ) +{ + return cav->cav_end - cav->cav_ptr; +} + +static ber_tag_t +comp_first_element( ComponentAssertionValue* cav ) +{ + eat_whsp( cav ); + if ( cav_cur_len( cav ) >= 8 && strncmp( cav->cav_ptr, "item", 4 ) == 0 ) { + return LDAP_COMP_FILTER_ITEM; + + } else if ( cav_cur_len( cav ) >= 7 && + strncmp( cav->cav_ptr, "and", 3 ) == 0 ) + { + return LDAP_COMP_FILTER_AND; + + } else if ( cav_cur_len( cav ) >= 6 && + strncmp( cav->cav_ptr, "or" , 2 ) == 0 ) + { + return LDAP_COMP_FILTER_OR; + + } else if ( cav_cur_len( cav ) >= 7 && + strncmp( cav->cav_ptr, "not", 3 ) == 0 ) + { + return LDAP_COMP_FILTER_NOT; + + } else { + return LDAP_COMP_FILTER_UNDEFINED; + } +} + +static ber_tag_t +comp_next_element( ComponentAssertionValue* cav ) +{ + eat_whsp( cav ); + if ( *(cav->cav_ptr) == ',' ) { + /* move pointer to the next CA */ + cav->cav_ptr++; + return comp_first_element( cav ); + } + else return LDAP_COMP_FILTER_UNDEFINED; +} + +static int +get_comp_filter_list( Operation *op, ComponentAssertionValue *cav, + ComponentFilter** f, const char** text ) +{ + ComponentFilter **new; + int err; + ber_tag_t tag; + + Debug( LDAP_DEBUG_FILTER, "get_comp_filter_list\n", 0, 0, 0 ); + new = f; + for ( tag = comp_first_element( cav ); + tag != LDAP_COMP_FILTER_UNDEFINED; + tag = comp_next_element( cav ) ) + { + err = parse_comp_filter( op, cav, new, text ); + if ( err != LDAP_SUCCESS ) return ( err ); + new = &(*new)->cf_next; + } + *new = NULL; + + return( LDAP_SUCCESS ); +} + +static int +get_componentId( Operation *op, ComponentAssertionValue* cav, + ComponentId ** cid, const char** text ) +{ + ber_tag_t type; + ComponentId _cid; + int len; + + type = peek_componentId_type( cav ); + + Debug( LDAP_DEBUG_FILTER, "get_compId [%lu]\n", + (unsigned long) type, 0, 0 ); + len = 0; + _cid.ci_type = type; + _cid.ci_next = NULL; + switch ( type ) { + case LDAP_COMPREF_IDENTIFIER : + _cid.ci_val.ci_identifier.bv_val = cav->cav_ptr; + for( ;cav->cav_ptr[len] != ' ' && cav->cav_ptr[len] != '\0' && + cav->cav_ptr[len] != '.' && cav->cav_ptr[len] != '\"' ; len++ ); + _cid.ci_val.ci_identifier.bv_len = len; + cav->cav_ptr += len; + break; + case LDAP_COMPREF_FROM_BEGINNING : + for( ;cav->cav_ptr[len] != ' ' && cav->cav_ptr[len] != '\0' && + cav->cav_ptr[len] != '.' && cav->cav_ptr[len] != '\"' ; len++ ); + _cid.ci_val.ci_from_beginning = strtol( cav->cav_ptr, NULL, 0 ); + cav->cav_ptr += len; + break; + case LDAP_COMPREF_FROM_END : + for( ;cav->cav_ptr[len] != ' ' && cav->cav_ptr[len] != '\0' && + cav->cav_ptr[len] != '.' && cav->cav_ptr[len] != '\"' ; len++ ); + _cid.ci_val.ci_from_end = strtol( cav->cav_ptr, NULL, 0 ); + cav->cav_ptr += len; + break; + case LDAP_COMPREF_COUNT : + _cid.ci_val.ci_count = 0; + cav->cav_ptr++; + break; + case LDAP_COMPREF_CONTENT : + _cid.ci_val.ci_content = 1; + cav->cav_ptr += strlen("content"); + break; + case LDAP_COMPREF_SELECT : + if ( cav->cav_ptr[len] != '(' ) return LDAP_COMPREF_UNDEFINED; + for( ;cav->cav_ptr[len] != ' ' && cav->cav_ptr[len] != '\0' && + cav->cav_ptr[len] != '\"' && cav->cav_ptr[len] != ')' + ; len++ ); + _cid.ci_val.ci_select_value.bv_val = cav->cav_ptr + 1; + _cid.ci_val.ci_select_value.bv_len = len - 1 ; + cav->cav_ptr += len + 1; + break; + case LDAP_COMPREF_ALL : + _cid.ci_val.ci_all = '*'; + cav->cav_ptr++; + break; + default : + return LDAP_COMPREF_UNDEFINED; + } + + if ( op ) { + *cid = op->o_tmpalloc( sizeof( ComponentId ), op->o_tmpmemctx ); + } else { + *cid = SLAP_MALLOC( sizeof( ComponentId ) ); + } + if (*cid == NULL) { + return LDAP_NO_MEMORY; + } + **cid = _cid; + return LDAP_SUCCESS; +} + +static int +peek_componentId_type( ComponentAssertionValue* cav ) +{ + eat_whsp( cav ); + + if ( cav->cav_ptr[0] == '-' ) { + return LDAP_COMPREF_FROM_END; + + } else if ( cav->cav_ptr[0] == '(' ) { + return LDAP_COMPREF_SELECT; + + } else if ( cav->cav_ptr[0] == '*' ) { + return LDAP_COMPREF_ALL; + + } else if ( cav->cav_ptr[0] == '0' ) { + return LDAP_COMPREF_COUNT; + + } else if ( cav->cav_ptr[0] > '0' && cav->cav_ptr[0] <= '9' ) { + return LDAP_COMPREF_FROM_BEGINNING; + + } else if ( (cav->cav_end - cav->cav_ptr) >= 7 && + strncmp(cav->cav_ptr,"content",7) == 0 ) + { + return LDAP_COMPREF_CONTENT; + } else if ( (cav->cav_ptr[0] >= 'a' && cav->cav_ptr[0] <= 'z') || + (cav->cav_ptr[0] >= 'A' && cav->cav_ptr[0] <= 'Z') ) + { + return LDAP_COMPREF_IDENTIFIER; + } + + return LDAP_COMPREF_UNDEFINED; +} + +static ber_tag_t +comp_next_id( ComponentAssertionValue* cav ) +{ + if ( *(cav->cav_ptr) == '.' ) { + cav->cav_ptr++; + return LDAP_COMPREF_DEFINED; + } + + return LDAP_COMPREF_UNDEFINED; +} + + + +static int +get_component_reference( + Operation *op, + ComponentAssertionValue* cav, + ComponentReference** cr, + const char** text ) +{ + int rc, count = 0; + ber_int_t type; + ComponentReference* ca_comp_ref; + ComponentId** cr_list; + char* start, *end; + + eat_whsp( cav ); + + start = cav->cav_ptr; + if ( ( rc = strip_cav_str( cav,"\"") ) != LDAP_SUCCESS ) return rc; + if ( op ) { + ca_comp_ref = op->o_tmpalloc( sizeof( ComponentReference ), + op->o_tmpmemctx ); + } else { + ca_comp_ref = SLAP_MALLOC( sizeof( ComponentReference ) ); + } + + if ( !ca_comp_ref ) return LDAP_NO_MEMORY; + + cr_list = &ca_comp_ref->cr_list; + + for ( type = peek_componentId_type( cav ) ; type != LDAP_COMPREF_UNDEFINED + ; type = comp_next_id( cav ), count++ ) + { + rc = get_componentId( op, cav, cr_list, text ); + if ( rc == LDAP_SUCCESS ) { + if ( count == 0 ) ca_comp_ref->cr_curr = ca_comp_ref->cr_list; + cr_list = &(*cr_list)->ci_next; + + } else if ( rc == LDAP_COMPREF_UNDEFINED ) { + if ( op ) { + op->o_tmpfree( ca_comp_ref , op->o_tmpmemctx ); + } else { + free( ca_comp_ref ); + } + return rc; + } + } + ca_comp_ref->cr_len = count; + end = cav->cav_ptr; + if ( ( rc = strip_cav_str( cav,"\"") ) != LDAP_SUCCESS ) { + if ( op ) { + op->o_tmpfree( ca_comp_ref , op->o_tmpmemctx ); + } else { + free( ca_comp_ref ); + } + return rc; + } + + *cr = ca_comp_ref; + **cr = *ca_comp_ref; + + (*cr)->cr_string.bv_val = start; + (*cr)->cr_string.bv_len = end - start + 1; + + return rc; +} + +int +insert_component_reference( + ComponentReference *cr, + ComponentReference** cr_list) +{ + if ( !cr ) return LDAP_PARAM_ERROR; + + if ( !(*cr_list) ) { + *cr_list = cr; + cr->cr_next = NULL; + } else { + cr->cr_next = *cr_list; + *cr_list = cr; + } + return LDAP_SUCCESS; +} + +/* + * If there is '.' in the name of a given attribute + * the first '.'- following characters are considered + * as a component reference of the attribute + * EX) userCertificate.toBeSigned.serialNumber + * attribute : userCertificate + * component reference : toBeSigned.serialNumber + */ +int +is_component_reference( char* attr ) { + int i; + for ( i=0; attr[i] != '\0' ; i++ ) { + if ( attr[i] == '.' ) return (1); + } + return (0); +} + +int +extract_component_reference( + char* attr, + ComponentReference** cr ) +{ + int i, rc; + char* cr_ptr; + int cr_len; + ComponentAssertionValue cav; + char text[1][128]; + + for ( i=0; attr[i] != '\0' ; i++ ) { + if ( attr[i] == '.' ) break; + } + + if (attr[i] != '.' ) return LDAP_PARAM_ERROR; + attr[i] = '\0'; + + cr_ptr = attr + i + 1 ; + cr_len = strlen ( cr_ptr ); + if ( cr_len <= 0 ) return LDAP_PARAM_ERROR; + + /* enclosed between double quotes*/ + cav.cav_ptr = cav.cav_buf = ch_malloc (cr_len+2); + memcpy( cav.cav_buf+1, cr_ptr, cr_len ); + cav.cav_buf[0] = '"'; + cav.cav_buf[cr_len+1] = '"'; + cav.cav_end = cr_ptr + cr_len + 2; + + rc = get_component_reference ( NULL, &cav, cr, (const char**)text ); + if ( rc != LDAP_SUCCESS ) return rc; + (*cr)->cr_string.bv_val = cav.cav_buf; + (*cr)->cr_string.bv_len = cr_len + 2; + + return LDAP_SUCCESS; +} + +static int +get_ca_use_default( Operation *op, + ComponentAssertionValue* cav, + int* ca_use_def, const char** text ) +{ + strip_cav_str( cav, "useDefaultValues" ); + + if ( peek_cav_str( cav, "TRUE" ) == LDAP_SUCCESS ) { + strip_cav_str( cav, "TRUE" ); + *ca_use_def = 1; + + } else if ( peek_cav_str( cav, "FALSE" ) == LDAP_SUCCESS ) { + strip_cav_str( cav, "FALSE" ); + *ca_use_def = 0; + + } else { + return LDAP_INVALID_SYNTAX; + } + + return LDAP_SUCCESS; +} + +static int +get_matching_rule( Operation *op, ComponentAssertionValue* cav, + MatchingRule** mr, const char** text ) +{ + int count = 0; + struct berval rule_text = { 0L, NULL }; + + eat_whsp( cav ); + + for ( ; ; count++ ) { + if ( cav->cav_ptr[count] == ' ' || cav->cav_ptr[count] == ',' || + cav->cav_ptr[count] == '\0' || cav->cav_ptr[count] == '{' || + cav->cav_ptr[count] == '}' || cav->cav_ptr[count] == '\n' ) + { + break; + } + } + + if ( count == 0 ) { + *text = "component matching rule not recognized"; + return LDAP_INAPPROPRIATE_MATCHING; + } + + rule_text.bv_len = count; + rule_text.bv_val = cav->cav_ptr; + *mr = mr_bvfind( &rule_text ); + cav->cav_ptr += count; + Debug( LDAP_DEBUG_FILTER, "get_matching_rule: %s\n", + (*mr)->smr_mrule.mr_oid, 0, 0 ); + if ( *mr == NULL ) { + *text = "component matching rule not recognized"; + return LDAP_INAPPROPRIATE_MATCHING; + } + return LDAP_SUCCESS; +} + +static int +get_GSER_value( ComponentAssertionValue* cav, struct berval* bv ) +{ + int count, sequent_dquote, unclosed_brace, succeed; + + eat_whsp( cav ); + /* + * Four cases of GSER <Values> + * 1) "..." : + * StringVal, GeneralizedTimeVal, UTCTimeVal, ObjectDescriptorVal + * 2) '...'B or '...'H : + * BitStringVal, OctetStringVal + * 3) {...} : + * SEQUENCE, SEQUENCEOF, SETOF, SET, CHOICE + * 4) Between two white spaces + * INTEGER, BOOLEAN, NULL,ENUMERATE, etc + */ + + succeed = 0; + if ( cav->cav_ptr[0] == '"' ) { + for( count = 1, sequent_dquote = 0 ; ; count++ ) { + /* In order to find escaped double quote */ + if ( cav->cav_ptr[count] == '"' ) sequent_dquote++; + else sequent_dquote = 0; + + if ( cav->cav_ptr[count] == '\0' || + (cav->cav_ptr+count) > cav->cav_end ) + { + break; + } + + if ( ( cav->cav_ptr[count] == '"' && + cav->cav_ptr[count-1] != '"') || + ( sequent_dquote > 2 && (sequent_dquote%2) == 1 ) ) + { + succeed = 1; + break; + } + } + + if ( !succeed || cav->cav_ptr[count] != '"' ) { + return LDAP_FILTER_ERROR; + } + + bv->bv_val = cav->cav_ptr + 1; + bv->bv_len = count - 1; /* exclude '"' */ + + } else if ( cav->cav_ptr[0] == '\'' ) { + for( count = 1 ; ; count++ ) { + if ( cav->cav_ptr[count] == '\0' || + (cav->cav_ptr+count) > cav->cav_end ) + { + break; + } + if ((cav->cav_ptr[count-1] == '\'' && cav->cav_ptr[count] == 'B') || + (cav->cav_ptr[count-1] == '\'' && cav->cav_ptr[count] == 'H') ) + { + succeed = 1; + break; + } + } + + if ( !succeed || + !(cav->cav_ptr[count] == 'H' || cav->cav_ptr[count] == 'B') ) + { + return LDAP_FILTER_ERROR; + } + + bv->bv_val = cav->cav_ptr + 1;/*the next to '"' */ + bv->bv_len = count - 2;/* exclude "'H" or "'B" */ + + } else if ( cav->cav_ptr[0] == '{' ) { + for( count = 1, unclosed_brace = 1 ; ; count++ ) { + if ( cav->cav_ptr[count] == '{' ) unclosed_brace++; + if ( cav->cav_ptr[count] == '}' ) unclosed_brace--; + + if ( cav->cav_ptr[count] == '\0' || + (cav->cav_ptr+count) > cav->cav_end ) + { + break; + } + if ( unclosed_brace == 0 ) { + succeed = 1; + break; + } + } + + if ( !succeed || cav->cav_ptr[count] != '}' ) return LDAP_FILTER_ERROR; + + bv->bv_val = cav->cav_ptr + 1;/*the next to '"' */ + bv->bv_len = count - 1;/* exclude "'B" */ + + } else { + succeed = 1; + /*Find following white space where the value is ended*/ + for( count = 1 ; ; count++ ) { + if ( cav->cav_ptr[count] == '\0' || + cav->cav_ptr[count] == ' ' || cav->cav_ptr[count] == '}' || + cav->cav_ptr[count] == '{' || + (cav->cav_ptr+count) > cav->cav_end ) + { + break; + } + } + bv->bv_val = cav->cav_ptr; + bv->bv_len = count; + } + + cav->cav_ptr += bv->bv_len; + return LDAP_SUCCESS; +} + +static int +get_matching_value( Operation *op, ComponentAssertion* ca, + ComponentAssertionValue* cav, struct berval* bv, + const char** text ) +{ + if ( !(ca->ca_ma_rule->smr_usage & (SLAP_MR_COMPONENT)) ) { + if ( get_GSER_value( cav, bv ) != LDAP_SUCCESS ) { + return LDAP_FILTER_ERROR; + } + + } else { + /* embeded componentFilterMatch Description */ + bv->bv_val = cav->cav_ptr; + bv->bv_len = cav_cur_len( cav ); + } + + return LDAP_SUCCESS; +} + +/* Don't move the position pointer, just peek given string */ +static int +peek_cav_str( ComponentAssertionValue* cav, char* str ) +{ + eat_whsp( cav ); + if ( cav_cur_len( cav ) >= strlen( str ) && + strncmp( cav->cav_ptr, str, strlen( str ) ) == 0 ) + { + return LDAP_SUCCESS; + } + + return LDAP_INVALID_SYNTAX; +} + +static int +strip_cav_str( ComponentAssertionValue* cav, char* str) +{ + eat_whsp( cav ); + if ( cav_cur_len( cav ) >= strlen( str ) && + strncmp( cav->cav_ptr, str, strlen( str ) ) == 0 ) + { + cav->cav_ptr += strlen( str ); + return LDAP_SUCCESS; + } + + return LDAP_INVALID_SYNTAX; +} + +/* + * TAG : "item", "and", "or", "not" + */ +static ber_tag_t +strip_cav_tag( ComponentAssertionValue* cav ) +{ + int rc; + + eat_whsp( cav ); + if ( cav_cur_len( cav ) >= 8 && strncmp( cav->cav_ptr, "item", 4 ) == 0 ) { + if ( strip_cav_str( cav , "item:" )) + goto fail; + return LDAP_COMP_FILTER_ITEM; + + } else if ( cav_cur_len( cav ) >= 7 && + strncmp( cav->cav_ptr, "and", 3 ) == 0 ) + { + if ( strip_cav_str( cav , "and:" )) + goto fail; + return LDAP_COMP_FILTER_AND; + + } else if ( cav_cur_len( cav ) >= 6 && + strncmp( cav->cav_ptr, "or" , 2 ) == 0 ) + { + if ( strip_cav_str( cav , "or:" )) + goto fail; + return LDAP_COMP_FILTER_OR; + + } else if ( cav_cur_len( cav ) >= 7 && + strncmp( cav->cav_ptr, "not", 3 ) == 0 ) + { + if ( strip_cav_str( cav , "not:" )) + goto fail; + return LDAP_COMP_FILTER_NOT; + } + +fail: + return LBER_ERROR; +} + +/* + * when encoding, "item" is denotation of ComponentAssertion + * ComponentAssertion :: SEQUENCE { + * component ComponentReference (SIZE(1..MAX)) OPTIONAL, + * useDefaultValues BOOLEAN DEFAULT TRUE, + * rule MATCHING-RULE.&id, + * value MATCHING-RULE.&AssertionType } + */ +static int +get_item( Operation *op, ComponentAssertionValue* cav, ComponentAssertion** ca, + const char** text ) +{ + int rc; + ComponentAssertion* _ca; + struct berval value; + MatchingRule* mr; + + Debug( LDAP_DEBUG_FILTER, "get_item \n", 0, 0, 0 ); + if ( op ) + _ca = op->o_tmpalloc( sizeof( ComponentAssertion ), op->o_tmpmemctx ); + else + _ca = SLAP_MALLOC( sizeof( ComponentAssertion ) ); + + if ( !_ca ) return LDAP_NO_MEMORY; + + _ca->ca_comp_data.cd_tree = NULL; + _ca->ca_comp_data.cd_mem_op = NULL; + + rc = peek_cav_str( cav, "component" ); + if ( rc == LDAP_SUCCESS ) { + strip_cav_str( cav, "component" ); + rc = get_component_reference( op, cav, &_ca->ca_comp_ref, text ); + if ( rc != LDAP_SUCCESS ) { + if ( op ) + op->o_tmpfree( _ca, op->o_tmpmemctx ); + else + free( _ca ); + return LDAP_INVALID_SYNTAX; + } + if ( ( rc = strip_cav_str( cav,",") ) != LDAP_SUCCESS ) + return rc; + } else { + _ca->ca_comp_ref = NULL; + } + + rc = peek_cav_str( cav, "useDefaultValues"); + if ( rc == LDAP_SUCCESS ) { + rc = get_ca_use_default( op, cav, &_ca->ca_use_def, text ); + if ( rc != LDAP_SUCCESS ) { + if ( op ) + op->o_tmpfree( _ca, op->o_tmpmemctx ); + else + free( _ca ); + return LDAP_INVALID_SYNTAX; + } + if ( ( rc = strip_cav_str( cav,",") ) != LDAP_SUCCESS ) + return rc; + } + else _ca->ca_use_def = 1; + + if ( !( strip_cav_str( cav, "rule" ) == LDAP_SUCCESS && + get_matching_rule( op, cav , &_ca->ca_ma_rule, text ) == LDAP_SUCCESS )) { + if ( op ) + op->o_tmpfree( _ca, op->o_tmpmemctx ); + else + free( _ca ); + return LDAP_INAPPROPRIATE_MATCHING; + } + + if ( ( rc = strip_cav_str( cav,",") ) != LDAP_SUCCESS ) + return rc; + if ( !(strip_cav_str( cav, "value" ) == LDAP_SUCCESS && + get_matching_value( op, _ca, cav,&value ,text ) == LDAP_SUCCESS )) { + if ( op ) + op->o_tmpfree( _ca, op->o_tmpmemctx ); + else + free( _ca ); + return LDAP_INVALID_SYNTAX; + } + + /* + * Normalize the value of this component assertion when the matching + * rule is one of existing matching rules + */ + mr = _ca->ca_ma_rule; + if ( op && !(mr->smr_usage & (SLAP_MR_COMPONENT)) && mr->smr_normalize ) { + + value.bv_val[value.bv_len] = '\0'; + rc = mr->smr_normalize ( + SLAP_MR_VALUE_OF_ASSERTION_SYNTAX, + NULL, mr, + &value, &_ca->ca_ma_value, op->o_tmpmemctx ); + if ( rc != LDAP_SUCCESS ) + return rc; + } + else + _ca->ca_ma_value = value; + /* + * Validate the value of this component assertion + */ + if ( op && mr->smr_syntax->ssyn_validate( mr->smr_syntax, &_ca->ca_ma_value) != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + + + /* componentFilterMatch contains componentFilterMatch in it */ + if ( strcmp(_ca->ca_ma_rule->smr_mrule.mr_oid, OID_COMP_FILTER_MATCH ) == 0) { + struct berval bv; + bv.bv_val = cav->cav_ptr; + bv.bv_len = cav_cur_len( cav ); + rc = get_comp_filter( op, &bv,(ComponentFilter**)&_ca->ca_cf, text ); + if ( rc != LDAP_SUCCESS ) { + if ( op ) + op->o_tmpfree( _ca, op->o_tmpmemctx ); + else + free( _ca ); + return rc; + } + cav->cav_ptr = bv.bv_val; + assert( cav->cav_end >= bv.bv_val ); + } + + *ca = _ca; + return LDAP_SUCCESS; +} + +static int +parse_comp_filter( Operation* op, ComponentAssertionValue* cav, + ComponentFilter** filt, const char** text ) +{ + /* + * A component filter looks like this coming in: + * Filter ::= CHOICE { + * item [0] ComponentAssertion, + * and [1] SEQUENCE OF ComponentFilter, + * or [2] SEQUENCE OF ComponentFilter, + * not [3] ComponentFilter, + * } + */ + + ber_tag_t tag; + int err = LDAP_SUCCESS; + ComponentFilter f; + /* TAG : item, and, or, not in RFC 4515 */ + tag = strip_cav_tag( cav ); + + if ( tag == LBER_ERROR ) { + *text = "error decoding comp filter"; + return LDAP_PROTOCOL_ERROR; + } + + if ( tag != LDAP_COMP_FILTER_NOT ) { + err = strip_cav_str( cav, "{"); + if ( err ) + goto invalid; + } + + f.cf_next = NULL; + f.cf_choice = tag; + + switch ( f.cf_choice ) { + case LDAP_COMP_FILTER_AND: + Debug( LDAP_DEBUG_FILTER, "LDAP_COMP_FILTER_AND\n", 0, 0, 0 ); + err = get_comp_filter_list( op, cav, &f.cf_and, text ); + if ( err != LDAP_SUCCESS ) { + break; + } + if ( f.cf_and == NULL ) { + f.cf_choice = SLAPD_FILTER_COMPUTED; + f.cf_result = LDAP_COMPARE_TRUE; + } + break; + + case LDAP_COMP_FILTER_OR: + Debug( LDAP_DEBUG_FILTER, "LDAP_COMP_FILTER_OR\n", 0, 0, 0 ); + err = get_comp_filter_list( op, cav, &f.cf_or, text ); + if ( err != LDAP_SUCCESS ) { + break; + } + if ( f.cf_or == NULL ) { + f.cf_choice = SLAPD_FILTER_COMPUTED; + f.cf_result = LDAP_COMPARE_FALSE; + } + /* no assert - list could be empty */ + break; + + case LDAP_COMP_FILTER_NOT: + Debug( LDAP_DEBUG_FILTER, "LDAP_COMP_FILTER_NOT\n", 0, 0, 0 ); + err = parse_comp_filter( op, cav, &f.cf_not, text ); + if ( err != LDAP_SUCCESS ) { + break; + } + + assert( f.cf_not != NULL ); + if ( f.cf_not->cf_choice == SLAPD_FILTER_COMPUTED ) { + int fresult = f.cf_not->cf_result; + f.cf_choice = SLAPD_FILTER_COMPUTED; + op->o_tmpfree( f.cf_not, op->o_tmpmemctx ); + f.cf_not = NULL; + + switch ( fresult ) { + case LDAP_COMPARE_TRUE: + f.cf_result = LDAP_COMPARE_FALSE; + break; + case LDAP_COMPARE_FALSE: + f.cf_result = LDAP_COMPARE_TRUE; + break; + default: ; + /* (!Undefined) is Undefined */ + } + } + break; + + case LDAP_COMP_FILTER_ITEM: + Debug( LDAP_DEBUG_FILTER, "LDAP_COMP_FILTER_ITEM\n", 0, 0, 0 ); + err = get_item( op, cav, &f.cf_ca, text ); + if ( err != LDAP_SUCCESS ) { + break; + } + + assert( f.cf_ca != NULL ); + break; + + default: + f.cf_choice = SLAPD_FILTER_COMPUTED; + f.cf_result = SLAPD_COMPARE_UNDEFINED; + break; + } + +invalid: + if ( err != LDAP_SUCCESS && err != SLAPD_DISCONNECT ) { + *text = "Component Filter Syntax Error"; + return err; + } + + if ( tag != LDAP_COMP_FILTER_NOT ) + err = strip_cav_str( cav, "}"); + + if ( err == LDAP_SUCCESS ) { + if ( op ) { + *filt = op->o_tmpalloc( sizeof(f), op->o_tmpmemctx ); + } else { + *filt = SLAP_MALLOC( sizeof(f) ); + } + if ( *filt == NULL ) { + return LDAP_NO_MEMORY; + } + **filt = f; + } + + return( err ); +} + +static int +test_comp_filter_and( + Syntax *syn, + ComponentSyntaxInfo *a, + ComponentFilter *flist ) +{ + ComponentFilter *f; + int rtn = LDAP_COMPARE_TRUE; + + for ( f = flist ; f != NULL; f = f->cf_next ) { + int rc = test_comp_filter( syn, a, f ); + if ( rc == LDAP_COMPARE_FALSE ) { + rtn = rc; + break; + } + + if ( rc != LDAP_COMPARE_TRUE ) { + rtn = rc; + } + } + + return rtn; +} + +static int +test_comp_filter_or( + Syntax *syn, + ComponentSyntaxInfo *a, + ComponentFilter *flist ) +{ + ComponentFilter *f; + int rtn = LDAP_COMPARE_TRUE; + + for ( f = flist ; f != NULL; f = f->cf_next ) { + int rc = test_comp_filter( syn, a, f ); + if ( rc == LDAP_COMPARE_TRUE ) { + rtn = rc; + break; + } + + if ( rc != LDAP_COMPARE_FALSE ) { + rtn = rc; + } + } + + return rtn; +} + +int +csi_value_match( MatchingRule *mr, struct berval* bv_attr, + struct berval* bv_assert ) +{ + int rc; + int match; + + assert( mr != NULL ); + assert( !(mr->smr_usage & SLAP_MR_COMPONENT) ); + + if( !mr->smr_match ) return LDAP_INAPPROPRIATE_MATCHING; + + rc = (mr->smr_match)( &match, 0, NULL /*ad->ad_type->sat_syntax*/, + mr, bv_attr, bv_assert ); + + if ( rc != LDAP_SUCCESS ) return rc; + + return match ? LDAP_COMPARE_FALSE : LDAP_COMPARE_TRUE; +} + +/* + * return codes : LDAP_COMPARE_TRUE, LDAP_COMPARE_FALSE + */ +static int +test_comp_filter_item( + Syntax *syn, + ComponentSyntaxInfo *csi_attr, + ComponentAssertion *ca ) +{ + int rc; + void *attr_nm, *assert_nm; + + if ( strcmp(ca->ca_ma_rule->smr_mrule.mr_oid, + OID_COMP_FILTER_MATCH ) == 0 && ca->ca_cf ) { + /* componentFilterMatch inside of componentFilterMatch */ + rc = test_comp_filter( syn, csi_attr, ca->ca_cf ); + return rc; + } + + /* Memory for storing will-be-extracted attribute values */ + attr_nm = nibble_mem_allocator ( 1024*4 , 1024 ); + if ( !attr_nm ) return LDAP_PROTOCOL_ERROR; + + /* Memory for storing component assertion values */ + if( !ca->ca_comp_data.cd_mem_op ) { + assert_nm = nibble_mem_allocator ( 256, 64 ); + if ( !assert_nm ) { + nibble_mem_free ( attr_nm ); + return LDAP_PROTOCOL_ERROR; + } + ca->ca_comp_data.cd_mem_op = assert_nm; + + } else { + assert_nm = ca->ca_comp_data.cd_mem_op; + } + + /* component reference initialization */ + if ( ca->ca_comp_ref ) { + ca->ca_comp_ref->cr_curr = ca->ca_comp_ref->cr_list; + } + rc = test_components( attr_nm, assert_nm, csi_attr, ca ); + + /* free memory used for storing extracted attribute value */ + nibble_mem_free ( attr_nm ); + return rc; +} + +static int +test_comp_filter( + Syntax *syn, + ComponentSyntaxInfo *a, + ComponentFilter *f ) +{ + int rc; + + if ( !f ) return LDAP_PROTOCOL_ERROR; + + Debug( LDAP_DEBUG_FILTER, "test_comp_filter\n", 0, 0, 0 ); + switch ( f->cf_choice ) { + case SLAPD_FILTER_COMPUTED: + rc = f->cf_result; + break; + case LDAP_COMP_FILTER_AND: + rc = test_comp_filter_and( syn, a, f->cf_and ); + break; + case LDAP_COMP_FILTER_OR: + rc = test_comp_filter_or( syn, a, f->cf_or ); + break; + case LDAP_COMP_FILTER_NOT: + rc = test_comp_filter( syn, a, f->cf_not ); + + switch ( rc ) { + case LDAP_COMPARE_TRUE: + rc = LDAP_COMPARE_FALSE; + break; + case LDAP_COMPARE_FALSE: + rc = LDAP_COMPARE_TRUE; + break; + } + break; + case LDAP_COMP_FILTER_ITEM: + rc = test_comp_filter_item( syn, a, f->cf_ca ); + break; + default: + rc = LDAP_PROTOCOL_ERROR; + } + + return( rc ); +} + +static void +free_comp_filter_list( ComponentFilter* f ) +{ + ComponentFilter* tmp; + for ( tmp = f; tmp; tmp = tmp->cf_next ) { + free_comp_filter( tmp ); + } +} + +static void +free_comp_filter( ComponentFilter* f ) +{ + if ( !f ) { + Debug( LDAP_DEBUG_FILTER, + "free_comp_filter: Invalid filter so failed to release memory\n", + 0, 0, 0 ); + return; + } + switch ( f->cf_choice ) { + case LDAP_COMP_FILTER_AND: + case LDAP_COMP_FILTER_OR: + free_comp_filter_list( f->cf_any ); + break; + case LDAP_COMP_FILTER_NOT: + free_comp_filter( f->cf_any ); + break; + case LDAP_COMP_FILTER_ITEM: + if ( nibble_mem_free && f->cf_ca->ca_comp_data.cd_mem_op ) { + nibble_mem_free( f->cf_ca->ca_comp_data.cd_mem_op ); + } + break; + default: + break; + } +} + +void +component_free( ComponentFilter *f ) { + free_comp_filter( f ); +} + +void +free_ComponentData( Attribute *a ) { + if ( a->a_comp_data->cd_mem_op ) + component_destructor( a->a_comp_data->cd_mem_op ); + free ( a->a_comp_data ); + a->a_comp_data = NULL; +} +#endif diff --git a/servers/slapd/component.h b/servers/slapd/component.h new file mode 100644 index 0000000..e6a24ac --- /dev/null +++ b/servers/slapd/component.h @@ -0,0 +1,76 @@ +/* component.h */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2021 The OpenLDAP Foundation. + * Portions Copyright 2004 by IBM Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#ifndef _H_SLAPD_COMPONENT +#define _H_SLAPD_COMPONENT + +#include "portable.h" + +#include <ac/string.h> +#include <ac/socket.h> +#include <ldap_pvt.h> +#include "lutil.h" +#include <ldap.h> +#include "slap.h" + +typedef enum { ASN_BASIC, ASN_COMPOSITE } AsnType; +/* + * Decoder Modes + * Different operation is required to handle Decoding(2), Extracted Component + * decoding(0), ANY DEFINED TYPe(2) + * b0 : Component Alloc(yes) + * Constructed type : Component Alloc (Yes) + * Primitive type : Component Alloc (Yes) + * set to mode 2 in inner decoders + * b1 : Component Alloc (No) + * Constructed type : Component Alloc (No) + * Primitive type : Component Alloc (No) + * set to mode 2 in inner decoders + * b2 : Default Mode + * Constructed type : Component Alloc (Yes) + * Primitive type : Component Alloc (No) + * in addition to above modes, the 4th bit has special meaning, + * b4 : if the 4th bit is clear, DecxxxContent is called + * b4 : if the 4th bit is set, Decxxx is called, then it is cleared. + */ +#define DEC_ALLOC_MODE_0 0x01 +#define DEC_ALLOC_MODE_1 0x02 +#define DEC_ALLOC_MODE_2 0x04 +#define CALL_TAG_DECODER 0x08 +#define CALL_CONTENT_DECODER ~0x08 +/* + * For Attribute Aliasing + */ +#define MAX_ALIASING_ENTRY 128 +typedef struct comp_attribute_aliasing { + AttributeDescription* aa_aliasing_ad; + AttributeDescription* aa_aliased_ad; + ComponentFilter* aa_cf; + MatchingRule* aa_mr; + char* aa_cf_str; +} AttributeAliasing; + +typedef struct comp_matchingrule_aliasing { + MatchingRule* mra_aliasing_attr; + MatchingRule* mra_aliased_attr; + AttributeDescription* mra_attr; + ComponentFilter* mra_cf; + MatchingRule* mra_mr; + char* mra_cf_str; +} MatchingRuleAliasing; + +#endif diff --git a/servers/slapd/config.c b/servers/slapd/config.c new file mode 100644 index 0000000..bd68a24 --- /dev/null +++ b/servers/slapd/config.c @@ -0,0 +1,2490 @@ +/* config.c - configuration file handling routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/ctype.h> +#include <ac/signal.h> +#include <ac/socket.h> +#include <ac/errno.h> +#include <ac/unistd.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#ifndef S_ISREG +#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) +#endif + +#include "slap.h" +#ifdef LDAP_SLAPI +#include "slapi/slapi.h" +#endif +#include "lutil.h" +#include "lutil_ldap.h" +#include "config.h" + +#ifdef _WIN32 +#define LUTIL_ATOULX lutil_atoullx +#define Z "I" +#else +#define LUTIL_ATOULX lutil_atoulx +#define Z "z" +#endif + +#define ARGS_STEP 512 + +/* + * defaults for various global variables + */ +slap_mask_t global_allows = 0; +slap_mask_t global_disallows = 0; +int global_gentlehup = 0; +int global_idletimeout = 0; +int global_writetimeout = 0; +char *global_host = NULL; +struct berval global_host_bv = BER_BVNULL; +char *global_realm = NULL; +char *sasl_host = NULL; +char **default_passwd_hash = NULL; +struct berval default_search_base = BER_BVNULL; +struct berval default_search_nbase = BER_BVNULL; + +ber_len_t sockbuf_max_incoming = SLAP_SB_MAX_INCOMING_DEFAULT; +ber_len_t sockbuf_max_incoming_auth= SLAP_SB_MAX_INCOMING_AUTH; + +int slap_conn_max_pending = SLAP_CONN_MAX_PENDING_DEFAULT; +int slap_conn_max_pending_auth = SLAP_CONN_MAX_PENDING_AUTH; + +char *slapd_pid_file = NULL; +char *slapd_args_file = NULL; + +int use_reverse_lookup = 0; + +#ifdef LDAP_SLAPI +int slapi_plugins_used = 0; +#endif + +static int fp_getline(FILE *fp, ConfigArgs *c); +static void fp_getline_init(ConfigArgs *c); + +static char *strtok_quote(char *line, char *sep, char **quote_ptr, int *inquote); +static char *strtok_quote_ldif(char **line); + +ConfigArgs * +new_config_args( BackendDB *be, const char *fname, int lineno, int argc, char **argv ) +{ + ConfigArgs *c; + c = ch_calloc( 1, sizeof( ConfigArgs ) ); + if ( c == NULL ) return(NULL); + c->be = be; + c->fname = fname; + c->argc = argc; + c->argv = argv; + c->lineno = lineno; + snprintf( c->log, sizeof( c->log ), "%s: line %d", fname, lineno ); + return(c); +} + +void +init_config_argv( ConfigArgs *c ) +{ + c->argv = ch_calloc( ARGS_STEP + 1, sizeof( *c->argv ) ); + c->argv_size = ARGS_STEP + 1; +} + +ConfigTable *config_find_keyword(ConfigTable *Conf, ConfigArgs *c) { + int i; + + for(i = 0; Conf[i].name; i++) + if( (Conf[i].length && (!strncasecmp(c->argv[0], Conf[i].name, Conf[i].length))) || + (!strcasecmp(c->argv[0], Conf[i].name)) ) break; + if ( !Conf[i].name ) return NULL; + return Conf+i; +} + +int config_check_vals(ConfigTable *Conf, ConfigArgs *c, int check_only ) { + int rc, arg_user, arg_type, arg_syn, iarg; + unsigned uiarg; + long larg; + size_t ularg; + ber_len_t barg; + + if(Conf->arg_type == ARG_IGNORED) { + Debug(LDAP_DEBUG_CONFIG, "%s: keyword <%s> ignored\n", + c->log, Conf->name, 0); + return(0); + } + arg_type = Conf->arg_type & ARGS_TYPES; + arg_user = Conf->arg_type & ARGS_USERLAND; + arg_syn = Conf->arg_type & ARGS_SYNTAX; + + if((arg_type == ARG_DN) && c->argc == 1) { + c->argc = 2; + c->argv[1] = ""; + } + if(Conf->min_args && (c->argc < Conf->min_args)) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> missing <%s> argument", + c->argv[0], Conf->what ? Conf->what : "" ); + Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: keyword %s\n", c->log, c->cr_msg, 0 ); + return(ARG_BAD_CONF); + } + if(Conf->max_args && (c->argc > Conf->max_args)) { + char *ignored = " ignored"; + + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> extra cruft after <%s>", + c->argv[0], Conf->what ); + + ignored = ""; + Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s%s.\n", + c->log, c->cr_msg, ignored ); + return(ARG_BAD_CONF); + } + if((arg_syn & ARG_DB) && !c->be) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> only allowed within database declaration", + c->argv[0] ); + Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: keyword %s\n", + c->log, c->cr_msg, 0); + return(ARG_BAD_CONF); + } + if((arg_syn & ARG_PRE_BI) && c->bi) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> must occur before any backend %sdeclaration", + c->argv[0], (arg_syn & ARG_PRE_DB) ? "or database " : "" ); + Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: keyword %s\n", + c->log, c->cr_msg, 0 ); + return(ARG_BAD_CONF); + } + if((arg_syn & ARG_PRE_DB) && c->be && c->be != frontendDB) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> must occur before any database declaration", + c->argv[0] ); + Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: keyword %s\n", + c->log, c->cr_msg, 0); + return(ARG_BAD_CONF); + } + if((arg_syn & ARG_PAREN) && *c->argv[1] != '(' /*')'*/) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> old format not supported", c->argv[0] ); + Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n", + c->log, c->cr_msg, 0); + return(ARG_BAD_CONF); + } + if(arg_type && !Conf->arg_item && !(arg_syn & ARG_OFFSET)) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> invalid config_table, arg_item is NULL", + c->argv[0] ); + Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n", + c->log, c->cr_msg, 0); + return(ARG_BAD_CONF); + } + c->type = arg_user; + memset(&c->values, 0, sizeof(c->values)); + if(arg_type == ARG_STRING) { + assert( c->argc == 2 ); + if ( !check_only ) + c->value_string = ch_strdup(c->argv[1]); + } else if(arg_type == ARG_BERVAL) { + assert( c->argc == 2 ); + if ( !check_only ) + ber_str2bv( c->argv[1], 0, 1, &c->value_bv ); + } else if(arg_type == ARG_DN) { + struct berval bv; + assert( c->argc == 2 ); + ber_str2bv( c->argv[1], 0, 0, &bv ); + rc = dnPrettyNormal( NULL, &bv, &c->value_dn, &c->value_ndn, NULL ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> invalid DN %d (%s)", + c->argv[0], rc, ldap_err2string( rc )); + Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n" , c->log, c->cr_msg, 0); + return(ARG_BAD_CONF); + } + if ( check_only ) { + ch_free( c->value_ndn.bv_val ); + ch_free( c->value_dn.bv_val ); + } + } else if(arg_type == ARG_ATDESC) { + const char *text = NULL; + assert( c->argc == 2 ); + c->value_ad = NULL; + rc = slap_str2ad( c->argv[1], &c->value_ad, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> invalid AttributeDescription %d (%s)", + c->argv[0], rc, text ); + Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n" , c->log, c->cr_msg, 0); + return(ARG_BAD_CONF); + } + } else { /* all numeric */ + int j; + iarg = 0; larg = 0; barg = 0; + switch(arg_type) { + case ARG_INT: + assert( c->argc == 2 ); + if ( lutil_atoix( &iarg, c->argv[1], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> unable to parse \"%s\" as int", + c->argv[0], c->argv[1] ); + Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n", + c->log, c->cr_msg, 0); + return(ARG_BAD_CONF); + } + break; + case ARG_UINT: + assert( c->argc == 2 ); + if ( lutil_atoux( &uiarg, c->argv[1], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> unable to parse \"%s\" as unsigned int", + c->argv[0], c->argv[1] ); + Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n", + c->log, c->cr_msg, 0); + return(ARG_BAD_CONF); + } + break; + case ARG_LONG: + assert( c->argc == 2 ); + if ( lutil_atolx( &larg, c->argv[1], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> unable to parse \"%s\" as long", + c->argv[0], c->argv[1] ); + Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n", + c->log, c->cr_msg, 0); + return(ARG_BAD_CONF); + } + break; + case ARG_ULONG: + assert( c->argc == 2 ); + if ( LUTIL_ATOULX( &ularg, c->argv[1], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> unable to parse \"%s\" as unsigned long", + c->argv[0], c->argv[1] ); + Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n", + c->log, c->cr_msg, 0); + return(ARG_BAD_CONF); + } + break; + case ARG_BER_LEN_T: { + unsigned long l; + assert( c->argc == 2 ); + if ( lutil_atoulx( &l, c->argv[1], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> unable to parse \"%s\" as ber_len_t", + c->argv[0], c->argv[1] ); + Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n", + c->log, c->cr_msg, 0); + return(ARG_BAD_CONF); + } + barg = (ber_len_t)l; + } break; + case ARG_ON_OFF: + /* note: this is an explicit exception + * to the "need exactly 2 args" rule */ + if (c->argc == 1) { + iarg = 1; + } else if ( !strcasecmp(c->argv[1], "on") || + !strcasecmp(c->argv[1], "true") || + !strcasecmp(c->argv[1], "yes") ) + { + iarg = 1; + } else if ( !strcasecmp(c->argv[1], "off") || + !strcasecmp(c->argv[1], "false") || + !strcasecmp(c->argv[1], "no") ) + { + iarg = 0; + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> invalid value", + c->argv[0] ); + Debug(LDAP_DEBUG_ANY|LDAP_DEBUG_NONE, "%s: %s\n", + c->log, c->cr_msg, 0 ); + return(ARG_BAD_CONF); + } + break; + } + j = (arg_type & ARG_NONZERO) ? 1 : 0; + if(iarg < j && larg < j && barg < (unsigned)j ) { + larg = larg ? larg : (barg ? (long)barg : iarg); + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> invalid value", + c->argv[0] ); + Debug(LDAP_DEBUG_ANY|LDAP_DEBUG_NONE, "%s: %s\n", + c->log, c->cr_msg, 0 ); + return(ARG_BAD_CONF); + } + switch(arg_type) { + case ARG_ON_OFF: + case ARG_INT: c->value_int = iarg; break; + case ARG_UINT: c->value_uint = uiarg; break; + case ARG_LONG: c->value_long = larg; break; + case ARG_ULONG: c->value_ulong = ularg; break; + case ARG_BER_LEN_T: c->value_ber_t = barg; break; + } + } + return 0; +} + +int config_set_vals(ConfigTable *Conf, ConfigArgs *c) { + int rc, arg_type; + void *ptr = NULL; + + arg_type = Conf->arg_type; + if(arg_type & ARG_MAGIC) { + if(!c->be) c->be = frontendDB; + c->cr_msg[0] = '\0'; + rc = (*((ConfigDriver*)Conf->arg_item))(c); +#if 0 + if(c->be == frontendDB) c->be = NULL; +#endif + if(rc) { + if ( !c->cr_msg[0] ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> handler exited with %d", + c->argv[0], rc ); + Debug(LDAP_DEBUG_CONFIG, "%s: %s!\n", + c->log, c->cr_msg, 0 ); + } + return(ARG_BAD_CONF); + } + return(0); + } + if(arg_type & ARG_OFFSET) { + if (c->be && c->table == Cft_Database) + ptr = c->be->be_private; + else if (c->bi) + ptr = c->bi->bi_private; + else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> offset is missing base pointer", + c->argv[0] ); + Debug(LDAP_DEBUG_CONFIG, "%s: %s!\n", + c->log, c->cr_msg, 0); + return(ARG_BAD_CONF); + } + ptr = (void *)((char *)ptr + (long)Conf->arg_item); + } else if (arg_type & ARGS_TYPES) { + ptr = Conf->arg_item; + } + if(arg_type & ARGS_TYPES) + switch(arg_type & ARGS_TYPES) { + case ARG_ON_OFF: + case ARG_INT: *(int*)ptr = c->value_int; break; + case ARG_UINT: *(unsigned*)ptr = c->value_uint; break; + case ARG_LONG: *(long*)ptr = c->value_long; break; + case ARG_ULONG: *(size_t*)ptr = c->value_ulong; break; + case ARG_BER_LEN_T: *(ber_len_t*)ptr = c->value_ber_t; break; + case ARG_STRING: { + char *cc = *(char**)ptr; + if(cc) { + if ((arg_type & ARG_UNIQUE) && c->op == SLAP_CONFIG_ADD ) { + Debug(LDAP_DEBUG_CONFIG, "%s: already set %s!\n", + c->log, Conf->name, 0 ); + return(ARG_BAD_CONF); + } + ch_free(cc); + } + *(char **)ptr = c->value_string; + break; + } + case ARG_BERVAL: + *(struct berval *)ptr = c->value_bv; + break; + case ARG_ATDESC: + *(AttributeDescription **)ptr = c->value_ad; + break; + } + return(0); +} + +int config_add_vals(ConfigTable *Conf, ConfigArgs *c) { + int rc, arg_type; + + arg_type = Conf->arg_type; + if(arg_type == ARG_IGNORED) { + Debug(LDAP_DEBUG_CONFIG, "%s: keyword <%s> ignored\n", + c->log, Conf->name, 0); + return(0); + } + rc = config_check_vals( Conf, c, 0 ); + if ( rc ) return rc; + return config_set_vals( Conf, c ); +} + +int +config_del_vals(ConfigTable *cf, ConfigArgs *c) +{ + int rc = 0; + + /* If there is no handler, just ignore it */ + if ( cf->arg_type & ARG_MAGIC ) { + c->argv[0] = cf->ad->ad_cname.bv_val; + c->op = LDAP_MOD_DELETE; + c->type = cf->arg_type & ARGS_USERLAND; + rc = (*((ConfigDriver*)cf->arg_item))(c); + } + return rc; +} + +int +config_get_vals(ConfigTable *cf, ConfigArgs *c) +{ + int rc = 0; + struct berval bv; + void *ptr; + + if ( cf->arg_type & ARG_IGNORED ) { + return 1; + } + + memset(&c->values, 0, sizeof(c->values)); + c->rvalue_vals = NULL; + c->rvalue_nvals = NULL; + c->op = SLAP_CONFIG_EMIT; + c->type = cf->arg_type & ARGS_USERLAND; + + if ( cf->arg_type & ARG_MAGIC ) { + rc = (*((ConfigDriver*)cf->arg_item))(c); + if ( rc ) return rc; + } else { + if ( cf->arg_type & ARG_OFFSET ) { + if (c->be && c->table == Cft_Database) + ptr = c->be->be_private; + else if ( c->bi ) + ptr = c->bi->bi_private; + else + return 1; + ptr = (void *)((char *)ptr + (long)cf->arg_item); + } else { + ptr = cf->arg_item; + } + + switch(cf->arg_type & ARGS_TYPES) { + case ARG_ON_OFF: + case ARG_INT: c->value_int = *(int *)ptr; break; + case ARG_UINT: c->value_uint = *(unsigned *)ptr; break; + case ARG_LONG: c->value_long = *(long *)ptr; break; + case ARG_ULONG: c->value_ulong = *(size_t *)ptr; break; + case ARG_BER_LEN_T: c->value_ber_t = *(ber_len_t *)ptr; break; + case ARG_STRING: + if ( *(char **)ptr ) + c->value_string = ch_strdup(*(char **)ptr); + break; + case ARG_BERVAL: + c->value_bv = *((struct berval *)ptr); break; + case ARG_ATDESC: + c->value_ad = *(AttributeDescription **)ptr; break; + } + } + if ( cf->arg_type & ARGS_TYPES) { + bv.bv_len = 0; + bv.bv_val = c->log; + switch(cf->arg_type & ARGS_TYPES) { + case ARG_INT: bv.bv_len = snprintf(bv.bv_val, sizeof( c->log ), "%d", c->value_int); break; + case ARG_UINT: bv.bv_len = snprintf(bv.bv_val, sizeof( c->log ), "%u", c->value_uint); break; + case ARG_LONG: bv.bv_len = snprintf(bv.bv_val, sizeof( c->log ), "%ld", c->value_long); break; + case ARG_ULONG: bv.bv_len = snprintf(bv.bv_val, sizeof( c->log ), "%" Z "u", c->value_ulong); break; + case ARG_BER_LEN_T: bv.bv_len = snprintf(bv.bv_val, sizeof( c->log ), "%ld", c->value_ber_t); break; + case ARG_ON_OFF: bv.bv_len = snprintf(bv.bv_val, sizeof( c->log ), "%s", + c->value_int ? "TRUE" : "FALSE"); break; + case ARG_STRING: + if ( c->value_string && c->value_string[0]) { + ber_str2bv( c->value_string, 0, 0, &bv); + } else { + return 1; + } + break; + case ARG_BERVAL: + if ( !BER_BVISEMPTY( &c->value_bv )) { + bv = c->value_bv; + } else { + return 1; + } + break; + case ARG_ATDESC: + if ( c->value_ad ) { + bv = c->value_ad->ad_cname; + } else { + return 1; + } + break; + default: + bv.bv_val = NULL; + break; + } + if (bv.bv_val == c->log && bv.bv_len >= sizeof( c->log ) ) { + return 1; + } + if (( cf->arg_type & ARGS_TYPES ) == ARG_STRING ) { + ber_bvarray_add(&c->rvalue_vals, &bv); + } else if ( !BER_BVISNULL( &bv ) ) { + value_add_one(&c->rvalue_vals, &bv); + } + /* else: maybe c->rvalue_vals already set? */ + } + return rc; +} + +int +init_config_attrs(ConfigTable *ct) { + int i, code; + + for (i=0; ct[i].name; i++ ) { + if ( !ct[i].attribute ) continue; + code = register_at( ct[i].attribute, &ct[i].ad, 1 ); + if ( code ) { + fprintf( stderr, "init_config_attrs: register_at failed\n" ); + return code; + } + } + + return 0; +} + +int +init_config_ocs( ConfigOCs *ocs ) { + int i, code; + + for (i=0;ocs[i].co_def;i++) { + code = register_oc( ocs[i].co_def, &ocs[i].co_oc, 1 ); + if ( code ) { + fprintf( stderr, "init_config_ocs: register_oc failed\n" ); + return code; + } + } + return 0; +} + +/* Split an LDIF line into space-separated tokens. Words may be grouped + * by quotes. A quoted string may begin in the middle of a word, but must + * end at the end of the word (be followed by whitespace or EOS). Any other + * quotes are passed through unchanged. All other characters are passed + * through unchanged. + */ +static char * +strtok_quote_ldif( char **line ) +{ + char *beg, *ptr, *quote=NULL; + int inquote=0; + + ptr = *line; + + if ( !ptr || !*ptr ) + return NULL; + + while( isspace( (unsigned char) *ptr )) ptr++; + + if ( *ptr == '"' ) { + inquote = 1; + ptr++; + } + + beg = ptr; + + for (;*ptr;ptr++) { + if ( *ptr == '"' ) { + if ( inquote && ( !ptr[1] || isspace((unsigned char) ptr[1]))) { + *ptr++ = '\0'; + break; + } + inquote = 1; + quote = ptr; + continue; + } + if ( inquote ) + continue; + if ( isspace( (unsigned char) *ptr )) { + *ptr++ = '\0'; + break; + } + } + if ( quote ) { + while ( quote < ptr ) { + *quote = quote[1]; + quote++; + } + } + if ( !*ptr ) { + *line = NULL; + } else { + while ( isspace( (unsigned char) *ptr )) ptr++; + *line = ptr; + } + return beg; +} + +void +config_parse_ldif( ConfigArgs *c ) +{ + char *next; + c->tline = ch_strdup(c->line); + next = c->tline; + + while ((c->argv[c->argc] = strtok_quote_ldif( &next )) != NULL) { + c->argc++; + if ( c->argc >= c->argv_size ) { + char **tmp = ch_realloc( c->argv, (c->argv_size + ARGS_STEP) * + sizeof( *c->argv )); + c->argv = tmp; + c->argv_size += ARGS_STEP; + } + } + c->argv[c->argc] = NULL; +} + +int +config_parse_vals(ConfigTable *ct, ConfigArgs *c, int valx) +{ + int rc = 0; + + snprintf( c->log, sizeof( c->log ), "%s: value #%d", + ct->ad->ad_cname.bv_val, valx ); + c->argc = 1; + c->argv[0] = ct->ad->ad_cname.bv_val; + + if ( ( ct->arg_type & ARG_QUOTE ) && c->line[ 0 ] != '"' ) { + c->argv[c->argc] = c->line; + c->argc++; + c->argv[c->argc] = NULL; + c->tline = NULL; + } else { + config_parse_ldif( c ); + } + rc = config_check_vals( ct, c, 1 ); + ch_free( c->tline ); + c->tline = NULL; + + if ( rc ) + rc = LDAP_CONSTRAINT_VIOLATION; + + return rc; +} + +int +config_parse_add(ConfigTable *ct, ConfigArgs *c, int valx) +{ + int rc = 0; + + snprintf( c->log, sizeof( c->log ), "%s: value #%d", + ct->ad->ad_cname.bv_val, valx ); + c->argc = 1; + c->argv[0] = ct->ad->ad_cname.bv_val; + + if ( ( ct->arg_type & ARG_QUOTE ) && c->line[ 0 ] != '"' ) { + c->argv[c->argc] = c->line; + c->argc++; + c->argv[c->argc] = NULL; + c->tline = NULL; + } else { + config_parse_ldif( c ); + } + c->op = LDAP_MOD_ADD; + rc = config_add_vals( ct, c ); + ch_free( c->tline ); + + return rc; +} + +int +read_config_file(const char *fname, int depth, ConfigArgs *cf, ConfigTable *cft) +{ + FILE *fp; + ConfigTable *ct; + ConfigArgs *c; + int rc; + struct stat s; + + c = ch_calloc( 1, sizeof( ConfigArgs ) ); + if ( c == NULL ) { + return 1; + } + + if ( depth ) { + memcpy( c, cf, sizeof( ConfigArgs ) ); + } else { + c->depth = depth; /* XXX */ + c->bi = NULL; + c->be = NULL; + } + + c->valx = -1; + c->fname = fname; + init_config_argv( c ); + + if ( stat( fname, &s ) != 0 ) { + ldap_syslog = 1; + Debug(LDAP_DEBUG_ANY, + "could not stat config file \"%s\": %s (%d)\n", + fname, strerror(errno), errno); + ch_free( c->argv ); + ch_free( c ); + return(1); + } + + if ( !S_ISREG( s.st_mode ) ) { + ldap_syslog = 1; + Debug(LDAP_DEBUG_ANY, + "regular file expected, got \"%s\"\n", + fname, 0, 0 ); + ch_free( c->argv ); + ch_free( c ); + return(1); + } + + fp = fopen( fname, "r" ); + if ( fp == NULL ) { + ldap_syslog = 1; + Debug(LDAP_DEBUG_ANY, + "could not open config file \"%s\": %s (%d)\n", + fname, strerror(errno), errno); + ch_free( c->argv ); + ch_free( c ); + return(1); + } + + Debug(LDAP_DEBUG_CONFIG, "reading config file %s\n", fname, 0, 0); + + fp_getline_init(c); + + c->tline = NULL; + + while ( fp_getline( fp, c ) ) { + /* skip comments and blank lines */ + if ( c->line[0] == '#' || c->line[0] == '\0' ) { + continue; + } + + snprintf( c->log, sizeof( c->log ), "%s: line %d", + c->fname, c->lineno ); + + c->argc = 0; + ch_free( c->tline ); + if ( config_fp_parse_line( c ) ) { + rc = 1; + goto done; + } + + if ( c->argc < 1 ) { + Debug( LDAP_DEBUG_ANY, "%s: bad config line.\n", + c->log, 0, 0); + rc = 1; + goto done; + } + + c->op = SLAP_CONFIG_ADD; + + ct = config_find_keyword( cft, c ); + if ( ct ) { + c->table = Cft_Global; + rc = config_add_vals( ct, c ); + if ( !rc ) continue; + + if ( rc & ARGS_USERLAND ) { + /* XXX a usertype would be opaque here */ + Debug(LDAP_DEBUG_CONFIG, "%s: unknown user type <%s>\n", + c->log, c->argv[0], 0); + rc = 1; + goto done; + + } else if ( rc == ARG_BAD_CONF ) { + rc = 1; + goto done; + } + + } else if ( c->bi && !c->be ) { + rc = SLAP_CONF_UNKNOWN; + if ( c->bi->bi_cf_ocs ) { + ct = config_find_keyword( c->bi->bi_cf_ocs->co_table, c ); + if ( ct ) { + c->table = c->bi->bi_cf_ocs->co_type; + rc = config_add_vals( ct, c ); + } + } + if ( c->bi->bi_config && rc == SLAP_CONF_UNKNOWN ) { + rc = (*c->bi->bi_config)(c->bi, c->fname, c->lineno, + c->argc, c->argv); + } + if ( rc ) { + switch(rc) { + case SLAP_CONF_UNKNOWN: + Debug( LDAP_DEBUG_ANY, "%s: unknown directive " + "<%s> inside backend info definition.\n", + c->log, *c->argv, 0); + default: + rc = 1; + goto done; + } + } + + } else if ( c->be && c->be != frontendDB ) { + rc = SLAP_CONF_UNKNOWN; + if ( c->be->be_cf_ocs ) { + ct = config_find_keyword( c->be->be_cf_ocs->co_table, c ); + if ( ct ) { + c->table = c->be->be_cf_ocs->co_type; + rc = config_add_vals( ct, c ); + } + } + if ( c->be->be_config && rc == SLAP_CONF_UNKNOWN ) { + rc = (*c->be->be_config)(c->be, c->fname, c->lineno, + c->argc, c->argv); + } + if ( rc == SLAP_CONF_UNKNOWN && SLAP_ISGLOBALOVERLAY( frontendDB ) ) + { + /* global overlays may need + * definitions inside other databases... + */ + rc = (*frontendDB->be_config)( frontendDB, + c->fname, (int)c->lineno, c->argc, c->argv ); + } + + switch ( rc ) { + case 0: + break; + + case SLAP_CONF_UNKNOWN: + Debug( LDAP_DEBUG_ANY, "%s: unknown directive " + "<%s> inside backend database definition.\n", + c->log, *c->argv, 0); + + default: + rc = 1; + goto done; + } + + } else if ( frontendDB->be_config ) { + rc = (*frontendDB->be_config)( frontendDB, + c->fname, (int)c->lineno, c->argc, c->argv); + if ( rc ) { + switch(rc) { + case SLAP_CONF_UNKNOWN: + Debug( LDAP_DEBUG_ANY, "%s: unknown directive " + "<%s> inside global database definition.\n", + c->log, *c->argv, 0); + + default: + rc = 1; + goto done; + } + } + + } else { + Debug( LDAP_DEBUG_ANY, "%s: unknown directive " + "<%s> outside backend info and database definitions.\n", + c->log, *c->argv, 0); + rc = 1; + goto done; + } + } + + rc = 0; + +done: + if ( cf ) { + cf->be = c->be; + cf->bi = c->bi; + } + ch_free(c->tline); + fclose(fp); + ch_free(c->argv); + ch_free(c); + return(rc); +} + +/* restrictops, allows, disallows, requires, loglevel */ + +int +bverb_to_mask(struct berval *bword, slap_verbmasks *v) { + int i; + for(i = 0; !BER_BVISNULL(&v[i].word); i++) { + if(!ber_bvstrcasecmp(bword, &v[i].word)) break; + } + return(i); +} + +int +verb_to_mask(const char *word, slap_verbmasks *v) { + struct berval bword; + ber_str2bv( word, 0, 0, &bword ); + return bverb_to_mask( &bword, v ); +} + +int +verbs_to_mask(int argc, char *argv[], slap_verbmasks *v, slap_mask_t *m) { + int i, j; + for(i = 1; i < argc; i++) { + j = verb_to_mask(argv[i], v); + if(BER_BVISNULL(&v[j].word)) return i; + while (!v[j].mask) j--; + *m |= v[j].mask; + } + return(0); +} + +/* Mask keywords that represent multiple bits should occur before single + * bit keywords in the verbmasks array. + */ +int +mask_to_verbs(slap_verbmasks *v, slap_mask_t m, BerVarray *bva) { + int i, rc = 1; + + if (m) { + for (i=0; !BER_BVISNULL(&v[i].word); i++) { + if (!v[i].mask) continue; + if (( m & v[i].mask ) == v[i].mask ) { + value_add_one( bva, &v[i].word ); + rc = 0; + m ^= v[i].mask; + if ( !m ) break; + } + } + } + return rc; +} + +/* Return the verbs as a single string, separated by delim */ +int +mask_to_verbstring(slap_verbmasks *v, slap_mask_t m0, char delim, struct berval *bv) +{ + int i, rc = 1; + + BER_BVZERO( bv ); + if (m0) { + slap_mask_t m = m0; + char *ptr; + for (i=0; !BER_BVISNULL(&v[i].word); i++) { + if (!v[i].mask) continue; + if (( m & v[i].mask ) == v[i].mask ) { + bv->bv_len += v[i].word.bv_len + 1; + rc = 0; + m ^= v[i].mask; + if ( !m ) break; + } + } + bv->bv_val = ch_malloc(bv->bv_len); + bv->bv_len--; + ptr = bv->bv_val; + m = m0; + for (i=0; !BER_BVISNULL(&v[i].word); i++) { + if (!v[i].mask) continue; + if (( m & v[i].mask ) == v[i].mask ) { + ptr = lutil_strcopy(ptr, v[i].word.bv_val); + *ptr++ = delim; + m ^= v[i].mask; + if ( !m ) break; + } + } + ptr[-1] = '\0'; + } + return rc; +} + +/* Parse a verbstring */ +int +verbstring_to_mask(slap_verbmasks *v, char *str, char delim, slap_mask_t *m) { + int j; + char *d; + struct berval bv; + + do { + bv.bv_val = str; + d = strchr( str, delim ); + if ( d ) + bv.bv_len = d - str; + else + bv.bv_len = strlen( str ); + j = bverb_to_mask( &bv, v ); + if(BER_BVISNULL(&v[j].word)) return 1; + while (!v[j].mask) j--; + *m |= v[j].mask; + str += bv.bv_len + 1; + } while ( d ); + return(0); +} + +int +slap_verbmasks_init( slap_verbmasks **vp, slap_verbmasks *v ) +{ + int i; + + assert( *vp == NULL ); + + for ( i = 0; !BER_BVISNULL( &v[ i ].word ); i++ ) /* EMPTY */; + + *vp = ch_calloc( i + 1, sizeof( slap_verbmasks ) ); + + for ( i = 0; !BER_BVISNULL( &v[ i ].word ); i++ ) { + ber_dupbv( &(*vp)[ i ].word, &v[ i ].word ); + *((slap_mask_t *)&(*vp)[ i ].mask) = v[ i ].mask; + } + + BER_BVZERO( &(*vp)[ i ].word ); + + return 0; +} + +int +slap_verbmasks_destroy( slap_verbmasks *v ) +{ + int i; + + assert( v != NULL ); + + for ( i = 0; !BER_BVISNULL( &v[ i ].word ); i++ ) { + ch_free( v[ i ].word.bv_val ); + } + + ch_free( v ); + + return 0; +} + +int +slap_verbmasks_append( + slap_verbmasks **vp, + slap_mask_t m, + struct berval *v, + slap_mask_t *ignore ) +{ + int i; + + if ( !m ) { + return LDAP_OPERATIONS_ERROR; + } + + for ( i = 0; !BER_BVISNULL( &(*vp)[ i ].word ); i++ ) { + if ( !(*vp)[ i ].mask ) continue; + + if ( ignore != NULL ) { + int j; + + for ( j = 0; ignore[ j ] != 0; j++ ) { + if ( (*vp)[ i ].mask == ignore[ j ] ) { + goto check_next; + } + } + } + + if ( ( m & (*vp)[ i ].mask ) == (*vp)[ i ].mask ) { + if ( ber_bvstrcasecmp( v, &(*vp)[ i ].word ) == 0 ) { + /* already set; ignore */ + return LDAP_SUCCESS; + } + /* conflicts */ + return LDAP_TYPE_OR_VALUE_EXISTS; + } + + if ( m & (*vp)[ i ].mask ) { + /* conflicts */ + return LDAP_CONSTRAINT_VIOLATION; + } +check_next:; + } + + *vp = ch_realloc( *vp, sizeof( slap_verbmasks ) * ( i + 2 ) ); + ber_dupbv( &(*vp)[ i ].word, v ); + *((slap_mask_t *)&(*vp)[ i ].mask) = m; + BER_BVZERO( &(*vp)[ i + 1 ].word ); + + return LDAP_SUCCESS; +} + +int +enum_to_verb(slap_verbmasks *v, slap_mask_t m, struct berval *bv) { + int i; + + for (i=0; !BER_BVISNULL(&v[i].word); i++) { + if ( m == v[i].mask ) { + if ( bv != NULL ) { + *bv = v[i].word; + } + return i; + } + } + return -1; +} + +/* register a new verbmask */ +static int +slap_verbmask_register( slap_verbmasks *vm_, slap_verbmasks **vmp, struct berval *bv, int mask ) +{ + slap_verbmasks *vm = *vmp; + int i; + + /* check for duplicate word */ + /* NOTE: we accept duplicate codes; the first occurrence will be used + * when mapping from mask to verb */ + i = verb_to_mask( bv->bv_val, vm ); + if ( !BER_BVISNULL( &vm[ i ].word ) ) { + return -1; + } + + for ( i = 0; !BER_BVISNULL( &vm[ i ].word ); i++ ) + ; + + if ( vm == vm_ ) { + /* first time: duplicate array */ + vm = ch_calloc( i + 2, sizeof( slap_verbmasks ) ); + for ( i = 0; !BER_BVISNULL( &vm_[ i ].word ); i++ ) + { + ber_dupbv( &vm[ i ].word, &vm_[ i ].word ); + *((slap_mask_t*)&vm[ i ].mask) = vm_[ i ].mask; + } + + } else { + vm = ch_realloc( vm, (i + 2) * sizeof( slap_verbmasks ) ); + } + + ber_dupbv( &vm[ i ].word, bv ); + *((slap_mask_t*)&vm[ i ].mask) = mask; + + BER_BVZERO( &vm[ i+1 ].word ); + + *vmp = vm; + + return i; +} + +static slap_verbmasks slap_ldap_response_code_[] = { + { BER_BVC("success"), LDAP_SUCCESS }, + + { BER_BVC("operationsError"), LDAP_OPERATIONS_ERROR }, + { BER_BVC("protocolError"), LDAP_PROTOCOL_ERROR }, + { BER_BVC("timelimitExceeded"), LDAP_TIMELIMIT_EXCEEDED }, + { BER_BVC("sizelimitExceeded"), LDAP_SIZELIMIT_EXCEEDED }, + { BER_BVC("compareFalse"), LDAP_COMPARE_FALSE }, + { BER_BVC("compareTrue"), LDAP_COMPARE_TRUE }, + + { BER_BVC("authMethodNotSupported"), LDAP_AUTH_METHOD_NOT_SUPPORTED }, + { BER_BVC("strongAuthNotSupported"), LDAP_STRONG_AUTH_NOT_SUPPORTED }, + { BER_BVC("strongAuthRequired"), LDAP_STRONG_AUTH_REQUIRED }, + { BER_BVC("strongerAuthRequired"), LDAP_STRONGER_AUTH_REQUIRED }, +#if 0 /* not LDAPv3 */ + { BER_BVC("partialResults"), LDAP_PARTIAL_RESULTS }, +#endif + + { BER_BVC("referral"), LDAP_REFERRAL }, + { BER_BVC("adminlimitExceeded"), LDAP_ADMINLIMIT_EXCEEDED }, + { BER_BVC("unavailableCriticalExtension"), LDAP_UNAVAILABLE_CRITICAL_EXTENSION }, + { BER_BVC("confidentialityRequired"), LDAP_CONFIDENTIALITY_REQUIRED }, + { BER_BVC("saslBindInProgress"), LDAP_SASL_BIND_IN_PROGRESS }, + + { BER_BVC("noSuchAttribute"), LDAP_NO_SUCH_ATTRIBUTE }, + { BER_BVC("undefinedType"), LDAP_UNDEFINED_TYPE }, + { BER_BVC("inappropriateMatching"), LDAP_INAPPROPRIATE_MATCHING }, + { BER_BVC("constraintViolation"), LDAP_CONSTRAINT_VIOLATION }, + { BER_BVC("typeOrValueExists"), LDAP_TYPE_OR_VALUE_EXISTS }, + { BER_BVC("invalidSyntax"), LDAP_INVALID_SYNTAX }, + + { BER_BVC("noSuchObject"), LDAP_NO_SUCH_OBJECT }, + { BER_BVC("aliasProblem"), LDAP_ALIAS_PROBLEM }, + { BER_BVC("invalidDnSyntax"), LDAP_INVALID_DN_SYNTAX }, +#if 0 /* not LDAPv3 */ + { BER_BVC("isLeaf"), LDAP_IS_LEAF }, +#endif + { BER_BVC("aliasDerefProblem"), LDAP_ALIAS_DEREF_PROBLEM }, + + { BER_BVC("proxyAuthzFailure"), LDAP_X_PROXY_AUTHZ_FAILURE }, + { BER_BVC("inappropriateAuth"), LDAP_INAPPROPRIATE_AUTH }, + { BER_BVC("invalidCredentials"), LDAP_INVALID_CREDENTIALS }, + { BER_BVC("insufficientAccess"), LDAP_INSUFFICIENT_ACCESS }, + + { BER_BVC("busy"), LDAP_BUSY }, + { BER_BVC("unavailable"), LDAP_UNAVAILABLE }, + { BER_BVC("unwillingToPerform"), LDAP_UNWILLING_TO_PERFORM }, + { BER_BVC("loopDetect"), LDAP_LOOP_DETECT }, + + { BER_BVC("namingViolation"), LDAP_NAMING_VIOLATION }, + { BER_BVC("objectClassViolation"), LDAP_OBJECT_CLASS_VIOLATION }, + { BER_BVC("notAllowedOnNonleaf"), LDAP_NOT_ALLOWED_ON_NONLEAF }, + { BER_BVC("notAllowedOnRdn"), LDAP_NOT_ALLOWED_ON_RDN }, + { BER_BVC("alreadyExists"), LDAP_ALREADY_EXISTS }, + { BER_BVC("noObjectClassMods"), LDAP_NO_OBJECT_CLASS_MODS }, + { BER_BVC("resultsTooLarge"), LDAP_RESULTS_TOO_LARGE }, + { BER_BVC("affectsMultipleDsas"), LDAP_AFFECTS_MULTIPLE_DSAS }, + + { BER_BVC("other"), LDAP_OTHER }, + + /* extension-specific */ + + { BER_BVC("cupResourcesExhausted"), LDAP_CUP_RESOURCES_EXHAUSTED }, + { BER_BVC("cupSecurityViolation"), LDAP_CUP_SECURITY_VIOLATION }, + { BER_BVC("cupInvalidData"), LDAP_CUP_INVALID_DATA }, + { BER_BVC("cupUnsupportedScheme"), LDAP_CUP_UNSUPPORTED_SCHEME }, + { BER_BVC("cupReloadRequired"), LDAP_CUP_RELOAD_REQUIRED }, + + { BER_BVC("cancelled"), LDAP_CANCELLED }, + { BER_BVC("noSuchOperation"), LDAP_NO_SUCH_OPERATION }, + { BER_BVC("tooLate"), LDAP_TOO_LATE }, + { BER_BVC("cannotCancel"), LDAP_CANNOT_CANCEL }, + + { BER_BVC("assertionFailed"), LDAP_ASSERTION_FAILED }, + + { BER_BVC("proxiedAuthorizationDenied"), LDAP_PROXIED_AUTHORIZATION_DENIED }, + + { BER_BVC("syncRefreshRequired"), LDAP_SYNC_REFRESH_REQUIRED }, + + { BER_BVC("noOperation"), LDAP_X_NO_OPERATION }, + + { BER_BVNULL, 0 } +}; + +slap_verbmasks *slap_ldap_response_code = slap_ldap_response_code_; + +int +slap_ldap_response_code_register( struct berval *bv, int err ) +{ + return slap_verbmask_register( slap_ldap_response_code_, + &slap_ldap_response_code, bv, err ); +} + +#ifdef HAVE_TLS +static slap_verbmasks tlskey[] = { + { BER_BVC("no"), SB_TLS_OFF }, + { BER_BVC("yes"), SB_TLS_ON }, + { BER_BVC("critical"), SB_TLS_CRITICAL }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks crlkeys[] = { + { BER_BVC("none"), LDAP_OPT_X_TLS_CRL_NONE }, + { BER_BVC("peer"), LDAP_OPT_X_TLS_CRL_PEER }, + { BER_BVC("all"), LDAP_OPT_X_TLS_CRL_ALL }, + { BER_BVNULL, 0 } + }; + +static slap_verbmasks vfykeys[] = { + { BER_BVC("never"), LDAP_OPT_X_TLS_NEVER }, + { BER_BVC("allow"), LDAP_OPT_X_TLS_ALLOW }, + { BER_BVC("try"), LDAP_OPT_X_TLS_TRY }, + { BER_BVC("demand"), LDAP_OPT_X_TLS_DEMAND }, + { BER_BVC("hard"), LDAP_OPT_X_TLS_HARD }, + { BER_BVC("true"), LDAP_OPT_X_TLS_HARD }, + { BER_BVNULL, 0 } + }; +#endif + +static slap_verbmasks methkey[] = { + { BER_BVC("none"), LDAP_AUTH_NONE }, + { BER_BVC("simple"), LDAP_AUTH_SIMPLE }, +#ifdef HAVE_CYRUS_SASL + { BER_BVC("sasl"), LDAP_AUTH_SASL }, +#endif + { BER_BVNULL, 0 } +}; + +static slap_verbmasks versionkey[] = { + { BER_BVC("2"), LDAP_VERSION2 }, + { BER_BVC("3"), LDAP_VERSION3 }, + { BER_BVNULL, 0 } +}; + +int +slap_keepalive_parse( + struct berval *val, + void *bc, + slap_cf_aux_table *tab0, + const char *tabmsg, + int unparse ) +{ + if ( unparse ) { + slap_keepalive *sk = (slap_keepalive *)bc; + int rc = snprintf( val->bv_val, val->bv_len, "%d:%d:%d", + sk->sk_idle, sk->sk_probes, sk->sk_interval ); + if ( rc < 0 ) { + return -1; + } + + if ( (unsigned)rc >= val->bv_len ) { + return -1; + } + + val->bv_len = rc; + + } else { + char *s = val->bv_val; + char *next; + slap_keepalive *sk = (slap_keepalive *)bc; + slap_keepalive sk2; + + if ( s[0] == ':' ) { + sk2.sk_idle = 0; + s++; + + } else { + sk2.sk_idle = strtol( s, &next, 10 ); + if ( next == s || next[0] != ':' ) { + return -1; + } + + if ( sk2.sk_idle < 0 ) { + return -1; + } + + s = ++next; + } + + if ( s[0] == ':' ) { + sk2.sk_probes = 0; + s++; + + } else { + sk2.sk_probes = strtol( s, &next, 10 ); + if ( next == s || next[0] != ':' ) { + return -1; + } + + if ( sk2.sk_probes < 0 ) { + return -1; + } + + s = ++next; + } + + if ( *s == '\0' ) { + sk2.sk_interval = 0; + + } else { + sk2.sk_interval = strtol( s, &next, 10 ); + if ( next == s || next[0] != '\0' ) { + return -1; + } + + if ( sk2.sk_interval < 0 ) { + return -1; + } + } + + *sk = sk2; + + ber_memfree( val->bv_val ); + BER_BVZERO( val ); + } + + return 0; +} + +static int +slap_sb_uri( + struct berval *val, + void *bcp, + slap_cf_aux_table *tab0, + const char *tabmsg, + int unparse ) +{ + slap_bindconf *bc = bcp; + if ( unparse ) { + if ( bc->sb_uri.bv_len >= val->bv_len ) + return -1; + val->bv_len = bc->sb_uri.bv_len; + AC_MEMCPY( val->bv_val, bc->sb_uri.bv_val, val->bv_len ); + } else { + bc->sb_uri = *val; +#ifdef HAVE_TLS + if ( ldap_is_ldaps_url( val->bv_val )) + bc->sb_tls_do_init = 1; +#endif + } + return 0; +} + +static slap_cf_aux_table bindkey[] = { + { BER_BVC("uri="), 0, 'x', 1, slap_sb_uri }, + { BER_BVC("version="), offsetof(slap_bindconf, sb_version), 'i', 0, versionkey }, + { BER_BVC("bindmethod="), offsetof(slap_bindconf, sb_method), 'i', 0, methkey }, + { BER_BVC("timeout="), offsetof(slap_bindconf, sb_timeout_api), 'i', 0, NULL }, + { BER_BVC("network-timeout="), offsetof(slap_bindconf, sb_timeout_net), 'i', 0, NULL }, + { BER_BVC("binddn="), offsetof(slap_bindconf, sb_binddn), 'b', 1, (slap_verbmasks *)dnNormalize }, + { BER_BVC("credentials="), offsetof(slap_bindconf, sb_cred), 'b', 1, NULL }, + { BER_BVC("saslmech="), offsetof(slap_bindconf, sb_saslmech), 'b', 0, NULL }, + { BER_BVC("secprops="), offsetof(slap_bindconf, sb_secprops), 's', 0, NULL }, + { BER_BVC("realm="), offsetof(slap_bindconf, sb_realm), 'b', 0, NULL }, + { BER_BVC("authcID="), offsetof(slap_bindconf, sb_authcId), 'b', 1, NULL }, + { BER_BVC("authzID="), offsetof(slap_bindconf, sb_authzId), 'b', 1, (slap_verbmasks *)authzNormalize }, + { BER_BVC("keepalive="), offsetof(slap_bindconf, sb_keepalive), 'x', 0, (slap_verbmasks *)slap_keepalive_parse }, +#ifdef HAVE_TLS + /* NOTE: replace "13" with the actual index + * of the first TLS-related line */ +#define aux_TLS (bindkey+13) /* beginning of TLS keywords */ + + { BER_BVC("starttls="), offsetof(slap_bindconf, sb_tls), 'i', 0, tlskey }, + { BER_BVC("tls_cert="), offsetof(slap_bindconf, sb_tls_cert), 's', 1, NULL }, + { BER_BVC("tls_key="), offsetof(slap_bindconf, sb_tls_key), 's', 1, NULL }, + { BER_BVC("tls_cacert="), offsetof(slap_bindconf, sb_tls_cacert), 's', 1, NULL }, + { BER_BVC("tls_cacertdir="), offsetof(slap_bindconf, sb_tls_cacertdir), 's', 1, NULL }, + { BER_BVC("tls_reqcert="), offsetof(slap_bindconf, sb_tls_reqcert), 's', 0, NULL }, + { BER_BVC("tls_reqsan="), offsetof(slap_bindconf, sb_tls_reqsan), 's', 0, NULL }, + { BER_BVC("tls_cipher_suite="), offsetof(slap_bindconf, sb_tls_cipher_suite), 's', 0, NULL }, + { BER_BVC("tls_protocol_min="), offsetof(slap_bindconf, sb_tls_protocol_min), 's', 0, NULL }, + { BER_BVC("tls_ecname="), offsetof(slap_bindconf, sb_tls_ecname), 's', 0, NULL }, +#ifdef HAVE_OPENSSL_CRL + { BER_BVC("tls_crlcheck="), offsetof(slap_bindconf, sb_tls_crlcheck), 's', 0, NULL }, +#endif +#endif + { BER_BVNULL, 0, 0, 0, NULL } +}; + +/* + * 's': char * + * 'b': struct berval; if !NULL, normalize using ((slap_mr_normalize_func *)aux) + * 'i': int; if !NULL, compute using ((slap_verbmasks *)aux) + * 'u': unsigned + * 'I': long + * 'U': unsigned long + */ + +int +slap_cf_aux_table_parse( const char *word, void *dst, slap_cf_aux_table *tab0, LDAP_CONST char *tabmsg ) +{ + int rc = SLAP_CONF_UNKNOWN; + slap_cf_aux_table *tab; + + for ( tab = tab0; !BER_BVISNULL( &tab->key ); tab++ ) { + if ( !strncasecmp( word, tab->key.bv_val, tab->key.bv_len ) ) { + char **cptr; + int *iptr, j; + unsigned *uptr; + long *lptr; + unsigned long *ulptr; + struct berval *bptr; + const char *val = word + tab->key.bv_len; + + switch ( tab->type ) { + case 's': + cptr = (char **)((char *)dst + tab->off); + *cptr = ch_strdup( val ); + rc = 0; + break; + + case 'b': + bptr = (struct berval *)((char *)dst + tab->off); + if ( tab->aux != NULL ) { + struct berval dn; + slap_mr_normalize_func *normalize = (slap_mr_normalize_func *)tab->aux; + + ber_str2bv( val, 0, 0, &dn ); + rc = normalize( 0, NULL, NULL, &dn, bptr, NULL ); + + } else { + ber_str2bv( val, 0, 1, bptr ); + rc = 0; + } + break; + + case 'i': + iptr = (int *)((char *)dst + tab->off); + + if ( tab->aux != NULL ) { + slap_verbmasks *aux = (slap_verbmasks *)tab->aux; + + assert( aux != NULL ); + + rc = 1; + for ( j = 0; !BER_BVISNULL( &aux[j].word ); j++ ) { + if ( !strcasecmp( val, aux[j].word.bv_val ) ) { + *iptr = aux[j].mask; + rc = 0; + break; + } + } + + } else { + rc = lutil_atoix( iptr, val, 0 ); + } + break; + + case 'u': + uptr = (unsigned *)((char *)dst + tab->off); + + rc = lutil_atoux( uptr, val, 0 ); + break; + + case 'I': + lptr = (long *)((char *)dst + tab->off); + + rc = lutil_atolx( lptr, val, 0 ); + break; + + case 'U': + ulptr = (unsigned long *)((char *)dst + tab->off); + + rc = lutil_atoulx( ulptr, val, 0 ); + break; + + case 'x': + if ( tab->aux != NULL ) { + struct berval value; + slap_cf_aux_table_parse_x *func = (slap_cf_aux_table_parse_x *)tab->aux; + + ber_str2bv( val, 0, 1, &value ); + + rc = func( &value, (void *)((char *)dst + tab->off), tab, tabmsg, 0 ); + + } else { + rc = 1; + } + break; + } + + if ( rc ) { + Debug( LDAP_DEBUG_ANY, "invalid %s value %s\n", + tabmsg, word, 0 ); + } + + return rc; + } + } + + return rc; +} + +int +slap_cf_aux_table_unparse( void *src, struct berval *bv, slap_cf_aux_table *tab0 ) +{ + char buf[AC_LINE_MAX], *ptr; + slap_cf_aux_table *tab; + struct berval tmp; + + ptr = buf; + for (tab = tab0; !BER_BVISNULL(&tab->key); tab++ ) { + char **cptr; + int *iptr, i; + unsigned *uptr; + long *lptr; + unsigned long *ulptr; + struct berval *bptr; + + cptr = (char **)((char *)src + tab->off); + + switch ( tab->type ) { + case 'b': + bptr = (struct berval *)((char *)src + tab->off); + cptr = &bptr->bv_val; + + case 's': + if ( *cptr ) { + *ptr++ = ' '; + ptr = lutil_strcopy( ptr, tab->key.bv_val ); + if ( tab->quote ) *ptr++ = '"'; + ptr = lutil_strcopy( ptr, *cptr ); + if ( tab->quote ) *ptr++ = '"'; + } + break; + + case 'i': + iptr = (int *)((char *)src + tab->off); + + if ( tab->aux != NULL ) { + slap_verbmasks *aux = (slap_verbmasks *)tab->aux; + + for ( i = 0; !BER_BVISNULL( &aux[i].word ); i++ ) { + if ( *iptr == aux[i].mask ) { + *ptr++ = ' '; + ptr = lutil_strcopy( ptr, tab->key.bv_val ); + ptr = lutil_strcopy( ptr, aux[i].word.bv_val ); + break; + } + } + + } else { + *ptr++ = ' '; + ptr = lutil_strcopy( ptr, tab->key.bv_val ); + ptr += snprintf( ptr, sizeof( buf ) - ( ptr - buf ), "%d", *iptr ); + } + break; + + case 'u': + uptr = (unsigned *)((char *)src + tab->off); + *ptr++ = ' '; + ptr = lutil_strcopy( ptr, tab->key.bv_val ); + ptr += snprintf( ptr, sizeof( buf ) - ( ptr - buf ), "%u", *uptr ); + break; + + case 'I': + lptr = (long *)((char *)src + tab->off); + *ptr++ = ' '; + ptr = lutil_strcopy( ptr, tab->key.bv_val ); + ptr += snprintf( ptr, sizeof( buf ) - ( ptr - buf ), "%ld", *lptr ); + break; + + case 'U': + ulptr = (unsigned long *)((char *)src + tab->off); + *ptr++ = ' '; + ptr = lutil_strcopy( ptr, tab->key.bv_val ); + ptr += snprintf( ptr, sizeof( buf ) - ( ptr - buf ), "%lu", *ulptr ); + break; + + case 'x': + { + char *saveptr=ptr; + *ptr++ = ' '; + ptr = lutil_strcopy( ptr, tab->key.bv_val ); + if ( tab->quote ) *ptr++ = '"'; + if ( tab->aux != NULL ) { + struct berval value; + slap_cf_aux_table_parse_x *func = (slap_cf_aux_table_parse_x *)tab->aux; + int rc; + + value.bv_val = ptr; + value.bv_len = buf + sizeof( buf ) - ptr; + + rc = func( &value, (void *)((char *)src + tab->off), tab, "(unparse)", 1 ); + if ( rc == 0 ) { + if (value.bv_len) { + ptr += value.bv_len; + } else { + ptr = saveptr; + break; + } + } + } + if ( tab->quote ) *ptr++ = '"'; + } + break; + + default: + assert( 0 ); + } + } + tmp.bv_val = buf; + tmp.bv_len = ptr - buf; + ber_dupbv( bv, &tmp ); + return 0; +} + +int +slap_tls_get_config( LDAP *ld, int opt, char **val ) +{ +#ifdef HAVE_TLS + slap_verbmasks *keys; + int i, ival; + + *val = NULL; + switch( opt ) { + case LDAP_OPT_X_TLS_CRLCHECK: + keys = crlkeys; + break; + case LDAP_OPT_X_TLS_REQUIRE_CERT: + keys = vfykeys; + break; + case LDAP_OPT_X_TLS_PROTOCOL_MIN: { + char buf[8]; + ldap_pvt_tls_get_option( ld, opt, &ival ); + snprintf( buf, sizeof( buf ), "%d.%d", + ( ival >> 8 ) & 0xff, ival & 0xff ); + *val = ch_strdup( buf ); + return 0; + } + default: + return -1; + } + ldap_pvt_tls_get_option( ld, opt, &ival ); + for (i=0; !BER_BVISNULL(&keys[i].word); i++) { + if (keys[i].mask == ival) { + *val = ch_strdup( keys[i].word.bv_val ); + return 0; + } + } +#endif + return -1; +} + +int +bindconf_tls_parse( const char *word, slap_bindconf *bc ) +{ +#ifdef HAVE_TLS + if ( slap_cf_aux_table_parse( word, bc, aux_TLS, "tls config" ) == 0 ) { + bc->sb_tls_do_init = 1; + return 0; + } +#endif + return -1; +} + +int +bindconf_tls_unparse( slap_bindconf *bc, struct berval *bv ) +{ +#ifdef HAVE_TLS + return slap_cf_aux_table_unparse( bc, bv, aux_TLS ); +#endif + return -1; +} + +int +bindconf_parse( const char *word, slap_bindconf *bc ) +{ +#ifdef HAVE_TLS + /* Detect TLS config changes explicitly */ + if ( bindconf_tls_parse( word, bc ) == 0 ) { + return 0; + } +#endif + return slap_cf_aux_table_parse( word, bc, bindkey, "bind config" ); +} + +int +bindconf_unparse( slap_bindconf *bc, struct berval *bv ) +{ + return slap_cf_aux_table_unparse( bc, bv, bindkey ); +} + +void bindconf_free( slap_bindconf *bc ) { + if ( !BER_BVISNULL( &bc->sb_uri ) ) { + ch_free( bc->sb_uri.bv_val ); + BER_BVZERO( &bc->sb_uri ); + } + if ( !BER_BVISNULL( &bc->sb_binddn ) ) { + ch_free( bc->sb_binddn.bv_val ); + BER_BVZERO( &bc->sb_binddn ); + } + if ( !BER_BVISNULL( &bc->sb_cred ) ) { + ch_free( bc->sb_cred.bv_val ); + BER_BVZERO( &bc->sb_cred ); + } + if ( !BER_BVISNULL( &bc->sb_saslmech ) ) { + ch_free( bc->sb_saslmech.bv_val ); + BER_BVZERO( &bc->sb_saslmech ); + } + if ( bc->sb_secprops ) { + ch_free( bc->sb_secprops ); + bc->sb_secprops = NULL; + } + if ( !BER_BVISNULL( &bc->sb_realm ) ) { + ch_free( bc->sb_realm.bv_val ); + BER_BVZERO( &bc->sb_realm ); + } + if ( !BER_BVISNULL( &bc->sb_authcId ) ) { + ch_free( bc->sb_authcId.bv_val ); + BER_BVZERO( &bc->sb_authcId ); + } + if ( !BER_BVISNULL( &bc->sb_authzId ) ) { + ch_free( bc->sb_authzId.bv_val ); + BER_BVZERO( &bc->sb_authzId ); + } +#ifdef HAVE_TLS + if ( bc->sb_tls_cert ) { + ch_free( bc->sb_tls_cert ); + bc->sb_tls_cert = NULL; + } + if ( bc->sb_tls_key ) { + ch_free( bc->sb_tls_key ); + bc->sb_tls_key = NULL; + } + if ( bc->sb_tls_cacert ) { + ch_free( bc->sb_tls_cacert ); + bc->sb_tls_cacert = NULL; + } + if ( bc->sb_tls_cacertdir ) { + ch_free( bc->sb_tls_cacertdir ); + bc->sb_tls_cacertdir = NULL; + } + if ( bc->sb_tls_reqcert ) { + ch_free( bc->sb_tls_reqcert ); + bc->sb_tls_reqcert = NULL; + } + if ( bc->sb_tls_reqsan ) { + ch_free( bc->sb_tls_reqsan ); + bc->sb_tls_reqsan = NULL; + } + if ( bc->sb_tls_cipher_suite ) { + ch_free( bc->sb_tls_cipher_suite ); + bc->sb_tls_cipher_suite = NULL; + } + if ( bc->sb_tls_protocol_min ) { + ch_free( bc->sb_tls_protocol_min ); + bc->sb_tls_protocol_min = NULL; + } + if ( bc->sb_tls_ecname ) { + ch_free( bc->sb_tls_ecname ); + bc->sb_tls_ecname = NULL; + } +#ifdef HAVE_OPENSSL_CRL + if ( bc->sb_tls_crlcheck ) { + ch_free( bc->sb_tls_crlcheck ); + bc->sb_tls_crlcheck = NULL; + } +#endif + if ( bc->sb_tls_ctx ) { + ldap_pvt_tls_ctx_free( bc->sb_tls_ctx ); + bc->sb_tls_ctx = NULL; + } +#endif +} + +void +bindconf_tls_defaults( slap_bindconf *bc ) +{ +#ifdef HAVE_TLS + if ( bc->sb_tls_do_init ) { + if ( !bc->sb_tls_cacert ) + ldap_pvt_tls_get_option( slap_tls_ld, LDAP_OPT_X_TLS_CACERTFILE, + &bc->sb_tls_cacert ); + if ( !bc->sb_tls_cacertdir ) + ldap_pvt_tls_get_option( slap_tls_ld, LDAP_OPT_X_TLS_CACERTDIR, + &bc->sb_tls_cacertdir ); + if ( !bc->sb_tls_cert ) + ldap_pvt_tls_get_option( slap_tls_ld, LDAP_OPT_X_TLS_CERTFILE, + &bc->sb_tls_cert ); + if ( !bc->sb_tls_key ) + ldap_pvt_tls_get_option( slap_tls_ld, LDAP_OPT_X_TLS_KEYFILE, + &bc->sb_tls_key ); + if ( !bc->sb_tls_cipher_suite ) + ldap_pvt_tls_get_option( slap_tls_ld, LDAP_OPT_X_TLS_CIPHER_SUITE, + &bc->sb_tls_cipher_suite ); + if ( !bc->sb_tls_reqcert ) + bc->sb_tls_reqcert = ch_strdup("demand"); + if ( !bc->sb_tls_reqsan ) + bc->sb_tls_reqsan = ch_strdup("allow"); + if ( !bc->sb_tls_ecname ) + slap_tls_get_config( slap_tls_ld, LDAP_OPT_X_TLS_ECNAME, + &bc->sb_tls_ecname ); +#ifdef HAVE_OPENSSL_CRL + if ( !bc->sb_tls_crlcheck ) + slap_tls_get_config( slap_tls_ld, LDAP_OPT_X_TLS_CRLCHECK, + &bc->sb_tls_crlcheck ); +#endif + } +#endif +} + +#ifdef HAVE_TLS +static struct { + const char *key; + size_t offset; + int opt; +} bindtlsopts[] = { + { "tls_cert", offsetof(slap_bindconf, sb_tls_cert), LDAP_OPT_X_TLS_CERTFILE }, + { "tls_key", offsetof(slap_bindconf, sb_tls_key), LDAP_OPT_X_TLS_KEYFILE }, + { "tls_cacert", offsetof(slap_bindconf, sb_tls_cacert), LDAP_OPT_X_TLS_CACERTFILE }, + { "tls_cacertdir", offsetof(slap_bindconf, sb_tls_cacertdir), LDAP_OPT_X_TLS_CACERTDIR }, + { "tls_cipher_suite", offsetof(slap_bindconf, sb_tls_cipher_suite), LDAP_OPT_X_TLS_CIPHER_SUITE }, + { "tls_ecname", offsetof(slap_bindconf, sb_tls_ecname), LDAP_OPT_X_TLS_ECNAME }, + {0, 0} +}; + +int bindconf_tls_set( slap_bindconf *bc, LDAP *ld ) +{ + int i, rc, newctx = 0, res = 0; + char *ptr = (char *)bc, **word; + + bc->sb_tls_do_init = 0; + + for (i=0; bindtlsopts[i].opt; i++) { + word = (char **)(ptr + bindtlsopts[i].offset); + if ( *word ) { + rc = ldap_set_option( ld, bindtlsopts[i].opt, *word ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "bindconf_tls_set: failed to set %s to %s\n", + bindtlsopts[i].key, *word, 0 ); + res = -1; + } else + newctx = 1; + } + } + if ( bc->sb_tls_reqcert ) { + rc = ldap_int_tls_config( ld, LDAP_OPT_X_TLS_REQUIRE_CERT, + bc->sb_tls_reqcert ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "bindconf_tls_set: failed to set tls_reqcert to %s\n", + bc->sb_tls_reqcert, 0, 0 ); + res = -1; + } else + newctx = 1; + } + if ( bc->sb_tls_reqsan ) { + rc = ldap_int_tls_config( ld, LDAP_OPT_X_TLS_REQUIRE_SAN, + bc->sb_tls_reqsan ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "bindconf_tls_set: failed to set tls_reqsan to %s\n", + bc->sb_tls_reqsan, 0, 0 ); + res = -1; + } + } + if ( bc->sb_tls_protocol_min ) { + rc = ldap_int_tls_config( ld, LDAP_OPT_X_TLS_PROTOCOL_MIN, + bc->sb_tls_protocol_min ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "bindconf_tls_set: failed to set tls_protocol_min to %s\n", + bc->sb_tls_protocol_min, 0, 0 ); + res = -1; + } else + newctx = 1; + } +#ifdef HAVE_OPENSSL_CRL + if ( bc->sb_tls_crlcheck ) { + rc = ldap_int_tls_config( ld, LDAP_OPT_X_TLS_CRLCHECK, + bc->sb_tls_crlcheck ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "bindconf_tls_set: failed to set tls_crlcheck to %s\n", + bc->sb_tls_crlcheck, 0, 0 ); + res = -1; + } else + newctx = 1; + } +#endif + if ( newctx ) { + int opt = 0; + + if ( bc->sb_tls_ctx ) { + ldap_pvt_tls_ctx_free( bc->sb_tls_ctx ); + bc->sb_tls_ctx = NULL; + } + rc = ldap_set_option( ld, LDAP_OPT_X_TLS_NEWCTX, &opt ); + if ( rc ) + res = rc; + else + ldap_get_option( ld, LDAP_OPT_X_TLS_CTX, &bc->sb_tls_ctx ); + } + + return res; +} +#endif + +/* + * set connection keepalive options + */ +void +slap_client_keepalive(LDAP *ld, slap_keepalive *sk) +{ + if (!sk) return; + + if ( sk->sk_idle ) { + ldap_set_option( ld, LDAP_OPT_X_KEEPALIVE_IDLE, &sk->sk_idle ); + } + + if ( sk->sk_probes ) { + ldap_set_option( ld, LDAP_OPT_X_KEEPALIVE_PROBES, &sk->sk_probes ); + } + + if ( sk->sk_interval ) { + ldap_set_option( ld, LDAP_OPT_X_KEEPALIVE_INTERVAL, &sk->sk_interval ); + } + + return; +} + +/* + * connect to a client using the bindconf data + * note: should move "version" into bindconf... + */ +int +slap_client_connect( LDAP **ldp, slap_bindconf *sb ) +{ + LDAP *ld = NULL; + int rc; + struct timeval tv; + + /* Init connection to provider */ + rc = ldap_initialize( &ld, sb->sb_uri.bv_val ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "slap_client_connect: " + "ldap_initialize(%s) failed (%d)\n", + sb->sb_uri.bv_val, rc, 0 ); + return rc; + } + + if ( sb->sb_version != 0 ) { + ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, + (const void *)&sb->sb_version ); + } + + if ( sb->sb_timeout_api ) { + tv.tv_sec = sb->sb_timeout_api; + tv.tv_usec = 0; + ldap_set_option( ld, LDAP_OPT_TIMEOUT, &tv ); + } + + if ( sb->sb_timeout_net ) { + tv.tv_sec = sb->sb_timeout_net; + tv.tv_usec = 0; + ldap_set_option( ld, LDAP_OPT_NETWORK_TIMEOUT, &tv ); + } + + /* setting network keepalive options */ + slap_client_keepalive(ld, &sb->sb_keepalive); + +#ifdef HAVE_TLS + if ( sb->sb_tls_do_init ) { + rc = bindconf_tls_set( sb, ld ); + + } else if ( sb->sb_tls_ctx ) { + rc = ldap_set_option( ld, LDAP_OPT_X_TLS_CTX, + sb->sb_tls_ctx ); + } + + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "slap_client_connect: " + "URI=%s TLS context initialization failed (%d)\n", + sb->sb_uri.bv_val, rc, 0 ); + goto done; + } +#endif + + /* Bind */ + if ( sb->sb_tls ) { + rc = ldap_start_tls_s( ld, NULL, NULL ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "slap_client_connect: URI=%s " + "%s, ldap_start_tls failed (%d)\n", + sb->sb_uri.bv_val, + sb->sb_tls == SB_TLS_CRITICAL ? + "Error" : "Warning", + rc ); + if ( sb->sb_tls == SB_TLS_CRITICAL ) { + goto done; + } + } + } + + if ( sb->sb_method == LDAP_AUTH_SASL ) { +#ifdef HAVE_CYRUS_SASL + void *defaults; + + if ( sb->sb_secprops != NULL ) { + rc = ldap_set_option( ld, + LDAP_OPT_X_SASL_SECPROPS, sb->sb_secprops); + + if( rc != LDAP_OPT_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "slap_client_connect: " + "error, ldap_set_option " + "(%s,SECPROPS,\"%s\") failed!\n", + sb->sb_uri.bv_val, sb->sb_secprops, 0 ); + goto done; + } + } + + defaults = lutil_sasl_defaults( ld, + sb->sb_saslmech.bv_val, + sb->sb_realm.bv_val, + sb->sb_authcId.bv_val, + sb->sb_cred.bv_val, + sb->sb_authzId.bv_val ); + if ( defaults == NULL ) { + rc = LDAP_OTHER; + goto done; + } + + rc = ldap_sasl_interactive_bind_s( ld, + sb->sb_binddn.bv_val, + sb->sb_saslmech.bv_val, + NULL, NULL, + LDAP_SASL_QUIET, + lutil_sasl_interact, + defaults ); + + lutil_sasl_freedefs( defaults ); + + /* FIXME: different error behaviors according to + * 1) return code + * 2) on err policy : exit, retry, backoff ... + */ + if ( rc != LDAP_SUCCESS ) { + static struct berval bv_GSSAPI = BER_BVC( "GSSAPI" ); + + Debug( LDAP_DEBUG_ANY, "slap_client_connect: URI=%s " + "ldap_sasl_interactive_bind_s failed (%d)\n", + sb->sb_uri.bv_val, rc, 0 ); + + /* FIXME (see above comment) */ + /* if Kerberos credentials cache is not active, retry */ + if ( ber_bvcmp( &sb->sb_saslmech, &bv_GSSAPI ) == 0 && + rc == LDAP_LOCAL_ERROR ) + { + rc = LDAP_SERVER_DOWN; + } + + goto done; + } +#else /* HAVE_CYRUS_SASL */ + /* Should never get here, we trapped this at config time */ + assert(0); + Debug( LDAP_DEBUG_SYNC, "not compiled with SASL support\n", 0, 0, 0 ); + rc = LDAP_OTHER; + goto done; +#endif + + } else if ( sb->sb_method == LDAP_AUTH_SIMPLE ) { + rc = ldap_sasl_bind_s( ld, + sb->sb_binddn.bv_val, LDAP_SASL_SIMPLE, + &sb->sb_cred, NULL, NULL, NULL ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "slap_client_connect: " + "URI=%s DN=\"%s\" " + "ldap_sasl_bind_s failed (%d)\n", + sb->sb_uri.bv_val, sb->sb_binddn.bv_val, rc ); + goto done; + } + } + +done:; + if ( rc ) { + if ( ld ) { + ldap_unbind_ext( ld, NULL, NULL ); + *ldp = NULL; + } + + } else { + *ldp = ld; + } + + return rc; +} + +/* -------------------------------------- */ + + +static char * +strtok_quote( char *line, char *sep, char **quote_ptr, int *iqp ) +{ + int inquote; + char *tmp; + static char *next; + + *quote_ptr = NULL; + if ( line != NULL ) { + next = line; + } + while ( *next && strchr( sep, *next ) ) { + next++; + } + + if ( *next == '\0' ) { + next = NULL; + return( NULL ); + } + tmp = next; + + for ( inquote = 0; *next; ) { + switch ( *next ) { + case '"': + if ( inquote ) { + inquote = 0; + } else { + inquote = 1; + } + AC_MEMCPY( next, next + 1, strlen( next + 1 ) + 1 ); + break; + + case '\\': + if ( next[1] ) + AC_MEMCPY( next, + next + 1, strlen( next + 1 ) + 1 ); + next++; /* dont parse the escaped character */ + break; + + default: + if ( ! inquote ) { + if ( strchr( sep, *next ) != NULL ) { + *quote_ptr = next; + *next++ = '\0'; + return( tmp ); + } + } + next++; + break; + } + } + *iqp = inquote; + + return( tmp ); +} + +static char buf[AC_LINE_MAX]; +static char *line; +static size_t lmax, lcur; + +#define CATLINE( buf ) \ + do { \ + size_t len = strlen( buf ); \ + while ( lcur + len + 1 > lmax ) { \ + lmax += AC_LINE_MAX; \ + line = (char *) ch_realloc( line, lmax ); \ + } \ + strcpy( line + lcur, buf ); \ + lcur += len; \ + } while( 0 ) + +static void +fp_getline_init(ConfigArgs *c) { + c->lineno = -1; + buf[0] = '\0'; +} + +static int +fp_getline( FILE *fp, ConfigArgs *c ) +{ + char *p; + + lcur = 0; + CATLINE(buf); + c->lineno++; + + /* avoid stack of bufs */ + if ( strncasecmp( line, "include", STRLENOF( "include" ) ) == 0 ) { + buf[0] = '\0'; + c->line = line; + return(1); + } + + while ( fgets( buf, sizeof( buf ), fp ) ) { + p = strchr( buf, '\n' ); + if ( p ) { + if ( p > buf && p[-1] == '\r' ) { + --p; + } + *p = '\0'; + } + /* XXX ugly */ + c->line = line; + if ( line[0] + && ( p = line + strlen( line ) - 1 )[0] == '\\' + && p[-1] != '\\' ) + { + p[0] = '\0'; + lcur--; + + } else { + if ( !isspace( (unsigned char)buf[0] ) ) { + return(1); + } + buf[0] = ' '; + } + CATLINE(buf); + c->lineno++; + } + + buf[0] = '\0'; + c->line = line; + return(line[0] ? 1 : 0); +} + +int +config_fp_parse_line(ConfigArgs *c) +{ + char *token; + static char *const hide[] = { + "rootpw", "replica", "syncrepl", /* in slapd */ + "acl-bind", "acl-method", "idassert-bind", /* in back-ldap */ + "acl-passwd", "bindpw", /* in back-<ldap/meta> */ + "pseudorootpw", /* in back-meta */ + "dbpasswd", /* in back-sql */ + NULL + }; + static char *const raw[] = { + "attributetype", "objectclass", "ditcontentrule", "ldapsyntax", NULL }; + char *quote_ptr; + int i = (int)(sizeof(hide)/sizeof(hide[0])) - 1; + int inquote = 0; + + c->tline = ch_strdup(c->line); + token = strtok_quote(c->tline, " \t", "e_ptr, &inquote); + + if(token) for(i = 0; hide[i]; i++) if(!strcasecmp(token, hide[i])) break; + if(quote_ptr) *quote_ptr = ' '; + Debug(LDAP_DEBUG_CONFIG, "%s (%s%s)\n", c->log, + hide[i] ? hide[i] : c->line, hide[i] ? " ***" : ""); + if(quote_ptr) *quote_ptr = '\0'; + + for(;; token = strtok_quote(NULL, " \t", "e_ptr, &inquote)) { + if(c->argc >= c->argv_size) { + char **tmp; + tmp = ch_realloc(c->argv, (c->argv_size + ARGS_STEP) * sizeof(*c->argv)); + if(!tmp) { + Debug(LDAP_DEBUG_ANY, "%s: out of memory\n", c->log, 0, 0); + return -1; + } + c->argv = tmp; + c->argv_size += ARGS_STEP; + } + if(token == NULL) + break; + c->argv[c->argc++] = token; + } + c->argv[c->argc] = NULL; + if (inquote) { + /* these directives parse c->line independently of argv tokenizing */ + for(i = 0; raw[i]; i++) if (!strcasecmp(c->argv[0], raw[i])) return 0; + + Debug(LDAP_DEBUG_ANY, "%s: unterminated quoted string \"%s\"\n", c->log, c->argv[c->argc-1], 0); + return -1; + } + return(0); +} + +void +config_destroy( ) +{ + ucdata_unload( UCDATA_ALL ); + if ( frontendDB ) { + /* NOTE: in case of early exit, frontendDB can be NULL */ + if ( frontendDB->be_schemandn.bv_val ) + free( frontendDB->be_schemandn.bv_val ); + if ( frontendDB->be_schemadn.bv_val ) + free( frontendDB->be_schemadn.bv_val ); + if ( frontendDB->be_acl ) + acl_destroy( frontendDB->be_acl ); + } + free( line ); + if ( slapd_args_file ) + free ( slapd_args_file ); + if ( slapd_pid_file ) + free ( slapd_pid_file ); + if ( default_passwd_hash ) + ldap_charray_free( default_passwd_hash ); +} + +char ** +slap_str2clist( char ***out, char *in, const char *brkstr ) +{ + char *str; + char *s; + char *lasts; + int i, j; + char **new; + + /* find last element in list */ + for (i = 0; *out && (*out)[i]; i++); + + /* protect the input string from strtok */ + str = ch_strdup( in ); + + if ( *str == '\0' ) { + free( str ); + return( *out ); + } + + /* Count words in string */ + j=1; + for ( s = str; *s; s++ ) { + if ( strchr( brkstr, *s ) != NULL ) { + j++; + } + } + + *out = ch_realloc( *out, ( i + j + 1 ) * sizeof( char * ) ); + new = *out + i; + for ( s = ldap_pvt_strtok( str, brkstr, &lasts ); + s != NULL; + s = ldap_pvt_strtok( NULL, brkstr, &lasts ) ) + { + *new = ch_strdup( s ); + new++; + } + + *new = NULL; + free( str ); + return( *out ); +} + +int config_generic_wrapper( Backend *be, const char *fname, int lineno, + int argc, char **argv ) +{ + ConfigArgs c = { 0 }; + ConfigTable *ct; + int rc; + + c.be = be; + c.fname = fname; + c.lineno = lineno; + c.argc = argc; + c.argv = argv; + c.valx = -1; + c.line = line; + c.op = SLAP_CONFIG_ADD; + snprintf( c.log, sizeof( c.log ), "%s: line %d", fname, lineno ); + + rc = SLAP_CONF_UNKNOWN; + ct = config_find_keyword( be->be_cf_ocs->co_table, &c ); + if ( ct ) { + c.table = be->be_cf_ocs->co_type; + rc = config_add_vals( ct, &c ); + } + return rc; +} + +/* See if the given URL (in plain and parsed form) matches + * any of the server's listener addresses. Return matching + * Listener or NULL for no match. + */ +Listener *config_check_my_url( const char *url, LDAPURLDesc *lud ) +{ + Listener **l = slapd_get_listeners(); + int i, isMe; + + /* Try a straight compare with Listener strings */ + for ( i=0; l && l[i]; i++ ) { + if ( !strcasecmp( url, l[i]->sl_url.bv_val )) { + return l[i]; + } + } + + isMe = 0; + /* If hostname is empty, or is localhost, or matches + * our hostname, this url refers to this host. + * Compare it against listeners and ports. + */ + if ( !lud->lud_host || !lud->lud_host[0] || + !strncasecmp("localhost", lud->lud_host, + STRLENOF("localhost")) || + !strcasecmp( global_host, lud->lud_host )) { + + for ( i=0; l && l[i]; i++ ) { + LDAPURLDesc *lu2; + ldap_url_parse( l[i]->sl_url.bv_val, &lu2 ); + do { + if ( strcasecmp( lud->lud_scheme, + lu2->lud_scheme )) + break; + if ( lud->lud_port != lu2->lud_port ) + break; + /* Listener on ANY address */ + if ( !lu2->lud_host || !lu2->lud_host[0] ) { + isMe = 1; + break; + } + /* URL on ANY address */ + if ( !lud->lud_host || !lud->lud_host[0] ) { + isMe = 1; + break; + } + /* Listener has specific host, must + * match it + */ + if ( !strcasecmp( lud->lud_host, + lu2->lud_host )) { + isMe = 1; + break; + } + } while(0); + ldap_free_urldesc( lu2 ); + if ( isMe ) { + return l[i]; + } + } + } + return NULL; +} diff --git a/servers/slapd/config.h b/servers/slapd/config.h new file mode 100644 index 0000000..b9b8b88 --- /dev/null +++ b/servers/slapd/config.h @@ -0,0 +1,227 @@ +/* config.h - configuration abstraction structure */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#ifndef CONFIG_H +#define CONFIG_H + +#include<ac/string.h> + +LDAP_BEGIN_DECL + +typedef struct ConfigTable { + const char *name; + const char *what; + int min_args; + int max_args; + int length; + unsigned int arg_type; + void *arg_item; + const char *attribute; + AttributeDescription *ad; + void *notify; +} ConfigTable; + +/* search entries are returned according to this order */ +typedef enum { + Cft_Abstract = 0, + Cft_Global, + Cft_Module, + Cft_Schema, + Cft_Backend, + Cft_Database, + Cft_Overlay, + Cft_Misc /* backend/overlay defined */ +} ConfigType; + +#define ARGS_USERLAND 0x00000fff + +/* types are enumerated, not a bitmask */ +#define ARGS_TYPES 0x0000f000 +#define ARG_INT 0x00001000 +#define ARG_LONG 0x00002000 +#define ARG_BER_LEN_T 0x00003000 +#define ARG_ON_OFF 0x00004000 +#define ARG_STRING 0x00005000 +#define ARG_BERVAL 0x00006000 +#define ARG_DN 0x00007000 +#define ARG_UINT 0x00008000 +#define ARG_ATDESC 0x00009000 +#define ARG_ULONG 0x0000a000 + +#define ARGS_SYNTAX 0xffff0000 +#define ARG_IGNORED 0x00080000 +#define ARG_PRE_BI 0x00100000 +#define ARG_PRE_DB 0x00200000 +#define ARG_DB 0x00400000 /* Only applies to DB */ +#define ARG_MAY_DB 0x00800000 /* May apply to DB */ +#define ARG_PAREN 0x01000000 +#define ARG_NONZERO 0x02000000 +#define ARG_NO_INSERT 0x04000000 /* no arbitrary inserting */ +#define ARG_NO_DELETE 0x08000000 /* no runtime deletes */ +#define ARG_UNIQUE 0x10000000 +#define ARG_QUOTE 0x20000000 /* wrap with quotes before parsing */ +#define ARG_OFFSET 0x40000000 +#define ARG_MAGIC 0x80000000 + +#define ARG_BAD_CONF 0xdead0000 /* overload return values */ + +/* This is a config entry's e_private data */ +typedef struct CfEntryInfo { + struct CfEntryInfo *ce_parent; + struct CfEntryInfo *ce_sibs; + struct CfEntryInfo *ce_kids; + Entry *ce_entry; + ConfigType ce_type; + BackendInfo *ce_bi; + BackendDB *ce_be; + void *ce_private; +} CfEntryInfo; + +struct config_args_s; + +/* Check if the child is allowed to be LDAPAdd'd to the parent */ +typedef int (ConfigLDAPadd)( + CfEntryInfo *parent, Entry *child, struct config_args_s *ca); + +/* Let the object create children out of slapd.conf */ +typedef int (ConfigCfAdd)( + Operation *op, SlapReply *rs, Entry *parent, struct config_args_s *ca ); + +#ifdef SLAP_CONFIG_DELETE +/* Called when deleting a Cft_Misc Child object from cn=config */ +typedef int (ConfigLDAPdel)( + CfEntryInfo *ce, Operation *op ); +#endif + +typedef struct ConfigOCs { + const char *co_def; + ConfigType co_type; + ConfigTable *co_table; + ConfigLDAPadd *co_ldadd; + ConfigCfAdd *co_cfadd; +#ifdef SLAP_CONFIG_DELETE + ConfigLDAPdel *co_lddel; +#endif + ObjectClass *co_oc; + struct berval *co_name; +} ConfigOCs; + +typedef int (ConfigDriver)(struct config_args_s *c); + +struct config_reply_s { + int err; + char msg[SLAP_TEXT_BUFLEN]; +}; + +typedef struct config_args_s { + int argc; + char **argv; + int argv_size; + char *line; + char *tline; + const char *fname; + int lineno; + char log[MAXPATHLEN + STRLENOF(": line ") + LDAP_PVT_INTTYPE_CHARS(unsigned long)]; +#define cr_msg reply.msg + ConfigReply reply; + int depth; + int valx; /* multi-valued value index */ + /* parsed first val for simple cases */ + union { + int v_int; + unsigned v_uint; + long v_long; + size_t v_ulong; + ber_len_t v_ber_t; + char *v_string; + struct berval v_bv; + struct { + struct berval vdn_dn; + struct berval vdn_ndn; + } v_dn; + AttributeDescription *v_ad; + } values; + /* return values for emit mode */ + BerVarray rvalue_vals; + BerVarray rvalue_nvals; +#define SLAP_CONFIG_EMIT 0x2000 /* emit instead of set */ +#define SLAP_CONFIG_ADD 0x4000 /* config file add vs LDAP add */ + int op; + int type; /* ConfigTable.arg_type & ARGS_USERLAND */ + Operation *ca_op; + BackendDB *be; + BackendInfo *bi; + Entry *ca_entry; /* entry being modified */ + void *ca_private; /* anything */ + ConfigDriver *cleanup; + ConfigType table; /* which config table did we come from */ +} ConfigArgs; + +/* If lineno is zero, we have an actual LDAP Add request from a client. + * Otherwise, we're reading a config file or a config dir. + */ +#define CONFIG_ONLINE_ADD(ca) (!((ca)->lineno)) + +#define value_int values.v_int +#define value_uint values.v_uint +#define value_long values.v_long +#define value_ulong values.v_ulong +#define value_ber_t values.v_ber_t +#define value_string values.v_string +#define value_bv values.v_bv +#define value_dn values.v_dn.vdn_dn +#define value_ndn values.v_dn.vdn_ndn +#define value_ad values.v_ad + +int config_fp_parse_line(ConfigArgs *c); + +int config_register_schema(ConfigTable *ct, ConfigOCs *co); +int config_del_vals(ConfigTable *cf, ConfigArgs *c); +int config_get_vals(ConfigTable *ct, ConfigArgs *c); +int config_add_vals(ConfigTable *ct, ConfigArgs *c); + +void init_config_argv( ConfigArgs *c ); +int init_config_attrs(ConfigTable *ct); +int init_config_ocs( ConfigOCs *ocs ); +void config_parse_ldif( ConfigArgs *c ); +int config_parse_vals(ConfigTable *ct, ConfigArgs *c, int valx); +int config_parse_add(ConfigTable *ct, ConfigArgs *c, int valx); +int read_config_file(const char *fname, int depth, ConfigArgs *cf, + ConfigTable *cft ); + +ConfigTable * config_find_keyword(ConfigTable *ct, ConfigArgs *c); +Entry * config_build_entry( Operation *op, SlapReply *rs, CfEntryInfo *parent, + ConfigArgs *c, struct berval *rdn, ConfigOCs *main, ConfigOCs *extra ); + +Listener *config_check_my_url(const char *url, LDAPURLDesc *lud); +int config_shadow( ConfigArgs *c, slap_mask_t flag ); +#define config_slurp_shadow(c) config_shadow((c), SLAP_DBFLAG_SLURP_SHADOW) +#define config_sync_shadow(c) config_shadow((c), SLAP_DBFLAG_SYNC_SHADOW) + + /* Make sure we don't exceed the bits reserved for userland */ +#define config_check_userland(last) \ + assert( ( ( (last) - 1 ) & ARGS_USERLAND ) == ( (last) - 1 ) ); + +#define SLAP_X_ORDERED_FMT "{%d}" + +LDAP_SLAPD_V (slap_verbmasks *) slap_ldap_response_code; +extern int slap_ldap_response_code_register( struct berval *bv, int err ); + +LDAP_SLAPD_V (ConfigTable) olcDatabaseDummy[]; + +LDAP_END_DECL + +#endif /* CONFIG_H */ diff --git a/servers/slapd/connection.c b/servers/slapd/connection.c new file mode 100644 index 0000000..44c3fc6 --- /dev/null +++ b/servers/slapd/connection.c @@ -0,0 +1,2141 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif + +#include <ac/socket.h> +#include <ac/errno.h> +#include <ac/string.h> +#include <ac/time.h> +#include <ac/unistd.h> + +#include "lutil.h" +#include "slap.h" + +#ifdef LDAP_CONNECTIONLESS +#include "../../libraries/liblber/lber-int.h" /* ber_int_sb_read() */ +#endif + +#ifdef LDAP_SLAPI +#include "slapi/slapi.h" +#endif + +/* protected by connections_mutex */ +static ldap_pvt_thread_mutex_t connections_mutex; +static Connection *connections = NULL; + +static ldap_pvt_thread_mutex_t conn_nextid_mutex; +static unsigned long conn_nextid = SLAPD_SYNC_SYNCCONN_OFFSET; + +static const char conn_lost_str[] = "connection lost"; + +const char * +connection_state2str( int state ) +{ + switch( state ) { + case SLAP_C_INVALID: return "!"; + case SLAP_C_INACTIVE: return "|"; + case SLAP_C_CLOSING: return "C"; + case SLAP_C_ACTIVE: return ""; + case SLAP_C_BINDING: return "B"; + case SLAP_C_CLIENT: return "L"; + } + + return "?"; +} + +static Connection* connection_get( ber_socket_t s ); + +typedef struct conn_readinfo { + Operation *op; + ldap_pvt_thread_start_t *func; + void *arg; + void *ctx; + int nullop; +} conn_readinfo; + +static int connection_input( Connection *c, conn_readinfo *cri ); +static void connection_close( Connection *c ); + +static int connection_op_activate( Operation *op ); +static void connection_op_queue( Operation *op ); +static int connection_resched( Connection *conn ); +static void connection_abandon( Connection *conn ); +static void connection_destroy( Connection *c ); + +static ldap_pvt_thread_start_t connection_operation; + +/* + * Initialize connection management infrastructure. + */ +int connections_init(void) +{ + int i; + + assert( connections == NULL ); + + if( connections != NULL) { + Debug( LDAP_DEBUG_ANY, "connections_init: already initialized.\n", + 0, 0, 0 ); + return -1; + } + + /* should check return of every call */ + ldap_pvt_thread_mutex_init( &connections_mutex ); + ldap_pvt_thread_mutex_init( &conn_nextid_mutex ); + + connections = (Connection *) ch_calloc( dtblsize, sizeof(Connection) ); + + if( connections == NULL ) { + Debug( LDAP_DEBUG_ANY, "connections_init: " + "allocation (%d*%ld) of connection array failed\n", + dtblsize, (long) sizeof(Connection), 0 ); + return -1; + } + + assert( connections[0].c_struct_state == SLAP_C_UNINITIALIZED ); + assert( connections[dtblsize-1].c_struct_state == SLAP_C_UNINITIALIZED ); + + for (i=0; i<dtblsize; i++) connections[i].c_conn_idx = i; + + /* + * per entry initialization of the Connection array initialization + * will be done by connection_init() + */ + + return 0; +} + +/* + * Destroy connection management infrastructure. + */ + +int connections_destroy(void) +{ + ber_socket_t i; + + /* should check return of every call */ + + if( connections == NULL) { + Debug( LDAP_DEBUG_ANY, "connections_destroy: nothing to destroy.\n", + 0, 0, 0 ); + return -1; + } + + for ( i = 0; i < dtblsize; i++ ) { + if( connections[i].c_struct_state != SLAP_C_UNINITIALIZED ) { + ber_sockbuf_free( connections[i].c_sb ); + ldap_pvt_thread_mutex_destroy( &connections[i].c_mutex ); + ldap_pvt_thread_mutex_destroy( &connections[i].c_write1_mutex ); + ldap_pvt_thread_mutex_destroy( &connections[i].c_write2_mutex ); + ldap_pvt_thread_cond_destroy( &connections[i].c_write1_cv ); + ldap_pvt_thread_cond_destroy( &connections[i].c_write2_cv ); +#ifdef LDAP_SLAPI + if ( slapi_plugins_used ) { + slapi_int_free_object_extensions( SLAPI_X_EXT_CONNECTION, + &connections[i] ); + } +#endif + } + } + + free( connections ); + connections = NULL; + + ldap_pvt_thread_mutex_destroy( &connections_mutex ); + ldap_pvt_thread_mutex_destroy( &conn_nextid_mutex ); + return 0; +} + +/* + * shutdown all connections + */ +int connections_shutdown(void) +{ + ber_socket_t i; + + for ( i = 0; i < dtblsize; i++ ) { + if( connections[i].c_struct_state != SLAP_C_UNINITIALIZED ) { + ldap_pvt_thread_mutex_lock( &connections[i].c_mutex ); + if( connections[i].c_struct_state == SLAP_C_USED ) { + + /* give persistent clients a chance to cleanup */ + if( connections[i].c_conn_state == SLAP_C_CLIENT ) { + ldap_pvt_thread_pool_submit( &connection_pool, + connections[i].c_clientfunc, connections[i].c_clientarg ); + } else { + /* c_mutex is locked */ + connection_closing( &connections[i], "slapd shutdown" ); + connection_close( &connections[i] ); + } + } + ldap_pvt_thread_mutex_unlock( &connections[i].c_mutex ); + } + } + + return 0; +} + +/* + * Timeout idle connections. + */ +int connections_timeout_idle(time_t now) +{ + int i = 0, writers = 0; + ber_socket_t connindex; + Connection* c; + time_t old; + + old = slapd_get_writetime(); + + for( c = connection_first( &connindex ); + c != NULL; + c = connection_next( c, &connindex ) ) + { + /* Don't timeout a slow-running request or a persistent + * outbound connection. But if it has a writewaiter, see + * if the waiter has been there too long. + */ + if(( c->c_n_ops_executing && !c->c_writewaiter) + || c->c_conn_state == SLAP_C_CLIENT ) { + continue; + } + + if( global_idletimeout && + difftime( c->c_activitytime+global_idletimeout, now) < 0 ) { + /* close it */ + connection_closing( c, "idletimeout" ); + connection_close( c ); + i++; + continue; + } + if ( c->c_writewaiter && global_writetimeout ) { + writers = 1; + if( difftime( c->c_activitytime+global_writetimeout, now) < 0 ) { + /* close it */ + connection_closing( c, "writetimeout" ); + connection_close( c ); + i++; + continue; + } + } + } + connection_done( c ); + if ( old && !writers ) + slapd_clr_writetime( old ); + + return i; +} + +/* Drop all client connections */ +void connections_drop() +{ + Connection* c; + ber_socket_t connindex; + + for( c = connection_first( &connindex ); + c != NULL; + c = connection_next( c, &connindex ) ) + { + /* Don't close a slow-running request or a persistent + * outbound connection. + */ + if(( c->c_n_ops_executing && !c->c_writewaiter) + || c->c_conn_state == SLAP_C_CLIENT ) { + continue; + } + connection_closing( c, "dropping" ); + connection_close( c ); + } + connection_done( c ); +} + +static Connection* connection_get( ber_socket_t s ) +{ + Connection *c; + + Debug( LDAP_DEBUG_ARGS, + "connection_get(%ld)\n", + (long) s, 0, 0 ); + + assert( connections != NULL ); + + if(s == AC_SOCKET_INVALID) return NULL; + + assert( s < dtblsize ); + c = &connections[s]; + + if( c != NULL ) { + ldap_pvt_thread_mutex_lock( &c->c_mutex ); + + assert( c->c_struct_state != SLAP_C_UNINITIALIZED ); + + if( c->c_struct_state != SLAP_C_USED ) { + /* connection must have been closed due to resched */ + + Debug( LDAP_DEBUG_CONNS, + "connection_get(%d): connection not used\n", + s, 0, 0 ); + assert( c->c_conn_state == SLAP_C_INVALID ); + assert( c->c_sd == AC_SOCKET_INVALID ); + + ldap_pvt_thread_mutex_unlock( &c->c_mutex ); + return NULL; + } + + Debug( LDAP_DEBUG_TRACE, + "connection_get(%d): got connid=%lu\n", + s, c->c_connid, 0 ); + + c->c_n_get++; + + assert( c->c_struct_state == SLAP_C_USED ); + assert( c->c_conn_state != SLAP_C_INVALID ); + assert( c->c_sd != AC_SOCKET_INVALID ); + +#ifndef SLAPD_MONITOR + if ( global_idletimeout > 0 ) +#endif /* ! SLAPD_MONITOR */ + { + c->c_activitytime = slap_get_time(); + } + } + + return c; +} + +static void connection_return( Connection *c ) +{ + ldap_pvt_thread_mutex_unlock( &c->c_mutex ); +} + +Connection * connection_init( + ber_socket_t s, + Listener *listener, + const char* dnsname, + const char* peername, + int flags, + slap_ssf_t ssf, + struct berval *authid + LDAP_PF_LOCAL_SENDMSG_ARG(struct berval *peerbv)) +{ + unsigned long id; + Connection *c; + int doinit = 0; + ber_socket_t sfd = SLAP_FD2SOCK(s); + + assert( connections != NULL ); + + assert( listener != NULL ); + assert( dnsname != NULL ); + assert( peername != NULL ); + +#ifndef HAVE_TLS + assert( !( flags & CONN_IS_TLS )); +#endif + + if( s == AC_SOCKET_INVALID ) { + Debug( LDAP_DEBUG_ANY, + "connection_init: init of socket %ld invalid.\n", (long)s, 0, 0 ); + return NULL; + } + + assert( s >= 0 ); + assert( s < dtblsize ); + c = &connections[s]; + if( c->c_struct_state == SLAP_C_UNINITIALIZED ) { + doinit = 1; + } else { + assert( c->c_struct_state == SLAP_C_UNUSED ); + } + + if( doinit ) { + c->c_send_ldap_result = slap_send_ldap_result; + c->c_send_search_entry = slap_send_search_entry; + c->c_send_search_reference = slap_send_search_reference; + c->c_send_ldap_extended = slap_send_ldap_extended; + c->c_send_ldap_intermediate = slap_send_ldap_intermediate; + + BER_BVZERO( &c->c_authmech ); + BER_BVZERO( &c->c_dn ); + BER_BVZERO( &c->c_ndn ); + + c->c_listener = NULL; + BER_BVZERO( &c->c_peer_domain ); + BER_BVZERO( &c->c_peer_name ); + + LDAP_STAILQ_INIT(&c->c_ops); + LDAP_STAILQ_INIT(&c->c_pending_ops); + +#ifdef LDAP_X_TXN + c->c_txn = CONN_TXN_INACTIVE; + c->c_txn_backend = NULL; + LDAP_STAILQ_INIT(&c->c_txn_ops); +#endif + + BER_BVZERO( &c->c_sasl_bind_mech ); + c->c_sasl_done = 0; + c->c_sasl_authctx = NULL; + c->c_sasl_sockctx = NULL; + c->c_sasl_extra = NULL; + c->c_sasl_bindop = NULL; + + c->c_sb = ber_sockbuf_alloc( ); + + { + ber_len_t max = sockbuf_max_incoming; + ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_MAX_INCOMING, &max ); + } + + c->c_currentber = NULL; + + /* should check status of thread calls */ + ldap_pvt_thread_mutex_init( &c->c_mutex ); + ldap_pvt_thread_mutex_init( &c->c_write1_mutex ); + ldap_pvt_thread_mutex_init( &c->c_write2_mutex ); + ldap_pvt_thread_cond_init( &c->c_write1_cv ); + ldap_pvt_thread_cond_init( &c->c_write2_cv ); + +#ifdef LDAP_SLAPI + if ( slapi_plugins_used ) { + slapi_int_create_object_extensions( SLAPI_X_EXT_CONNECTION, c ); + } +#endif + } + + ldap_pvt_thread_mutex_lock( &c->c_mutex ); + + assert( BER_BVISNULL( &c->c_authmech ) ); + assert( BER_BVISNULL( &c->c_dn ) ); + assert( BER_BVISNULL( &c->c_ndn ) ); + assert( c->c_listener == NULL ); + assert( BER_BVISNULL( &c->c_peer_domain ) ); + assert( BER_BVISNULL( &c->c_peer_name ) ); + assert( LDAP_STAILQ_EMPTY(&c->c_ops) ); + assert( LDAP_STAILQ_EMPTY(&c->c_pending_ops) ); +#ifdef LDAP_X_TXN + assert( c->c_txn == CONN_TXN_INACTIVE ); + assert( c->c_txn_backend == NULL ); + assert( LDAP_STAILQ_EMPTY(&c->c_txn_ops) ); +#endif + assert( BER_BVISNULL( &c->c_sasl_bind_mech ) ); + assert( c->c_sasl_done == 0 ); + assert( c->c_sasl_authctx == NULL ); + assert( c->c_sasl_sockctx == NULL ); + assert( c->c_sasl_extra == NULL ); + assert( c->c_sasl_bindop == NULL ); + assert( c->c_currentber == NULL ); + assert( c->c_writewaiter == 0); + assert( c->c_writers == 0); + + c->c_listener = listener; + c->c_sd = s; + + if ( flags & CONN_IS_CLIENT ) { + c->c_connid = 0; + ldap_pvt_thread_mutex_lock( &connections_mutex ); + c->c_conn_state = SLAP_C_CLIENT; + c->c_struct_state = SLAP_C_USED; + ldap_pvt_thread_mutex_unlock( &connections_mutex ); + c->c_close_reason = "?"; /* should never be needed */ + ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_FD, &sfd ); + ldap_pvt_thread_mutex_unlock( &c->c_mutex ); + + return c; + } + + ber_str2bv( dnsname, 0, 1, &c->c_peer_domain ); + ber_str2bv( peername, 0, 1, &c->c_peer_name ); + + c->c_n_ops_received = 0; + c->c_n_ops_executing = 0; + c->c_n_ops_pending = 0; + c->c_n_ops_completed = 0; + + c->c_n_get = 0; + c->c_n_read = 0; + c->c_n_write = 0; + + /* set to zero until bind, implies LDAP_VERSION3 */ + c->c_protocol = 0; + +#ifndef SLAPD_MONITOR + if ( global_idletimeout > 0 ) +#endif /* ! SLAPD_MONITOR */ + { + c->c_activitytime = c->c_starttime = slap_get_time(); + } + +#ifdef LDAP_CONNECTIONLESS + c->c_is_udp = 0; + if( flags & CONN_IS_UDP ) { + c->c_is_udp = 1; +#ifdef LDAP_DEBUG + ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_PROVIDER, (void*)"udp_" ); +#endif + ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_udp, + LBER_SBIOD_LEVEL_PROVIDER, (void *)&sfd ); + ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_readahead, + LBER_SBIOD_LEVEL_PROVIDER, NULL ); + } else +#endif /* LDAP_CONNECTIONLESS */ +#ifdef LDAP_PF_LOCAL + if ( flags & CONN_IS_IPC ) { +#ifdef LDAP_DEBUG + ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_PROVIDER, (void*)"ipc_" ); +#endif + ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_fd, + LBER_SBIOD_LEVEL_PROVIDER, (void *)&sfd ); +#ifdef LDAP_PF_LOCAL_SENDMSG + if ( !BER_BVISEMPTY( peerbv )) + ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_UNGET_BUF, peerbv ); +#endif + } else +#endif /* LDAP_PF_LOCAL */ + { +#ifdef LDAP_DEBUG + ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_PROVIDER, (void*)"tcp_" ); +#endif + ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_tcp, + LBER_SBIOD_LEVEL_PROVIDER, (void *)&sfd ); + } + +#ifdef LDAP_DEBUG + ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_debug, + INT_MAX, (void*)"ldap_" ); +#endif + + if( ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_NONBLOCK, + c /* non-NULL */ ) < 0 ) + { + Debug( LDAP_DEBUG_ANY, + "connection_init(%d, %s): set nonblocking failed\n", + s, c->c_peer_name.bv_val, 0 ); + + c->c_listener = NULL; + if(c->c_peer_domain.bv_val != NULL) { + free(c->c_peer_domain.bv_val); + } + BER_BVZERO( &c->c_peer_domain ); + if(c->c_peer_name.bv_val != NULL) { + free(c->c_peer_name.bv_val); + } + BER_BVZERO( &c->c_peer_name ); + + ber_sockbuf_free( c->c_sb ); + c->c_sb = NULL; + c->c_sd = AC_SOCKET_INVALID; + ldap_pvt_thread_mutex_unlock( &c->c_mutex ); + + return NULL; + } + + ldap_pvt_thread_mutex_lock( &conn_nextid_mutex ); + id = c->c_connid = conn_nextid++; + ldap_pvt_thread_mutex_unlock( &conn_nextid_mutex ); + + ldap_pvt_thread_mutex_lock( &connections_mutex ); + c->c_conn_state = SLAP_C_INACTIVE; + c->c_struct_state = SLAP_C_USED; + ldap_pvt_thread_mutex_unlock( &connections_mutex ); + c->c_close_reason = "?"; /* should never be needed */ + + c->c_ssf = c->c_transport_ssf = ssf; + c->c_tls_ssf = c->c_sasl_ssf = 0; + +#ifdef HAVE_TLS + if ( flags & CONN_IS_TLS ) { + c->c_is_tls = 1; + c->c_needs_tls_accept = 1; + } else { + c->c_is_tls = 0; + c->c_needs_tls_accept = 0; + } +#endif + + slap_sasl_open( c, 0 ); + slap_sasl_external( c, ssf, authid ); + + slapd_add_internal( s, 1 ); + + backend_connection_init(c); + ldap_pvt_thread_mutex_unlock( &c->c_mutex ); + + if ( !(flags & CONN_IS_UDP )) + Statslog( LDAP_DEBUG_STATS, + "conn=%ld fd=%ld ACCEPT from %s (%s)\n", + id, (long) s, peername, listener->sl_name.bv_val, 0 ); + + return c; +} + +void connection2anonymous( Connection *c ) +{ + assert( connections != NULL ); + assert( c != NULL ); + + { + ber_len_t max = sockbuf_max_incoming; + ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_MAX_INCOMING, &max ); + } + + if ( !BER_BVISNULL( &c->c_authmech ) ) { + ch_free(c->c_authmech.bv_val); + } + BER_BVZERO( &c->c_authmech ); + + if ( !BER_BVISNULL( &c->c_dn ) ) { + ch_free(c->c_dn.bv_val); + } + BER_BVZERO( &c->c_dn ); + + if ( !BER_BVISNULL( &c->c_ndn ) ) { + ch_free(c->c_ndn.bv_val); + } + BER_BVZERO( &c->c_ndn ); + + if ( !BER_BVISNULL( &c->c_sasl_authz_dn ) ) { + ber_memfree_x( c->c_sasl_authz_dn.bv_val, NULL ); + } + BER_BVZERO( &c->c_sasl_authz_dn ); + + c->c_authz_backend = NULL; +} + +static void +connection_destroy( Connection *c ) +{ + unsigned long connid; + const char *close_reason; + Sockbuf *sb; + ber_socket_t sd; + + assert( connections != NULL ); + assert( c != NULL ); + assert( c->c_struct_state != SLAP_C_UNUSED ); + assert( c->c_conn_state != SLAP_C_INVALID ); + assert( LDAP_STAILQ_EMPTY(&c->c_ops) ); + assert( LDAP_STAILQ_EMPTY(&c->c_pending_ops) ); +#ifdef LDAP_X_TXN + assert( c->c_txn == CONN_TXN_INACTIVE ); + assert( c->c_txn_backend == NULL ); + assert( LDAP_STAILQ_EMPTY(&c->c_txn_ops) ); +#endif + assert( c->c_writewaiter == 0); + assert( c->c_writers == 0); + + /* only for stats (print -1 as "%lu" may give unexpected results ;) */ + connid = c->c_connid; + close_reason = c->c_close_reason; + + ldap_pvt_thread_mutex_lock( &connections_mutex ); + c->c_struct_state = SLAP_C_PENDING; + ldap_pvt_thread_mutex_unlock( &connections_mutex ); + + backend_connection_destroy(c); + + c->c_protocol = 0; + c->c_connid = -1; + + c->c_activitytime = c->c_starttime = 0; + + connection2anonymous( c ); + c->c_listener = NULL; + + if(c->c_peer_domain.bv_val != NULL) { + free(c->c_peer_domain.bv_val); + } + BER_BVZERO( &c->c_peer_domain ); + if(c->c_peer_name.bv_val != NULL) { + free(c->c_peer_name.bv_val); + } + BER_BVZERO( &c->c_peer_name ); + + c->c_sasl_bind_in_progress = 0; + if(c->c_sasl_bind_mech.bv_val != NULL) { + free(c->c_sasl_bind_mech.bv_val); + } + BER_BVZERO( &c->c_sasl_bind_mech ); + + slap_sasl_close( c ); + + if ( c->c_currentber != NULL ) { + ber_free( c->c_currentber, 1 ); + c->c_currentber = NULL; + } + + +#ifdef LDAP_SLAPI + /* call destructors, then constructors; avoids unnecessary allocation */ + if ( slapi_plugins_used ) { + slapi_int_clear_object_extensions( SLAPI_X_EXT_CONNECTION, c ); + } +#endif + + sd = c->c_sd; + c->c_sd = AC_SOCKET_INVALID; + c->c_conn_state = SLAP_C_INVALID; + c->c_struct_state = SLAP_C_UNUSED; + c->c_close_reason = "?"; /* should never be needed */ + + sb = c->c_sb; + c->c_sb = ber_sockbuf_alloc( ); + { + ber_len_t max = sockbuf_max_incoming; + ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_MAX_INCOMING, &max ); + } + + /* c must be fully reset by this point; when we call slapd_remove + * it may get immediately reused by a new connection. + */ + if ( sd != AC_SOCKET_INVALID ) { + slapd_remove( sd, sb, 1, 0, 0 ); + + if ( close_reason == NULL ) { + Statslog( LDAP_DEBUG_STATS, "conn=%lu fd=%ld closed\n", + connid, (long) sd, 0, 0, 0 ); + } else { + Statslog( LDAP_DEBUG_STATS, "conn=%lu fd=%ld closed (%s)\n", + connid, (long) sd, close_reason, 0, 0 ); + } + } +} + +int connection_valid( Connection *c ) +{ + /* c_mutex must be locked by caller */ + + assert( c != NULL ); + + return c->c_struct_state == SLAP_C_USED && + c->c_conn_state >= SLAP_C_ACTIVE && + c->c_conn_state <= SLAP_C_CLIENT; +} + +static void connection_abandon( Connection *c ) +{ + /* c_mutex must be locked by caller */ + + Operation *o, *next, op = {0}; + Opheader ohdr = {0}; + + op.o_hdr = &ohdr; + op.o_conn = c; + op.o_connid = c->c_connid; + op.o_tag = LDAP_REQ_ABANDON; + + for ( o = LDAP_STAILQ_FIRST( &c->c_ops ); o; o=next ) { + SlapReply rs = {REP_RESULT}; + + next = LDAP_STAILQ_NEXT( o, o_next ); + /* don't abandon an op twice */ + if ( o->o_abandon ) + continue; + op.orn_msgid = o->o_msgid; + o->o_abandon = 1; + op.o_bd = frontendDB; + frontendDB->be_abandon( &op, &rs ); + } + +#ifdef LDAP_X_TXN + /* remove operations in pending transaction */ + while ( (o = LDAP_STAILQ_FIRST( &c->c_txn_ops )) != NULL) { + LDAP_STAILQ_REMOVE_HEAD( &c->c_txn_ops, o_next ); + LDAP_STAILQ_NEXT(o, o_next) = NULL; + slap_op_free( o, NULL ); + } + + /* clear transaction */ + c->c_txn_backend = NULL; + c->c_txn = CONN_TXN_INACTIVE; +#endif + + /* remove pending operations */ + while ( (o = LDAP_STAILQ_FIRST( &c->c_pending_ops )) != NULL) { + LDAP_STAILQ_REMOVE_HEAD( &c->c_pending_ops, o_next ); + LDAP_STAILQ_NEXT(o, o_next) = NULL; + slap_op_free( o, NULL ); + } +} + +static void +connection_wake_writers( Connection *c ) +{ + /* wake write blocked operations */ + ldap_pvt_thread_mutex_lock( &c->c_write1_mutex ); + if ( c->c_writers > 0 ) { + c->c_writers = -c->c_writers; + ldap_pvt_thread_cond_broadcast( &c->c_write1_cv ); + ldap_pvt_thread_mutex_unlock( &c->c_write1_mutex ); + if ( c->c_writewaiter ) { + ldap_pvt_thread_mutex_lock( &c->c_write2_mutex ); + ldap_pvt_thread_cond_signal( &c->c_write2_cv ); + slapd_clr_write( c->c_sd, 1 ); + ldap_pvt_thread_mutex_unlock( &c->c_write2_mutex ); + } + ldap_pvt_thread_mutex_lock( &c->c_write1_mutex ); + while ( c->c_writers ) { + ldap_pvt_thread_cond_wait( &c->c_write1_cv, &c->c_write1_mutex ); + } + ldap_pvt_thread_mutex_unlock( &c->c_write1_mutex ); + } else { + ldap_pvt_thread_mutex_unlock( &c->c_write1_mutex ); + slapd_clr_write( c->c_sd, 1 ); + } +} + +void connection_closing( Connection *c, const char *why ) +{ + assert( connections != NULL ); + assert( c != NULL ); + + if ( c->c_struct_state != SLAP_C_USED ) return; + + assert( c->c_conn_state != SLAP_C_INVALID ); + + /* c_mutex must be locked by caller */ + + if( c->c_conn_state != SLAP_C_CLOSING ) { + Debug( LDAP_DEBUG_CONNS, + "connection_closing: readying conn=%lu sd=%d for close\n", + c->c_connid, c->c_sd, 0 ); + /* update state to closing */ + c->c_conn_state = SLAP_C_CLOSING; + c->c_close_reason = why; + + /* don't listen on this port anymore */ + slapd_clr_read( c->c_sd, 0 ); + + /* abandon active operations */ + connection_abandon( c ); + + /* wake write blocked operations */ + connection_wake_writers( c ); + + } else if( why == NULL && c->c_close_reason == conn_lost_str ) { + /* Client closed connection after doing Unbind. */ + c->c_close_reason = NULL; + } +} + +static void +connection_close( Connection *c ) +{ + assert( connections != NULL ); + assert( c != NULL ); + + if ( c->c_struct_state != SLAP_C_USED ) return; + + assert( c->c_conn_state == SLAP_C_CLOSING ); + + /* NOTE: c_mutex should be locked by caller */ + + if ( !LDAP_STAILQ_EMPTY(&c->c_ops) || + !LDAP_STAILQ_EMPTY(&c->c_pending_ops) ) + { + Debug( LDAP_DEBUG_CONNS, + "connection_close: deferring conn=%lu sd=%d\n", + c->c_connid, c->c_sd, 0 ); + return; + } + + Debug( LDAP_DEBUG_TRACE, "connection_close: conn=%lu sd=%d\n", + c->c_connid, c->c_sd, 0 ); + + connection_destroy( c ); +} + +unsigned long connections_nextid(void) +{ + unsigned long id; + assert( connections != NULL ); + + ldap_pvt_thread_mutex_lock( &conn_nextid_mutex ); + + id = conn_nextid; + + ldap_pvt_thread_mutex_unlock( &conn_nextid_mutex ); + + return id; +} + +/* + * Loop through the connections: + * + * for (c = connection_first(&i); c; c = connection_next(c, &i)) ...; + * connection_done(c); + * + * 'i' is the cursor, initialized by connection_first(). + * 'c_mutex' is locked in the returned connection. The functions must + * be passed the previous return value so they can unlock it again. + */ + +Connection* connection_first( ber_socket_t *index ) +{ + assert( connections != NULL ); + assert( index != NULL ); + + ldap_pvt_thread_mutex_lock( &connections_mutex ); + for( *index = 0; *index < dtblsize; (*index)++) { + if( connections[*index].c_struct_state != SLAP_C_UNINITIALIZED ) { + break; + } + } + ldap_pvt_thread_mutex_unlock( &connections_mutex ); + + return connection_next(NULL, index); +} + +/* Next connection in loop, see connection_first() */ +Connection* connection_next( Connection *c, ber_socket_t *index ) +{ + assert( connections != NULL ); + assert( index != NULL ); + assert( *index <= dtblsize ); + + if( c != NULL ) ldap_pvt_thread_mutex_unlock( &c->c_mutex ); + + c = NULL; + + ldap_pvt_thread_mutex_lock( &connections_mutex ); + for(; *index < dtblsize; (*index)++) { + int c_struct; + if( connections[*index].c_struct_state == SLAP_C_UNINITIALIZED ) { + /* FIXME: accessing c_conn_state without locking c_mutex */ + assert( connections[*index].c_conn_state == SLAP_C_INVALID ); + continue; + } + + if( connections[*index].c_struct_state == SLAP_C_USED ) { + c = &connections[(*index)++]; + if ( ldap_pvt_thread_mutex_trylock( &c->c_mutex )) { + /* avoid deadlock */ + ldap_pvt_thread_mutex_unlock( &connections_mutex ); + ldap_pvt_thread_mutex_lock( &c->c_mutex ); + ldap_pvt_thread_mutex_lock( &connections_mutex ); + if ( c->c_struct_state != SLAP_C_USED ) { + ldap_pvt_thread_mutex_unlock( &c->c_mutex ); + c = NULL; + continue; + } + } + assert( c->c_conn_state != SLAP_C_INVALID ); + break; + } + + c_struct = connections[*index].c_struct_state; + if ( c_struct == SLAP_C_PENDING ) + continue; + assert( c_struct == SLAP_C_UNUSED ); + /* FIXME: accessing c_conn_state without locking c_mutex */ + assert( connections[*index].c_conn_state == SLAP_C_INVALID ); + } + + ldap_pvt_thread_mutex_unlock( &connections_mutex ); + return c; +} + +/* End connection loop, see connection_first() */ +void connection_done( Connection *c ) +{ + assert( connections != NULL ); + + if( c != NULL ) ldap_pvt_thread_mutex_unlock( &c->c_mutex ); +} + +/* + * connection_activity - handle the request operation op on connection + * conn. This routine figures out what kind of operation it is and + * calls the appropriate stub to handle it. + */ + +#ifdef SLAPD_MONITOR +/* FIXME: returns 0 in case of failure */ +#define INCR_OP_INITIATED(index) \ + do { \ + ldap_pvt_thread_mutex_lock( &op->o_counters->sc_mutex ); \ + ldap_pvt_mp_add_ulong(op->o_counters->sc_ops_initiated_[(index)], 1); \ + ldap_pvt_thread_mutex_unlock( &op->o_counters->sc_mutex ); \ + } while (0) +#define INCR_OP_COMPLETED(index) \ + do { \ + ldap_pvt_thread_mutex_lock( &op->o_counters->sc_mutex ); \ + ldap_pvt_mp_add_ulong(op->o_counters->sc_ops_completed, 1); \ + ldap_pvt_mp_add_ulong(op->o_counters->sc_ops_completed_[(index)], 1); \ + ldap_pvt_thread_mutex_unlock( &op->o_counters->sc_mutex ); \ + } while (0) +#else /* !SLAPD_MONITOR */ +#define INCR_OP_INITIATED(index) do { } while (0) +#define INCR_OP_COMPLETED(index) \ + do { \ + ldap_pvt_thread_mutex_lock( &op->o_counters->sc_mutex ); \ + ldap_pvt_mp_add_ulong(op->o_counters->sc_ops_completed, 1); \ + ldap_pvt_thread_mutex_unlock( &op->o_counters->sc_mutex ); \ + } while (0) +#endif /* !SLAPD_MONITOR */ + +/* + * NOTE: keep in sync with enum in slapd.h + */ +static BI_op_func *opfun[] = { + do_bind, + do_unbind, + do_search, + do_compare, + do_modify, + do_modrdn, + do_add, + do_delete, + do_abandon, + do_extended, + NULL +}; + +/* Counters are per-thread, not per-connection. + */ +static void +conn_counter_destroy( void *key, void *data ) +{ + slap_counters_t **prev, *sc; + + ldap_pvt_thread_mutex_lock( &slap_counters.sc_mutex ); + for ( prev = &slap_counters.sc_next, sc = slap_counters.sc_next; sc; + prev = &sc->sc_next, sc = sc->sc_next ) { + if ( sc == data ) { + int i; + + *prev = sc->sc_next; + /* Copy data to main counter */ + ldap_pvt_mp_add( slap_counters.sc_bytes, sc->sc_bytes ); + ldap_pvt_mp_add( slap_counters.sc_pdu, sc->sc_pdu ); + ldap_pvt_mp_add( slap_counters.sc_entries, sc->sc_entries ); + ldap_pvt_mp_add( slap_counters.sc_refs, sc->sc_refs ); + ldap_pvt_mp_add( slap_counters.sc_ops_initiated, sc->sc_ops_initiated ); + ldap_pvt_mp_add( slap_counters.sc_ops_completed, sc->sc_ops_completed ); +#ifdef SLAPD_MONITOR + for ( i = 0; i < SLAP_OP_LAST; i++ ) { + ldap_pvt_mp_add( slap_counters.sc_ops_initiated_[ i ], sc->sc_ops_initiated_[ i ] ); + ldap_pvt_mp_add( slap_counters.sc_ops_initiated_[ i ], sc->sc_ops_completed_[ i ] ); + } +#endif /* SLAPD_MONITOR */ + slap_counters_destroy( sc ); + ber_memfree_x( data, NULL ); + break; + } + } + ldap_pvt_thread_mutex_unlock( &slap_counters.sc_mutex ); +} + +static void +conn_counter_init( Operation *op, void *ctx ) +{ + slap_counters_t *sc; + void *vsc = NULL; + + if ( ldap_pvt_thread_pool_getkey( + ctx, (void *)conn_counter_init, &vsc, NULL ) || !vsc ) { + vsc = ch_malloc( sizeof( slap_counters_t )); + sc = vsc; + slap_counters_init( sc ); + ldap_pvt_thread_pool_setkey( ctx, (void*)conn_counter_init, vsc, + conn_counter_destroy, NULL, NULL ); + + ldap_pvt_thread_mutex_lock( &slap_counters.sc_mutex ); + sc->sc_next = slap_counters.sc_next; + slap_counters.sc_next = sc; + ldap_pvt_thread_mutex_unlock( &slap_counters.sc_mutex ); + } + op->o_counters = vsc; +} + +static void * +connection_operation( void *ctx, void *arg_v ) +{ + int rc = LDAP_OTHER, cancel; + Operation *op = arg_v; + SlapReply rs = {REP_RESULT}; + ber_tag_t tag = op->o_tag; + slap_op_t opidx = SLAP_OP_LAST; + Connection *conn = op->o_conn; + void *memctx = NULL; + void *memctx_null = NULL; + ber_len_t memsiz; + + conn_counter_init( op, ctx ); + ldap_pvt_thread_mutex_lock( &op->o_counters->sc_mutex ); + /* FIXME: returns 0 in case of failure */ + ldap_pvt_mp_add_ulong(op->o_counters->sc_ops_initiated, 1); + ldap_pvt_thread_mutex_unlock( &op->o_counters->sc_mutex ); + + op->o_threadctx = ctx; + op->o_tid = ldap_pvt_thread_pool_tid( ctx ); + + switch ( tag ) { + case LDAP_REQ_BIND: + case LDAP_REQ_UNBIND: + case LDAP_REQ_ADD: + case LDAP_REQ_DELETE: + case LDAP_REQ_MODDN: + case LDAP_REQ_MODIFY: + case LDAP_REQ_COMPARE: + case LDAP_REQ_SEARCH: + case LDAP_REQ_ABANDON: + case LDAP_REQ_EXTENDED: + break; + default: + Debug( LDAP_DEBUG_ANY, "connection_operation: " + "conn %lu unknown LDAP request 0x%lx\n", + conn->c_connid, tag, 0 ); + op->o_tag = LBER_ERROR; + rs.sr_err = LDAP_PROTOCOL_ERROR; + rs.sr_text = "unknown LDAP request"; + send_ldap_disconnect( op, &rs ); + rc = SLAPD_DISCONNECT; + goto operations_error; + } + + if( conn->c_sasl_bind_in_progress && tag != LDAP_REQ_BIND ) { + Debug( LDAP_DEBUG_ANY, "connection_operation: " + "error: SASL bind in progress (tag=%ld).\n", + (long) tag, 0, 0 ); + send_ldap_error( op, &rs, LDAP_OPERATIONS_ERROR, + "SASL bind in progress" ); + rc = LDAP_OPERATIONS_ERROR; + goto operations_error; + } + +#ifdef LDAP_X_TXN + if (( conn->c_txn == CONN_TXN_SPECIFY ) && ( + ( tag == LDAP_REQ_ADD ) || + ( tag == LDAP_REQ_DELETE ) || + ( tag == LDAP_REQ_MODIFY ) || + ( tag == LDAP_REQ_MODRDN ))) + { + /* Disable SLAB allocator for all update operations + issued inside of a transaction */ + op->o_tmpmemctx = NULL; + op->o_tmpmfuncs = &ch_mfuncs; + } else +#endif + { + /* We can use Thread-Local storage for most mallocs. We can + * also use TL for ber parsing, but not on Add or Modify. + */ +#if 0 + memsiz = ber_len( op->o_ber ) * 64; + if ( SLAP_SLAB_SIZE > memsiz ) memsiz = SLAP_SLAB_SIZE; +#endif + memsiz = SLAP_SLAB_SIZE; + + memctx = slap_sl_mem_create( memsiz, SLAP_SLAB_STACK, ctx, 1 ); + op->o_tmpmemctx = memctx; + op->o_tmpmfuncs = &slap_sl_mfuncs; + if ( tag != LDAP_REQ_ADD && tag != LDAP_REQ_MODIFY ) { + /* Note - the ber and its buffer are already allocated from + * regular memory; this only affects subsequent mallocs that + * ber_scanf may invoke. + */ + ber_set_option( op->o_ber, LBER_OPT_BER_MEMCTX, &memctx ); + } + } + + opidx = slap_req2op( tag ); + assert( opidx != SLAP_OP_LAST ); + INCR_OP_INITIATED( opidx ); + rc = (*(opfun[opidx]))( op, &rs ); + +operations_error: + if ( rc == SLAPD_DISCONNECT ) { + tag = LBER_ERROR; + + } else if ( opidx != SLAP_OP_LAST ) { + /* increment completed operations count + * only if operation was initiated + * and rc != SLAPD_DISCONNECT */ + INCR_OP_COMPLETED( opidx ); + } + + ldap_pvt_thread_mutex_lock( &conn->c_mutex ); + + if ( opidx == SLAP_OP_BIND && conn->c_conn_state == SLAP_C_BINDING ) + conn->c_conn_state = SLAP_C_ACTIVE; + + cancel = op->o_cancel; + if ( cancel != SLAP_CANCEL_NONE && cancel != SLAP_CANCEL_DONE ) { + if ( cancel == SLAP_CANCEL_REQ ) { + op->o_cancel = rc == SLAPD_ABANDON + ? SLAP_CANCEL_ACK : LDAP_TOO_LATE; + } + + do { + /* Fake a cond_wait with thread_yield, then + * verify the result properly mutex-protected. + */ + ldap_pvt_thread_mutex_unlock( &conn->c_mutex ); + do { + ldap_pvt_thread_yield(); + } while ( (cancel = op->o_cancel) != SLAP_CANCEL_NONE + && cancel != SLAP_CANCEL_DONE ); + ldap_pvt_thread_mutex_lock( &conn->c_mutex ); + } while ( (cancel = op->o_cancel) != SLAP_CANCEL_NONE + && cancel != SLAP_CANCEL_DONE ); + } + + ber_set_option( op->o_ber, LBER_OPT_BER_MEMCTX, &memctx_null ); + + LDAP_STAILQ_REMOVE( &conn->c_ops, op, Operation, o_next); + LDAP_STAILQ_NEXT(op, o_next) = NULL; + conn->c_n_ops_executing--; + conn->c_n_ops_completed++; + + switch( tag ) { + case LBER_ERROR: + case LDAP_REQ_UNBIND: + /* c_mutex is locked */ + connection_closing( conn, + tag == LDAP_REQ_UNBIND ? NULL : "operations error" ); + break; + } + + connection_resched( conn ); + ldap_pvt_thread_mutex_unlock( &conn->c_mutex ); + slap_op_free( op, ctx ); + return NULL; +} + +static const Listener dummy_list = { BER_BVC(""), BER_BVC("") }; + +Connection *connection_client_setup( + ber_socket_t s, + ldap_pvt_thread_start_t *func, + void *arg ) +{ + Connection *c; + ber_socket_t sfd = SLAP_SOCKNEW( s ); + + c = connection_init( sfd, (Listener *)&dummy_list, "", "", + CONN_IS_CLIENT, 0, NULL + LDAP_PF_LOCAL_SENDMSG_ARG(NULL)); + if ( c ) { + c->c_clientfunc = func; + c->c_clientarg = arg; + + slapd_add_internal( sfd, 0 ); + } + return c; +} + +void connection_client_enable( + Connection *c ) +{ + slapd_set_read( c->c_sd, 1 ); +} + +void connection_client_stop( + Connection *c ) +{ + Sockbuf *sb; + ber_socket_t s = c->c_sd; + + /* get (locked) connection */ + c = connection_get( s ); + + assert( c->c_conn_state == SLAP_C_CLIENT ); + + c->c_listener = NULL; + c->c_conn_state = SLAP_C_INVALID; + c->c_struct_state = SLAP_C_UNUSED; + c->c_sd = AC_SOCKET_INVALID; + c->c_close_reason = "?"; /* should never be needed */ + sb = c->c_sb; + c->c_sb = ber_sockbuf_alloc( ); + { + ber_len_t max = sockbuf_max_incoming; + ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_MAX_INCOMING, &max ); + } + slapd_remove( s, sb, 0, 1, 0 ); + + connection_return( c ); +} + +static int connection_read( ber_socket_t s, conn_readinfo *cri ); + +static void* connection_read_thread( void* ctx, void* argv ) +{ + int rc ; + conn_readinfo cri = { NULL, NULL, NULL, NULL, 0 }; + ber_socket_t s = (long)argv; + + /* + * read incoming LDAP requests. If there is more than one, + * the first one is returned with new_op + */ + cri.ctx = ctx; + if( ( rc = connection_read( s, &cri ) ) < 0 ) { + Debug( LDAP_DEBUG_CONNS, "connection_read(%d) error\n", s, 0, 0 ); + return (void*)(long)rc; + } + + /* execute a single queued request in the same thread */ + if( cri.op && !cri.nullop ) { + rc = (long)connection_operation( ctx, cri.op ); + } else if ( cri.func ) { + rc = (long)cri.func( ctx, cri.arg ); + } + + return (void*)(long)rc; +} + +int connection_read_activate( ber_socket_t s ) +{ + int rc; + + /* + * suspend reading on this file descriptor until a connection processing + * thread reads data on it. Otherwise the listener thread will repeatedly + * submit the same event on it to the pool. + */ + rc = slapd_clr_read( s, 0 ); + if ( rc ) + return rc; + + /* Don't let blocked writers block a pause request */ + if ( connections[s].c_writewaiter && + ldap_pvt_thread_pool_pausing( &connection_pool )) + connection_wake_writers( &connections[s] ); + + rc = ldap_pvt_thread_pool_submit( &connection_pool, + connection_read_thread, (void *)(long)s ); + + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "connection_read_activate(%d): submit failed (%d)\n", + s, rc, 0 ); + } + + return rc; +} + +static int +connection_read( ber_socket_t s, conn_readinfo *cri ) +{ + int rc = 0; + Connection *c; + + assert( connections != NULL ); + + /* get (locked) connection */ + c = connection_get( s ); + + if( c == NULL ) { + Debug( LDAP_DEBUG_ANY, + "connection_read(%ld): no connection!\n", + (long) s, 0, 0 ); + + return -1; + } + + c->c_n_read++; + + if( c->c_conn_state == SLAP_C_CLOSING ) { + Debug( LDAP_DEBUG_CONNS, + "connection_read(%d): closing, ignoring input for id=%lu\n", + s, c->c_connid, 0 ); + connection_return( c ); + return 0; + } + + if ( c->c_conn_state == SLAP_C_CLIENT ) { + cri->func = c->c_clientfunc; + cri->arg = c->c_clientarg; + /* read should already be cleared */ + connection_return( c ); + return 0; + } + + Debug( LDAP_DEBUG_TRACE, + "connection_read(%d): checking for input on id=%lu\n", + s, c->c_connid, 0 ); + +#ifdef HAVE_TLS + if ( c->c_is_tls && c->c_needs_tls_accept ) { + rc = ldap_pvt_tls_accept( c->c_sb, slap_tls_ctx ); + if ( rc < 0 ) { + Debug( LDAP_DEBUG_TRACE, + "connection_read(%d): TLS accept failure " + "error=%d id=%lu, closing\n", + s, rc, c->c_connid ); + + c->c_needs_tls_accept = 0; + /* c_mutex is locked */ + connection_closing( c, "TLS negotiation failure" ); + connection_close( c ); + connection_return( c ); + return 0; + + } else if ( rc == 0 ) { + void *ssl; + struct berval authid = BER_BVNULL; + + c->c_needs_tls_accept = 0; + + /* we need to let SASL know */ + ssl = ldap_pvt_tls_sb_ctx( c->c_sb ); + + c->c_tls_ssf = (slap_ssf_t) ldap_pvt_tls_get_strength( ssl ); + if( c->c_tls_ssf > c->c_ssf ) { + c->c_ssf = c->c_tls_ssf; + } + + rc = dnX509peerNormalize( ssl, &authid ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "connection_read(%d): " + "unable to get TLS client DN, error=%d id=%lu\n", + s, rc, c->c_connid ); + } + Statslog( LDAP_DEBUG_STATS, + "conn=%lu fd=%d TLS established tls_ssf=%u ssf=%u\n", + c->c_connid, (int) s, c->c_tls_ssf, c->c_ssf, 0 ); + slap_sasl_external( c, c->c_tls_ssf, &authid ); + if ( authid.bv_val ) free( authid.bv_val ); + } else if ( rc == 1 && ber_sockbuf_ctrl( c->c_sb, + LBER_SB_OPT_NEEDS_WRITE, NULL )) { /* need to retry */ + slapd_set_write( s, 1 ); + connection_return( c ); + return 0; + } + + /* if success and data is ready, fall thru to data input loop */ + if( !ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_DATA_READY, NULL ) ) + { + slapd_set_read( s, 1 ); + connection_return( c ); + return 0; + } + } +#endif + +#ifdef HAVE_CYRUS_SASL + if ( c->c_sasl_layers ) { + /* If previous layer is not removed yet, give up for now */ + if ( !c->c_sasl_sockctx ) { + slapd_set_read( s, 1 ); + connection_return( c ); + return 0; + } + + c->c_sasl_layers = 0; + + rc = ldap_pvt_sasl_install( c->c_sb, c->c_sasl_sockctx ); + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "connection_read(%d): SASL install error " + "error=%d id=%lu, closing\n", + s, rc, c->c_connid ); + + /* c_mutex is locked */ + connection_closing( c, "SASL layer install failure" ); + connection_close( c ); + connection_return( c ); + return 0; + } + } +#endif + +#define CONNECTION_INPUT_LOOP 1 +/* #define DATA_READY_LOOP 1 */ + + do { + /* How do we do this without getting into a busy loop ? */ + rc = connection_input( c, cri ); + } +#ifdef DATA_READY_LOOP + while( !rc && ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_DATA_READY, NULL )); +#elif defined CONNECTION_INPUT_LOOP + while(!rc); +#else + while(0); +#endif + + if( rc < 0 ) { + Debug( LDAP_DEBUG_CONNS, + "connection_read(%d): input error=%d id=%lu, closing.\n", + s, rc, c->c_connid ); + + /* c_mutex is locked */ + connection_closing( c, conn_lost_str ); + connection_close( c ); + connection_return( c ); + return 0; + } + + if ( ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_NEEDS_WRITE, NULL ) ) { + slapd_set_write( s, 0 ); + } + + slapd_set_read( s, 1 ); + connection_return( c ); + + return 0; +} + +static int +connection_input( Connection *conn , conn_readinfo *cri ) +{ + Operation *op; + ber_tag_t tag; + ber_len_t len; + ber_int_t msgid; + BerElement *ber; + int rc; +#ifdef LDAP_CONNECTIONLESS + Sockaddr peeraddr; + char *cdn = NULL; +#endif + char *defer = NULL; + void *ctx; + + if ( conn->c_currentber == NULL && + ( conn->c_currentber = ber_alloc()) == NULL ) + { + Debug( LDAP_DEBUG_ANY, "ber_alloc failed\n", 0, 0, 0 ); + return -1; + } + + sock_errset(0); + +#ifdef LDAP_CONNECTIONLESS + if ( conn->c_is_udp ) { +#if defined(LDAP_PF_INET6) + char peername[sizeof("IP=[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535")]; + char addr[INET6_ADDRSTRLEN]; +#else + char peername[sizeof("IP=255.255.255.255:65336")]; + char addr[INET_ADDRSTRLEN]; +#endif + const char *peeraddr_string = NULL; + + len = ber_int_sb_read(conn->c_sb, &peeraddr, sizeof(Sockaddr)); + if (len != sizeof(Sockaddr)) return 1; + +#if defined(LDAP_PF_INET6) + if (peeraddr.sa_addr.sa_family == AF_INET6) { + if ( IN6_IS_ADDR_V4MAPPED(&peeraddr.sa_in6_addr.sin6_addr) ) { +#if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP ) + peeraddr_string = inet_ntop( AF_INET, + ((struct in_addr *)&peeraddr.sa_in6_addr.sin6_addr.s6_addr[12]), + addr, sizeof(addr) ); +#else /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + peeraddr_string = inet_ntoa( *((struct in_addr *) + &peeraddr.sa_in6_addr.sin6_addr.s6_addr[12]) ); +#endif /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + if ( !peeraddr_string ) peeraddr_string = SLAP_STRING_UNKNOWN; + sprintf( peername, "IP=%s:%d", peeraddr_string, + (unsigned) ntohs( peeraddr.sa_in6_addr.sin6_port ) ); + } else { + peeraddr_string = inet_ntop( AF_INET6, + &peeraddr.sa_in6_addr.sin6_addr, + addr, sizeof addr ); + if ( !peeraddr_string ) peeraddr_string = SLAP_STRING_UNKNOWN; + sprintf( peername, "IP=[%s]:%d", peeraddr_string, + (unsigned) ntohs( peeraddr.sa_in6_addr.sin6_port ) ); + } + } else +#endif +#if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP ) + { + peeraddr_string = inet_ntop( AF_INET, &peeraddr.sa_in_addr.sin_addr, + addr, sizeof(addr) ); +#else /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + peeraddr_string = inet_ntoa( peeraddr.sa_in_addr.sin_addr ); +#endif /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + sprintf( peername, "IP=%s:%d", + peeraddr_string, + (unsigned) ntohs( peeraddr.sa_in_addr.sin_port ) ); + } + Statslog( LDAP_DEBUG_STATS, + "conn=%lu UDP request from %s (%s) accepted.\n", + conn->c_connid, peername, conn->c_sock_name.bv_val, 0, 0 ); + } +#endif + + tag = ber_get_next( conn->c_sb, &len, conn->c_currentber ); + if ( tag != LDAP_TAG_MESSAGE ) { + int err = sock_errno(); + + if ( err != EWOULDBLOCK && err != EAGAIN ) { + /* log, close and send error */ + Debug( LDAP_DEBUG_TRACE, + "ber_get_next on fd %d failed errno=%d (%s)\n", + conn->c_sd, err, sock_errstr(err) ); + ber_free( conn->c_currentber, 1 ); + conn->c_currentber = NULL; + + return -2; + } + return 1; + } + + ber = conn->c_currentber; + conn->c_currentber = NULL; + + if ( (tag = ber_get_int( ber, &msgid )) != LDAP_TAG_MSGID ) { + /* log, close and send error */ + Debug( LDAP_DEBUG_ANY, "ber_get_int returns 0x%lx\n", tag, 0, 0 ); + ber_free( ber, 1 ); + return -1; + } + + if ( (tag = ber_peek_tag( ber, &len )) == LBER_ERROR ) { + /* log, close and send error */ + Debug( LDAP_DEBUG_ANY, "ber_peek_tag returns 0x%lx\n", tag, 0, 0 ); + ber_free( ber, 1 ); + + return -1; + } + +#ifdef LDAP_CONNECTIONLESS + if( conn->c_is_udp ) { + if( tag == LBER_OCTETSTRING ) { + if ( (tag = ber_get_stringa( ber, &cdn )) != LBER_ERROR ) + tag = ber_peek_tag( ber, &len ); + } + if( tag != LDAP_REQ_ABANDON && tag != LDAP_REQ_SEARCH ) { + Debug( LDAP_DEBUG_ANY, "invalid req for UDP 0x%lx\n", tag, 0, 0 ); + ber_free( ber, 1 ); + return 0; + } + } +#endif + + if(tag == LDAP_REQ_BIND) { + /* immediately abandon all existing operations upon BIND */ + connection_abandon( conn ); + } + + ctx = cri->ctx; + op = slap_op_alloc( ber, msgid, tag, conn->c_n_ops_received++, ctx ); + + Debug( LDAP_DEBUG_TRACE, "op tag 0x%lx, time %ld\n", tag, + (long) op->o_time, 0); + + op->o_conn = conn; + /* clear state if the connection is being reused from inactive */ + if ( conn->c_conn_state == SLAP_C_INACTIVE ) { + memset( &conn->c_pagedresults_state, 0, + sizeof( conn->c_pagedresults_state ) ); + } + + op->o_res_ber = NULL; + +#ifdef LDAP_CONNECTIONLESS + if (conn->c_is_udp) { + if ( cdn ) { + ber_str2bv( cdn, 0, 1, &op->o_dn ); + op->o_protocol = LDAP_VERSION2; + } + op->o_res_ber = ber_alloc_t( LBER_USE_DER ); + if (op->o_res_ber == NULL) return 1; + + rc = ber_write( op->o_res_ber, (char *)&peeraddr, + sizeof(struct sockaddr), 0 ); + + if (rc != sizeof(struct sockaddr)) { + Debug( LDAP_DEBUG_ANY, "ber_write failed\n", 0, 0, 0 ); + return 1; + } + + if (op->o_protocol == LDAP_VERSION2) { + rc = ber_printf(op->o_res_ber, "{is{" /*}}*/, op->o_msgid, ""); + if (rc == -1) { + Debug( LDAP_DEBUG_ANY, "ber_write failed\n", 0, 0, 0 ); + return rc; + } + } + } +#endif /* LDAP_CONNECTIONLESS */ + + rc = 0; + + /* Don't process requests when the conn is in the middle of a + * Bind, or if it's closing. Also, don't let any single conn + * use up all the available threads, and don't execute if we're + * currently blocked on output. And don't execute if there are + * already pending ops, let them go first. Abandon operations + * get exceptions to some, but not all, cases. + */ + switch( tag ){ + default: + /* Abandon and Unbind are exempt from these checks */ + if (conn->c_conn_state == SLAP_C_CLOSING) { + defer = "closing"; + break; + } else if (conn->c_writewaiter) { + defer = "awaiting write"; + break; + } else if (conn->c_n_ops_pending) { + defer = "pending operations"; + break; + } + /* FALLTHRU */ + case LDAP_REQ_ABANDON: + /* Unbind is exempt from these checks */ + if (conn->c_n_ops_executing >= connection_pool_max/2) { + defer = "too many executing"; + break; + } else if (conn->c_conn_state == SLAP_C_BINDING) { + defer = "binding"; + break; + } + /* FALLTHRU */ + case LDAP_REQ_UNBIND: + break; + } + + if( defer ) { + int max = conn->c_dn.bv_len + ? slap_conn_max_pending_auth + : slap_conn_max_pending; + + Debug( LDAP_DEBUG_ANY, + "connection_input: conn=%lu deferring operation: %s\n", + conn->c_connid, defer, 0 ); + conn->c_n_ops_pending++; + LDAP_STAILQ_INSERT_TAIL( &conn->c_pending_ops, op, o_next ); + rc = ( conn->c_n_ops_pending > max ) ? -1 : 0; + + } else { + conn->c_n_ops_executing++; + + /* + * The first op will be processed in the same thread context, + * as long as there is only one op total. + * Subsequent ops will be submitted to the pool by + * calling connection_op_activate() + */ + if ( cri->op == NULL ) { + /* the first incoming request */ + connection_op_queue( op ); + cri->op = op; + } else { + if ( !cri->nullop ) { + cri->nullop = 1; + rc = ldap_pvt_thread_pool_submit( &connection_pool, + connection_operation, (void *) cri->op ); + } + connection_op_activate( op ); + } + } + +#ifdef NO_THREADS + if ( conn->c_struct_state != SLAP_C_USED ) { + /* connection must have got closed underneath us */ + return 1; + } +#endif + + assert( conn->c_struct_state == SLAP_C_USED ); + return rc; +} + +static int +connection_resched( Connection *conn ) +{ + Operation *op; + + if( conn->c_writewaiter ) + return 0; + + if( conn->c_conn_state == SLAP_C_CLOSING ) { + Debug( LDAP_DEBUG_CONNS, "connection_resched: " + "attempting closing conn=%lu sd=%d\n", + conn->c_connid, conn->c_sd, 0 ); + connection_close( conn ); + return 0; + } + + if( conn->c_conn_state != SLAP_C_ACTIVE ) { + /* other states need different handling */ + return 0; + } + + while ((op = LDAP_STAILQ_FIRST( &conn->c_pending_ops )) != NULL) { + if ( conn->c_n_ops_executing > connection_pool_max/2 ) break; + + LDAP_STAILQ_REMOVE_HEAD( &conn->c_pending_ops, o_next ); + LDAP_STAILQ_NEXT(op, o_next) = NULL; + + /* pending operations should not be marked for abandonment */ + assert(!op->o_abandon); + + conn->c_n_ops_pending--; + conn->c_n_ops_executing++; + + connection_op_activate( op ); + + if ( conn->c_conn_state == SLAP_C_BINDING ) break; + } + return 0; +} + +static void +connection_init_log_prefix( Operation *op ) +{ + if ( op->o_connid == (unsigned long)(-1) ) { + snprintf( op->o_log_prefix, sizeof( op->o_log_prefix ), + "conn=-1 op=%lu", op->o_opid ); + + } else { + snprintf( op->o_log_prefix, sizeof( op->o_log_prefix ), + "conn=%lu op=%lu", op->o_connid, op->o_opid ); + } +} + +static int connection_bind_cleanup_cb( Operation *op, SlapReply *rs ) +{ + op->o_conn->c_sasl_bindop = NULL; + + ch_free( op->o_callback ); + op->o_callback = NULL; + + return SLAP_CB_CONTINUE; +} + +static int connection_bind_cb( Operation *op, SlapReply *rs ) +{ + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + op->o_conn->c_sasl_bind_in_progress = + ( rs->sr_err == LDAP_SASL_BIND_IN_PROGRESS ); + + /* Moved here from bind.c due to ITS#4158 */ + op->o_conn->c_sasl_bindop = NULL; + if ( op->orb_method == LDAP_AUTH_SASL ) { + if( rs->sr_err == LDAP_SUCCESS ) { + ber_dupbv(&op->o_conn->c_dn, &op->orb_edn); + if( !BER_BVISEMPTY( &op->orb_edn ) ) { + /* edn is always normalized already */ + ber_dupbv( &op->o_conn->c_ndn, &op->o_conn->c_dn ); + } + op->o_tmpfree( op->orb_edn.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &op->orb_edn ); + op->o_conn->c_authmech = op->o_conn->c_sasl_bind_mech; + BER_BVZERO( &op->o_conn->c_sasl_bind_mech ); + + op->o_conn->c_sasl_ssf = op->orb_ssf; + if( op->orb_ssf > op->o_conn->c_ssf ) { + op->o_conn->c_ssf = op->orb_ssf; + } + + if( !BER_BVISEMPTY( &op->o_conn->c_dn ) ) { + ber_len_t max = sockbuf_max_incoming_auth; + ber_sockbuf_ctrl( op->o_conn->c_sb, + LBER_SB_OPT_SET_MAX_INCOMING, &max ); + } + + /* log authorization identity */ + Statslog( LDAP_DEBUG_STATS, + "%s BIND dn=\"%s\" mech=%s sasl_ssf=%d ssf=%d\n", + op->o_log_prefix, + BER_BVISNULL( &op->o_conn->c_dn ) ? "<empty>" : op->o_conn->c_dn.bv_val, + op->o_conn->c_authmech.bv_val, + op->orb_ssf, op->o_conn->c_ssf ); + + Debug( LDAP_DEBUG_TRACE, + "do_bind: SASL/%s bind: dn=\"%s\" sasl_ssf=%d\n", + op->o_conn->c_authmech.bv_val, + BER_BVISNULL( &op->o_conn->c_dn ) ? "<empty>" : op->o_conn->c_dn.bv_val, + op->orb_ssf ); + + } else if ( rs->sr_err != LDAP_SASL_BIND_IN_PROGRESS ) { + if ( !BER_BVISNULL( &op->o_conn->c_sasl_bind_mech ) ) { + free( op->o_conn->c_sasl_bind_mech.bv_val ); + BER_BVZERO( &op->o_conn->c_sasl_bind_mech ); + } + } + } + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + + ch_free( op->o_callback ); + op->o_callback = NULL; + + return SLAP_CB_CONTINUE; +} + +static void connection_op_queue( Operation *op ) +{ + ber_tag_t tag = op->o_tag; + + if (tag == LDAP_REQ_BIND) { + slap_callback *sc = ch_calloc( 1, sizeof( slap_callback )); + sc->sc_response = connection_bind_cb; + sc->sc_cleanup = connection_bind_cleanup_cb; + sc->sc_next = op->o_callback; + op->o_callback = sc; + op->o_conn->c_conn_state = SLAP_C_BINDING; + } + + if (!op->o_dn.bv_len) { + op->o_authz = op->o_conn->c_authz; + if ( BER_BVISNULL( &op->o_conn->c_sasl_authz_dn )) { + ber_dupbv( &op->o_dn, &op->o_conn->c_dn ); + ber_dupbv( &op->o_ndn, &op->o_conn->c_ndn ); + } else { + ber_dupbv( &op->o_dn, &op->o_conn->c_sasl_authz_dn ); + ber_dupbv( &op->o_ndn, &op->o_conn->c_sasl_authz_dn ); + } + } + + op->o_authtype = op->o_conn->c_authtype; + ber_dupbv( &op->o_authmech, &op->o_conn->c_authmech ); + + if (!op->o_protocol) { + op->o_protocol = op->o_conn->c_protocol + ? op->o_conn->c_protocol : LDAP_VERSION3; + } + + if (op->o_conn->c_conn_state == SLAP_C_INACTIVE && + op->o_protocol > LDAP_VERSION2) + { + op->o_conn->c_conn_state = SLAP_C_ACTIVE; + } + + op->o_connid = op->o_conn->c_connid; + connection_init_log_prefix( op ); + + LDAP_STAILQ_INSERT_TAIL( &op->o_conn->c_ops, op, o_next ); +} + +static int connection_op_activate( Operation *op ) +{ + int rc; + + connection_op_queue( op ); + + rc = ldap_pvt_thread_pool_submit( &connection_pool, + connection_operation, (void *) op ); + + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "connection_op_activate: submit failed (%d) for conn=%lu\n", + rc, op->o_connid, 0 ); + /* should move op to pending list */ + } + + return rc; +} + +int connection_write(ber_socket_t s) +{ + Connection *c; + Operation *op; + + assert( connections != NULL ); + + c = connection_get( s ); + if( c == NULL ) { + Debug( LDAP_DEBUG_ANY, + "connection_write(%ld): no connection!\n", + (long)s, 0, 0 ); + return -1; + } + + slapd_clr_write( s, 0 ); + +#ifdef HAVE_TLS + if ( c->c_is_tls && c->c_needs_tls_accept ) { + connection_return( c ); + connection_read_activate( s ); + return 0; + } +#endif + + c->c_n_write++; + + Debug( LDAP_DEBUG_TRACE, + "connection_write(%d): waking output for id=%lu\n", + s, c->c_connid, 0 ); + ldap_pvt_thread_mutex_lock( &c->c_write2_mutex ); + ldap_pvt_thread_cond_signal( &c->c_write2_cv ); + ldap_pvt_thread_mutex_unlock( &c->c_write2_mutex ); + + if ( ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_NEEDS_READ, NULL ) ) { + slapd_set_read( s, 1 ); + } + if ( ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_NEEDS_WRITE, NULL ) ) { + slapd_set_write( s, 1 ); + } + + /* If there are ops pending because of a writewaiter, + * start one up. + */ + while ((op = LDAP_STAILQ_FIRST( &c->c_pending_ops )) != NULL) { + if ( !c->c_writewaiter ) break; + if ( c->c_n_ops_executing > connection_pool_max/2 ) break; + + LDAP_STAILQ_REMOVE_HEAD( &c->c_pending_ops, o_next ); + LDAP_STAILQ_NEXT(op, o_next) = NULL; + + /* pending operations should not be marked for abandonment */ + assert(!op->o_abandon); + + c->c_n_ops_pending--; + c->c_n_ops_executing++; + + connection_op_activate( op ); + + break; + } + + connection_return( c ); + return 0; +} + +#ifdef LDAP_SLAPI +typedef struct conn_fake_extblock { + void *eb_conn; + void *eb_op; +} conn_fake_extblock; + +static void +connection_fake_destroy( + void *key, + void *data ) +{ + Connection conn = {0}; + Operation op = {0}; + Opheader ohdr = {0}; + + conn_fake_extblock *eb = data; + + op.o_hdr = &ohdr; + op.o_hdr->oh_extensions = eb->eb_op; + conn.c_extensions = eb->eb_conn; + op.o_conn = &conn; + conn.c_connid = -1; + op.o_connid = -1; + + ber_memfree_x( eb, NULL ); + slapi_int_free_object_extensions( SLAPI_X_EXT_OPERATION, &op ); + slapi_int_free_object_extensions( SLAPI_X_EXT_CONNECTION, &conn ); +} +#endif + +void +connection_fake_init( + Connection *conn, + OperationBuffer *opbuf, + void *ctx ) +{ + connection_fake_init2( conn, opbuf, ctx, 1 ); +} + +void +operation_fake_init( + Connection *conn, + Operation *op, + void *ctx, + int newmem ) +{ + /* set memory context */ + op->o_tmpmemctx = slap_sl_mem_create(SLAP_SLAB_SIZE, SLAP_SLAB_STACK, ctx, + newmem ); + op->o_tmpmfuncs = &slap_sl_mfuncs; + op->o_threadctx = ctx; + op->o_tid = ldap_pvt_thread_pool_tid( ctx ); + + op->o_counters = &slap_counters; + op->o_conn = conn; + op->o_connid = op->o_conn->c_connid; + connection_init_log_prefix( op ); +} + + +void +connection_fake_init2( + Connection *conn, + OperationBuffer *opbuf, + void *ctx, + int newmem ) +{ + Operation *op = (Operation *) opbuf; + + conn->c_connid = -1; + conn->c_conn_idx = -1; + conn->c_send_ldap_result = slap_send_ldap_result; + conn->c_send_search_entry = slap_send_search_entry; + conn->c_send_search_reference = slap_send_search_reference; + conn->c_send_ldap_extended = slap_send_ldap_extended; + conn->c_send_ldap_intermediate = slap_send_ldap_intermediate; + conn->c_listener = (Listener *)&dummy_list; + conn->c_peer_domain = slap_empty_bv; + conn->c_peer_name = slap_empty_bv; + + memset( opbuf, 0, sizeof( *opbuf )); + op->o_hdr = &opbuf->ob_hdr; + op->o_controls = opbuf->ob_controls; + + operation_fake_init( conn, op, ctx, newmem ); + +#ifdef LDAP_SLAPI + if ( slapi_plugins_used ) { + conn_fake_extblock *eb; + void *ebx = NULL; + + /* Use thread keys to make sure these eventually get cleaned up */ + if ( ldap_pvt_thread_pool_getkey( ctx, (void *)connection_fake_init, + &ebx, NULL )) { + eb = ch_malloc( sizeof( *eb )); + slapi_int_create_object_extensions( SLAPI_X_EXT_CONNECTION, conn ); + slapi_int_create_object_extensions( SLAPI_X_EXT_OPERATION, op ); + eb->eb_conn = conn->c_extensions; + eb->eb_op = op->o_hdr->oh_extensions; + ldap_pvt_thread_pool_setkey( ctx, (void *)connection_fake_init, + eb, connection_fake_destroy, NULL, NULL ); + } else { + eb = ebx; + conn->c_extensions = eb->eb_conn; + op->o_hdr->oh_extensions = eb->eb_op; + } + } +#endif /* LDAP_SLAPI */ + + slap_op_time( &op->o_time, &op->o_tincr ); +} + +void +connection_assign_nextid( Connection *conn ) +{ + ldap_pvt_thread_mutex_lock( &conn_nextid_mutex ); + conn->c_connid = conn_nextid++; + ldap_pvt_thread_mutex_unlock( &conn_nextid_mutex ); +} diff --git a/servers/slapd/controls.c b/servers/slapd/controls.c new file mode 100644 index 0000000..5d4eb1e --- /dev/null +++ b/servers/slapd/controls.c @@ -0,0 +1,2154 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "ldif.h" +#include "lutil.h" + +#include "../../libraries/liblber/lber-int.h" + +static SLAP_CTRL_PARSE_FN parseAssert; +static SLAP_CTRL_PARSE_FN parseDomainScope; +static SLAP_CTRL_PARSE_FN parseDontUseCopy; +static SLAP_CTRL_PARSE_FN parseManageDSAit; +static SLAP_CTRL_PARSE_FN parseNoOp; +static SLAP_CTRL_PARSE_FN parsePagedResults; +static SLAP_CTRL_PARSE_FN parsePermissiveModify; +static SLAP_CTRL_PARSE_FN parsePreRead, parsePostRead; +static SLAP_CTRL_PARSE_FN parseProxyAuthz; +static SLAP_CTRL_PARSE_FN parseRelax; +static SLAP_CTRL_PARSE_FN parseSearchOptions; +#ifdef SLAP_CONTROL_X_SORTEDRESULTS +static SLAP_CTRL_PARSE_FN parseSortedResults; +#endif +static SLAP_CTRL_PARSE_FN parseSubentries; +#ifdef SLAP_CONTROL_X_TREE_DELETE +static SLAP_CTRL_PARSE_FN parseTreeDelete; +#endif +static SLAP_CTRL_PARSE_FN parseValuesReturnFilter; +#ifdef SLAP_CONTROL_X_SESSION_TRACKING +static SLAP_CTRL_PARSE_FN parseSessionTracking; +#endif +#ifdef SLAP_CONTROL_X_WHATFAILED +static SLAP_CTRL_PARSE_FN parseWhatFailed; +#endif + +#undef sc_mask /* avoid conflict with Irix 6.5 <sys/signal.h> */ + +const struct berval slap_pre_read_bv = BER_BVC(LDAP_CONTROL_PRE_READ); +const struct berval slap_post_read_bv = BER_BVC(LDAP_CONTROL_POST_READ); + +struct slap_control_ids slap_cids; + +struct slap_control { + /* Control OID */ + char *sc_oid; + + /* The controlID for this control */ + int sc_cid; + + /* Operations supported by control */ + slap_mask_t sc_mask; + + /* Extended operations supported by control */ + char **sc_extendedops; /* input */ + BerVarray sc_extendedopsbv; /* run-time use */ + + /* Control parsing callback */ + SLAP_CTRL_PARSE_FN *sc_parse; + + LDAP_SLIST_ENTRY(slap_control) sc_next; +}; + +static LDAP_SLIST_HEAD(ControlsList, slap_control) controls_list + = LDAP_SLIST_HEAD_INITIALIZER(&controls_list); + +/* + * all known request control OIDs should be added to this list + */ +/* + * NOTE: initialize num_known_controls to 1 so that cid = 0 always + * addresses an undefined control; this allows to safely test for + * well known controls even if they are not registered, e.g. if + * they get moved to modules. An example is sc_LDAPsync, which + * is implemented in the syncprov overlay and thus, if configured + * as dynamic module, may not be registered. One side effect is that + * slap_known_controls[0] == NULL, so it should always be used + * starting from 1. + * FIXME: should we define the "undefined control" oid? + */ +char *slap_known_controls[SLAP_MAX_CIDS+1]; +static int num_known_controls = 1; + +static char *proxy_authz_extops[] = { + LDAP_EXOP_MODIFY_PASSWD, + LDAP_EXOP_WHO_AM_I, + LDAP_EXOP_REFRESH, + NULL +}; + +static char *manageDSAit_extops[] = { + LDAP_EXOP_REFRESH, + NULL +}; + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING +static char *session_tracking_extops[] = { + LDAP_EXOP_MODIFY_PASSWD, + LDAP_EXOP_WHO_AM_I, + LDAP_EXOP_REFRESH, + NULL +}; +#endif + +static struct slap_control control_defs[] = { + { LDAP_CONTROL_ASSERT, + (int)offsetof(struct slap_control_ids, sc_assert), + SLAP_CTRL_UPDATE|SLAP_CTRL_COMPARE|SLAP_CTRL_SEARCH, + NULL, NULL, + parseAssert, LDAP_SLIST_ENTRY_INITIALIZER(next) }, + { LDAP_CONTROL_PRE_READ, + (int)offsetof(struct slap_control_ids, sc_preRead), + SLAP_CTRL_DELETE|SLAP_CTRL_MODIFY|SLAP_CTRL_RENAME, + NULL, NULL, + parsePreRead, LDAP_SLIST_ENTRY_INITIALIZER(next) }, + { LDAP_CONTROL_POST_READ, + (int)offsetof(struct slap_control_ids, sc_postRead), + SLAP_CTRL_ADD|SLAP_CTRL_MODIFY|SLAP_CTRL_RENAME, + NULL, NULL, + parsePostRead, LDAP_SLIST_ENTRY_INITIALIZER(next) }, + { LDAP_CONTROL_VALUESRETURNFILTER, + (int)offsetof(struct slap_control_ids, sc_valuesReturnFilter), + SLAP_CTRL_GLOBAL|SLAP_CTRL_SEARCH, + NULL, NULL, + parseValuesReturnFilter, LDAP_SLIST_ENTRY_INITIALIZER(next) }, + { LDAP_CONTROL_PAGEDRESULTS, + (int)offsetof(struct slap_control_ids, sc_pagedResults), + SLAP_CTRL_SEARCH, + NULL, NULL, + parsePagedResults, LDAP_SLIST_ENTRY_INITIALIZER(next) }, +#ifdef SLAP_CONTROL_X_SORTEDRESULTS + { LDAP_CONTROL_SORTREQUEST, + (int)offsetof(struct slap_control_ids, sc_sortedResults), + SLAP_CTRL_GLOBAL|SLAP_CTRL_SEARCH|SLAP_CTRL_HIDE, + NULL, NULL, + parseSortedResults, LDAP_SLIST_ENTRY_INITIALIZER(next) }, +#endif + { LDAP_CONTROL_X_DOMAIN_SCOPE, + (int)offsetof(struct slap_control_ids, sc_domainScope), + SLAP_CTRL_GLOBAL|SLAP_CTRL_SEARCH|SLAP_CTRL_HIDE, + NULL, NULL, + parseDomainScope, LDAP_SLIST_ENTRY_INITIALIZER(next) }, + { LDAP_CONTROL_DONTUSECOPY, + (int)offsetof(struct slap_control_ids, sc_dontUseCopy), + SLAP_CTRL_GLOBAL|SLAP_CTRL_INTROGATE, + NULL, NULL, + parseDontUseCopy, LDAP_SLIST_ENTRY_INITIALIZER(next) }, + { LDAP_CONTROL_X_PERMISSIVE_MODIFY, + (int)offsetof(struct slap_control_ids, sc_permissiveModify), + SLAP_CTRL_MODIFY|SLAP_CTRL_HIDE, + NULL, NULL, + parsePermissiveModify, LDAP_SLIST_ENTRY_INITIALIZER(next) }, +#ifdef SLAP_CONTROL_X_TREE_DELETE + { LDAP_CONTROL_X_TREE_DELETE, + (int)offsetof(struct slap_control_ids, sc_treeDelete), + SLAP_CTRL_DELETE|SLAP_CTRL_HIDE, + NULL, NULL, + parseTreeDelete, LDAP_SLIST_ENTRY_INITIALIZER(next) }, +#endif + { LDAP_CONTROL_X_SEARCH_OPTIONS, + (int)offsetof(struct slap_control_ids, sc_searchOptions), + SLAP_CTRL_GLOBAL|SLAP_CTRL_SEARCH|SLAP_CTRL_HIDE, + NULL, NULL, + parseSearchOptions, LDAP_SLIST_ENTRY_INITIALIZER(next) }, + { LDAP_CONTROL_SUBENTRIES, + (int)offsetof(struct slap_control_ids, sc_subentries), + SLAP_CTRL_SEARCH, + NULL, NULL, + parseSubentries, LDAP_SLIST_ENTRY_INITIALIZER(next) }, + { LDAP_CONTROL_NOOP, + (int)offsetof(struct slap_control_ids, sc_noOp), + SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE, + NULL, NULL, + parseNoOp, LDAP_SLIST_ENTRY_INITIALIZER(next) }, + { LDAP_CONTROL_RELAX, + (int)offsetof(struct slap_control_ids, sc_relax), + SLAP_CTRL_GLOBAL|SLAP_CTRL_UPDATE|SLAP_CTRL_HIDE, + NULL, NULL, + parseRelax, LDAP_SLIST_ENTRY_INITIALIZER(next) }, +#ifdef LDAP_X_TXN + { LDAP_CONTROL_X_TXN_SPEC, + (int)offsetof(struct slap_control_ids, sc_txnSpec), + SLAP_CTRL_UPDATE|SLAP_CTRL_HIDE, + NULL, NULL, + txn_spec_ctrl, LDAP_SLIST_ENTRY_INITIALIZER(next) }, +#endif + { LDAP_CONTROL_MANAGEDSAIT, + (int)offsetof(struct slap_control_ids, sc_manageDSAit), + SLAP_CTRL_ACCESS, + manageDSAit_extops, NULL, + parseManageDSAit, LDAP_SLIST_ENTRY_INITIALIZER(next) }, + { LDAP_CONTROL_PROXY_AUTHZ, + (int)offsetof(struct slap_control_ids, sc_proxyAuthz), + SLAP_CTRL_GLOBAL|SLAP_CTRL_ACCESS, + proxy_authz_extops, NULL, + parseProxyAuthz, LDAP_SLIST_ENTRY_INITIALIZER(next) }, +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + { LDAP_CONTROL_X_SESSION_TRACKING, + (int)offsetof(struct slap_control_ids, sc_sessionTracking), + SLAP_CTRL_GLOBAL|SLAP_CTRL_ACCESS|SLAP_CTRL_BIND|SLAP_CTRL_HIDE, + session_tracking_extops, NULL, + parseSessionTracking, LDAP_SLIST_ENTRY_INITIALIZER(next) }, +#endif +#ifdef SLAP_CONTROL_X_WHATFAILED + { LDAP_CONTROL_X_WHATFAILED, + (int)offsetof(struct slap_control_ids, sc_whatFailed), + SLAP_CTRL_GLOBAL|SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE, + NULL, NULL, + parseWhatFailed, LDAP_SLIST_ENTRY_INITIALIZER(next) }, +#endif + + { NULL, 0, 0, NULL, 0, NULL, LDAP_SLIST_ENTRY_INITIALIZER(next) } +}; + +static struct slap_control * +find_ctrl( const char *oid ); + +/* + * Register a supported control. + * + * This can be called by an OpenLDAP plugin or, indirectly, by a + * SLAPI plugin calling slapi_register_supported_control(). + * + * NOTE: if flags == 1 the control is replaced if already registered; + * otherwise registering an already registered control is not allowed. + */ +int +register_supported_control2(const char *controloid, + slap_mask_t controlmask, + char **controlexops, + SLAP_CTRL_PARSE_FN *controlparsefn, + unsigned flags, + int *controlcid) +{ + struct slap_control *sc = NULL; + int i; + BerVarray extendedopsbv = NULL; + + if ( num_known_controls >= SLAP_MAX_CIDS ) { + Debug( LDAP_DEBUG_ANY, "Too many controls registered." + " Recompile slapd with SLAP_MAX_CIDS defined > %d\n", + num_known_controls, 0, 0 ); + return LDAP_OTHER; + } + + if ( controloid == NULL ) { + return LDAP_PARAM_ERROR; + } + + /* check if already registered */ + for ( i = 0; slap_known_controls[ i ]; i++ ) { + if ( strcmp( controloid, slap_known_controls[ i ] ) == 0 ) { + if ( flags == 1 ) { + Debug( LDAP_DEBUG_TRACE, + "Control %s already registered; replacing.\n", + controloid, 0, 0 ); + /* (find and) replace existing handler */ + sc = find_ctrl( controloid ); + assert( sc != NULL ); + break; + } + + Debug( LDAP_DEBUG_ANY, + "Control %s already registered.\n", + controloid, 0, 0 ); + return LDAP_PARAM_ERROR; + } + } + + /* turn compatible extended operations into bervals */ + if ( controlexops != NULL ) { + int i; + + for ( i = 0; controlexops[ i ]; i++ ); + + extendedopsbv = ber_memcalloc( i + 1, sizeof( struct berval ) ); + if ( extendedopsbv == NULL ) { + return LDAP_NO_MEMORY; + } + + for ( i = 0; controlexops[ i ]; i++ ) { + ber_str2bv( controlexops[ i ], 0, 1, &extendedopsbv[ i ] ); + } + } + + if ( sc == NULL ) { + sc = (struct slap_control *)SLAP_MALLOC( sizeof( *sc ) ); + if ( sc == NULL ) { + ber_bvarray_free( extendedopsbv ); + return LDAP_NO_MEMORY; + } + + sc->sc_oid = ch_strdup( controloid ); + sc->sc_cid = num_known_controls; + + /* Update slap_known_controls, too. */ + slap_known_controls[num_known_controls - 1] = sc->sc_oid; + slap_known_controls[num_known_controls++] = NULL; + + LDAP_SLIST_NEXT( sc, sc_next ) = NULL; + LDAP_SLIST_INSERT_HEAD( &controls_list, sc, sc_next ); + + } else { + if ( sc->sc_extendedopsbv ) { + /* FIXME: in principle, we should rather merge + * existing extops with those supported by the + * new control handling implementation. + * In fact, whether a control is compatible with + * an extop should not be a matter of implementation. + * We likely also need a means for a newly + * registered extop to declare that it is + * comptible with an already registered control. + */ + ber_bvarray_free( sc->sc_extendedopsbv ); + sc->sc_extendedopsbv = NULL; + sc->sc_extendedops = NULL; + } + } + + sc->sc_extendedopsbv = extendedopsbv; + sc->sc_mask = controlmask; + sc->sc_parse = controlparsefn; + if ( controlcid ) { + *controlcid = sc->sc_cid; + } + + return LDAP_SUCCESS; +} + +#ifdef SLAP_CONFIG_DELETE +int +unregister_supported_control( const char *controloid ) +{ + struct slap_control *sc; + int i; + + if ( controloid == NULL || (sc = find_ctrl( controloid )) == NULL ){ + return -1; + } + + for ( i = 0; slap_known_controls[ i ]; i++ ) { + if ( strcmp( controloid, slap_known_controls[ i ] ) == 0 ) { + do { + slap_known_controls[ i ] = slap_known_controls[ i+1 ]; + } while ( slap_known_controls[ i++ ] ); + num_known_controls--; + break; + } + } + + LDAP_SLIST_REMOVE(&controls_list, sc, slap_control, sc_next); + ch_free( sc->sc_oid ); + if ( sc->sc_extendedopsbv != NULL ) { + ber_bvarray_free( sc->sc_extendedopsbv ); + } + ch_free( sc ); + + return 0; +} +#endif /* SLAP_CONFIG_DELETE */ + +/* + * One-time initialization of internal controls. + */ +int +slap_controls_init( void ) +{ + int i, rc; + + rc = LDAP_SUCCESS; + + for ( i = 0; control_defs[i].sc_oid != NULL; i++ ) { + int *cid = (int *)(((char *)&slap_cids) + control_defs[i].sc_cid ); + rc = register_supported_control( control_defs[i].sc_oid, + control_defs[i].sc_mask, control_defs[i].sc_extendedops, + control_defs[i].sc_parse, cid ); + if ( rc != LDAP_SUCCESS ) break; + } + + return rc; +} + +/* + * Free memory associated with list of supported controls. + */ +void +controls_destroy( void ) +{ + struct slap_control *sc; + + while ( !LDAP_SLIST_EMPTY(&controls_list) ) { + sc = LDAP_SLIST_FIRST(&controls_list); + LDAP_SLIST_REMOVE_HEAD(&controls_list, sc_next); + + ch_free( sc->sc_oid ); + if ( sc->sc_extendedopsbv != NULL ) { + ber_bvarray_free( sc->sc_extendedopsbv ); + } + ch_free( sc ); + } +} + +/* + * Format the supportedControl attribute of the root DSE, + * detailing which controls are supported by the directory + * server. + */ +int +controls_root_dse_info( Entry *e ) +{ + AttributeDescription *ad_supportedControl + = slap_schema.si_ad_supportedControl; + struct berval vals[2]; + struct slap_control *sc; + + vals[1].bv_val = NULL; + vals[1].bv_len = 0; + + LDAP_SLIST_FOREACH( sc, &controls_list, sc_next ) { + if( sc->sc_mask & SLAP_CTRL_HIDE ) continue; + + vals[0].bv_val = sc->sc_oid; + vals[0].bv_len = strlen( sc->sc_oid ); + + if ( attr_merge( e, ad_supportedControl, vals, NULL ) ) { + return -1; + } + } + + return 0; +} + +/* + * Return a list of OIDs and operation masks for supported + * controls. Used by SLAPI. + */ +int +get_supported_controls(char ***ctrloidsp, + slap_mask_t **ctrlmasks) +{ + int n; + char **oids; + slap_mask_t *masks; + struct slap_control *sc; + + n = 0; + + LDAP_SLIST_FOREACH( sc, &controls_list, sc_next ) { + n++; + } + + if ( n == 0 ) { + *ctrloidsp = NULL; + *ctrlmasks = NULL; + return LDAP_SUCCESS; + } + + oids = (char **)SLAP_MALLOC( (n + 1) * sizeof(char *) ); + if ( oids == NULL ) { + return LDAP_NO_MEMORY; + } + masks = (slap_mask_t *)SLAP_MALLOC( (n + 1) * sizeof(slap_mask_t) ); + if ( masks == NULL ) { + SLAP_FREE( oids ); + return LDAP_NO_MEMORY; + } + + n = 0; + + LDAP_SLIST_FOREACH( sc, &controls_list, sc_next ) { + oids[n] = ch_strdup( sc->sc_oid ); + masks[n] = sc->sc_mask; + n++; + } + oids[n] = NULL; + masks[n] = 0; + + *ctrloidsp = oids; + *ctrlmasks = masks; + + return LDAP_SUCCESS; +} + +/* + * Find a control given its OID. + */ +static struct slap_control * +find_ctrl( const char *oid ) +{ + struct slap_control *sc; + + LDAP_SLIST_FOREACH( sc, &controls_list, sc_next ) { + if ( strcmp( oid, sc->sc_oid ) == 0 ) { + return sc; + } + } + + return NULL; +} + +int +slap_find_control_id( + const char *oid, + int *cid ) +{ + struct slap_control *ctrl = find_ctrl( oid ); + if ( ctrl ) { + if ( cid ) *cid = ctrl->sc_cid; + return LDAP_SUCCESS; + } + return LDAP_CONTROL_NOT_FOUND; +} + +int +slap_global_control( Operation *op, const char *oid, int *cid ) +{ + struct slap_control *ctrl = find_ctrl( oid ); + + if ( ctrl == NULL ) { + /* should not be reachable */ + Debug( LDAP_DEBUG_ANY, + "slap_global_control: unrecognized control: %s\n", + oid, 0, 0 ); + return LDAP_CONTROL_NOT_FOUND; + } + + if ( cid ) *cid = ctrl->sc_cid; + + if ( ( ctrl->sc_mask & SLAP_CTRL_GLOBAL ) || + ( ( op->o_tag & LDAP_REQ_SEARCH ) && + ( ctrl->sc_mask & SLAP_CTRL_GLOBAL_SEARCH ) ) ) + { + return LDAP_COMPARE_TRUE; + } + +#if 0 + Debug( LDAP_DEBUG_TRACE, + "slap_global_control: unavailable control: %s\n", + oid, 0, 0 ); +#endif + + return LDAP_COMPARE_FALSE; +} + +void slap_free_ctrls( + Operation *op, + LDAPControl **ctrls ) +{ + int i; + + if( ctrls == op->o_ctrls ) { + if( op->o_assertion != NULL ) { + filter_free_x( op, op->o_assertion, 1 ); + op->o_assertion = NULL; + } + if( op->o_vrFilter != NULL) { + vrFilter_free( op, op->o_vrFilter ); + op->o_vrFilter = NULL; + } + if( op->o_preread_attrs != NULL ) { + op->o_tmpfree( op->o_preread_attrs, op->o_tmpmemctx ); + op->o_preread_attrs = NULL; + } + if( op->o_postread_attrs != NULL ) { + op->o_tmpfree( op->o_postread_attrs, op->o_tmpmemctx ); + op->o_postread_attrs = NULL; + } + if( op->o_pagedresults_state != NULL ) { + op->o_tmpfree( op->o_pagedresults_state, op->o_tmpmemctx ); + op->o_pagedresults_state = NULL; + } + } + + for (i=0; ctrls[i]; i++) { + op->o_tmpfree(ctrls[i], op->o_tmpmemctx ); + } + op->o_tmpfree( ctrls, op->o_tmpmemctx ); +} + +int slap_add_ctrls( + Operation *op, + SlapReply *rs, + LDAPControl **ctrls ) +{ + int i = 0, j; + LDAPControl **ctrlsp; + + if ( rs->sr_ctrls ) { + for ( ; rs->sr_ctrls[ i ]; i++ ) ; + } + + for ( j=0; ctrls[j]; j++ ) ; + + ctrlsp = op->o_tmpalloc(( i+j+1 )*sizeof(LDAPControl *), op->o_tmpmemctx ); + i = 0; + if ( rs->sr_ctrls ) { + for ( ; rs->sr_ctrls[i]; i++ ) + ctrlsp[i] = rs->sr_ctrls[i]; + } + for ( j=0; ctrls[j]; j++) + ctrlsp[i++] = ctrls[j]; + ctrlsp[i] = NULL; + + if ( rs->sr_flags & REP_CTRLS_MUSTBEFREED ) + op->o_tmpfree( rs->sr_ctrls, op->o_tmpmemctx ); + rs->sr_ctrls = ctrlsp; + rs->sr_flags |= REP_CTRLS_MUSTBEFREED; + return i; +} + +int slap_parse_ctrl( + Operation *op, + SlapReply *rs, + LDAPControl *control, + const char **text ) +{ + struct slap_control *sc; + int rc = LDAP_SUCCESS; + + sc = find_ctrl( control->ldctl_oid ); + if( sc != NULL ) { + /* recognized control */ + slap_mask_t tagmask; + switch( op->o_tag ) { + case LDAP_REQ_ADD: + tagmask = SLAP_CTRL_ADD; + break; + case LDAP_REQ_BIND: + tagmask = SLAP_CTRL_BIND; + break; + case LDAP_REQ_COMPARE: + tagmask = SLAP_CTRL_COMPARE; + break; + case LDAP_REQ_DELETE: + tagmask = SLAP_CTRL_DELETE; + break; + case LDAP_REQ_MODIFY: + tagmask = SLAP_CTRL_MODIFY; + break; + case LDAP_REQ_RENAME: + tagmask = SLAP_CTRL_RENAME; + break; + case LDAP_REQ_SEARCH: + tagmask = SLAP_CTRL_SEARCH; + break; + case LDAP_REQ_UNBIND: + tagmask = SLAP_CTRL_UNBIND; + break; + case LDAP_REQ_ABANDON: + tagmask = SLAP_CTRL_ABANDON; + break; + case LDAP_REQ_EXTENDED: + tagmask=~0L; + assert( op->ore_reqoid.bv_val != NULL ); + if( sc->sc_extendedopsbv != NULL ) { + int i; + for( i=0; !BER_BVISNULL( &sc->sc_extendedopsbv[i] ); i++ ) { + if( bvmatch( &op->ore_reqoid, + &sc->sc_extendedopsbv[i] ) ) + { + tagmask=0L; + break; + } + } + } + break; + default: + *text = "controls internal error"; + return LDAP_OTHER; + } + + if (( sc->sc_mask & tagmask ) == tagmask ) { + /* available extension */ + if ( sc->sc_parse ) { + rc = sc->sc_parse( op, rs, control ); + assert( rc != LDAP_UNAVAILABLE_CRITICAL_EXTENSION ); + + } else if ( control->ldctl_iscritical ) { + *text = "not yet implemented"; + rc = LDAP_OTHER; + } + + + } else if ( control->ldctl_iscritical ) { + /* unavailable CRITICAL control */ + *text = "critical extension is unavailable"; + rc = LDAP_UNAVAILABLE_CRITICAL_EXTENSION; + } + + } else if ( control->ldctl_iscritical ) { + /* unrecognized CRITICAL control */ + *text = "critical extension is not recognized"; + rc = LDAP_UNAVAILABLE_CRITICAL_EXTENSION; + } + + return rc; +} + +int get_ctrls( + Operation *op, + SlapReply *rs, + int sendres ) +{ + int nctrls = 0; + ber_tag_t tag; + ber_len_t len; + char *opaque; + BerElement *ber = op->o_ber; + struct berval bv; +#ifdef SLAP_CONTROL_X_WHATFAILED + /* NOTE: right now, slapd checks the validity of each control + * while parsing. As a consequence, it can only detect one + * cause of failure at a time. This results in returning + * exactly one OID with the whatFailed control, or no control + * at all. + */ + char *failed_oid = NULL; +#endif + + len = ber_pvt_ber_remaining(ber); + + if( len == 0) { + /* no controls */ + rs->sr_err = LDAP_SUCCESS; + return rs->sr_err; + } + + if(( tag = ber_peek_tag( ber, &len )) != LDAP_TAG_CONTROLS ) { + if( tag == LBER_ERROR ) { + rs->sr_err = SLAPD_DISCONNECT; + rs->sr_text = "unexpected data in PDU"; + } + + goto return_results; + } + + Debug( LDAP_DEBUG_TRACE, + "=> get_ctrls\n", 0, 0, 0 ); + + if( op->o_protocol < LDAP_VERSION3 ) { + rs->sr_err = SLAPD_DISCONNECT; + rs->sr_text = "controls require LDAPv3"; + goto return_results; + } + + /* one for first control, one for termination */ + op->o_ctrls = op->o_tmpalloc( 2 * sizeof(LDAPControl *), op->o_tmpmemctx ); + +#if 0 + if( op->ctrls == NULL ) { + rs->sr_err = LDAP_NO_MEMORY; + rs->sr_text = "no memory"; + goto return_results; + } +#endif + + op->o_ctrls[nctrls] = NULL; + + /* step through each element */ + for( tag = ber_first_element( ber, &len, &opaque ); + tag != LBER_ERROR; + tag = ber_next_element( ber, &len, opaque ) ) + { + LDAPControl *c; + LDAPControl **tctrls; + + c = op->o_tmpalloc( sizeof(LDAPControl), op->o_tmpmemctx ); + memset(c, 0, sizeof(LDAPControl)); + + /* allocate pointer space for current controls (nctrls) + * + this control + extra NULL + */ + tctrls = op->o_tmprealloc( op->o_ctrls, + (nctrls+2) * sizeof(LDAPControl *), op->o_tmpmemctx ); + +#if 0 + if( tctrls == NULL ) { + ch_free( c ); + ldap_controls_free(op->o_ctrls); + op->o_ctrls = NULL; + + rs->sr_err = LDAP_NO_MEMORY; + rs->sr_text = "no memory"; + goto return_results; + } +#endif + op->o_ctrls = tctrls; + + op->o_ctrls[nctrls++] = c; + op->o_ctrls[nctrls] = NULL; + + tag = ber_scanf( ber, "{m" /*}*/, &bv ); + c->ldctl_oid = bv.bv_val; + + if( tag == LBER_ERROR ) { + Debug( LDAP_DEBUG_TRACE, "=> get_ctrls: get oid failed.\n", + 0, 0, 0 ); + + slap_free_ctrls( op, op->o_ctrls ); + op->o_ctrls = NULL; + rs->sr_err = SLAPD_DISCONNECT; + rs->sr_text = "decoding controls error"; + goto return_results; + + } else if( c->ldctl_oid == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "get_ctrls: conn %lu got empty OID.\n", + op->o_connid, 0, 0 ); + + slap_free_ctrls( op, op->o_ctrls ); + op->o_ctrls = NULL; + rs->sr_err = LDAP_PROTOCOL_ERROR; + rs->sr_text = "OID field is empty"; + goto return_results; + } + + tag = ber_peek_tag( ber, &len ); + + if( tag == LBER_BOOLEAN ) { + ber_int_t crit; + tag = ber_scanf( ber, "b", &crit ); + + if( tag == LBER_ERROR ) { + Debug( LDAP_DEBUG_TRACE, "=> get_ctrls: get crit failed.\n", + 0, 0, 0 ); + slap_free_ctrls( op, op->o_ctrls ); + op->o_ctrls = NULL; + rs->sr_err = SLAPD_DISCONNECT; + rs->sr_text = "decoding controls error"; + goto return_results; + } + + c->ldctl_iscritical = (crit != 0); + tag = ber_peek_tag( ber, &len ); + } + + if( tag == LBER_OCTETSTRING ) { + tag = ber_scanf( ber, "m", &c->ldctl_value ); + + if( tag == LBER_ERROR ) { + Debug( LDAP_DEBUG_TRACE, "=> get_ctrls: conn %lu: " + "%s (%scritical): get value failed.\n", + op->o_connid, c->ldctl_oid, + c->ldctl_iscritical ? "" : "non" ); + slap_free_ctrls( op, op->o_ctrls ); + op->o_ctrls = NULL; + rs->sr_err = SLAPD_DISCONNECT; + rs->sr_text = "decoding controls error"; + goto return_results; + } + } + + Debug( LDAP_DEBUG_TRACE, + "=> get_ctrls: oid=\"%s\" (%scritical)\n", + c->ldctl_oid, c->ldctl_iscritical ? "" : "non", 0 ); + + rs->sr_err = slap_parse_ctrl( op, rs, c, &rs->sr_text ); + if ( rs->sr_err != LDAP_SUCCESS ) { +#ifdef SLAP_CONTROL_X_WHATFAILED + failed_oid = c->ldctl_oid; +#endif + goto return_results; + } + } + +return_results: + Debug( LDAP_DEBUG_TRACE, + "<= get_ctrls: n=%d rc=%d err=\"%s\"\n", + nctrls, rs->sr_err, rs->sr_text ? rs->sr_text : ""); + + if( sendres && rs->sr_err != LDAP_SUCCESS ) { + if( rs->sr_err == SLAPD_DISCONNECT ) { + rs->sr_err = LDAP_PROTOCOL_ERROR; + send_ldap_disconnect( op, rs ); + rs->sr_err = SLAPD_DISCONNECT; + } else { +#ifdef SLAP_CONTROL_X_WHATFAILED + /* might have not been parsed yet? */ + if ( failed_oid != NULL ) { + if ( !get_whatFailed( op ) ) { + /* look it up */ + + /* step through each remaining element */ + for ( ; tag != LBER_ERROR; tag = ber_next_element( ber, &len, opaque ) ) + { + LDAPControl c = { 0 }; + + tag = ber_scanf( ber, "{m" /*}*/, &bv ); + c.ldctl_oid = bv.bv_val; + + if ( tag == LBER_ERROR ) { + slap_free_ctrls( op, op->o_ctrls ); + op->o_ctrls = NULL; + break; + + } else if ( c.ldctl_oid == NULL ) { + slap_free_ctrls( op, op->o_ctrls ); + op->o_ctrls = NULL; + break; + } + + tag = ber_peek_tag( ber, &len ); + if ( tag == LBER_BOOLEAN ) { + ber_int_t crit; + tag = ber_scanf( ber, "b", &crit ); + if( tag == LBER_ERROR ) { + slap_free_ctrls( op, op->o_ctrls ); + op->o_ctrls = NULL; + break; + } + + tag = ber_peek_tag( ber, &len ); + } + + if ( tag == LBER_OCTETSTRING ) { + tag = ber_scanf( ber, "m", &c.ldctl_value ); + + if( tag == LBER_ERROR ) { + slap_free_ctrls( op, op->o_ctrls ); + op->o_ctrls = NULL; + break; + } + } + + if ( strcmp( c.ldctl_oid, LDAP_CONTROL_X_WHATFAILED ) == 0 ) { + const char *text; + slap_parse_ctrl( op, rs, &c, &text ); + break; + } + } + } + + if ( get_whatFailed( op ) ) { + char *oids[ 2 ]; + oids[ 0 ] = failed_oid; + oids[ 1 ] = NULL; + slap_ctrl_whatFailed_add( op, rs, oids ); + } + } +#endif + + send_ldap_result( op, rs ); + } + } + + return rs->sr_err; +} + +int +slap_remove_control( + Operation *op, + SlapReply *rs, + int ctrl, + BI_chk_controls fnc ) +{ + int i, j; + + switch ( op->o_ctrlflag[ ctrl ] ) { + case SLAP_CONTROL_NONCRITICAL: + for ( i = 0, j = -1; op->o_ctrls[ i ] != NULL; i++ ) { + if ( strcmp( op->o_ctrls[ i ]->ldctl_oid, + slap_known_controls[ ctrl - 1 ] ) == 0 ) + { + j = i; + } + } + + if ( j == -1 ) { + rs->sr_err = LDAP_OTHER; + break; + } + + if ( fnc ) { + (void)fnc( op, rs ); + } + + op->o_tmpfree( op->o_ctrls[ j ], op->o_tmpmemctx ); + + if ( i > 1 ) { + AC_MEMCPY( &op->o_ctrls[ j ], &op->o_ctrls[ j + 1 ], + ( i - j ) * sizeof( LDAPControl * ) ); + + } else { + op->o_tmpfree( op->o_ctrls, op->o_tmpmemctx ); + op->o_ctrls = NULL; + } + + op->o_ctrlflag[ ctrl ] = SLAP_CONTROL_IGNORED; + + Debug( LDAP_DEBUG_ANY, "%s: " + "non-critical control \"%s\" not supported; stripped.\n", + op->o_log_prefix, slap_known_controls[ ctrl ], 0 ); + /* fall thru */ + + case SLAP_CONTROL_IGNORED: + case SLAP_CONTROL_NONE: + rs->sr_err = SLAP_CB_CONTINUE; + break; + + case SLAP_CONTROL_CRITICAL: + rs->sr_err = LDAP_UNAVAILABLE_CRITICAL_EXTENSION; + if ( fnc ) { + (void)fnc( op, rs ); + } + Debug( LDAP_DEBUG_ANY, "%s: " + "critical control \"%s\" not supported.\n", + op->o_log_prefix, slap_known_controls[ ctrl ], 0 ); + break; + + default: + /* handle all cases! */ + assert( 0 ); + } + + return rs->sr_err; +} + +static int parseDontUseCopy ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + if ( op->o_dontUseCopy != SLAP_CONTROL_NONE ) { + rs->sr_text = "dontUseCopy control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( !BER_BVISNULL( &ctrl->ldctl_value )) { + rs->sr_text = "dontUseCopy control value not absent"; + return LDAP_PROTOCOL_ERROR; + } + + if ( ( global_disallows & SLAP_DISALLOW_DONTUSECOPY_N_CRIT ) + && !ctrl->ldctl_iscritical ) + { + rs->sr_text = "dontUseCopy criticality of FALSE not allowed"; + return LDAP_PROTOCOL_ERROR; + } + + op->o_dontUseCopy = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + return LDAP_SUCCESS; +} + +static int parseRelax ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + if ( op->o_relax != SLAP_CONTROL_NONE ) { + rs->sr_text = "relax control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( !BER_BVISNULL( &ctrl->ldctl_value )) { + rs->sr_text = "relax control value not absent"; + return LDAP_PROTOCOL_ERROR; + } + + op->o_relax = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + return LDAP_SUCCESS; +} + +static int parseManageDSAit ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + if ( op->o_managedsait != SLAP_CONTROL_NONE ) { + rs->sr_text = "manageDSAit control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( !BER_BVISNULL( &ctrl->ldctl_value )) { + rs->sr_text = "manageDSAit control value not absent"; + return LDAP_PROTOCOL_ERROR; + } + + op->o_managedsait = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + return LDAP_SUCCESS; +} + +static int parseProxyAuthz ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + int rc; + struct berval dn = BER_BVNULL; + + if ( op->o_proxy_authz != SLAP_CONTROL_NONE ) { + rs->sr_text = "proxy authorization control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISNULL( &ctrl->ldctl_value )) { + rs->sr_text = "proxy authorization control value absent"; + return LDAP_PROTOCOL_ERROR; + } + + if ( ( global_disallows & SLAP_DISALLOW_PROXY_AUTHZ_N_CRIT ) + && !ctrl->ldctl_iscritical ) + { + rs->sr_text = "proxied authorization criticality of FALSE not allowed"; + return LDAP_PROTOCOL_ERROR; + } + + if ( !( global_allows & SLAP_ALLOW_PROXY_AUTHZ_ANON ) + && BER_BVISEMPTY( &op->o_ndn ) ) + { + rs->sr_text = "anonymous proxied authorization not allowed"; + return LDAP_PROXIED_AUTHORIZATION_DENIED; + } + + op->o_proxy_authz = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + Debug( LDAP_DEBUG_ARGS, + "parseProxyAuthz: conn %lu authzid=\"%s\"\n", + op->o_connid, + ctrl->ldctl_value.bv_len ? ctrl->ldctl_value.bv_val : "anonymous", + 0 ); + + if ( BER_BVISEMPTY( &ctrl->ldctl_value )) { + Debug( LDAP_DEBUG_TRACE, + "parseProxyAuthz: conn=%lu anonymous\n", + op->o_connid, 0, 0 ); + + /* anonymous */ + if ( !BER_BVISNULL( &op->o_ndn ) ) { + op->o_ndn.bv_val[ 0 ] = '\0'; + } + op->o_ndn.bv_len = 0; + + if ( !BER_BVISNULL( &op->o_dn ) ) { + op->o_dn.bv_val[ 0 ] = '\0'; + } + op->o_dn.bv_len = 0; + + return LDAP_SUCCESS; + } + + rc = slap_sasl_getdn( op->o_conn, op, &ctrl->ldctl_value, + NULL, &dn, SLAP_GETDN_AUTHZID ); + + /* FIXME: empty DN in proxyAuthz control should be legal... */ + if( rc != LDAP_SUCCESS /* || !dn.bv_len */ ) { + if ( dn.bv_val ) { + ch_free( dn.bv_val ); + } + rs->sr_text = "authzId mapping failed"; + return LDAP_PROXIED_AUTHORIZATION_DENIED; + } + + Debug( LDAP_DEBUG_TRACE, + "parseProxyAuthz: conn=%lu \"%s\"\n", + op->o_connid, + dn.bv_len ? dn.bv_val : "(NULL)", 0 ); + + rc = slap_sasl_authorized( op, &op->o_ndn, &dn ); + + if ( rc ) { + ch_free( dn.bv_val ); + rs->sr_text = "not authorized to assume identity"; + return LDAP_PROXIED_AUTHORIZATION_DENIED; + } + + ch_free( op->o_ndn.bv_val ); + + /* + * NOTE: since slap_sasl_getdn() returns a normalized dn, + * from now on op->o_dn is normalized + */ + op->o_ndn = dn; + ber_bvreplace( &op->o_dn, &dn ); + + Statslog( LDAP_DEBUG_STATS, "%s PROXYAUTHZ dn=\"%s\"\n", + op->o_log_prefix, dn.bv_val, 0, 0, 0 ); + + return LDAP_SUCCESS; +} + +static int parseNoOp ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + if ( op->o_noop != SLAP_CONTROL_NONE ) { + rs->sr_text = "noop control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( !BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = "noop control value not empty"; + return LDAP_PROTOCOL_ERROR; + } + + op->o_noop = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + return LDAP_SUCCESS; +} + +static int parsePagedResults ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + struct berval cookie; + PagedResultsState *ps; + int rc = LDAP_SUCCESS; + ber_tag_t tag; + ber_int_t size; + + if ( op->o_pagedresults != SLAP_CONTROL_NONE ) { + rs->sr_text = "paged results control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = "paged results control value is absent"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) { + rs->sr_text = "paged results control value is empty"; + return LDAP_PROTOCOL_ERROR; + } + + /* Parse the control value + * realSearchControlValue ::= SEQUENCE { + * size INTEGER (0..maxInt), + * -- requested page size from client + * -- result set size estimate from server + * cookie OCTET STRING + * } + */ + ber_init2( ber, &ctrl->ldctl_value, LBER_USE_DER ); + + tag = ber_scanf( ber, "{im}", &size, &cookie ); + + if ( tag == LBER_ERROR ) { + rs->sr_text = "paged results control could not be decoded"; + rc = LDAP_PROTOCOL_ERROR; + goto done; + } + + if ( size < 0 ) { + rs->sr_text = "paged results control size invalid"; + rc = LDAP_PROTOCOL_ERROR; + goto done; + } + + ps = op->o_tmpalloc( sizeof(PagedResultsState), op->o_tmpmemctx ); + *ps = op->o_conn->c_pagedresults_state; + ps->ps_size = size; + ps->ps_cookieval = cookie; + op->o_pagedresults_state = ps; + if ( !cookie.bv_len ) { + ps->ps_count = 0; + ps->ps_cookie = 0; + /* taint ps_cookie, to detect whether it's set */ + op->o_conn->c_pagedresults_state.ps_cookie = NOID; + } + + /* NOTE: according to RFC 2696 3.: + + If the page size is greater than or equal to the sizeLimit value, the + server should ignore the control as the request can be satisfied in a + single page. + + * NOTE: this assumes that the op->ors_slimit be set + * before the controls are parsed. + */ + + if ( op->ors_slimit > 0 && size >= op->ors_slimit ) { + op->o_pagedresults = SLAP_CONTROL_IGNORED; + + } else if ( ctrl->ldctl_iscritical ) { + op->o_pagedresults = SLAP_CONTROL_CRITICAL; + + } else { + op->o_pagedresults = SLAP_CONTROL_NONCRITICAL; + } + +done:; + return rc; +} + +#ifdef SLAP_CONTROL_X_SORTEDRESULTS +static int parseSortedResults ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + int rc = LDAP_SUCCESS; + + if ( op->o_sortedresults != SLAP_CONTROL_NONE ) { + rs->sr_text = "sorted results control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = "sorted results control value is absent"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) { + rs->sr_text = "sorted results control value is empty"; + return LDAP_PROTOCOL_ERROR; + } + + /* blow off parsing the value */ + + op->o_sortedresults = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + return rc; +} +#endif + +static int parseAssert ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + BerElement *ber; + struct berval fstr = BER_BVNULL; + + if ( op->o_assert != SLAP_CONTROL_NONE ) { + rs->sr_text = "assert control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISNULL( &ctrl->ldctl_value )) { + rs->sr_text = "assert control value is absent"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISEMPTY( &ctrl->ldctl_value )) { + rs->sr_text = "assert control value is empty"; + return LDAP_PROTOCOL_ERROR; + } + + ber = ber_init( &(ctrl->ldctl_value) ); + if (ber == NULL) { + rs->sr_text = "assert control: internal error"; + return LDAP_OTHER; + } + + rs->sr_err = get_filter( op, ber, (Filter **)&(op->o_assertion), + &rs->sr_text); + (void) ber_free( ber, 1 ); + if( rs->sr_err != LDAP_SUCCESS ) { + if( rs->sr_err == SLAPD_DISCONNECT ) { + rs->sr_err = LDAP_PROTOCOL_ERROR; + send_ldap_disconnect( op, rs ); + rs->sr_err = SLAPD_DISCONNECT; + } else { + send_ldap_result( op, rs ); + } + if( op->o_assertion != NULL ) { + filter_free_x( op, op->o_assertion, 1 ); + op->o_assertion = NULL; + } + return rs->sr_err; + } + +#ifdef LDAP_DEBUG + filter2bv_x( op, op->o_assertion, &fstr ); + + Debug( LDAP_DEBUG_ARGS, "parseAssert: conn %ld assert: %s\n", + op->o_connid, fstr.bv_len ? fstr.bv_val : "empty" , 0 ); + op->o_tmpfree( fstr.bv_val, op->o_tmpmemctx ); +#endif + + op->o_assert = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + rs->sr_err = LDAP_SUCCESS; + return LDAP_SUCCESS; +} + +#define READMSG(post, msg) \ + ( post ? "postread control: " msg : "preread control: " msg ) + +static int +parseReadAttrs( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl, + int post ) +{ + ber_len_t siz, off, i; + BerElement *ber; + AttributeName *an = NULL; + + if ( ( post && op->o_postread != SLAP_CONTROL_NONE ) || + ( !post && op->o_preread != SLAP_CONTROL_NONE ) ) + { + rs->sr_text = READMSG( post, "specified multiple times" ); + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = READMSG( post, "value is absent" ); + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) { + rs->sr_text = READMSG( post, "value is empty" ); + return LDAP_PROTOCOL_ERROR; + } + +#ifdef LDAP_X_TXN + if ( op->o_txnSpec ) { /* temporary limitation */ + rs->sr_text = READMSG( post, "cannot perform in transaction" ); + return LDAP_UNWILLING_TO_PERFORM; + } +#endif + + ber = ber_init( &ctrl->ldctl_value ); + if ( ber == NULL ) { + rs->sr_text = READMSG( post, "internal error" ); + return LDAP_OTHER; + } + + rs->sr_err = LDAP_SUCCESS; + siz = sizeof( AttributeName ); + off = offsetof( AttributeName, an_name ); + if ( ber_scanf( ber, "{M}", &an, &siz, off ) == LBER_ERROR ) { + rs->sr_text = READMSG( post, "decoding error" ); + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + + for ( i = 0; i < siz; i++ ) { + const char *dummy = NULL; + int rc; + + an[i].an_desc = NULL; + an[i].an_oc = NULL; + an[i].an_flags = 0; + rc = slap_bv2ad( &an[i].an_name, &an[i].an_desc, &dummy ); + if ( rc == LDAP_SUCCESS ) { + an[i].an_name = an[i].an_desc->ad_cname; + + } else { + int j; + static struct berval special_attrs[] = { + BER_BVC( LDAP_NO_ATTRS ), + BER_BVC( LDAP_ALL_USER_ATTRIBUTES ), + BER_BVC( LDAP_ALL_OPERATIONAL_ATTRIBUTES ), + BER_BVNULL + }; + + /* deal with special attribute types */ + for ( j = 0; !BER_BVISNULL( &special_attrs[ j ] ); j++ ) { + if ( bvmatch( &an[i].an_name, &special_attrs[ j ] ) ) { + an[i].an_name = special_attrs[ j ]; + break; + } + } + + if ( BER_BVISNULL( &special_attrs[ j ] ) && ctrl->ldctl_iscritical ) { + rs->sr_err = rc; + rs->sr_text = dummy ? dummy + : READMSG( post, "unknown attributeType" ); + goto done; + } + } + } + + if ( post ) { + op->o_postread_attrs = an; + op->o_postread = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + } else { + op->o_preread_attrs = an; + op->o_preread = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + } + +done: + (void) ber_free( ber, 1 ); + return rs->sr_err; +} + +static int parsePreRead ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + return parseReadAttrs( op, rs, ctrl, 0 ); +} + +static int parsePostRead ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + return parseReadAttrs( op, rs, ctrl, 1 ); +} + +static int parseValuesReturnFilter ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + BerElement *ber; + struct berval fstr = BER_BVNULL; + + if ( op->o_valuesreturnfilter != SLAP_CONTROL_NONE ) { + rs->sr_text = "valuesReturnFilter control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISNULL( &ctrl->ldctl_value )) { + rs->sr_text = "valuesReturnFilter control value is absent"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISEMPTY( &ctrl->ldctl_value )) { + rs->sr_text = "valuesReturnFilter control value is empty"; + return LDAP_PROTOCOL_ERROR; + } + + ber = ber_init( &(ctrl->ldctl_value) ); + if (ber == NULL) { + rs->sr_text = "internal error"; + return LDAP_OTHER; + } + + rs->sr_err = get_vrFilter( op, ber, + (ValuesReturnFilter **)&(op->o_vrFilter), &rs->sr_text); + + (void) ber_free( ber, 1 ); + + if( rs->sr_err != LDAP_SUCCESS ) { + if( rs->sr_err == SLAPD_DISCONNECT ) { + rs->sr_err = LDAP_PROTOCOL_ERROR; + send_ldap_disconnect( op, rs ); + rs->sr_err = SLAPD_DISCONNECT; + } else { + send_ldap_result( op, rs ); + } + if( op->o_vrFilter != NULL) { + vrFilter_free( op, op->o_vrFilter ); + op->o_vrFilter = NULL; + } + } +#ifdef LDAP_DEBUG + else { + vrFilter2bv( op, op->o_vrFilter, &fstr ); + } + + Debug( LDAP_DEBUG_ARGS, " vrFilter: %s\n", + fstr.bv_len ? fstr.bv_val : "empty", 0, 0 ); + op->o_tmpfree( fstr.bv_val, op->o_tmpmemctx ); +#endif + + op->o_valuesreturnfilter = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + rs->sr_err = LDAP_SUCCESS; + return LDAP_SUCCESS; +} + +static int parseSubentries ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + if ( op->o_subentries != SLAP_CONTROL_NONE ) { + rs->sr_text = "subentries control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + /* FIXME: should use BER library */ + if( ( ctrl->ldctl_value.bv_len != 3 ) + || ( ctrl->ldctl_value.bv_val[0] != 0x01 ) + || ( ctrl->ldctl_value.bv_val[1] != 0x01 )) + { + rs->sr_text = "subentries control value encoding is bogus"; + return LDAP_PROTOCOL_ERROR; + } + + op->o_subentries = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + if (ctrl->ldctl_value.bv_val[2]) { + set_subentries_visibility( op ); + } + + return LDAP_SUCCESS; +} + +static int parsePermissiveModify ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + if ( op->o_permissive_modify != SLAP_CONTROL_NONE ) { + rs->sr_text = "permissiveModify control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( !BER_BVISNULL( &ctrl->ldctl_value )) { + rs->sr_text = "permissiveModify control value not absent"; + return LDAP_PROTOCOL_ERROR; + } + + op->o_permissive_modify = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + return LDAP_SUCCESS; +} + +static int parseDomainScope ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + if ( op->o_domain_scope != SLAP_CONTROL_NONE ) { + rs->sr_text = "domainScope control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + /* this should be checking BVISNULL, but M$ clients are broken + * and include the value even though the M$ spec says it must be + * omitted. ITS#9100. + */ + if ( !BER_BVISEMPTY( &ctrl->ldctl_value )) { + rs->sr_text = "domainScope control value not absent"; + return LDAP_PROTOCOL_ERROR; + } + + op->o_domain_scope = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + return LDAP_SUCCESS; +} + +#ifdef SLAP_CONTROL_X_TREE_DELETE +static int parseTreeDelete ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + if ( op->o_tree_delete != SLAP_CONTROL_NONE ) { + rs->sr_text = "treeDelete control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( !BER_BVISNULL( &ctrl->ldctl_value )) { + rs->sr_text = "treeDelete control value not absent"; + return LDAP_PROTOCOL_ERROR; + } + + op->o_tree_delete = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + return LDAP_SUCCESS; +} +#endif + +static int parseSearchOptions ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + BerElement *ber; + ber_int_t search_flags; + ber_tag_t tag; + + if ( BER_BVISNULL( &ctrl->ldctl_value )) { + rs->sr_text = "searchOptions control value is absent"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISEMPTY( &ctrl->ldctl_value )) { + rs->sr_text = "searchOptions control value is empty"; + return LDAP_PROTOCOL_ERROR; + } + + ber = ber_init( &ctrl->ldctl_value ); + if( ber == NULL ) { + rs->sr_text = "internal error"; + return LDAP_OTHER; + } + + tag = ber_scanf( ber, "{i}", &search_flags ); + (void) ber_free( ber, 1 ); + + if ( tag == LBER_ERROR ) { + rs->sr_text = "searchOptions control decoding error"; + return LDAP_PROTOCOL_ERROR; + } + + if ( search_flags & ~(LDAP_SEARCH_FLAG_DOMAIN_SCOPE) ) { + /* Search flags not recognised so far, + * including: + * LDAP_SEARCH_FLAG_PHANTOM_ROOT + */ + if ( ctrl->ldctl_iscritical ) { + rs->sr_text = "searchOptions contained unrecognized flag"; + return LDAP_UNWILLING_TO_PERFORM; + } + + /* Ignore */ + Debug( LDAP_DEBUG_TRACE, + "searchOptions: conn=%lu unrecognized flag(s) 0x%x (non-critical)\n", + op->o_connid, (unsigned)search_flags, 0 ); + + return LDAP_SUCCESS; + } + + if ( search_flags & LDAP_SEARCH_FLAG_DOMAIN_SCOPE ) { + if ( op->o_domain_scope != SLAP_CONTROL_NONE ) { + rs->sr_text = "searchOptions control specified multiple times " + "or with domainScope control"; + return LDAP_PROTOCOL_ERROR; + } + + op->o_domain_scope = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + } + + return LDAP_SUCCESS; +} + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING +struct berval session_tracking_formats[] = { + BER_BVC( LDAP_CONTROL_X_SESSION_TRACKING_RADIUS_ACCT_SESSION_ID ), + BER_BVC( "RADIUS-Acct-Session-Id" ), + BER_BVC( LDAP_CONTROL_X_SESSION_TRACKING_RADIUS_ACCT_MULTI_SESSION_ID ), + BER_BVC( "RADIUS-Acct-Multi-Session-Id" ), + BER_BVC( LDAP_CONTROL_X_SESSION_TRACKING_USERNAME ), + BER_BVC( "USERNAME" ), + + BER_BVNULL +}; + +static int parseSessionTracking( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + BerElement *ber; + ber_tag_t tag; + ber_len_t len; + int i, rc; + + struct berval sessionSourceIp = BER_BVNULL, + sessionSourceName = BER_BVNULL, + formatOID = BER_BVNULL, + sessionTrackingIdentifier = BER_BVNULL; + + size_t st_len, st_pos; + + if ( ctrl->ldctl_iscritical ) { + rs->sr_text = "sessionTracking criticality is TRUE"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = "sessionTracking control value is absent"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) { + rs->sr_text = "sessionTracking control value is empty"; + return LDAP_PROTOCOL_ERROR; + } + + /* TODO: add the capability to determine if a client is allowed + * to use this control, based on identity, ip and so */ + + ber = ber_init( &ctrl->ldctl_value ); + if ( ber == NULL ) { + rs->sr_text = "internal error"; + return LDAP_OTHER; + } + + tag = ber_skip_tag( ber, &len ); + if ( tag != LBER_SEQUENCE ) { + tag = LBER_ERROR; + goto error; + } + + /* sessionSourceIp */ + tag = ber_peek_tag( ber, &len ); + if ( tag == LBER_DEFAULT ) { + tag = LBER_ERROR; + goto error; + } + + if ( len == 0 ) { + tag = ber_skip_tag( ber, &len ); + + } else if ( len > 128 ) { + rs->sr_text = "sessionTracking.sessionSourceIp too long"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto error; + + } else { + tag = ber_scanf( ber, "m", &sessionSourceIp ); + } + + if ( ldif_is_not_printable( sessionSourceIp.bv_val, sessionSourceIp.bv_len ) ) { + BER_BVZERO( &sessionSourceIp ); + } + + /* sessionSourceName */ + tag = ber_peek_tag( ber, &len ); + if ( tag == LBER_DEFAULT ) { + tag = LBER_ERROR; + goto error; + } + + if ( len == 0 ) { + tag = ber_skip_tag( ber, &len ); + + } else if ( len > 65536 ) { + rs->sr_text = "sessionTracking.sessionSourceName too long"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto error; + + } else { + tag = ber_scanf( ber, "m", &sessionSourceName ); + } + + if ( ldif_is_not_printable( sessionSourceName.bv_val, sessionSourceName.bv_len ) ) { + BER_BVZERO( &sessionSourceName ); + } + + /* formatOID */ + tag = ber_peek_tag( ber, &len ); + if ( tag == LBER_DEFAULT ) { + tag = LBER_ERROR; + goto error; + } + + if ( len == 0 ) { + rs->sr_text = "sessionTracking.formatOID empty"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto error; + + } else if ( len > 1024 ) { + rs->sr_text = "sessionTracking.formatOID too long"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto error; + + } else { + tag = ber_scanf( ber, "m", &formatOID ); + } + + rc = numericoidValidate( NULL, &formatOID ); + if ( rc != LDAP_SUCCESS ) { + rs->sr_text = "sessionTracking.formatOID invalid"; + goto error; + } + + for ( i = 0; !BER_BVISNULL( &session_tracking_formats[ i ] ); i += 2 ) + { + if ( bvmatch( &formatOID, &session_tracking_formats[ i ] ) ) { + formatOID = session_tracking_formats[ i + 1 ]; + break; + } + } + + /* sessionTrackingIdentifier */ + tag = ber_peek_tag( ber, &len ); + if ( tag == LBER_DEFAULT ) { + tag = LBER_ERROR; + goto error; + } + + if ( len == 0 ) { + tag = ber_skip_tag( ber, &len ); + + } else { + /* note: should not be more than 65536... */ + tag = ber_scanf( ber, "m", &sessionTrackingIdentifier ); + if ( ldif_is_not_printable( sessionTrackingIdentifier.bv_val, sessionTrackingIdentifier.bv_len ) ) { + /* we want the OID printed, at least */ + BER_BVSTR( &sessionTrackingIdentifier, "" ); + } + } + + /* closure */ + tag = ber_skip_tag( ber, &len ); + if ( tag != LBER_DEFAULT || len != 0 ) { + tag = LBER_ERROR; + goto error; + } + tag = 0; + + st_len = 0; + if ( !BER_BVISNULL( &sessionSourceIp ) ) { + st_len += STRLENOF( "IP=" ) + sessionSourceIp.bv_len; + } + if ( !BER_BVISNULL( &sessionSourceName ) ) { + if ( st_len ) st_len++; + st_len += STRLENOF( "NAME=" ) + sessionSourceName.bv_len; + } + if ( !BER_BVISNULL( &sessionTrackingIdentifier ) ) { + if ( st_len ) st_len++; + st_len += formatOID.bv_len + STRLENOF( "=" ) + + sessionTrackingIdentifier.bv_len; + } + + if ( st_len == 0 ) { + goto error; + } + + st_len += STRLENOF( " []" ); + st_pos = strlen( op->o_log_prefix ); + + if ( sizeof( op->o_log_prefix ) - st_pos > st_len ) { + char *ptr = &op->o_log_prefix[ st_pos ]; + + ptr = lutil_strcopy( ptr, " [" /*]*/ ); + + st_len = 0; + if ( !BER_BVISNULL( &sessionSourceIp ) ) { + ptr = lutil_strcopy( ptr, "IP=" ); + ptr = lutil_strcopy( ptr, sessionSourceIp.bv_val ); + st_len++; + } + + if ( !BER_BVISNULL( &sessionSourceName ) ) { + if ( st_len ) *ptr++ = ' '; + ptr = lutil_strcopy( ptr, "NAME=" ); + ptr = lutil_strcopy( ptr, sessionSourceName.bv_val ); + st_len++; + } + + if ( !BER_BVISNULL( &sessionTrackingIdentifier ) ) { + if ( st_len ) *ptr++ = ' '; + ptr = lutil_strcopy( ptr, formatOID.bv_val ); + *ptr++ = '='; + ptr = lutil_strcopy( ptr, sessionTrackingIdentifier.bv_val ); + } + + *ptr++ = /*[*/ ']'; + *ptr = '\0'; + } + +error:; + (void)ber_free( ber, 1 ); + + if ( tag == LBER_ERROR ) { + rs->sr_text = "sessionTracking control decoding error"; + return LDAP_PROTOCOL_ERROR; + } + + + return rs->sr_err; +} + +int +slap_ctrl_session_tracking_add( + Operation *op, + SlapReply *rs, + struct berval *ip, + struct berval *name, + struct berval *id, + LDAPControl *ctrl ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + + static struct berval oid = BER_BVC( LDAP_CONTROL_X_SESSION_TRACKING_USERNAME ); + + assert( ctrl != NULL ); + + ber_init2( ber, NULL, LBER_USE_DER ); + + ber_printf( ber, "{OOOO}", ip, name, &oid, id ); + + if ( ber_flatten2( ber, &ctrl->ldctl_value, 0 ) == -1 ) { + rs->sr_err = LDAP_OTHER; + goto done; + } + + ctrl->ldctl_oid = LDAP_CONTROL_X_SESSION_TRACKING; + ctrl->ldctl_iscritical = 0; + + rs->sr_err = LDAP_SUCCESS; + +done:; + return rs->sr_err; +} + +int +slap_ctrl_session_tracking_request_add( Operation *op, SlapReply *rs, LDAPControl *ctrl ) +{ + static struct berval bv_unknown = BER_BVC( SLAP_STRING_UNKNOWN ); + struct berval ip = BER_BVNULL, + name = BER_BVNULL, + id = BER_BVNULL; + + if ( !BER_BVISNULL( &op->o_conn->c_peer_name ) && + memcmp( op->o_conn->c_peer_name.bv_val, "IP=", STRLENOF( "IP=" ) ) == 0 ) + { + char *ptr; + + ip.bv_val = op->o_conn->c_peer_name.bv_val + STRLENOF( "IP=" ); + ip.bv_len = op->o_conn->c_peer_name.bv_len - STRLENOF( "IP=" ); + + ptr = ber_bvchr( &ip, ':' ); + if ( ptr ) { + ip.bv_len = ptr - ip.bv_val; + } + } + + if ( !BER_BVISNULL( &op->o_conn->c_peer_domain ) && + !bvmatch( &op->o_conn->c_peer_domain, &bv_unknown ) ) + { + name = op->o_conn->c_peer_domain; + } + + if ( !BER_BVISNULL( &op->o_dn ) && !BER_BVISEMPTY( &op->o_dn ) ) { + id = op->o_dn; + } + + return slap_ctrl_session_tracking_add( op, rs, &ip, &name, &id, ctrl ); +} +#endif + +#ifdef SLAP_CONTROL_X_WHATFAILED +static int parseWhatFailed( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + if ( op->o_whatFailed != SLAP_CONTROL_NONE ) { + rs->sr_text = "\"WHat Failed?\" control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( !BER_BVISNULL( &ctrl->ldctl_value )) { + rs->sr_text = "\"What Failed?\" control value not absent"; + return LDAP_PROTOCOL_ERROR; + } + + op->o_whatFailed = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + return LDAP_SUCCESS; +} + +int +slap_ctrl_whatFailed_add( + Operation *op, + SlapReply *rs, + char **oids ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *) &berbuf; + LDAPControl **ctrls = NULL; + struct berval ctrlval; + int i, rc = LDAP_SUCCESS; + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + ber_printf( ber, "[" /*]*/ ); + for ( i = 0; oids[ i ] != NULL; i++ ) { + ber_printf( ber, "s", oids[ i ] ); + } + ber_printf( ber, /*[*/ "]" ); + + if ( ber_flatten2( ber, &ctrlval, 0 ) == -1 ) { + rc = LDAP_OTHER; + goto done; + } + + i = 0; + if ( rs->sr_ctrls != NULL ) { + for ( ; rs->sr_ctrls[ i ] != NULL; i++ ) { + if ( strcmp( rs->sr_ctrls[ i ]->ldctl_oid, LDAP_CONTROL_X_WHATFAILED ) != 0 ) { + /* TODO: add */ + assert( 0 ); + } + } + } + + ctrls = op->o_tmprealloc( rs->sr_ctrls, + sizeof(LDAPControl *)*( i + 2 ) + + sizeof(LDAPControl) + + ctrlval.bv_len + 1, + op->o_tmpmemctx ); + if ( ctrls == NULL ) { + rc = LDAP_OTHER; + goto done; + } + ctrls[ i + 1 ] = NULL; + ctrls[ i ] = (LDAPControl *)&ctrls[ i + 2 ]; + ctrls[ i ]->ldctl_oid = LDAP_CONTROL_X_WHATFAILED; + ctrls[ i ]->ldctl_iscritical = 0; + ctrls[ i ]->ldctl_value.bv_val = (char *)&ctrls[ i ][ 1 ]; + AC_MEMCPY( ctrls[ i ]->ldctl_value.bv_val, ctrlval.bv_val, ctrlval.bv_len + 1 ); + ctrls[ i ]->ldctl_value.bv_len = ctrlval.bv_len; + + ber_free_buf( ber ); + + rs->sr_ctrls = ctrls; + +done:; + return rc; +} +#endif diff --git a/servers/slapd/cr.c b/servers/slapd/cr.c new file mode 100644 index 0000000..11f0719 --- /dev/null +++ b/servers/slapd/cr.c @@ -0,0 +1,501 @@ +/* cr.c - content rule routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" + +struct cindexrec { + struct berval cir_name; + ContentRule *cir_cr; +}; + +static Avlnode *cr_index = NULL; +static LDAP_STAILQ_HEAD(CRList, ContentRule) cr_list + = LDAP_STAILQ_HEAD_INITIALIZER(cr_list); + +static int +cr_index_cmp( + const void *v_cir1, + const void *v_cir2 ) +{ + const struct cindexrec *cir1 = v_cir1; + const struct cindexrec *cir2 = v_cir2; + int i = cir1->cir_name.bv_len - cir2->cir_name.bv_len; + if (i) return i; + return strcasecmp( cir1->cir_name.bv_val, cir2->cir_name.bv_val ); +} + +static int +cr_index_name_cmp( + const void *v_name, + const void *v_cir ) +{ + const struct berval *name = v_name; + const struct cindexrec *cir = v_cir; + int i = name->bv_len - cir->cir_name.bv_len; + if (i) return i; + return strncasecmp( name->bv_val, cir->cir_name.bv_val, name->bv_len ); +} + +ContentRule * +cr_find( const char *crname ) +{ + struct berval bv; + + bv.bv_val = (char *)crname; + bv.bv_len = strlen( crname ); + + return( cr_bvfind( &bv ) ); +} + +ContentRule * +cr_bvfind( struct berval *crname ) +{ + struct cindexrec *cir; + + cir = avl_find( cr_index, crname, cr_index_name_cmp ); + + if ( cir != NULL ) { + return( cir->cir_cr ); + } + + return( NULL ); +} + +static int +cr_destroy_one( ContentRule *c ) +{ + assert( c != NULL ); + + if (c->scr_auxiliaries) ldap_memfree(c->scr_auxiliaries); + if (c->scr_required) ldap_memfree(c->scr_required); + if (c->scr_allowed) ldap_memfree(c->scr_allowed); + if (c->scr_precluded) ldap_memfree(c->scr_precluded); + ldap_contentrule_free((LDAPContentRule *)c); + + return 0; +} + +void +cr_destroy( void ) +{ + ContentRule *c; + + avl_free(cr_index, ldap_memfree); + + while( !LDAP_STAILQ_EMPTY(&cr_list) ) { + c = LDAP_STAILQ_FIRST(&cr_list); + LDAP_STAILQ_REMOVE_HEAD(&cr_list, scr_next); + + cr_destroy_one( c ); + } +} + +static int +cr_insert( + ContentRule *scr, + const char **err +) +{ + struct cindexrec *cir; + char **names; + + assert( scr != NULL ); + + if ( scr->scr_oid ) { + cir = (struct cindexrec *) + ch_calloc( 1, sizeof(struct cindexrec) ); + cir->cir_name.bv_val = scr->scr_oid; + cir->cir_name.bv_len = strlen( scr->scr_oid ); + cir->cir_cr = scr; + + if ( avl_insert( &cr_index, (caddr_t) cir, + cr_index_cmp, avl_dup_error ) ) + { + *err = scr->scr_oid; + ldap_memfree(cir); + return SLAP_SCHERR_CR_DUP; + } + + /* FIX: temporal consistency check */ + assert( cr_bvfind(&cir->cir_name) != NULL ); + } + + if ( (names = scr->scr_names) ) { + while ( *names ) { + cir = (struct cindexrec *) + ch_calloc( 1, sizeof(struct cindexrec) ); + cir->cir_name.bv_val = *names; + cir->cir_name.bv_len = strlen( *names ); + cir->cir_cr = scr; + + if ( avl_insert( &cr_index, (caddr_t) cir, + cr_index_cmp, avl_dup_error ) ) + { + *err = *names; + ldap_memfree(cir); + return SLAP_SCHERR_CR_DUP; + } + + /* FIX: temporal consistency check */ + assert( cr_bvfind(&cir->cir_name) != NULL ); + + names++; + } + } + + LDAP_STAILQ_INSERT_TAIL(&cr_list, scr, scr_next); + + return 0; +} + +static int +cr_add_auxiliaries( + ContentRule *scr, + int *op, + const char **err ) +{ + int naux; + + if( scr->scr_oc_oids_aux == NULL ) return 0; + + for( naux=0; scr->scr_oc_oids_aux[naux]; naux++ ) { + /* count them */ ; + } + + scr->scr_auxiliaries = ch_calloc( naux+1, sizeof(ObjectClass *)); + + for( naux=0; scr->scr_oc_oids_aux[naux]; naux++ ) { + ObjectClass *soc = scr->scr_auxiliaries[naux] + = oc_find(scr->scr_oc_oids_aux[naux]); + if ( !soc ) { + *err = scr->scr_oc_oids_aux[naux]; + return SLAP_SCHERR_CLASS_NOT_FOUND; + } + + if( soc->soc_flags & SLAP_OC_OPERATIONAL && + soc != slap_schema.si_oc_extensibleObject ) + { + (*op)++; + } + + if( soc->soc_kind != LDAP_SCHEMA_AUXILIARY ) { + *err = scr->scr_oc_oids_aux[naux]; + return SLAP_SCHERR_CR_BAD_AUX; + } + } + + scr->scr_auxiliaries[naux] = NULL; + return 0; +} + +static int +cr_create_required( + ContentRule *scr, + int *op, + const char **err ) +{ + char **attrs = scr->scr_at_oids_must; + char **attrs1; + AttributeType *sat; + + if ( attrs ) { + attrs1 = attrs; + while ( *attrs1 ) { + sat = at_find(*attrs1); + if ( !sat ) { + *err = *attrs1; + return SLAP_SCHERR_ATTR_NOT_FOUND; + } + + if( is_at_operational( sat )) (*op)++; + + if ( at_find_in_list(sat, scr->scr_required) < 0) { + if ( at_append_to_list(sat, &scr->scr_required) ) { + *err = *attrs1; + return SLAP_SCHERR_OUTOFMEM; + } + } else { + *err = *attrs1; + return SLAP_SCHERR_CR_BAD_AT; + } + attrs1++; + } + } + return 0; +} + +static int +cr_create_allowed( + ContentRule *scr, + int *op, + const char **err ) +{ + char **attrs = scr->scr_at_oids_may; + char **attrs1; + AttributeType *sat; + + if ( attrs ) { + attrs1 = attrs; + while ( *attrs1 ) { + sat = at_find(*attrs1); + if ( !sat ) { + *err = *attrs1; + return SLAP_SCHERR_ATTR_NOT_FOUND; + } + + if( is_at_operational( sat )) (*op)++; + + if ( at_find_in_list(sat, scr->scr_required) < 0 && + at_find_in_list(sat, scr->scr_allowed) < 0 ) + { + if ( at_append_to_list(sat, &scr->scr_allowed) ) { + *err = *attrs1; + return SLAP_SCHERR_OUTOFMEM; + } + } else { + *err = *attrs1; + return SLAP_SCHERR_CR_BAD_AT; + } + attrs1++; + } + } + return 0; +} + +static int +cr_create_precluded( + ContentRule *scr, + int *op, + const char **err ) +{ + char **attrs = scr->scr_at_oids_not; + char **attrs1; + AttributeType *sat; + + if ( attrs ) { + attrs1 = attrs; + while ( *attrs1 ) { + sat = at_find(*attrs1); + if ( !sat ) { + *err = *attrs1; + return SLAP_SCHERR_ATTR_NOT_FOUND; + } + + if( is_at_operational( sat )) (*op)++; + + /* FIXME: should also make sure attribute type is not + a required attribute of the structural class or + any auxiliary class */ + if ( at_find_in_list(sat, scr->scr_required) < 0 && + at_find_in_list(sat, scr->scr_allowed) < 0 && + at_find_in_list(sat, scr->scr_precluded) < 0 ) + { + if ( at_append_to_list(sat, &scr->scr_precluded) ) { + *err = *attrs1; + return SLAP_SCHERR_OUTOFMEM; + } + } else { + *err = *attrs1; + return SLAP_SCHERR_CR_BAD_AT; + } + attrs1++; + } + } + return 0; +} + +int +cr_add( + LDAPContentRule *cr, + int user, + ContentRule **rscr, + const char **err +) +{ + ContentRule *scr; + int code; + int op = 0; + char *oidm = NULL; + + if ( cr->cr_names != NULL ) { + int i; + + for( i=0; cr->cr_names[i]; i++ ) { + if( !slap_valid_descr( cr->cr_names[i] ) ) { + return SLAP_SCHERR_BAD_DESCR; + } + } + } + + if ( !OID_LEADCHAR( cr->cr_oid[0] )) { + /* Expand OID macros */ + char *oid = oidm_find( cr->cr_oid ); + if ( !oid ) { + *err = cr->cr_oid; + return SLAP_SCHERR_OIDM; + } + if ( oid != cr->cr_oid ) { + oidm = cr->cr_oid; + cr->cr_oid = oid; + } + } + + scr = (ContentRule *) ch_calloc( 1, sizeof(ContentRule) ); + AC_MEMCPY( &scr->scr_crule, cr, sizeof(LDAPContentRule) ); + + scr->scr_oidmacro = oidm; + scr->scr_sclass = oc_find(cr->cr_oid); + if ( !scr->scr_sclass ) { + *err = cr->cr_oid; + code = SLAP_SCHERR_CLASS_NOT_FOUND; + goto fail; + } + + /* check object class usage */ + if( scr->scr_sclass->soc_kind != LDAP_SCHEMA_STRUCTURAL ) + { + *err = cr->cr_oid; + code = SLAP_SCHERR_CR_BAD_STRUCT; + goto fail; + } + + if( scr->scr_sclass->soc_flags & SLAP_OC_OPERATIONAL ) op++; + + code = cr_add_auxiliaries( scr, &op, err ); + if ( code != 0 ) goto fail; + + code = cr_create_required( scr, &op, err ); + if ( code != 0 ) goto fail; + + code = cr_create_allowed( scr, &op, err ); + if ( code != 0 ) goto fail; + + code = cr_create_precluded( scr, &op, err ); + if ( code != 0 ) goto fail; + + if( user && op ) { + code = SLAP_SCHERR_CR_BAD_AUX; + goto fail; + } + + code = cr_insert(scr,err); + if ( code == 0 && rscr ) + *rscr = scr; + return code; +fail: + ch_free( scr ); + return code; +} + +void +cr_unparse( BerVarray *res, ContentRule *start, ContentRule *end, int sys ) +{ + ContentRule *cr; + int i, num; + struct berval bv, *bva = NULL, idx; + char ibuf[32]; + + if ( !start ) + start = LDAP_STAILQ_FIRST( &cr_list ); + + /* count the result size */ + i = 0; + for ( cr=start; cr; cr=LDAP_STAILQ_NEXT(cr, scr_next)) { + if ( sys && !(cr->scr_flags & SLAP_CR_HARDCODE)) continue; + i++; + if ( cr == end ) break; + } + if (!i) return; + + num = i; + bva = ch_malloc( (num+1) * sizeof(struct berval) ); + BER_BVZERO( bva ); + idx.bv_val = ibuf; + if ( sys ) { + idx.bv_len = 0; + ibuf[0] = '\0'; + } + i = 0; + for ( cr=start; cr; cr=LDAP_STAILQ_NEXT(cr, scr_next)) { + LDAPContentRule lcr, *lcrp; + if ( sys && !(cr->scr_flags & SLAP_CR_HARDCODE)) continue; + if ( cr->scr_oidmacro ) { + lcr = cr->scr_crule; + lcr.cr_oid = cr->scr_oidmacro; + lcrp = &lcr; + } else { + lcrp = &cr->scr_crule; + } + if ( ldap_contentrule2bv( lcrp, &bv ) == NULL ) { + ber_bvarray_free( bva ); + } + if ( !sys ) { + idx.bv_len = sprintf(idx.bv_val, "{%d}", i); + } + bva[i].bv_len = idx.bv_len + bv.bv_len; + bva[i].bv_val = ch_malloc( bva[i].bv_len + 1 ); + strcpy( bva[i].bv_val, ibuf ); + strcpy( bva[i].bv_val + idx.bv_len, bv.bv_val ); + i++; + bva[i].bv_val = NULL; + ldap_memfree( bv.bv_val ); + if ( cr == end ) break; + } + *res = bva; +} + +int +cr_schema_info( Entry *e ) +{ + AttributeDescription *ad_ditContentRules + = slap_schema.si_ad_ditContentRules; + ContentRule *cr; + + struct berval val; + struct berval nval; + + LDAP_STAILQ_FOREACH(cr, &cr_list, scr_next) { + if ( ldap_contentrule2bv( &cr->scr_crule, &val ) == NULL ) { + return -1; + } + +#if 0 + if( cr->scr_flags & SLAP_CR_HIDE ) continue; +#endif +#if 0 + Debug( LDAP_DEBUG_TRACE, "Merging cr [%ld] %s\n", + (long) val.bv_len, val.bv_val, 0 ); +#endif + + nval.bv_val = cr->scr_oid; + nval.bv_len = strlen(cr->scr_oid); + + if( attr_merge_one( e, ad_ditContentRules, &val, &nval ) ) + { + return -1; + } + ldap_memfree( val.bv_val ); + } + return 0; +} diff --git a/servers/slapd/ctxcsn.c b/servers/slapd/ctxcsn.c new file mode 100644 index 0000000..5d69ca8 --- /dev/null +++ b/servers/slapd/ctxcsn.c @@ -0,0 +1,218 @@ +/* ctxcsn.c -- Context CSN Management Routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * Portions Copyright 2003 IBM Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "lutil.h" +#include "slap.h" +#include "lutil_ldap.h" + +const struct berval slap_ldapsync_bv = BER_BVC("ldapsync"); +const struct berval slap_ldapsync_cn_bv = BER_BVC("cn=ldapsync"); +int slap_serverID; + +/* maxcsn->bv_val must point to a char buf[LDAP_PVT_CSNSTR_BUFSIZE] */ +void +slap_get_commit_csn( + Operation *op, + struct berval *maxcsn, + int *foundit +) +{ + struct slap_csn_entry *csne, *committed_csne = NULL; + BackendDB *be = op->o_bd->bd_self; + int sid = -1; + + if ( maxcsn ) { + assert( maxcsn->bv_val != NULL ); + assert( maxcsn->bv_len >= LDAP_PVT_CSNSTR_BUFSIZE ); + } + if ( foundit ) { + *foundit = 0; + } + + ldap_pvt_thread_mutex_lock( &be->be_pcl_mutex ); + + if ( !BER_BVISEMPTY( &op->o_csn )) { + sid = slap_parse_csn_sid( &op->o_csn ); + } + + LDAP_TAILQ_FOREACH( csne, be->be_pending_csn_list, ce_csn_link ) { + if ( csne->ce_op == op ) { + csne->ce_state = SLAP_CSN_COMMIT; + if ( foundit ) *foundit = 1; + break; + } + } + + LDAP_TAILQ_FOREACH( csne, be->be_pending_csn_list, ce_csn_link ) { + if ( sid != -1 && sid == csne->ce_sid ) { + if ( csne->ce_state == SLAP_CSN_COMMIT ) committed_csne = csne; + if ( csne->ce_state == SLAP_CSN_PENDING ) break; + } + } + + if ( maxcsn ) { + if ( committed_csne ) { + if ( committed_csne->ce_csn.bv_len < maxcsn->bv_len ) + maxcsn->bv_len = committed_csne->ce_csn.bv_len; + AC_MEMCPY( maxcsn->bv_val, committed_csne->ce_csn.bv_val, + maxcsn->bv_len+1 ); + } else { + maxcsn->bv_len = 0; + maxcsn->bv_val[0] = 0; + } + } + ldap_pvt_thread_mutex_unlock( &be->be_pcl_mutex ); +} + +void +slap_rewind_commit_csn( Operation *op ) +{ + struct slap_csn_entry *csne; + BackendDB *be = op->o_bd->bd_self; + + ldap_pvt_thread_mutex_lock( &be->be_pcl_mutex ); + + LDAP_TAILQ_FOREACH( csne, be->be_pending_csn_list, ce_csn_link ) { + if ( csne->ce_op == op ) { + csne->ce_state = SLAP_CSN_PENDING; + break; + } + } + + ldap_pvt_thread_mutex_unlock( &be->be_pcl_mutex ); +} + +void +slap_graduate_commit_csn( Operation *op ) +{ + struct slap_csn_entry *csne; + BackendDB *be; + + if ( op == NULL ) return; + if ( op->o_bd == NULL ) return; + be = op->o_bd->bd_self; + + ldap_pvt_thread_mutex_lock( &be->be_pcl_mutex ); + + LDAP_TAILQ_FOREACH( csne, be->be_pending_csn_list, ce_csn_link ) { + if ( csne->ce_op == op ) { + LDAP_TAILQ_REMOVE( be->be_pending_csn_list, + csne, ce_csn_link ); + Debug( LDAP_DEBUG_SYNC, "slap_graduate_commit_csn: removing %p %s\n", + csne, csne->ce_csn.bv_val, 0 ); + if ( op->o_csn.bv_val == csne->ce_csn.bv_val ) { + BER_BVZERO( &op->o_csn ); + } + ch_free( csne->ce_csn.bv_val ); + ch_free( csne ); + break; + } + } + + ldap_pvt_thread_mutex_unlock( &be->be_pcl_mutex ); + + return; +} + +static struct berval ocbva[] = { + BER_BVC("top"), + BER_BVC("subentry"), + BER_BVC("syncProviderSubentry"), + BER_BVNULL +}; + +Entry * +slap_create_context_csn_entry( + Backend *be, + struct berval *context_csn ) +{ + Entry* e; + + struct berval bv; + + e = entry_alloc(); + + attr_merge( e, slap_schema.si_ad_objectClass, + ocbva, NULL ); + attr_merge_one( e, slap_schema.si_ad_structuralObjectClass, + &ocbva[1], NULL ); + attr_merge_one( e, slap_schema.si_ad_cn, + (struct berval *)&slap_ldapsync_bv, NULL ); + + if ( context_csn ) { + attr_merge_one( e, slap_schema.si_ad_contextCSN, + context_csn, NULL ); + } + + BER_BVSTR( &bv, "{}" ); + attr_merge_one( e, slap_schema.si_ad_subtreeSpecification, &bv, NULL ); + + build_new_dn( &e->e_name, &be->be_nsuffix[0], + (struct berval *)&slap_ldapsync_cn_bv, NULL ); + ber_dupbv( &e->e_nname, &e->e_name ); + + return e; +} + +void +slap_queue_csn( + Operation *op, + struct berval *csn ) +{ + struct slap_csn_entry *pending; + BackendDB *be = op->o_bd->bd_self; + + pending = (struct slap_csn_entry *) ch_calloc( 1, + sizeof( struct slap_csn_entry )); + + Debug( LDAP_DEBUG_SYNC, "slap_queue_csn: queueing %p %s\n", pending, csn->bv_val, 0 ); + + ldap_pvt_thread_mutex_lock( &be->be_pcl_mutex ); + + ber_dupbv( &pending->ce_csn, csn ); + ber_bvreplace_x( &op->o_csn, &pending->ce_csn, op->o_tmpmemctx ); + pending->ce_sid = slap_parse_csn_sid( csn ); + pending->ce_op = op; + pending->ce_state = SLAP_CSN_PENDING; + LDAP_TAILQ_INSERT_TAIL( be->be_pending_csn_list, + pending, ce_csn_link ); + ldap_pvt_thread_mutex_unlock( &be->be_pcl_mutex ); +} + +int +slap_get_csn( + Operation *op, + struct berval *csn, + int manage_ctxcsn ) +{ + if ( csn == NULL ) return LDAP_OTHER; + + csn->bv_len = ldap_pvt_csnstr( csn->bv_val, csn->bv_len, slap_serverID, 0 ); + Debug( LDAP_DEBUG_SYNC, "slap_get_csn: %s generated new csn=%s manage=%d\n", + op->o_log_prefix, csn->bv_val, manage_ctxcsn ); + if ( manage_ctxcsn ) + slap_queue_csn( op, csn ); + + return LDAP_SUCCESS; +} diff --git a/servers/slapd/daemon.c b/servers/slapd/daemon.c new file mode 100644 index 0000000..50a13d1 --- /dev/null +++ b/servers/slapd/daemon.c @@ -0,0 +1,3156 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * Portions Copyright 2007 by Howard Chu, Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> +#include <ac/unistd.h> + +#include "slap.h" +#include "ldap_pvt_thread.h" +#include "lutil.h" + +#include "ldap_rq.h" + +#if defined(HAVE_SYS_EPOLL_H) && defined(HAVE_EPOLL) +# include <sys/epoll.h> +#elif defined(SLAP_X_DEVPOLL) && defined(HAVE_SYS_DEVPOLL_H) && defined(HAVE_DEVPOLL) +# include <sys/types.h> +# include <sys/stat.h> +# include <fcntl.h> +# include <sys/devpoll.h> +#endif /* ! epoll && ! /dev/poll */ + +#ifdef HAVE_TCPD +int allow_severity = LOG_INFO; +int deny_severity = LOG_NOTICE; +#endif /* TCP Wrappers */ + +#ifdef LDAP_PF_LOCAL +# include <sys/stat.h> +/* this should go in <ldap.h> as soon as it is accepted */ +# define LDAPI_MOD_URLEXT "x-mod" +#endif /* LDAP_PF_LOCAL */ + +#ifdef LDAP_PF_INET6 +int slap_inet4or6 = AF_UNSPEC; +#else /* ! INETv6 */ +int slap_inet4or6 = AF_INET; +#endif /* ! INETv6 */ + +/* globals */ +time_t starttime; +ber_socket_t dtblsize; +slap_ssf_t local_ssf = LDAP_PVT_SASL_LOCAL_SSF; +struct runqueue_s slapd_rq; + +#ifndef SLAPD_MAX_DAEMON_THREADS +#define SLAPD_MAX_DAEMON_THREADS 16 +#endif +int slapd_daemon_threads = 1; +int slapd_daemon_mask; + +#ifdef LDAP_TCP_BUFFER +int slapd_tcp_rmem; +int slapd_tcp_wmem; +#endif /* LDAP_TCP_BUFFER */ + +Listener **slap_listeners = NULL; +static volatile sig_atomic_t listening = 1; /* 0 when slap_listeners closed */ +static ldap_pvt_thread_t *listener_tid; + +#ifndef SLAPD_LISTEN_BACKLOG +#define SLAPD_LISTEN_BACKLOG 1024 +#endif /* ! SLAPD_LISTEN_BACKLOG */ + +#define DAEMON_ID(fd) (fd & slapd_daemon_mask) + +static ber_socket_t wake_sds[SLAPD_MAX_DAEMON_THREADS][2]; +static int emfile; + +static time_t chk_writetime; + +static volatile int waking; +#ifdef NO_THREADS +#define WAKE_LISTENER(l,w) do { \ + if ((w) && ++waking < 5) { \ + tcp_write( SLAP_FD2SOCK(wake_sds[l][1]), "0", 1 ); \ + } \ +} while (0) +#else /* ! NO_THREADS */ +#define WAKE_LISTENER(l,w) do { \ + if (w) { \ + tcp_write( SLAP_FD2SOCK(wake_sds[l][1]), "0", 1 ); \ + } \ +} while (0) +#endif /* ! NO_THREADS */ + +volatile sig_atomic_t slapd_shutdown = 0; +volatile sig_atomic_t slapd_gentle_shutdown = 0; +volatile sig_atomic_t slapd_abrupt_shutdown = 0; + +#ifdef HAVE_WINSOCK +ldap_pvt_thread_mutex_t slapd_ws_mutex; +SOCKET *slapd_ws_sockets; +#define SD_READ 1 +#define SD_WRITE 2 +#define SD_ACTIVE 4 +#define SD_LISTENER 8 +#endif + +#ifdef HAVE_TCPD +static ldap_pvt_thread_mutex_t sd_tcpd_mutex; +#endif /* TCP Wrappers */ + +typedef struct slap_daemon_st { + ldap_pvt_thread_mutex_t sd_mutex; + + ber_socket_t sd_nactives; + int sd_nwriters; + int sd_nfds; + +#if defined(HAVE_EPOLL) + struct epoll_event *sd_epolls; + int *sd_index; + int sd_epfd; +#elif defined(SLAP_X_DEVPOLL) && defined(HAVE_DEVPOLL) + /* eXperimental */ + struct pollfd *sd_pollfd; + int *sd_index; + Listener **sd_l; + int sd_dpfd; +#else /* ! epoll && ! /dev/poll */ +#ifdef HAVE_WINSOCK + char *sd_flags; + char *sd_rflags; +#else /* ! HAVE_WINSOCK */ + fd_set sd_actives; + fd_set sd_readers; + fd_set sd_writers; +#endif /* ! HAVE_WINSOCK */ +#endif /* ! epoll && ! /dev/poll */ +} slap_daemon_st; + +static slap_daemon_st slap_daemon[SLAPD_MAX_DAEMON_THREADS]; + +/* + * NOTE: naming convention for macros: + * + * - SLAP_SOCK_* and SLAP_EVENT_* for public interface that deals + * with file descriptors and events respectively + * + * - SLAP_<type>_* for private interface; type by now is one of + * EPOLL, DEVPOLL, SELECT + * + * private interface should not be used in the code. + */ +#if defined(HAVE_EPOLL) +/*************************************** + * Use epoll infrastructure - epoll(4) * + ***************************************/ +# define SLAP_EVENT_FNAME "epoll" +# define SLAP_EVENTS_ARE_INDEXED 0 +# define SLAP_EPOLL_SOCK_IX(t,s) (slap_daemon[t].sd_index[(s)]) +# define SLAP_EPOLL_SOCK_EP(t,s) (slap_daemon[t].sd_epolls[SLAP_EPOLL_SOCK_IX(t,s)]) +# define SLAP_EPOLL_SOCK_EV(t,s) (SLAP_EPOLL_SOCK_EP(t,s).events) +# define SLAP_SOCK_IS_ACTIVE(t,s) (SLAP_EPOLL_SOCK_IX(t,s) != -1) +# define SLAP_SOCK_NOT_ACTIVE(t,s) (SLAP_EPOLL_SOCK_IX(t,s) == -1) +# define SLAP_EPOLL_SOCK_IS_SET(t,s, mode) (SLAP_EPOLL_SOCK_EV(t,s) & (mode)) + +# define SLAP_SOCK_IS_READ(t,s) SLAP_EPOLL_SOCK_IS_SET(t,(s), EPOLLIN) +# define SLAP_SOCK_IS_WRITE(t,s) SLAP_EPOLL_SOCK_IS_SET(t,(s), EPOLLOUT) + +# define SLAP_EPOLL_SOCK_SET(t,s, mode) do { \ + if ( (SLAP_EPOLL_SOCK_EV(t,s) & (mode)) != (mode) ) { \ + SLAP_EPOLL_SOCK_EV(t,s) |= (mode); \ + epoll_ctl( slap_daemon[t].sd_epfd, EPOLL_CTL_MOD, (s), \ + &SLAP_EPOLL_SOCK_EP(t,s) ); \ + } \ +} while (0) + +# define SLAP_EPOLL_SOCK_CLR(t,s, mode) do { \ + if ( (SLAP_EPOLL_SOCK_EV(t,s) & (mode)) ) { \ + SLAP_EPOLL_SOCK_EV(t,s) &= ~(mode); \ + epoll_ctl( slap_daemon[t].sd_epfd, EPOLL_CTL_MOD, s, \ + &SLAP_EPOLL_SOCK_EP(t,s) ); \ + } \ +} while (0) + +# define SLAP_SOCK_SET_READ(t,s) SLAP_EPOLL_SOCK_SET(t,s, EPOLLIN) +# define SLAP_SOCK_SET_WRITE(t,s) SLAP_EPOLL_SOCK_SET(t,s, EPOLLOUT) + +# define SLAP_SOCK_CLR_READ(t,s) SLAP_EPOLL_SOCK_CLR(t,(s), EPOLLIN) +# define SLAP_SOCK_CLR_WRITE(t,s) SLAP_EPOLL_SOCK_CLR(t,(s), EPOLLOUT) + +# define SLAP_SOCK_SET_SUSPEND(t,s) \ + ( slap_daemon[t].sd_suspend[SLAP_EPOLL_SOCK_IX(t,s)] = 1 ) +# define SLAP_SOCK_CLR_SUSPEND(t,s) \ + ( slap_daemon[t].sd_suspend[SLAP_EPOLL_SOCK_IX(t,s)] = 0 ) +# define SLAP_SOCK_IS_SUSPEND(t,s) \ + ( slap_daemon[t].sd_suspend[SLAP_EPOLL_SOCK_IX(t,s)] == 1 ) + +# define SLAP_EPOLL_EVENT_CLR(i, mode) (revents[(i)].events &= ~(mode)) + +# define SLAP_EVENT_MAX(t) slap_daemon[t].sd_nfds + +/* If a Listener address is provided, store that as the epoll data. + * Otherwise, store the address of this socket's slot in the + * index array. If we can't do this add, the system is out of + * resources and we need to shutdown. + */ +# define SLAP_SOCK_ADD(t, s, l) do { \ + int rc; \ + SLAP_EPOLL_SOCK_IX(t,(s)) = slap_daemon[t].sd_nfds; \ + SLAP_EPOLL_SOCK_EP(t,(s)).data.ptr = (l) ? (l) : (void *)(&SLAP_EPOLL_SOCK_IX(t,s)); \ + SLAP_EPOLL_SOCK_EV(t,(s)) = EPOLLIN; \ + rc = epoll_ctl(slap_daemon[t].sd_epfd, EPOLL_CTL_ADD, \ + (s), &SLAP_EPOLL_SOCK_EP(t,(s))); \ + if ( rc == 0 ) { \ + slap_daemon[t].sd_nfds++; \ + } else { \ + Debug( LDAP_DEBUG_ANY, \ + "daemon: epoll_ctl(ADD,fd=%d) failed, errno=%d, shutting down\n", \ + s, errno, 0 ); \ + slapd_shutdown = 2; \ + } \ +} while (0) + +# define SLAP_EPOLL_EV_LISTENER(t,ptr) \ + (((int *)(ptr) >= slap_daemon[t].sd_index && \ + (int *)(ptr) <= &slap_daemon[t].sd_index[dtblsize]) ? 0 : 1 ) + +# define SLAP_EPOLL_EV_PTRFD(t,ptr) (SLAP_EPOLL_EV_LISTENER(t,ptr) ? \ + ((Listener *)ptr)->sl_sd : \ + (ber_socket_t) ((int *)(ptr) - slap_daemon[t].sd_index)) + +# define SLAP_SOCK_DEL(t,s) do { \ + int fd, rc, index = SLAP_EPOLL_SOCK_IX(t,(s)); \ + if ( index < 0 ) break; \ + rc = epoll_ctl(slap_daemon[t].sd_epfd, EPOLL_CTL_DEL, \ + (s), &SLAP_EPOLL_SOCK_EP(t,(s))); \ + slap_daemon[t].sd_epolls[index] = \ + slap_daemon[t].sd_epolls[slap_daemon[t].sd_nfds-1]; \ + fd = SLAP_EPOLL_EV_PTRFD(t,slap_daemon[t].sd_epolls[index].data.ptr); \ + slap_daemon[t].sd_index[fd] = index; \ + slap_daemon[t].sd_index[(s)] = -1; \ + slap_daemon[t].sd_nfds--; \ +} while (0) + +# define SLAP_EVENT_CLR_READ(i) SLAP_EPOLL_EVENT_CLR((i), EPOLLIN) +# define SLAP_EVENT_CLR_WRITE(i) SLAP_EPOLL_EVENT_CLR((i), EPOLLOUT) + +# define SLAP_EPOLL_EVENT_CHK(i, mode) (revents[(i)].events & mode) + +# define SLAP_EVENT_IS_READ(i) SLAP_EPOLL_EVENT_CHK((i), EPOLLIN) +# define SLAP_EVENT_IS_WRITE(i) SLAP_EPOLL_EVENT_CHK((i), EPOLLOUT) +# define SLAP_EVENT_IS_LISTENER(t,i) SLAP_EPOLL_EV_LISTENER(t,revents[(i)].data.ptr) +# define SLAP_EVENT_LISTENER(t,i) ((Listener *)(revents[(i)].data.ptr)) + +# define SLAP_EVENT_FD(t,i) SLAP_EPOLL_EV_PTRFD(t,revents[(i)].data.ptr) + +# define SLAP_SOCK_INIT(t) do { \ + int j; \ + slap_daemon[t].sd_epolls = ch_calloc(1, \ + ( sizeof(struct epoll_event) * 2 \ + + sizeof(int) ) * dtblsize * 2); \ + slap_daemon[t].sd_index = (int *)&slap_daemon[t].sd_epolls[ 2 * dtblsize ]; \ + slap_daemon[t].sd_epfd = epoll_create( dtblsize / slapd_daemon_threads ); \ + for ( j = 0; j < dtblsize; j++ ) slap_daemon[t].sd_index[j] = -1; \ +} while (0) + +# define SLAP_SOCK_DESTROY(t) do { \ + if ( slap_daemon[t].sd_epolls != NULL ) { \ + ch_free( slap_daemon[t].sd_epolls ); \ + slap_daemon[t].sd_epolls = NULL; \ + slap_daemon[t].sd_index = NULL; \ + close( slap_daemon[t].sd_epfd ); \ + } \ +} while ( 0 ) + +# define SLAP_EVENT_DECL struct epoll_event *revents + +# define SLAP_EVENT_INIT(t) do { \ + revents = slap_daemon[t].sd_epolls + dtblsize; \ +} while (0) + +# define SLAP_EVENT_WAIT(t, tvp, nsp) do { \ + *(nsp) = epoll_wait( slap_daemon[t].sd_epfd, revents, \ + dtblsize, (tvp) ? ((tvp)->tv_sec * 1000 + (tvp)->tv_usec / 1000) : -1 ); \ +} while (0) + +#elif defined(SLAP_X_DEVPOLL) && defined(HAVE_DEVPOLL) + +/************************************************************* + * Use Solaris' (>= 2.7) /dev/poll infrastructure - poll(7d) * + *************************************************************/ +# define SLAP_EVENT_FNAME "/dev/poll" +# define SLAP_EVENTS_ARE_INDEXED 0 +/* + * - sd_index is used much like with epoll() + * - sd_l is maintained as an array containing the address + * of the listener; the index is the fd itself + * - sd_pollfd is used to keep track of what data has been + * registered in /dev/poll + */ +# define SLAP_DEVPOLL_SOCK_IX(t,s) (slap_daemon[t].sd_index[(s)]) +# define SLAP_DEVPOLL_SOCK_LX(t,s) (slap_daemon[t].sd_l[(s)]) +# define SLAP_DEVPOLL_SOCK_EP(t,s) (slap_daemon[t].sd_pollfd[SLAP_DEVPOLL_SOCK_IX(t,(s))]) +# define SLAP_DEVPOLL_SOCK_FD(t,s) (SLAP_DEVPOLL_SOCK_EP(t,(s)).fd) +# define SLAP_DEVPOLL_SOCK_EV(t,s) (SLAP_DEVPOLL_SOCK_EP(t,(s)).events) +# define SLAP_SOCK_IS_ACTIVE(t,s) (SLAP_DEVPOLL_SOCK_IX(t,(s)) != -1) +# define SLAP_SOCK_NOT_ACTIVE(t,s) (SLAP_DEVPOLL_SOCK_IX(t,(s)) == -1) +# define SLAP_SOCK_IS_SET(t,s, mode) (SLAP_DEVPOLL_SOCK_EV(t,(s)) & (mode)) + +# define SLAP_SOCK_IS_READ(t,s) SLAP_SOCK_IS_SET(t,(s), POLLIN) +# define SLAP_SOCK_IS_WRITE(t,s) SLAP_SOCK_IS_SET(t,(s), POLLOUT) + +/* as far as I understand, any time we need to communicate with the kernel + * about the number and/or properties of a file descriptor we need it to + * wait for, we have to rewrite the whole set */ +# define SLAP_DEVPOLL_WRITE_POLLFD(t,s, pfd, n, what, shdn) do { \ + int rc; \ + size_t size = (n) * sizeof( struct pollfd ); \ + /* FIXME: use pwrite? */ \ + rc = write( slap_daemon[t].sd_dpfd, (pfd), size ); \ + if ( rc != size ) { \ + Debug( LDAP_DEBUG_ANY, "daemon: " SLAP_EVENT_FNAME ": " \ + "%s fd=%d failed errno=%d\n", \ + (what), (s), errno ); \ + if ( (shdn) ) { \ + slapd_shutdown = 2; \ + } \ + } \ +} while (0) + +# define SLAP_DEVPOLL_SOCK_SET(t,s, mode) do { \ + Debug( LDAP_DEBUG_CONNS, "SLAP_SOCK_SET_%s(%d)=%d\n", \ + (mode) == POLLIN ? "READ" : "WRITE", (s), \ + ( (SLAP_DEVPOLL_SOCK_EV(t,(s)) & (mode)) != (mode) ) ); \ + if ( (SLAP_DEVPOLL_SOCK_EV(t,(s)) & (mode)) != (mode) ) { \ + struct pollfd pfd; \ + SLAP_DEVPOLL_SOCK_EV(t,(s)) |= (mode); \ + pfd.fd = SLAP_DEVPOLL_SOCK_FD(t,(s)); \ + pfd.events = /* (mode) */ SLAP_DEVPOLL_SOCK_EV(t,(s)); \ + SLAP_DEVPOLL_WRITE_POLLFD(t,(s), &pfd, 1, "SET", 0); \ + } \ +} while (0) + +# define SLAP_DEVPOLL_SOCK_CLR(t,s, mode) do { \ + Debug( LDAP_DEBUG_CONNS, "SLAP_SOCK_CLR_%s(%d)=%d\n", \ + (mode) == POLLIN ? "READ" : "WRITE", (s), \ + ( (SLAP_DEVPOLL_SOCK_EV(t,(s)) & (mode)) == (mode) ) ); \ + if ((SLAP_DEVPOLL_SOCK_EV(t,(s)) & (mode)) == (mode) ) { \ + struct pollfd pfd[2]; \ + SLAP_DEVPOLL_SOCK_EV(t,(s)) &= ~(mode); \ + pfd[0].fd = SLAP_DEVPOLL_SOCK_FD(t,(s)); \ + pfd[0].events = POLLREMOVE; \ + pfd[1] = SLAP_DEVPOLL_SOCK_EP(t,(s)); \ + SLAP_DEVPOLL_WRITE_POLLFD(t,(s), &pfd[0], 2, "CLR", 0); \ + } \ +} while (0) + +# define SLAP_SOCK_SET_READ(t,s) SLAP_DEVPOLL_SOCK_SET(t,s, POLLIN) +# define SLAP_SOCK_SET_WRITE(t,s) SLAP_DEVPOLL_SOCK_SET(t,s, POLLOUT) + +# define SLAP_SOCK_CLR_READ(t,s) SLAP_DEVPOLL_SOCK_CLR(t,(s), POLLIN) +# define SLAP_SOCK_CLR_WRITE(t,s) SLAP_DEVPOLL_SOCK_CLR(t,(s), POLLOUT) + +# define SLAP_SOCK_SET_SUSPEND(t,s) \ + ( slap_daemon[t].sd_suspend[SLAP_DEVPOLL_SOCK_IX(t,(s))] = 1 ) +# define SLAP_SOCK_CLR_SUSPEND(t,s) \ + ( slap_daemon[t].sd_suspend[SLAP_DEVPOLL_SOCK_IX(t,(s))] = 0 ) +# define SLAP_SOCK_IS_SUSPEND(t,s) \ + ( slap_daemon[t].sd_suspend[SLAP_DEVPOLL_SOCK_IX(t,(s))] == 1 ) + +# define SLAP_DEVPOLL_EVENT_CLR(i, mode) (revents[(i)].events &= ~(mode)) + +# define SLAP_EVENT_MAX(t) slap_daemon[t].sd_nfds + +/* If a Listener address is provided, store that in the sd_l array. + * If we can't do this add, the system is out of resources and we + * need to shutdown. + */ +# define SLAP_SOCK_ADD(t, s, l) do { \ + Debug( LDAP_DEBUG_CONNS, "SLAP_SOCK_ADD(%d, %p)\n", (s), (l), 0 ); \ + SLAP_DEVPOLL_SOCK_IX(t,(s)) = slap_daemon[t].sd_nfds; \ + SLAP_DEVPOLL_SOCK_LX(t,(s)) = (l); \ + SLAP_DEVPOLL_SOCK_FD(t,(s)) = (s); \ + SLAP_DEVPOLL_SOCK_EV(t,(s)) = POLLIN; \ + SLAP_DEVPOLL_WRITE_POLLFD(t,(s), &SLAP_DEVPOLL_SOCK_EP((s)), 1, "ADD", 1); \ + slap_daemon[t].sd_nfds++; \ +} while (0) + +# define SLAP_DEVPOLL_EV_LISTENER(ptr) ((ptr) != NULL) + +# define SLAP_SOCK_DEL(t,s) do { \ + int fd, index = SLAP_DEVPOLL_SOCK_IX(t,(s)); \ + Debug( LDAP_DEBUG_CONNS, "SLAP_SOCK_DEL(%d)\n", (s), 0, 0 ); \ + if ( index < 0 ) break; \ + if ( index < slap_daemon[t].sd_nfds - 1 ) { \ + struct pollfd pfd = slap_daemon[t].sd_pollfd[index]; \ + fd = slap_daemon[t].sd_pollfd[slap_daemon[t].sd_nfds - 1].fd; \ + slap_daemon[t].sd_pollfd[index] = slap_daemon[t].sd_pollfd[slap_daemon[t].sd_nfds - 1]; \ + slap_daemon[t].sd_pollfd[slap_daemon[t].sd_nfds - 1] = pfd; \ + slap_daemon[t].sd_index[fd] = index; \ + } \ + slap_daemon[t].sd_index[(s)] = -1; \ + slap_daemon[t].sd_pollfd[slap_daemon[t].sd_nfds - 1].events = POLLREMOVE; \ + SLAP_DEVPOLL_WRITE_POLLFD(t,(s), &slap_daemon[t].sd_pollfd[slap_daemon[t].sd_nfds - 1], 1, "DEL", 0); \ + slap_daemon[t].sd_pollfd[slap_daemon[t].sd_nfds - 1].events = 0; \ + slap_daemon[t].sd_nfds--; \ +} while (0) + +# define SLAP_EVENT_CLR_READ(i) SLAP_DEVPOLL_EVENT_CLR((i), POLLIN) +# define SLAP_EVENT_CLR_WRITE(i) SLAP_DEVPOLL_EVENT_CLR((i), POLLOUT) + +# define SLAP_DEVPOLL_EVENT_CHK(i, mode) (revents[(i)].events & (mode)) + +# define SLAP_EVENT_FD(t,i) (revents[(i)].fd) + +# define SLAP_EVENT_IS_READ(i) SLAP_DEVPOLL_EVENT_CHK((i), POLLIN) +# define SLAP_EVENT_IS_WRITE(i) SLAP_DEVPOLL_EVENT_CHK((i), POLLOUT) +# define SLAP_EVENT_IS_LISTENER(t,i) SLAP_DEVPOLL_EV_LISTENER(SLAP_DEVPOLL_SOCK_LX(SLAP_EVENT_FD(t,(i)))) +# define SLAP_EVENT_LISTENER(t,i) SLAP_DEVPOLL_SOCK_LX(SLAP_EVENT_FD(t,(i))) + +# define SLAP_SOCK_INIT(t) do { \ + slap_daemon[t].sd_pollfd = ch_calloc( 1, \ + ( sizeof(struct pollfd) * 2 \ + + sizeof( int ) \ + + sizeof( Listener * ) ) * dtblsize ); \ + slap_daemon[t].sd_index = (int *)&slap_daemon[t].sd_pollfd[ 2 * dtblsize ]; \ + slap_daemon[t].sd_l = (Listener **)&slap_daemon[t].sd_index[ dtblsize ]; \ + slap_daemon[t].sd_dpfd = open( SLAP_EVENT_FNAME, O_RDWR ); \ + if ( slap_daemon[t].sd_dpfd == -1 ) { \ + Debug( LDAP_DEBUG_ANY, "daemon: " SLAP_EVENT_FNAME ": " \ + "open(\"" SLAP_EVENT_FNAME "\") failed errno=%d\n", \ + errno, 0, 0 ); \ + SLAP_SOCK_DESTROY; \ + return -1; \ + } \ + for ( i = 0; i < dtblsize; i++ ) { \ + slap_daemon[t].sd_pollfd[i].fd = -1; \ + slap_daemon[t].sd_index[i] = -1; \ + } \ +} while (0) + +# define SLAP_SOCK_DESTROY(t) do { \ + if ( slap_daemon[t].sd_pollfd != NULL ) { \ + ch_free( slap_daemon[t].sd_pollfd ); \ + slap_daemon[t].sd_pollfd = NULL; \ + slap_daemon[t].sd_index = NULL; \ + slap_daemon[t].sd_l = NULL; \ + close( slap_daemon[t].sd_dpfd ); \ + } \ +} while ( 0 ) + +# define SLAP_EVENT_DECL struct pollfd *revents + +# define SLAP_EVENT_INIT(t) do { \ + revents = &slap_daemon[t].sd_pollfd[ dtblsize ]; \ +} while (0) + +# define SLAP_EVENT_WAIT(t, tvp, nsp) do { \ + struct dvpoll sd_dvpoll; \ + sd_dvpoll.dp_timeout = (tvp) ? ((tvp)->tv_sec * 1000 + (tvp)->tv_usec / 1000) : -1; \ + sd_dvpoll.dp_nfds = dtblsize; \ + sd_dvpoll.dp_fds = revents; \ + *(nsp) = ioctl( slap_daemon[t].sd_dpfd, DP_POLL, &sd_dvpoll ); \ +} while (0) + +#else /* ! epoll && ! /dev/poll */ +# ifdef HAVE_WINSOCK +# define SLAP_EVENT_FNAME "WSselect" +/* Winsock provides a "select" function but its fd_sets are + * actually arrays of sockets. Since these sockets are handles + * and not a contiguous range of small integers, we manage our + * own "fd" table of socket handles and use their indices as + * descriptors. + * + * All of our listener/connection structures use fds; the actual + * I/O functions use sockets. The SLAP_FD2SOCK macro in proto-slap.h + * handles the mapping. + * + * Despite the mapping overhead, this is about 45% more efficient + * than just using Winsock's select and FD_ISSET directly. + * + * Unfortunately Winsock's select implementation doesn't scale well + * as the number of connections increases. This probably needs to be + * rewritten to use the Winsock overlapped/asynchronous I/O functions. + */ +# define SLAP_EVENTS_ARE_INDEXED 1 +# define SLAP_EVENT_DECL fd_set readfds, writefds; char *rflags +# define SLAP_EVENT_INIT(t) do { \ + int i; \ + FD_ZERO( &readfds ); \ + FD_ZERO( &writefds ); \ + rflags = slap_daemon[t].sd_rflags; \ + memset( rflags, 0, slap_daemon[t].sd_nfds ); \ + for ( i=0; i<slap_daemon[t].sd_nfds; i++ ) { \ + if ( slap_daemon[t].sd_flags[i] & SD_READ ) \ + FD_SET( slapd_ws_sockets[i], &readfds );\ + if ( slap_daemon[t].sd_flags[i] & SD_WRITE ) \ + FD_SET( slapd_ws_sockets[i], &writefds ); \ + } } while ( 0 ) + +# define SLAP_EVENT_MAX(t) slap_daemon[t].sd_nfds + +# define SLAP_EVENT_WAIT(t, tvp, nsp) do { \ + int i; \ + *(nsp) = select( SLAP_EVENT_MAX(t), &readfds, \ + nwriters > 0 ? &writefds : NULL, NULL, (tvp) ); \ + for ( i=0; i<readfds.fd_count; i++) { \ + int fd = slapd_sock2fd(readfds.fd_array[i]); \ + if ( fd >= 0 ) { \ + slap_daemon[t].sd_rflags[fd] = SD_READ; \ + if ( fd >= *(nsp)) *(nsp) = fd+1; \ + } \ + } \ + for ( i=0; i<writefds.fd_count; i++) { \ + int fd = slapd_sock2fd(writefds.fd_array[i]); \ + if ( fd >= 0 ) { \ + slap_daemon[t].sd_rflags[fd] = SD_WRITE; \ + if ( fd >= *(nsp)) *(nsp) = fd+1; \ + } \ + } \ +} while (0) + +# define SLAP_EVENT_IS_READ(fd) (rflags[fd] & SD_READ) +# define SLAP_EVENT_IS_WRITE(fd) (rflags[fd] & SD_WRITE) + +# define SLAP_EVENT_CLR_READ(fd) rflags[fd] &= ~SD_READ +# define SLAP_EVENT_CLR_WRITE(fd) rflags[fd] &= ~SD_WRITE + +# define SLAP_SOCK_INIT(t) do { \ + if (!t) { \ + ldap_pvt_thread_mutex_init( &slapd_ws_mutex ); \ + slapd_ws_sockets = ch_malloc( dtblsize * ( sizeof(SOCKET) + 2)); \ + memset( slapd_ws_sockets, -1, dtblsize * sizeof(SOCKET) ); \ + } \ + slap_daemon[t].sd_flags = (char *)(slapd_ws_sockets + dtblsize); \ + slap_daemon[t].sd_rflags = slap_daemon[t].sd_flags + dtblsize; \ + memset( slap_daemon[t].sd_flags, 0, dtblsize ); \ + slapd_ws_sockets[t*2] = wake_sds[t][0]; \ + slapd_ws_sockets[t*2+1] = wake_sds[t][1]; \ + wake_sds[t][0] = t*2; \ + wake_sds[t][1] = t*2+1; \ + slap_daemon[t].sd_nfds = t*2 + 2; \ + } while ( 0 ) + +# define SLAP_SOCK_DESTROY(t) do { \ + ch_free( slapd_ws_sockets ); slapd_ws_sockets = NULL; \ + slap_daemon[t].sd_flags = NULL; \ + slap_daemon[t].sd_rflags = NULL; \ + ldap_pvt_thread_mutex_destroy( &slapd_ws_mutex ); \ + } while ( 0 ) + +# define SLAP_SOCK_IS_ACTIVE(t,fd) ( slap_daemon[t].sd_flags[fd] & SD_ACTIVE ) +# define SLAP_SOCK_IS_READ(t,fd) ( slap_daemon[t].sd_flags[fd] & SD_READ ) +# define SLAP_SOCK_IS_WRITE(t,fd) ( slap_daemon[t].sd_flags[fd] & SD_WRITE ) +# define SLAP_SOCK_NOT_ACTIVE(t,fd) (!slap_daemon[t].sd_flags[fd]) + +# define SLAP_SOCK_SET_READ(t,fd) ( slap_daemon[t].sd_flags[fd] |= SD_READ ) +# define SLAP_SOCK_SET_WRITE(t,fd) ( slap_daemon[t].sd_flags[fd] |= SD_WRITE ) + +# define SLAP_SELECT_ADDTEST(t,s) do { \ + if ((s) >= slap_daemon[t].sd_nfds) slap_daemon[t].sd_nfds = (s)+1; \ +} while (0) + +# define SLAP_SOCK_CLR_READ(t,fd) ( slap_daemon[t].sd_flags[fd] &= ~SD_READ ) +# define SLAP_SOCK_CLR_WRITE(t,fd) ( slap_daemon[t].sd_flags[fd] &= ~SD_WRITE ) + +# define SLAP_SOCK_ADD(t,s, l) do { \ + SLAP_SELECT_ADDTEST(t,(s)); \ + slap_daemon[t].sd_flags[s] = SD_ACTIVE|SD_READ; \ +} while ( 0 ) + +# define SLAP_SOCK_DEL(t,s) do { \ + slap_daemon[t].sd_flags[s] = 0; \ + slapd_sockdel( s ); \ +} while ( 0 ) + +# else /* !HAVE_WINSOCK */ + +/************************************** + * Use select system call - select(2) * + **************************************/ +# define SLAP_EVENT_FNAME "select" +/* select */ +# define SLAP_EVENTS_ARE_INDEXED 1 +# define SLAP_EVENT_DECL fd_set readfds, writefds + +# define SLAP_EVENT_INIT(t) do { \ + AC_MEMCPY( &readfds, &slap_daemon[t].sd_readers, sizeof(fd_set) ); \ + if ( nwriters ) { \ + AC_MEMCPY( &writefds, &slap_daemon[t].sd_writers, sizeof(fd_set) ); \ + } else { \ + FD_ZERO( &writefds ); \ + } \ +} while (0) + +# ifdef FD_SETSIZE +# define SLAP_SELECT_CHK_SETSIZE do { \ + if (dtblsize > FD_SETSIZE) dtblsize = FD_SETSIZE; \ +} while (0) +# else /* ! FD_SETSIZE */ +# define SLAP_SELECT_CHK_SETSIZE do { ; } while (0) +# endif /* ! FD_SETSIZE */ + +# define SLAP_SOCK_INIT(t) do { \ + SLAP_SELECT_CHK_SETSIZE; \ + FD_ZERO(&slap_daemon[t].sd_actives); \ + FD_ZERO(&slap_daemon[t].sd_readers); \ + FD_ZERO(&slap_daemon[t].sd_writers); \ +} while (0) + +# define SLAP_SOCK_DESTROY(t) + +# define SLAP_SOCK_IS_ACTIVE(t,fd) FD_ISSET((fd), &slap_daemon[t].sd_actives) +# define SLAP_SOCK_IS_READ(t,fd) FD_ISSET((fd), &slap_daemon[t].sd_readers) +# define SLAP_SOCK_IS_WRITE(t,fd) FD_ISSET((fd), &slap_daemon[t].sd_writers) + +# define SLAP_SOCK_NOT_ACTIVE(t,fd) (!SLAP_SOCK_IS_ACTIVE(t,fd) && \ + !SLAP_SOCK_IS_READ(t,fd) && !SLAP_SOCK_IS_WRITE(t,fd)) + +# define SLAP_SOCK_SET_READ(t,fd) FD_SET((fd), &slap_daemon[t].sd_readers) +# define SLAP_SOCK_SET_WRITE(t,fd) FD_SET((fd), &slap_daemon[t].sd_writers) + +# define SLAP_EVENT_MAX(t) slap_daemon[t].sd_nfds +# define SLAP_SELECT_ADDTEST(t,s) do { \ + if ((s) >= slap_daemon[t].sd_nfds) slap_daemon[t].sd_nfds = (s)+1; \ +} while (0) + +# define SLAP_SOCK_CLR_READ(t,fd) FD_CLR((fd), &slap_daemon[t].sd_readers) +# define SLAP_SOCK_CLR_WRITE(t,fd) FD_CLR((fd), &slap_daemon[t].sd_writers) + +# define SLAP_SOCK_ADD(t,s, l) do { \ + SLAP_SELECT_ADDTEST(t,(s)); \ + FD_SET((s), &slap_daemon[t].sd_actives); \ + FD_SET((s), &slap_daemon[t].sd_readers); \ +} while (0) + +# define SLAP_SOCK_DEL(t,s) do { \ + FD_CLR((s), &slap_daemon[t].sd_actives); \ + FD_CLR((s), &slap_daemon[t].sd_readers); \ + FD_CLR((s), &slap_daemon[t].sd_writers); \ +} while (0) + +# define SLAP_EVENT_IS_READ(fd) FD_ISSET((fd), &readfds) +# define SLAP_EVENT_IS_WRITE(fd) FD_ISSET((fd), &writefds) + +# define SLAP_EVENT_CLR_READ(fd) FD_CLR((fd), &readfds) +# define SLAP_EVENT_CLR_WRITE(fd) FD_CLR((fd), &writefds) + +# define SLAP_EVENT_WAIT(t, tvp, nsp) do { \ + *(nsp) = select( SLAP_EVENT_MAX(t), &readfds, \ + nwriters > 0 ? &writefds : NULL, NULL, (tvp) ); \ +} while (0) +# endif /* !HAVE_WINSOCK */ +#endif /* ! epoll && ! /dev/poll */ + +#ifdef HAVE_SLP +/* + * SLP related functions + */ +#include <slp.h> + +#define LDAP_SRVTYPE_PREFIX "service:ldap://" +#define LDAPS_SRVTYPE_PREFIX "service:ldaps://" +static char** slapd_srvurls = NULL; +static SLPHandle slapd_hslp = 0; +int slapd_register_slp = 0; +const char *slapd_slp_attrs = NULL; + +static SLPError slapd_slp_cookie; + +static void +slapd_slp_init( const char* urls ) +{ + int i; + SLPError err; + + slapd_srvurls = ldap_str2charray( urls, " " ); + + if ( slapd_srvurls == NULL ) return; + + /* find and expand INADDR_ANY URLs */ + for ( i = 0; slapd_srvurls[i] != NULL; i++ ) { + if ( strcmp( slapd_srvurls[i], "ldap:///" ) == 0 ) { + slapd_srvurls[i] = (char *) ch_realloc( slapd_srvurls[i], + global_host_bv.bv_len + + sizeof( LDAP_SRVTYPE_PREFIX ) ); + strcpy( lutil_strcopy(slapd_srvurls[i], + LDAP_SRVTYPE_PREFIX ), global_host_bv.bv_val ); + } else if ( strcmp( slapd_srvurls[i], "ldaps:///" ) == 0 ) { + slapd_srvurls[i] = (char *) ch_realloc( slapd_srvurls[i], + global_host_bv.bv_len + + sizeof( LDAPS_SRVTYPE_PREFIX ) ); + strcpy( lutil_strcopy(slapd_srvurls[i], + LDAPS_SRVTYPE_PREFIX ), global_host_bv.bv_val ); + } + } + + /* open the SLP handle */ + err = SLPOpen( "en", 0, &slapd_hslp ); + + if ( err != SLP_OK ) { + Debug( LDAP_DEBUG_CONNS, "daemon: SLPOpen() failed with %ld\n", + (long)err, 0, 0 ); + } +} + +static void +slapd_slp_deinit( void ) +{ + if ( slapd_srvurls == NULL ) return; + + ldap_charray_free( slapd_srvurls ); + slapd_srvurls = NULL; + + /* close the SLP handle */ + SLPClose( slapd_hslp ); +} + +static void +slapd_slp_regreport( + SLPHandle hslp, + SLPError errcode, + void *cookie ) +{ + /* return the error code in the cookie */ + *(SLPError*)cookie = errcode; +} + +static void +slapd_slp_reg() +{ + int i; + SLPError err; + + if ( slapd_srvurls == NULL ) return; + + for ( i = 0; slapd_srvurls[i] != NULL; i++ ) { + if ( strncmp( slapd_srvurls[i], LDAP_SRVTYPE_PREFIX, + sizeof( LDAP_SRVTYPE_PREFIX ) - 1 ) == 0 || + strncmp( slapd_srvurls[i], LDAPS_SRVTYPE_PREFIX, + sizeof( LDAPS_SRVTYPE_PREFIX ) - 1 ) == 0 ) + { + err = SLPReg( slapd_hslp, + slapd_srvurls[i], + SLP_LIFETIME_MAXIMUM, + "ldap", + (slapd_slp_attrs) ? slapd_slp_attrs : "", + SLP_TRUE, + slapd_slp_regreport, + &slapd_slp_cookie ); + + if ( err != SLP_OK || slapd_slp_cookie != SLP_OK ) { + Debug( LDAP_DEBUG_CONNS, + "daemon: SLPReg(%s) failed with %ld, cookie = %ld\n", + slapd_srvurls[i], (long)err, (long)slapd_slp_cookie ); + } + } + } +} + +static void +slapd_slp_dereg( void ) +{ + int i; + SLPError err; + + if ( slapd_srvurls == NULL ) return; + + for ( i = 0; slapd_srvurls[i] != NULL; i++ ) { + err = SLPDereg( slapd_hslp, + slapd_srvurls[i], + slapd_slp_regreport, + &slapd_slp_cookie ); + + if ( err != SLP_OK || slapd_slp_cookie != SLP_OK ) { + Debug( LDAP_DEBUG_CONNS, + "daemon: SLPDereg(%s) failed with %ld, cookie = %ld\n", + slapd_srvurls[i], (long)err, (long)slapd_slp_cookie ); + } + } +} +#endif /* HAVE_SLP */ + +#ifdef HAVE_WINSOCK +/* Manage the descriptor to socket table */ +ber_socket_t +slapd_socknew( ber_socket_t s ) +{ + ber_socket_t i; + ldap_pvt_thread_mutex_lock( &slapd_ws_mutex ); + for ( i = 0; i < dtblsize && slapd_ws_sockets[i] != INVALID_SOCKET; i++ ); + if ( i == dtblsize ) { + WSASetLastError( WSAEMFILE ); + } else { + slapd_ws_sockets[i] = s; + } + ldap_pvt_thread_mutex_unlock( &slapd_ws_mutex ); + return i; +} + +void +slapd_sockdel( ber_socket_t s ) +{ + ldap_pvt_thread_mutex_lock( &slapd_ws_mutex ); + slapd_ws_sockets[s] = INVALID_SOCKET; + ldap_pvt_thread_mutex_unlock( &slapd_ws_mutex ); +} + +ber_socket_t +slapd_sock2fd( ber_socket_t s ) +{ + ber_socket_t i; + for ( i=0; i<dtblsize && slapd_ws_sockets[i] != s; i++); + if ( i == dtblsize ) + i = -1; + return i; +} +#endif + +#ifdef DEBUG_CLOSE +/* Was used to find a bug causing slapd's descriptors to be closed + * out from under it. Tracked it down to a long-standing (from 2009) + * bug in Heimdal https://github.com/heimdal/heimdal/issues/431 . + * Leaving this here for future use, if necessary. + */ +#include <dlfcn.h> +#ifndef RTLD_NEXT +#define RTLD_NEXT (void *)-1L +#endif +static char *newconns; +typedef int (closefunc)(int fd); +static closefunc *close_ptr; +int close( int s ) +{ + if (newconns) { + Debug( LDAP_DEBUG_CONNS, + "daemon: close(%ld)\n", s, 0, 0 ); + if (s >= 0 && s < dtblsize && newconns[s]) + assert(newconns[s] == 2); + } + return close_ptr ? close_ptr(s) : -1; +} + +void slapd_debug_close() +{ + if (dtblsize) + newconns = ch_calloc(1, dtblsize); + close_ptr = dlsym(RTLD_NEXT, "close"); +} + +void slapd_set_close(int fd) +{ + newconns[fd] = 3; +} +#define SETUP_CLOSE() slapd_debug_close() +#define SET_CLOSE(fd) slapd_set_close(fd) +#define CLR_CLOSE(fd) if (newconns[fd]) newconns[fd]-- +#else +#define SETUP_CLOSE(fd) +#define SET_CLOSE(fd) +#define CLR_CLOSE(fd) +#endif + +/* + * Add a descriptor to daemon control + * + * If isactive, the descriptor is a live server session and is subject + * to idletimeout control. Otherwise, the descriptor is a passive + * listener or an outbound client session, and not subject to + * idletimeout. The underlying event handler may record the Listener + * argument to differentiate Listener's from real sessions. + */ +static void +slapd_add( ber_socket_t s, int isactive, Listener *sl, int id ) +{ + if (id < 0) + id = DAEMON_ID(s); + ldap_pvt_thread_mutex_lock( &slap_daemon[id].sd_mutex ); + + assert( SLAP_SOCK_NOT_ACTIVE(id, s) ); + + if ( isactive ) slap_daemon[id].sd_nactives++; + + SLAP_SOCK_ADD(id, s, sl); + + Debug( LDAP_DEBUG_CONNS, "daemon: added %ldr%s listener=%p\n", + (long) s, isactive ? " (active)" : "", (void *)sl ); + + ldap_pvt_thread_mutex_unlock( &slap_daemon[id].sd_mutex ); + + WAKE_LISTENER(id,1); +} + +/* + * Remove the descriptor from daemon control + */ +void +slapd_remove( + ber_socket_t s, + Sockbuf *sb, + int wasactive, + int wake, + int locked ) +{ + int waswriter; + int wasreader; + int id = DAEMON_ID(s); + + if ( !locked ) + ldap_pvt_thread_mutex_lock( &slap_daemon[id].sd_mutex ); + + assert( SLAP_SOCK_IS_ACTIVE( id, s )); + + if ( wasactive ) slap_daemon[id].sd_nactives--; + + waswriter = SLAP_SOCK_IS_WRITE(id, s); + wasreader = SLAP_SOCK_IS_READ(id, s); + + Debug( LDAP_DEBUG_CONNS, "daemon: removing %ld%s%s\n", + (long) s, + wasreader ? "r" : "", + waswriter ? "w" : "" ); + + if ( waswriter ) slap_daemon[id].sd_nwriters--; + + SLAP_SOCK_DEL(id, s); + CLR_CLOSE(s); + + if ( sb ) + ber_sockbuf_free(sb); + + /* If we ran out of file descriptors, we dropped a listener from + * the select() loop. Now that we're removing a session from our + * control, we can try to resume a dropped listener to use. + */ + if ( emfile && listening ) { + int i; + for ( i = 0; slap_listeners[i] != NULL; i++ ) { + Listener *lr = slap_listeners[i]; + + if ( lr->sl_sd == AC_SOCKET_INVALID ) continue; + if ( lr->sl_sd == s ) continue; + if ( lr->sl_mute ) { + lr->sl_mute = 0; + emfile--; + if ( DAEMON_ID(lr->sl_sd) != id ) + WAKE_LISTENER(DAEMON_ID(lr->sl_sd), wake); + break; + } + } + /* Walked the entire list without enabling anything; emfile + * counter is stale. Reset it. + */ + if ( slap_listeners[i] == NULL ) emfile = 0; + } + ldap_pvt_thread_mutex_unlock( &slap_daemon[id].sd_mutex ); + WAKE_LISTENER(id, wake || slapd_gentle_shutdown == 2); +} + +void +slapd_clr_write( ber_socket_t s, int wake ) +{ + int id = DAEMON_ID(s); + ldap_pvt_thread_mutex_lock( &slap_daemon[id].sd_mutex ); + + if ( SLAP_SOCK_IS_WRITE( id, s )) { + assert( SLAP_SOCK_IS_ACTIVE( id, s )); + + SLAP_SOCK_CLR_WRITE( id, s ); + slap_daemon[id].sd_nwriters--; + } + + ldap_pvt_thread_mutex_unlock( &slap_daemon[id].sd_mutex ); + WAKE_LISTENER(id,wake); +} + +void +slapd_set_write( ber_socket_t s, int wake ) +{ + int id = DAEMON_ID(s); + ldap_pvt_thread_mutex_lock( &slap_daemon[id].sd_mutex ); + + assert( SLAP_SOCK_IS_ACTIVE( id, s )); + + if ( !SLAP_SOCK_IS_WRITE( id, s )) { + SLAP_SOCK_SET_WRITE( id, s ); + slap_daemon[id].sd_nwriters++; + } + if (( wake & 2 ) && global_writetimeout && !chk_writetime ) { + if (id) + ldap_pvt_thread_mutex_lock( &slap_daemon[0].sd_mutex ); + if (!chk_writetime) + chk_writetime = slap_get_time(); + if (id) + ldap_pvt_thread_mutex_unlock( &slap_daemon[0].sd_mutex ); + } + + ldap_pvt_thread_mutex_unlock( &slap_daemon[id].sd_mutex ); + WAKE_LISTENER(id,wake); +} + +int +slapd_clr_read( ber_socket_t s, int wake ) +{ + int rc = 1; + int id = DAEMON_ID(s); + ldap_pvt_thread_mutex_lock( &slap_daemon[id].sd_mutex ); + + if ( SLAP_SOCK_IS_ACTIVE( id, s )) { + SLAP_SOCK_CLR_READ( id, s ); + rc = 0; + } + ldap_pvt_thread_mutex_unlock( &slap_daemon[id].sd_mutex ); + if ( !rc ) + WAKE_LISTENER(id,wake); + return rc; +} + +void +slapd_set_read( ber_socket_t s, int wake ) +{ + int do_wake = 1; + int id = DAEMON_ID(s); + ldap_pvt_thread_mutex_lock( &slap_daemon[id].sd_mutex ); + + if( SLAP_SOCK_IS_ACTIVE( id, s ) && !SLAP_SOCK_IS_READ( id, s )) { + SLAP_SOCK_SET_READ( id, s ); + } else { + do_wake = 0; + } + ldap_pvt_thread_mutex_unlock( &slap_daemon[id].sd_mutex ); + if ( do_wake ) + WAKE_LISTENER(id,wake); +} + +time_t +slapd_get_writetime() +{ + time_t cur; + ldap_pvt_thread_mutex_lock( &slap_daemon[0].sd_mutex ); + cur = chk_writetime; + ldap_pvt_thread_mutex_unlock( &slap_daemon[0].sd_mutex ); + return cur; +} + +void +slapd_clr_writetime( time_t old ) +{ + ldap_pvt_thread_mutex_lock( &slap_daemon[0].sd_mutex ); + if ( chk_writetime == old ) + chk_writetime = 0; + ldap_pvt_thread_mutex_unlock( &slap_daemon[0].sd_mutex ); +} + +static void +slapd_close( ber_socket_t s ) +{ + Debug( LDAP_DEBUG_CONNS, "daemon: closing %ld\n", + (long) s, 0, 0 ); + CLR_CLOSE( SLAP_FD2SOCK(s) ); + tcp_close( SLAP_FD2SOCK(s) ); +#ifdef HAVE_WINSOCK + slapd_sockdel( s ); +#endif +} + +static void +slap_free_listener_addresses( struct sockaddr **sal ) +{ + struct sockaddr **sap; + if (sal == NULL) return; + for (sap = sal; *sap != NULL; sap++) ch_free(*sap); + ch_free(sal); +} + +#if defined(LDAP_PF_LOCAL) || defined(SLAP_X_LISTENER_MOD) +static int +get_url_perms( + char **exts, + mode_t *perms, + int *crit ) +{ + int i; + + assert( exts != NULL ); + assert( perms != NULL ); + assert( crit != NULL ); + + *crit = 0; + for ( i = 0; exts[ i ]; i++ ) { + char *type = exts[ i ]; + int c = 0; + + if ( type[ 0 ] == '!' ) { + c = 1; + type++; + } + + if ( strncasecmp( type, LDAPI_MOD_URLEXT "=", + sizeof(LDAPI_MOD_URLEXT "=") - 1 ) == 0 ) + { + char *value = type + ( sizeof(LDAPI_MOD_URLEXT "=") - 1 ); + mode_t p = 0; + int j; + + switch (strlen(value)) { + case 4: + /* skip leading '0' */ + if ( value[ 0 ] != '0' ) return LDAP_OTHER; + value++; + + case 3: + for ( j = 0; j < 3; j++) { + int v; + + v = value[ j ] - '0'; + + if ( v < 0 || v > 7 ) return LDAP_OTHER; + + p |= v << 3*(2-j); + } + break; + + case 10: + for ( j = 1; j < 10; j++ ) { + static mode_t m[] = { 0, + S_IRUSR, S_IWUSR, S_IXUSR, + S_IRGRP, S_IWGRP, S_IXGRP, + S_IROTH, S_IWOTH, S_IXOTH + }; + static const char c[] = "-rwxrwxrwx"; + + if ( value[ j ] == c[ j ] ) { + p |= m[ j ]; + + } else if ( value[ j ] != '-' ) { + return LDAP_OTHER; + } + } + break; + + default: + return LDAP_OTHER; + } + + *crit = c; + *perms = p; + + return LDAP_SUCCESS; + } + } + + return LDAP_OTHER; +} +#endif /* LDAP_PF_LOCAL || SLAP_X_LISTENER_MOD */ + +/* port = 0 indicates AF_LOCAL */ +static int +slap_get_listener_addresses( + const char *host, + unsigned short port, + struct sockaddr ***sal ) +{ + struct sockaddr **sap; + +#ifdef LDAP_PF_LOCAL + if ( port == 0 ) { + *sal = ch_malloc(2 * sizeof(void *)); + if (*sal == NULL) return -1; + + sap = *sal; + *sap = ch_malloc(sizeof(struct sockaddr_un)); + if (*sap == NULL) goto errexit; + sap[1] = NULL; + + if ( strlen(host) > + (sizeof(((struct sockaddr_un *)*sap)->sun_path) - 1) ) + { + Debug( LDAP_DEBUG_ANY, + "daemon: domain socket path (%s) too long in URL", + host, 0, 0); + goto errexit; + } + + (void)memset( (void *)*sap, '\0', sizeof(struct sockaddr_un) ); + (*sap)->sa_family = AF_LOCAL; + strcpy( ((struct sockaddr_un *)*sap)->sun_path, host ); + } else +#endif /* LDAP_PF_LOCAL */ + { +#ifdef HAVE_GETADDRINFO + struct addrinfo hints, *res, *sai; + int n, err; + char serv[7]; + + memset( &hints, '\0', sizeof(hints) ); + hints.ai_flags = AI_PASSIVE; + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = slap_inet4or6; + snprintf(serv, sizeof serv, "%d", port); + + if ( (err = getaddrinfo(host, serv, &hints, &res)) ) { + Debug( LDAP_DEBUG_ANY, "daemon: getaddrinfo() failed: %s\n", + AC_GAI_STRERROR(err), 0, 0); + return -1; + } + + sai = res; + for (n=2; (sai = sai->ai_next) != NULL; n++) { + /* EMPTY */ ; + } + *sal = ch_calloc(n, sizeof(void *)); + if (*sal == NULL) return -1; + + sap = *sal; + *sap = NULL; + + for ( sai=res; sai; sai=sai->ai_next ) { + if( sai->ai_addr == NULL ) { + Debug( LDAP_DEBUG_ANY, "slap_get_listener_addresses: " + "getaddrinfo ai_addr is NULL?\n", 0, 0, 0 ); + freeaddrinfo(res); + goto errexit; + } + + switch (sai->ai_family) { +# ifdef LDAP_PF_INET6 + case AF_INET6: + *sap = ch_malloc(sizeof(struct sockaddr_in6)); + if (*sap == NULL) { + freeaddrinfo(res); + goto errexit; + } + *(struct sockaddr_in6 *)*sap = + *((struct sockaddr_in6 *)sai->ai_addr); + break; +# endif /* LDAP_PF_INET6 */ + case AF_INET: + *sap = ch_malloc(sizeof(struct sockaddr_in)); + if (*sap == NULL) { + freeaddrinfo(res); + goto errexit; + } + *(struct sockaddr_in *)*sap = + *((struct sockaddr_in *)sai->ai_addr); + break; + default: + *sap = NULL; + break; + } + + if (*sap != NULL) { + (*sap)->sa_family = sai->ai_family; + sap++; + *sap = NULL; + } + } + + freeaddrinfo(res); + +#else /* ! HAVE_GETADDRINFO */ + int i, n = 1; + struct in_addr in; + struct hostent *he = NULL; + + if ( host == NULL ) { + in.s_addr = htonl(INADDR_ANY); + + } else if ( !inet_aton( host, &in ) ) { + he = gethostbyname( host ); + if( he == NULL ) { + Debug( LDAP_DEBUG_ANY, + "daemon: invalid host %s", host, 0, 0); + return -1; + } + for (n = 0; he->h_addr_list[n]; n++) /* empty */; + } + + *sal = ch_malloc((n+1) * sizeof(void *)); + if (*sal == NULL) return -1; + + sap = *sal; + for ( i = 0; i<n; i++ ) { + sap[i] = ch_malloc(sizeof(struct sockaddr_in)); + if (*sap == NULL) goto errexit; + + (void)memset( (void *)sap[i], '\0', sizeof(struct sockaddr_in) ); + sap[i]->sa_family = AF_INET; + ((struct sockaddr_in *)sap[i])->sin_port = htons(port); + AC_MEMCPY( &((struct sockaddr_in *)sap[i])->sin_addr, + he ? (struct in_addr *)he->h_addr_list[i] : &in, + sizeof(struct in_addr) ); + } + sap[i] = NULL; +#endif /* ! HAVE_GETADDRINFO */ + } + + return 0; + +errexit: + slap_free_listener_addresses(*sal); + return -1; +} + +static int +slap_open_listener( + const char* url, + int *listeners, + int *cur ) +{ + int num, tmp, rc; + Listener l; + Listener *li; + LDAPURLDesc *lud; + unsigned short port; + int err, addrlen = 0; + struct sockaddr **sal = NULL, **psal; + int socktype = SOCK_STREAM; /* default to COTS */ + ber_socket_t s; + +#if defined(LDAP_PF_LOCAL) || defined(SLAP_X_LISTENER_MOD) + /* + * use safe defaults + */ + int crit = 1; +#endif /* LDAP_PF_LOCAL || SLAP_X_LISTENER_MOD */ + + rc = ldap_url_parse( url, &lud ); + + if( rc != LDAP_URL_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "daemon: listen URL \"%s\" parse error=%d\n", + url, rc, 0 ); + return rc; + } + + l.sl_url.bv_val = NULL; + l.sl_mute = 0; + l.sl_busy = 0; + +#ifndef HAVE_TLS + if( ldap_pvt_url_scheme2tls( lud->lud_scheme ) ) { + Debug( LDAP_DEBUG_ANY, "daemon: TLS not supported (%s)\n", + url, 0, 0 ); + ldap_free_urldesc( lud ); + return -1; + } + + if(! lud->lud_port ) lud->lud_port = LDAP_PORT; + +#else /* HAVE_TLS */ + l.sl_is_tls = ldap_pvt_url_scheme2tls( lud->lud_scheme ); + + if(! lud->lud_port ) { + lud->lud_port = l.sl_is_tls ? LDAPS_PORT : LDAP_PORT; + } +#endif /* HAVE_TLS */ + +#ifdef LDAP_TCP_BUFFER + l.sl_tcp_rmem = 0; + l.sl_tcp_wmem = 0; +#endif /* LDAP_TCP_BUFFER */ + + port = (unsigned short) lud->lud_port; + + tmp = ldap_pvt_url_scheme2proto(lud->lud_scheme); + if ( tmp == LDAP_PROTO_IPC ) { +#ifdef LDAP_PF_LOCAL + if ( lud->lud_host == NULL || lud->lud_host[0] == '\0' ) { + err = slap_get_listener_addresses(LDAPI_SOCK, 0, &sal); + } else { + err = slap_get_listener_addresses(lud->lud_host, 0, &sal); + } +#else /* ! LDAP_PF_LOCAL */ + + Debug( LDAP_DEBUG_ANY, "daemon: URL scheme not supported: %s", + url, 0, 0); + ldap_free_urldesc( lud ); + return -1; +#endif /* ! LDAP_PF_LOCAL */ + } else { + if( lud->lud_host == NULL || lud->lud_host[0] == '\0' + || strcmp(lud->lud_host, "*") == 0 ) + { + err = slap_get_listener_addresses(NULL, port, &sal); + } else { + err = slap_get_listener_addresses(lud->lud_host, port, &sal); + } + } + +#ifdef LDAP_CONNECTIONLESS + l.sl_is_udp = ( tmp == LDAP_PROTO_UDP ); +#endif /* LDAP_CONNECTIONLESS */ + +#if defined(LDAP_PF_LOCAL) || defined(SLAP_X_LISTENER_MOD) + if ( lud->lud_exts ) { + err = get_url_perms( lud->lud_exts, &l.sl_perms, &crit ); + } else { + l.sl_perms = S_IRWXU | S_IRWXO; + } +#endif /* LDAP_PF_LOCAL || SLAP_X_LISTENER_MOD */ + + if ( lud->lud_dn && lud->lud_dn[0] ) { + sprintf( (char *)url, "%s://%s/", lud->lud_scheme, lud->lud_host ); + Debug( LDAP_DEBUG_ANY, "daemon: listener URL %s<junk> DN must be absent (%s)\n", + url, lud->lud_dn, 0 ); + ldap_free_urldesc( lud ); + return -1; + } + + ldap_free_urldesc( lud ); + if ( err ) { + slap_free_listener_addresses(sal); + return -1; + } + + /* If we got more than one address returned, we need to make space + * for it in the slap_listeners array. + */ + for ( num=0; sal[num]; num++ ) /* empty */; + if ( num > 1 ) { + *listeners += num-1; + slap_listeners = ch_realloc( slap_listeners, + (*listeners + 1) * sizeof(Listener *) ); + } + + psal = sal; + while ( *sal != NULL ) { + char *af; + switch( (*sal)->sa_family ) { + case AF_INET: + af = "IPv4"; + break; +#ifdef LDAP_PF_INET6 + case AF_INET6: + af = "IPv6"; + break; +#endif /* LDAP_PF_INET6 */ +#ifdef LDAP_PF_LOCAL + case AF_LOCAL: + af = "Local"; + break; +#endif /* LDAP_PF_LOCAL */ + default: + sal++; + continue; + } + +#ifdef LDAP_CONNECTIONLESS + if( l.sl_is_udp ) socktype = SOCK_DGRAM; +#endif /* LDAP_CONNECTIONLESS */ + + s = socket( (*sal)->sa_family, socktype, 0); + if ( s == AC_SOCKET_INVALID ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, + "daemon: %s socket() failed errno=%d (%s)\n", + af, err, sock_errstr(err) ); + sal++; + continue; + } + l.sl_sd = SLAP_SOCKNEW( s ); + + if ( l.sl_sd >= dtblsize ) { + Debug( LDAP_DEBUG_ANY, + "daemon: listener descriptor %ld is too great %ld\n", + (long) l.sl_sd, (long) dtblsize, 0 ); + tcp_close( s ); + sal++; + continue; + } + +#ifdef LDAP_PF_LOCAL + if ( (*sal)->sa_family == AF_LOCAL ) { + unlink( ((struct sockaddr_un *)*sal)->sun_path ); + } else +#endif /* LDAP_PF_LOCAL */ + { +#ifdef SO_REUSEADDR + /* enable address reuse */ + tmp = 1; + rc = setsockopt( s, SOL_SOCKET, SO_REUSEADDR, + (char *) &tmp, sizeof(tmp) ); + if ( rc == AC_SOCKET_ERROR ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, "slapd(%ld): " + "setsockopt(SO_REUSEADDR) failed errno=%d (%s)\n", + (long) l.sl_sd, err, sock_errstr(err) ); + } +#endif /* SO_REUSEADDR */ + } + + switch( (*sal)->sa_family ) { + case AF_INET: + addrlen = sizeof(struct sockaddr_in); + break; +#ifdef LDAP_PF_INET6 + case AF_INET6: +#ifdef IPV6_V6ONLY + /* Try to use IPv6 sockets for IPv6 only */ + tmp = 1; + rc = setsockopt( s , IPPROTO_IPV6, IPV6_V6ONLY, + (char *) &tmp, sizeof(tmp) ); + if ( rc == AC_SOCKET_ERROR ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, "slapd(%ld): " + "setsockopt(IPV6_V6ONLY) failed errno=%d (%s)\n", + (long) l.sl_sd, err, sock_errstr(err) ); + } +#endif /* IPV6_V6ONLY */ + addrlen = sizeof(struct sockaddr_in6); + break; +#endif /* LDAP_PF_INET6 */ + +#ifdef LDAP_PF_LOCAL + case AF_LOCAL: +#ifdef LOCAL_CREDS + { + int one = 1; + setsockopt( s, 0, LOCAL_CREDS, &one, sizeof( one ) ); + } +#endif /* LOCAL_CREDS */ + + addrlen = sizeof( struct sockaddr_un ); + break; +#endif /* LDAP_PF_LOCAL */ + } + +#ifdef LDAP_PF_LOCAL + /* create socket with all permissions set for those systems + * that honor permissions on sockets (e.g. Linux); typically, + * only write is required. To exploit filesystem permissions, + * place the socket in a directory and use directory's + * permissions. Need write perms to the directory to + * create/unlink the socket; likely need exec perms to access + * the socket (ITS#4709) */ + { + mode_t old_umask = 0; + + if ( (*sal)->sa_family == AF_LOCAL ) { + old_umask = umask( 0 ); + } +#endif /* LDAP_PF_LOCAL */ + rc = bind( s, *sal, addrlen ); +#ifdef LDAP_PF_LOCAL + if ( old_umask != 0 ) { + umask( old_umask ); + } + } +#endif /* LDAP_PF_LOCAL */ + if ( rc ) { + err = sock_errno(); + Debug( LDAP_DEBUG_ANY, + "daemon: bind(%ld) failed errno=%d (%s)\n", + (long)l.sl_sd, err, sock_errstr( err ) ); + tcp_close( s ); + sal++; + continue; + } + + switch ( (*sal)->sa_family ) { +#ifdef LDAP_PF_LOCAL + case AF_LOCAL: { + char *path = ((struct sockaddr_un *)*sal)->sun_path; + l.sl_name.bv_len = strlen(path) + STRLENOF("PATH="); + l.sl_name.bv_val = ber_memalloc( l.sl_name.bv_len + 1 ); + snprintf( l.sl_name.bv_val, l.sl_name.bv_len + 1, + "PATH=%s", path ); + } break; +#endif /* LDAP_PF_LOCAL */ + + case AF_INET: { + char addr[INET_ADDRSTRLEN]; + const char *s; +#if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP ) + s = inet_ntop( AF_INET, &((struct sockaddr_in *)*sal)->sin_addr, + addr, sizeof(addr) ); +#else /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + s = inet_ntoa( ((struct sockaddr_in *) *sal)->sin_addr ); +#endif /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + if (!s) s = SLAP_STRING_UNKNOWN; + port = ntohs( ((struct sockaddr_in *)*sal) ->sin_port ); + l.sl_name.bv_val = + ber_memalloc( sizeof("IP=255.255.255.255:65535") ); + snprintf( l.sl_name.bv_val, sizeof("IP=255.255.255.255:65535"), + "IP=%s:%d", s, port ); + l.sl_name.bv_len = strlen( l.sl_name.bv_val ); + } break; + +#ifdef LDAP_PF_INET6 + case AF_INET6: { + char addr[INET6_ADDRSTRLEN]; + const char *s; + s = inet_ntop( AF_INET6, &((struct sockaddr_in6 *)*sal)->sin6_addr, + addr, sizeof addr); + if (!s) s = SLAP_STRING_UNKNOWN; + port = ntohs( ((struct sockaddr_in6 *)*sal)->sin6_port ); + l.sl_name.bv_len = strlen(s) + sizeof("IP=[]:65535"); + l.sl_name.bv_val = ber_memalloc( l.sl_name.bv_len ); + snprintf( l.sl_name.bv_val, l.sl_name.bv_len, "IP=[%s]:%d", + s, port ); + l.sl_name.bv_len = strlen( l.sl_name.bv_val ); + } break; +#endif /* LDAP_PF_INET6 */ + + default: + Debug( LDAP_DEBUG_ANY, "daemon: unsupported address family (%d)\n", + (int) (*sal)->sa_family, 0, 0 ); + break; + } + + AC_MEMCPY(&l.sl_sa, *sal, addrlen); + ber_str2bv( url, 0, 1, &l.sl_url); + li = ch_malloc( sizeof( Listener ) ); + *li = l; + slap_listeners[*cur] = li; + (*cur)++; + sal++; + } + + slap_free_listener_addresses(psal); + + if ( l.sl_url.bv_val == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "slap_open_listener: failed on %s\n", url, 0, 0 ); + return -1; + } + + Debug( LDAP_DEBUG_TRACE, "daemon: listener initialized %s\n", + l.sl_url.bv_val, 0, 0 ); + return 0; +} + +static int sockinit(void); +static int sockdestroy(void); + +static int daemon_inited = 0; + +int +slapd_daemon_init( const char *urls ) +{ + int i, j, n, rc; + char **u; + + Debug( LDAP_DEBUG_ARGS, "daemon_init: %s\n", + urls ? urls : "<null>", 0, 0 ); + + for ( i=0; i<SLAPD_MAX_DAEMON_THREADS; i++ ) { + wake_sds[i][0] = AC_SOCKET_INVALID; + wake_sds[i][1] = AC_SOCKET_INVALID; + } + + ldap_pvt_thread_mutex_init( &slap_daemon[0].sd_mutex ); +#ifdef HAVE_TCPD + ldap_pvt_thread_mutex_init( &sd_tcpd_mutex ); +#endif /* TCP Wrappers */ + + daemon_inited = 1; + + if( (rc = sockinit()) != 0 ) return rc; + +#ifdef HAVE_SYSCONF + dtblsize = sysconf( _SC_OPEN_MAX ); +#elif defined(HAVE_GETDTABLESIZE) + dtblsize = getdtablesize(); +#else /* ! HAVE_SYSCONF && ! HAVE_GETDTABLESIZE */ + dtblsize = FD_SETSIZE; +#endif /* ! HAVE_SYSCONF && ! HAVE_GETDTABLESIZE */ + + SETUP_CLOSE(); + + /* open a pipe (or something equivalent connected to itself). + * we write a byte on this fd whenever we catch a signal. The main + * loop will be select'ing on this socket, and will wake up when + * this byte arrives. + */ + if( (rc = lutil_pair( wake_sds[0] )) < 0 ) { + Debug( LDAP_DEBUG_ANY, + "daemon: lutil_pair() failed rc=%d\n", rc, 0, 0 ); + return rc; + } + ber_pvt_socket_set_nonblock( wake_sds[0][1], 1 ); + + SLAP_SOCK_INIT(0); + + if( urls == NULL ) urls = "ldap:///"; + + u = ldap_str2charray( urls, " " ); + + if( u == NULL || u[0] == NULL ) { + Debug( LDAP_DEBUG_ANY, "daemon_init: no urls (%s) provided.\n", + urls, 0, 0 ); + if ( u ) + ldap_charray_free( u ); + return -1; + } + + for( i=0; u[i] != NULL; i++ ) { + Debug( LDAP_DEBUG_TRACE, "daemon_init: listen on %s\n", + u[i], 0, 0 ); + } + + if( i == 0 ) { + Debug( LDAP_DEBUG_ANY, "daemon_init: no listeners to open (%s)\n", + urls, 0, 0 ); + ldap_charray_free( u ); + return -1; + } + + Debug( LDAP_DEBUG_TRACE, "daemon_init: %d listeners to open...\n", + i, 0, 0 ); + slap_listeners = ch_malloc( (i+1)*sizeof(Listener *) ); + + for(n = 0, j = 0; u[n]; n++ ) { + if ( slap_open_listener( u[n], &i, &j ) ) { + ldap_charray_free( u ); + return -1; + } + } + slap_listeners[j] = NULL; + + Debug( LDAP_DEBUG_TRACE, "daemon_init: %d listeners opened\n", + i, 0, 0 ); + + +#ifdef HAVE_SLP + if( slapd_register_slp ) { + slapd_slp_init( urls ); + slapd_slp_reg(); + } +#endif /* HAVE_SLP */ + + ldap_charray_free( u ); + + return !i; +} + + +int +slapd_daemon_destroy( void ) +{ + connections_destroy(); + if ( daemon_inited ) { + int i; + + for ( i=0; i<slapd_daemon_threads; i++ ) { +#ifdef HAVE_WINSOCK + if ( wake_sds[i][1] != INVALID_SOCKET && + SLAP_FD2SOCK( wake_sds[i][1] ) != SLAP_FD2SOCK( wake_sds[i][0] )) +#endif /* HAVE_WINSOCK */ + tcp_close( SLAP_FD2SOCK(wake_sds[i][1]) ); +#ifdef HAVE_WINSOCK + if ( wake_sds[i][0] != INVALID_SOCKET ) +#endif /* HAVE_WINSOCK */ + tcp_close( SLAP_FD2SOCK(wake_sds[i][0]) ); + ldap_pvt_thread_mutex_destroy( &slap_daemon[i].sd_mutex ); + SLAP_SOCK_DESTROY(i); + } + daemon_inited = 0; +#ifdef HAVE_TCPD + ldap_pvt_thread_mutex_destroy( &sd_tcpd_mutex ); +#endif /* TCP Wrappers */ + } + sockdestroy(); + +#ifdef HAVE_SLP + if( slapd_register_slp ) { + slapd_slp_dereg(); + slapd_slp_deinit(); + } +#endif /* HAVE_SLP */ + + return 0; +} + + +static void +close_listeners( + int remove ) +{ + int l; + + if ( !listening ) + return; + listening = 0; + + for ( l = 0; slap_listeners[l] != NULL; l++ ) { + Listener *lr = slap_listeners[l]; + + if ( lr->sl_sd != AC_SOCKET_INVALID ) { + int s = lr->sl_sd; + lr->sl_sd = AC_SOCKET_INVALID; + if ( remove ) slapd_remove( s, NULL, 0, 0, 0 ); + +#ifdef LDAP_PF_LOCAL + if ( lr->sl_sa.sa_addr.sa_family == AF_LOCAL ) { + unlink( lr->sl_sa.sa_un_addr.sun_path ); + } +#endif /* LDAP_PF_LOCAL */ + + slapd_close( s ); + } + } +} + +static void +destroy_listeners( void ) +{ + Listener *lr, **ll = slap_listeners; + + if ( ll == NULL ) + return; + + while ( (lr = *ll++) != NULL ) { + if ( lr->sl_url.bv_val ) { + ber_memfree( lr->sl_url.bv_val ); + } + + if ( lr->sl_name.bv_val ) { + ber_memfree( lr->sl_name.bv_val ); + } + + free( lr ); + } + + free( slap_listeners ); + slap_listeners = NULL; +} + +static int +slap_listener( + Listener *sl ) +{ + Sockaddr from; + + ber_socket_t s, sfd; + ber_socklen_t len = sizeof(from); + Connection *c; + slap_ssf_t ssf = 0; + struct berval authid = BER_BVNULL; +#ifdef SLAPD_RLOOKUPS + char hbuf[NI_MAXHOST]; +#endif /* SLAPD_RLOOKUPS */ + + char *dnsname = NULL; + const char *peeraddr = NULL; + /* we assume INET6_ADDRSTRLEN > INET_ADDRSTRLEN */ + char addr[INET6_ADDRSTRLEN]; +#ifdef LDAP_PF_LOCAL + char peername[MAXPATHLEN + sizeof("PATH=")]; +#ifdef LDAP_PF_LOCAL_SENDMSG + char peerbuf[8]; + struct berval peerbv = BER_BVNULL; +#endif +#elif defined(LDAP_PF_INET6) + char peername[sizeof("IP=[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535")]; +#else /* ! LDAP_PF_LOCAL && ! LDAP_PF_INET6 */ + char peername[sizeof("IP=255.255.255.255:65336")]; +#endif /* LDAP_PF_LOCAL */ + int cflag; + int tid; + + Debug( LDAP_DEBUG_TRACE, + ">>> slap_listener(%s)\n", + sl->sl_url.bv_val, 0, 0 ); + + peername[0] = '\0'; + +#ifdef LDAP_CONNECTIONLESS + if ( sl->sl_is_udp ) return 1; +#endif /* LDAP_CONNECTIONLESS */ + +# ifdef LDAP_PF_LOCAL + /* FIXME: apparently accept doesn't fill + * the sun_path sun_path member */ + from.sa_un_addr.sun_path[0] = '\0'; +# endif /* LDAP_PF_LOCAL */ + + s = accept( SLAP_FD2SOCK( sl->sl_sd ), (struct sockaddr *) &from, &len ); + if ( s != AC_SOCKET_INVALID ) { + SET_CLOSE(s); + } + Debug( LDAP_DEBUG_CONNS, + "daemon: accept() = %ld\n", s, 0, 0 ); + + /* Resume the listener FD to allow concurrent-processing of + * additional incoming connections. + */ + sl->sl_busy = 0; + WAKE_LISTENER(DAEMON_ID(sl->sl_sd),1); + + if ( s == AC_SOCKET_INVALID ) { + int err = sock_errno(); + + if( +#ifdef EMFILE + err == EMFILE || +#endif /* EMFILE */ +#ifdef ENFILE + err == ENFILE || +#endif /* ENFILE */ + 0 ) + { + ldap_pvt_thread_mutex_lock( &slap_daemon[0].sd_mutex ); + emfile++; + /* Stop listening until an existing session closes */ + sl->sl_mute = 1; + ldap_pvt_thread_mutex_unlock( &slap_daemon[0].sd_mutex ); + } + + Debug( LDAP_DEBUG_ANY, + "daemon: accept(%ld) failed errno=%d (%s)\n", + (long) sl->sl_sd, err, sock_errstr(err) ); + ldap_pvt_thread_yield(); + return 0; + } + sfd = SLAP_SOCKNEW( s ); + + /* make sure descriptor number isn't too great */ + if ( sfd >= dtblsize ) { + Debug( LDAP_DEBUG_ANY, + "daemon: %ld beyond descriptor table size %ld\n", + (long) sfd, (long) dtblsize, 0 ); + + tcp_close(s); + ldap_pvt_thread_yield(); + return 0; + } + tid = DAEMON_ID(sfd); + +#ifdef LDAP_DEBUG + ldap_pvt_thread_mutex_lock( &slap_daemon[tid].sd_mutex ); + /* newly accepted stream should not be in any of the FD SETS */ + assert( SLAP_SOCK_NOT_ACTIVE( tid, sfd )); + ldap_pvt_thread_mutex_unlock( &slap_daemon[tid].sd_mutex ); +#endif /* LDAP_DEBUG */ + +#if defined( SO_KEEPALIVE ) || defined( TCP_NODELAY ) +#ifdef LDAP_PF_LOCAL + /* for IPv4 and IPv6 sockets only */ + if ( from.sa_addr.sa_family != AF_LOCAL ) +#endif /* LDAP_PF_LOCAL */ + { + int rc; + int tmp; +#ifdef SO_KEEPALIVE + /* enable keep alives */ + tmp = 1; + rc = setsockopt( s, SOL_SOCKET, SO_KEEPALIVE, + (char *) &tmp, sizeof(tmp) ); + if ( rc == AC_SOCKET_ERROR ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, + "slapd(%ld): setsockopt(SO_KEEPALIVE) failed " + "errno=%d (%s)\n", (long) sfd, err, sock_errstr(err) ); + slapd_close(sfd); + return 0; + } +#endif /* SO_KEEPALIVE */ +#ifdef TCP_NODELAY + /* enable no delay */ + tmp = 1; + rc = setsockopt( s, IPPROTO_TCP, TCP_NODELAY, + (char *)&tmp, sizeof(tmp) ); + if ( rc == AC_SOCKET_ERROR ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, + "slapd(%ld): setsockopt(TCP_NODELAY) failed " + "errno=%d (%s)\n", (long) sfd, err, sock_errstr(err) ); + slapd_close(sfd); + return 0; + } +#endif /* TCP_NODELAY */ + } +#endif /* SO_KEEPALIVE || TCP_NODELAY */ + + Debug( LDAP_DEBUG_CONNS, + "daemon: listen=%ld, new connection on %ld\n", + (long) sl->sl_sd, (long) sfd, 0 ); + + cflag = 0; + switch ( from.sa_addr.sa_family ) { +# ifdef LDAP_PF_LOCAL + case AF_LOCAL: + cflag |= CONN_IS_IPC; + + /* FIXME: apparently accept doesn't fill + * the sun_path sun_path member */ + if ( from.sa_un_addr.sun_path[0] == '\0' ) { + AC_MEMCPY( from.sa_un_addr.sun_path, + sl->sl_sa.sa_un_addr.sun_path, + sizeof( from.sa_un_addr.sun_path ) ); + } + + sprintf( peername, "PATH=%s", from.sa_un_addr.sun_path ); + ssf = local_ssf; + { + uid_t uid; + gid_t gid; + +#ifdef LDAP_PF_LOCAL_SENDMSG + peerbv.bv_val = peerbuf; + peerbv.bv_len = sizeof( peerbuf ); +#endif + if( LUTIL_GETPEEREID( s, &uid, &gid, &peerbv ) == 0 ) { + authid.bv_val = ch_malloc( + STRLENOF( "gidNumber=4294967295+uidNumber=4294967295," + "cn=peercred,cn=external,cn=auth" ) + 1 ); + authid.bv_len = sprintf( authid.bv_val, + "gidNumber=%d+uidNumber=%d," + "cn=peercred,cn=external,cn=auth", + (int) gid, (int) uid ); + assert( authid.bv_len <= + STRLENOF( "gidNumber=4294967295+uidNumber=4294967295," + "cn=peercred,cn=external,cn=auth" ) ); + } + } + dnsname = "local"; + break; +#endif /* LDAP_PF_LOCAL */ + +# ifdef LDAP_PF_INET6 + case AF_INET6: + if ( IN6_IS_ADDR_V4MAPPED(&from.sa_in6_addr.sin6_addr) ) { +#if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP ) + peeraddr = inet_ntop( AF_INET, + ((struct in_addr *)&from.sa_in6_addr.sin6_addr.s6_addr[12]), + addr, sizeof(addr) ); +#else /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + peeraddr = inet_ntoa( *((struct in_addr *) + &from.sa_in6_addr.sin6_addr.s6_addr[12]) ); +#endif /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + if ( !peeraddr ) peeraddr = SLAP_STRING_UNKNOWN; + sprintf( peername, "IP=%s:%d", peeraddr, + (unsigned) ntohs( from.sa_in6_addr.sin6_port ) ); + } else { + peeraddr = inet_ntop( AF_INET6, + &from.sa_in6_addr.sin6_addr, + addr, sizeof addr ); + if ( !peeraddr ) peeraddr = SLAP_STRING_UNKNOWN; + sprintf( peername, "IP=[%s]:%d", peeraddr, + (unsigned) ntohs( from.sa_in6_addr.sin6_port ) ); + } + break; +# endif /* LDAP_PF_INET6 */ + + case AF_INET: { +#if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP ) + peeraddr = inet_ntop( AF_INET, &from.sa_in_addr.sin_addr, + addr, sizeof(addr) ); +#else /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + peeraddr = inet_ntoa( from.sa_in_addr.sin_addr ); +#endif /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + if ( !peeraddr ) peeraddr = SLAP_STRING_UNKNOWN; + sprintf( peername, "IP=%s:%d", peeraddr, + (unsigned) ntohs( from.sa_in_addr.sin_port ) ); + } break; + + default: + slapd_close(sfd); + return 0; + } + + if ( ( from.sa_addr.sa_family == AF_INET ) +#ifdef LDAP_PF_INET6 + || ( from.sa_addr.sa_family == AF_INET6 ) +#endif /* LDAP_PF_INET6 */ + ) + { + dnsname = NULL; +#ifdef SLAPD_RLOOKUPS + if ( use_reverse_lookup ) { + char *herr; + if (ldap_pvt_get_hname( (const struct sockaddr *)&from, len, hbuf, + sizeof(hbuf), &herr ) == 0) { + ldap_pvt_str2lower( hbuf ); + dnsname = hbuf; + } + } +#endif /* SLAPD_RLOOKUPS */ + +#ifdef HAVE_TCPD + { + int rc; + ldap_pvt_thread_mutex_lock( &sd_tcpd_mutex ); + rc = hosts_ctl("slapd", + dnsname != NULL ? dnsname : SLAP_STRING_UNKNOWN, + peeraddr, + SLAP_STRING_UNKNOWN ); + ldap_pvt_thread_mutex_unlock( &sd_tcpd_mutex ); + if ( !rc ) { + /* DENY ACCESS */ + Statslog( LDAP_DEBUG_STATS, + "fd=%ld DENIED from %s (%s)\n", + (long) sfd, + dnsname != NULL ? dnsname : SLAP_STRING_UNKNOWN, + peeraddr, 0, 0 ); + slapd_close(sfd); + return 0; + } + } +#endif /* HAVE_TCPD */ + } + +#ifdef HAVE_TLS + if ( sl->sl_is_tls ) cflag |= CONN_IS_TLS; +#endif + c = connection_init(sfd, sl, + dnsname != NULL ? dnsname : SLAP_STRING_UNKNOWN, + peername, cflag, ssf, + authid.bv_val ? &authid : NULL + LDAP_PF_LOCAL_SENDMSG_ARG(&peerbv)); + + if( authid.bv_val ) ch_free(authid.bv_val); + + if( !c ) { + Debug( LDAP_DEBUG_ANY, + "daemon: connection_init(%ld, %s, %s) failed.\n", + (long) sfd, peername, sl->sl_name.bv_val ); + slapd_close(sfd); + } + + return 0; +} + +static void* +slap_listener_thread( + void* ctx, + void* ptr ) +{ + int rc; + Listener *sl = (Listener *)ptr; + + rc = slap_listener( sl ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "slap_listener_thread(%s): failed err=%d", + sl->sl_url.bv_val, rc, 0 ); + } + + return (void*)NULL; +} + +static int +slap_listener_activate( + Listener* sl ) +{ + int rc; + + Debug( LDAP_DEBUG_TRACE, "slap_listener_activate(%d): %s\n", + sl->sl_sd, sl->sl_busy ? "busy" : "", 0 ); + + sl->sl_busy = 1; + + rc = ldap_pvt_thread_pool_submit( &connection_pool, + slap_listener_thread, (void *) sl ); + + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "slap_listener_activate(%d): submit failed (%d)\n", + sl->sl_sd, rc, 0 ); + } + return rc; +} + +static void * +slapd_daemon_task( + void *ptr ) +{ + int l; + time_t last_idle_check = 0; + int ebadf = 0; + int tid = (ldap_pvt_thread_t *) ptr - listener_tid; + +#define SLAPD_IDLE_CHECK_LIMIT 4 + + slapd_add( wake_sds[tid][0], 0, NULL, tid ); + if ( tid ) + goto loop; + + /* Init stuff done only by thread 0 */ + + last_idle_check = slap_get_time(); + + for ( l = 0; slap_listeners[l] != NULL; l++ ) { + if ( slap_listeners[l]->sl_sd == AC_SOCKET_INVALID ) continue; + +#ifdef LDAP_CONNECTIONLESS + /* Since this is connectionless, the data port is the + * listening port. The listen() and accept() calls + * are unnecessary. + */ + if ( slap_listeners[l]->sl_is_udp ) + continue; +#endif /* LDAP_CONNECTIONLESS */ + + /* FIXME: TCP-only! */ +#ifdef LDAP_TCP_BUFFER + if ( 1 ) { + int origsize, size, realsize, rc; + socklen_t optlen; + char buf[ SLAP_TEXT_BUFLEN ]; + + size = 0; + if ( slap_listeners[l]->sl_tcp_rmem > 0 ) { + size = slap_listeners[l]->sl_tcp_rmem; + } else if ( slapd_tcp_rmem > 0 ) { + size = slapd_tcp_rmem; + } + + if ( size > 0 ) { + optlen = sizeof( origsize ); + rc = getsockopt( SLAP_FD2SOCK( slap_listeners[l]->sl_sd ), + SOL_SOCKET, + SO_RCVBUF, + (void *)&origsize, + &optlen ); + + if ( rc ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, + "slapd_daemon_task: getsockopt(SO_RCVBUF) failed errno=%d (%s)\n", + err, sock_errstr(err), 0 ); + } + + optlen = sizeof( size ); + rc = setsockopt( SLAP_FD2SOCK( slap_listeners[l]->sl_sd ), + SOL_SOCKET, + SO_RCVBUF, + (const void *)&size, + optlen ); + + if ( rc ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, + "slapd_daemon_task: setsockopt(SO_RCVBUF) failed errno=%d (%s)\n", + err, sock_errstr(err), 0 ); + } + + optlen = sizeof( realsize ); + rc = getsockopt( SLAP_FD2SOCK( slap_listeners[l]->sl_sd ), + SOL_SOCKET, + SO_RCVBUF, + (void *)&realsize, + &optlen ); + + if ( rc ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, + "slapd_daemon_task: getsockopt(SO_RCVBUF) failed errno=%d (%s)\n", + err, sock_errstr(err), 0 ); + } + + snprintf( buf, sizeof( buf ), + "url=%s (#%d) RCVBUF original size=%d requested size=%d real size=%d", + slap_listeners[l]->sl_url.bv_val, l, origsize, size, realsize ); + Debug( LDAP_DEBUG_ANY, + "slapd_daemon_task: %s\n", + buf, 0, 0 ); + } + + size = 0; + if ( slap_listeners[l]->sl_tcp_wmem > 0 ) { + size = slap_listeners[l]->sl_tcp_wmem; + } else if ( slapd_tcp_wmem > 0 ) { + size = slapd_tcp_wmem; + } + + if ( size > 0 ) { + optlen = sizeof( origsize ); + rc = getsockopt( SLAP_FD2SOCK( slap_listeners[l]->sl_sd ), + SOL_SOCKET, + SO_SNDBUF, + (void *)&origsize, + &optlen ); + + if ( rc ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, + "slapd_daemon_task: getsockopt(SO_SNDBUF) failed errno=%d (%s)\n", + err, sock_errstr(err), 0 ); + } + + optlen = sizeof( size ); + rc = setsockopt( SLAP_FD2SOCK( slap_listeners[l]->sl_sd ), + SOL_SOCKET, + SO_SNDBUF, + (const void *)&size, + optlen ); + + if ( rc ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, + "slapd_daemon_task: setsockopt(SO_SNDBUF) failed errno=%d (%s)", + err, sock_errstr(err), 0 ); + } + + optlen = sizeof( realsize ); + rc = getsockopt( SLAP_FD2SOCK( slap_listeners[l]->sl_sd ), + SOL_SOCKET, + SO_SNDBUF, + (void *)&realsize, + &optlen ); + + if ( rc ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, + "slapd_daemon_task: getsockopt(SO_SNDBUF) failed errno=%d (%s)\n", + err, sock_errstr(err), 0 ); + } + + snprintf( buf, sizeof( buf ), + "url=%s (#%d) SNDBUF original size=%d requested size=%d real size=%d", + slap_listeners[l]->sl_url.bv_val, l, origsize, size, realsize ); + Debug( LDAP_DEBUG_ANY, + "slapd_daemon_task: %s\n", + buf, 0, 0 ); + } + } +#endif /* LDAP_TCP_BUFFER */ + + if ( listen( SLAP_FD2SOCK( slap_listeners[l]->sl_sd ), SLAPD_LISTEN_BACKLOG ) == -1 ) { + int err = sock_errno(); + +#ifdef LDAP_PF_INET6 + /* If error is EADDRINUSE, we are trying to listen to INADDR_ANY and + * we are already listening to in6addr_any, then we want to ignore + * this and continue. + */ + if ( err == EADDRINUSE ) { + int i; + struct sockaddr_in sa = slap_listeners[l]->sl_sa.sa_in_addr; + struct sockaddr_in6 sa6; + + if ( sa.sin_family == AF_INET && + sa.sin_addr.s_addr == htonl(INADDR_ANY) ) { + for ( i = 0 ; i < l; i++ ) { + sa6 = slap_listeners[i]->sl_sa.sa_in6_addr; + if ( sa6.sin6_family == AF_INET6 && + !memcmp( &sa6.sin6_addr, &in6addr_any, + sizeof(struct in6_addr) ) ) + { + break; + } + } + + if ( i < l ) { + /* We are already listening to in6addr_any */ + Debug( LDAP_DEBUG_CONNS, + "daemon: Attempt to listen to 0.0.0.0 failed, " + "already listening on ::, assuming IPv4 included\n", + 0, 0, 0 ); + slapd_close( slap_listeners[l]->sl_sd ); + slap_listeners[l]->sl_sd = AC_SOCKET_INVALID; + continue; + } + } + } +#endif /* LDAP_PF_INET6 */ + Debug( LDAP_DEBUG_ANY, + "daemon: listen(%s, 5) failed errno=%d (%s)\n", + slap_listeners[l]->sl_url.bv_val, err, + sock_errstr(err) ); + return (void*)-1; + } + + /* make the listening socket non-blocking */ + if ( ber_pvt_socket_set_nonblock( SLAP_FD2SOCK( slap_listeners[l]->sl_sd ), 1 ) < 0 ) { + Debug( LDAP_DEBUG_ANY, "slapd_daemon_task: " + "set nonblocking on a listening socket failed\n", + 0, 0, 0 ); + slapd_shutdown = 2; + return (void*)-1; + } + + slapd_add( slap_listeners[l]->sl_sd, 0, slap_listeners[l], -1 ); + } + +#ifdef HAVE_NT_SERVICE_MANAGER + if ( started_event != NULL ) { + ldap_pvt_thread_cond_signal( &started_event ); + } +#endif /* HAVE_NT_SERVICE_MANAGER */ + +loop: + + /* initialization complete. Here comes the loop. */ + + while ( !slapd_shutdown ) { + ber_socket_t i; + int ns, nwriters; + int at; + ber_socket_t nfds; +#if SLAP_EVENTS_ARE_INDEXED + ber_socket_t nrfds, nwfds; +#endif /* SLAP_EVENTS_ARE_INDEXED */ +#define SLAPD_EBADF_LIMIT 16 + + time_t now; + + SLAP_EVENT_DECL; + + struct timeval tv; + struct timeval *tvp; + + struct timeval cat; + time_t tdelta = 1; + struct re_s* rtask; + + now = slap_get_time(); + + if ( !tid && ( global_idletimeout > 0 || chk_writetime )) { + int check = 0; + /* Set the select timeout. + * Don't just truncate, preserve the fractions of + * seconds to prevent sleeping for zero time. + */ + if ( chk_writetime ) { + tv.tv_sec = global_writetimeout; + tv.tv_usec = 0; + if ( difftime( chk_writetime, now ) < 0 ) + check = 2; + } else { + tv.tv_sec = global_idletimeout / SLAPD_IDLE_CHECK_LIMIT; + tv.tv_usec = global_idletimeout - \ + ( tv.tv_sec * SLAPD_IDLE_CHECK_LIMIT ); + tv.tv_usec *= 1000000 / SLAPD_IDLE_CHECK_LIMIT; + if ( difftime( last_idle_check + + global_idletimeout/SLAPD_IDLE_CHECK_LIMIT, now ) < 0 ) + check = 1; + } + if ( check ) { + connections_timeout_idle( now ); + last_idle_check = now; + } + } else { + tv.tv_sec = 0; + tv.tv_usec = 0; + } + +#ifdef SIGHUP + if ( slapd_gentle_shutdown ) { + ber_socket_t active; + + if ( !tid && slapd_gentle_shutdown == 1 ) { + BackendDB *be; + Debug( LDAP_DEBUG_ANY, "slapd gentle shutdown\n", 0, 0, 0 ); + close_listeners( 1 ); + frontendDB->be_restrictops |= SLAP_RESTRICT_OP_WRITES; + LDAP_STAILQ_FOREACH(be, &backendDB, be_next) { + be->be_restrictops |= SLAP_RESTRICT_OP_WRITES; + } + slapd_gentle_shutdown = 2; + } + + ldap_pvt_thread_mutex_lock( &slap_daemon[tid].sd_mutex ); + active = slap_daemon[tid].sd_nactives; + ldap_pvt_thread_mutex_unlock( &slap_daemon[tid].sd_mutex ); + + if ( active == 0 ) { + if ( !tid ) { + for ( l=1; l<slapd_daemon_threads; l++ ) { + ldap_pvt_thread_mutex_lock( &slap_daemon[l].sd_mutex ); + active += slap_daemon[l].sd_nactives; + ldap_pvt_thread_mutex_unlock( &slap_daemon[l].sd_mutex ); + } + if ( !active ) + slapd_shutdown = 1; + } + if ( !active ) + break; + } + } +#endif /* SIGHUP */ + at = 0; + + ldap_pvt_thread_mutex_lock( &slap_daemon[tid].sd_mutex ); + + nwriters = slap_daemon[tid].sd_nwriters; + + if ( listening ) + for ( l = 0; slap_listeners[l] != NULL; l++ ) { + Listener *lr = slap_listeners[l]; + + if ( lr->sl_sd == AC_SOCKET_INVALID ) continue; + if ( DAEMON_ID( lr->sl_sd ) != tid ) continue; + + if ( lr->sl_mute || lr->sl_busy ) + { + SLAP_SOCK_CLR_READ( tid, lr->sl_sd ); + } else { + SLAP_SOCK_SET_READ( tid, lr->sl_sd ); + } + } + + SLAP_EVENT_INIT(tid); + + nfds = SLAP_EVENT_MAX(tid); + + if (( chk_writetime || global_idletimeout ) && slap_daemon[tid].sd_nactives ) at = 1; + + ldap_pvt_thread_mutex_unlock( &slap_daemon[tid].sd_mutex ); + + if ( at +#if defined(HAVE_YIELDING_SELECT) || defined(NO_THREADS) + && ( tv.tv_sec || tv.tv_usec ) +#endif /* HAVE_YIELDING_SELECT || NO_THREADS */ + ) + { + tvp = &tv; + } else { + tvp = NULL; + } + + /* Only thread 0 handles runqueue */ + if ( !tid ) { + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + rtask = ldap_pvt_runqueue_next_sched( &slapd_rq, &cat ); + while ( rtask && cat.tv_sec && cat.tv_sec <= now ) { + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, rtask )) { + ldap_pvt_runqueue_resched( &slapd_rq, rtask, 0 ); + } else { + ldap_pvt_runqueue_runtask( &slapd_rq, rtask ); + ldap_pvt_runqueue_resched( &slapd_rq, rtask, 0 ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + ldap_pvt_thread_pool_submit( &connection_pool, + rtask->routine, (void *) rtask ); + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + } + rtask = ldap_pvt_runqueue_next_sched( &slapd_rq, &cat ); + } + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + if ( rtask && cat.tv_sec ) { + /* NOTE: diff __should__ always be >= 0, + * AFAI understand; however (ITS#4872), + * time_t might be unsigned in some systems, + * while difftime() returns a double */ + double diff = difftime( cat.tv_sec, now ); + if ( diff <= 0 ) { + diff = tdelta; + } + if ( tvp == NULL || diff < tv.tv_sec ) { + tv.tv_sec = diff; + tv.tv_usec = 0; + tvp = &tv; + } + } + } + + for ( l = 0; slap_listeners[l] != NULL; l++ ) { + Listener *lr = slap_listeners[l]; + + if ( lr->sl_sd == AC_SOCKET_INVALID ) { + continue; + } + + if ( lr->sl_mute ) { + Debug( LDAP_DEBUG_CONNS, + "daemon: " SLAP_EVENT_FNAME ": " + "listen=%d muted\n", + lr->sl_sd, 0, 0 ); + continue; + } + + if ( lr->sl_busy ) { + Debug( LDAP_DEBUG_CONNS, + "daemon: " SLAP_EVENT_FNAME ": " + "listen=%d busy\n", + lr->sl_sd, 0, 0 ); + continue; + } + + Debug( LDAP_DEBUG_CONNS, + "daemon: " SLAP_EVENT_FNAME ": " + "listen=%d active_threads=%d tvp=%s\n", + lr->sl_sd, at, tvp == NULL ? "NULL" : "zero" ); + } + + SLAP_EVENT_WAIT( tid, tvp, &ns ); + switch ( ns ) { + case -1: { /* failure - try again */ + int err = sock_errno(); + + if ( err != EINTR ) { + ebadf++; + + /* Don't log unless we got it twice in a row */ + if ( !( ebadf & 1 ) ) { + Debug( LDAP_DEBUG_ANY, + "daemon: " + SLAP_EVENT_FNAME + " failed count %d " + "err (%d): %s\n", + ebadf, err, + sock_errstr( err ) ); + } + if ( ebadf >= SLAPD_EBADF_LIMIT ) { + slapd_shutdown = 2; + } + } + } + continue; + + case 0: /* timeout - let threads run */ + ebadf = 0; +#ifndef HAVE_YIELDING_SELECT + Debug( LDAP_DEBUG_CONNS, "daemon: " SLAP_EVENT_FNAME + "timeout - yielding\n", + 0, 0, 0 ); + + ldap_pvt_thread_yield(); +#endif /* ! HAVE_YIELDING_SELECT */ + continue; + + default: /* something happened - deal with it */ + if ( slapd_shutdown ) continue; + + ebadf = 0; + Debug( LDAP_DEBUG_CONNS, + "daemon: activity on %d descriptor%s\n", + ns, ns != 1 ? "s" : "", 0 ); + /* FALL THRU */ + } + +#if SLAP_EVENTS_ARE_INDEXED + if ( SLAP_EVENT_IS_READ( wake_sds[tid][0] ) ) { + char c[BUFSIZ]; + SLAP_EVENT_CLR_READ( wake_sds[tid][0] ); + waking = 0; + tcp_read( SLAP_FD2SOCK(wake_sds[tid][0]), c, sizeof(c) ); + Debug( LDAP_DEBUG_CONNS, "daemon: waked\n", 0, 0, 0 ); + continue; + } + + /* The event slot equals the descriptor number - this is + * true for Unix select and poll. We treat Windows select + * like this too, even though it's a kludge. + */ + if ( listening ) + for ( l = 0; slap_listeners[l] != NULL; l++ ) { + int rc; + + if ( ns <= 0 ) break; + if ( slap_listeners[l]->sl_sd == AC_SOCKET_INVALID ) continue; +#ifdef LDAP_CONNECTIONLESS + if ( slap_listeners[l]->sl_is_udp ) continue; +#endif /* LDAP_CONNECTIONLESS */ + if ( !SLAP_EVENT_IS_READ( slap_listeners[l]->sl_sd ) ) continue; + + /* clear events */ + SLAP_EVENT_CLR_READ( slap_listeners[l]->sl_sd ); + SLAP_EVENT_CLR_WRITE( slap_listeners[l]->sl_sd ); + ns--; + + rc = slap_listener_activate( slap_listeners[l] ); + } + + /* bypass the following tests if no descriptors left */ + if ( ns <= 0 ) { +#ifndef HAVE_YIELDING_SELECT + ldap_pvt_thread_yield(); +#endif /* HAVE_YIELDING_SELECT */ + continue; + } + + Debug( LDAP_DEBUG_CONNS, "daemon: activity on:", 0, 0, 0 ); + nrfds = 0; + nwfds = 0; + for ( i = 0; i < nfds; i++ ) { + int r, w; + + r = SLAP_EVENT_IS_READ( i ); + /* writefds was not initialized if nwriters was zero */ + w = nwriters ? SLAP_EVENT_IS_WRITE( i ) : 0; + if ( r || w ) { + Debug( LDAP_DEBUG_CONNS, " %d%s%s", i, + r ? "r" : "", w ? "w" : "" ); + if ( r ) { + nrfds++; + ns--; + } + if ( w ) { + nwfds++; + ns--; + } + } + if ( ns <= 0 ) break; + } + Debug( LDAP_DEBUG_CONNS, "\n", 0, 0, 0 ); + + /* loop through the writers */ + for ( i = 0; nwfds > 0; i++ ) { + ber_socket_t wd; + if ( ! SLAP_EVENT_IS_WRITE( i ) ) continue; + wd = i; + + SLAP_EVENT_CLR_WRITE( wd ); + nwfds--; + + Debug( LDAP_DEBUG_CONNS, + "daemon: write active on %d\n", + wd, 0, 0 ); + + /* + * NOTE: it is possible that the connection was closed + * and that the stream is now inactive. + * connection_write() must validate the stream is still + * active. + * + * ITS#4338: if the stream is invalid, there is no need to + * close it here. It has already been closed in connection.c. + */ + if ( connection_write( wd ) < 0 ) { + if ( SLAP_EVENT_IS_READ( wd ) ) { + SLAP_EVENT_CLR_READ( (unsigned) wd ); + nrfds--; + } + } + } + + for ( i = 0; nrfds > 0; i++ ) { + ber_socket_t rd; + if ( ! SLAP_EVENT_IS_READ( i ) ) continue; + rd = i; + SLAP_EVENT_CLR_READ( rd ); + nrfds--; + + Debug ( LDAP_DEBUG_CONNS, + "daemon: read activity on %d\n", rd, 0, 0 ); + /* + * NOTE: it is possible that the connection was closed + * and that the stream is now inactive. + * connection_read() must valid the stream is still + * active. + */ + + connection_read_activate( rd ); + } +#else /* !SLAP_EVENTS_ARE_INDEXED */ + /* FIXME */ + /* The events are returned in an arbitrary list. This is true + * for /dev/poll, epoll and kqueue. In order to prioritize things + * so that we can handle wake_sds first, listeners second, and then + * all other connections last (as we do for select), we would need + * to use multiple event handles and cascade them. + * + * That seems like a bit of hassle. So the wake_sds check has been + * skipped. For epoll and kqueue we can associate arbitrary data with + * an event, so we could use pointers to the listener structure + * instead of just the file descriptor. For /dev/poll we have to + * search the listeners array for a matching descriptor. + * + * We now handle wake events when we see them; they are not given + * higher priority. + */ +#ifdef LDAP_DEBUG + Debug( LDAP_DEBUG_CONNS, "daemon: activity on:", 0, 0, 0 ); + + for ( i = 0; i < ns; i++ ) { + int r, w, fd; + + /* Don't log listener events */ + if ( SLAP_EVENT_IS_LISTENER( tid, i ) +#ifdef LDAP_CONNECTIONLESS + && !( (SLAP_EVENT_LISTENER( tid, i ))->sl_is_udp ) +#endif /* LDAP_CONNECTIONLESS */ + ) + { + continue; + } + + fd = SLAP_EVENT_FD( tid, i ); + /* Don't log internal wake events */ + if ( fd == wake_sds[tid][0] ) continue; + + r = SLAP_EVENT_IS_READ( i ); + w = SLAP_EVENT_IS_WRITE( i ); + if ( r || w ) { + Debug( LDAP_DEBUG_CONNS, " %d%s%s", fd, + r ? "r" : "", w ? "w" : "" ); + } + } + Debug( LDAP_DEBUG_CONNS, "\n", 0, 0, 0 ); +#endif /* LDAP_DEBUG */ + + for ( i = 0; i < ns; i++ ) { + int rc = 1, fd, w = 0, r = 0; + + if ( SLAP_EVENT_IS_LISTENER( tid, i ) ) { + rc = slap_listener_activate( SLAP_EVENT_LISTENER( tid, i ) ); + } + + /* If we found a regular listener, rc is now zero, and we + * can skip the data portion. But if it was a UDP listener + * then rc is still 1, and we want to handle the data. + */ + if ( rc ) { + fd = SLAP_EVENT_FD( tid, i ); + + /* Handle wake events */ + if ( fd == wake_sds[tid][0] ) { + char c[BUFSIZ]; + waking = 0; + tcp_read( SLAP_FD2SOCK(wake_sds[tid][0]), c, sizeof(c) ); + continue; + } + + if ( SLAP_EVENT_IS_WRITE( i ) ) { + Debug( LDAP_DEBUG_CONNS, + "daemon: write active on %d\n", + fd, 0, 0 ); + + SLAP_EVENT_CLR_WRITE( i ); + w = 1; + + /* + * NOTE: it is possible that the connection was closed + * and that the stream is now inactive. + * connection_write() must valid the stream is still + * active. + */ + if ( connection_write( fd ) < 0 ) { + continue; + } + } + /* If event is a read */ + if ( SLAP_EVENT_IS_READ( i )) { + r = 1; + Debug( LDAP_DEBUG_CONNS, + "daemon: read active on %d\n", + fd, 0, 0 ); + + SLAP_EVENT_CLR_READ( i ); + connection_read_activate( fd ); + } else if ( !w ) { +#ifdef HAVE_EPOLL + /* Don't keep reporting the hangup + */ + if ( SLAP_SOCK_IS_ACTIVE( tid, fd )) { + SLAP_EPOLL_SOCK_SET( tid, fd, EPOLLET ); + } +#endif + } + } + } +#endif /* SLAP_EVENTS_ARE_INDEXED */ + +#ifndef HAVE_YIELDING_SELECT + ldap_pvt_thread_yield(); +#endif /* ! HAVE_YIELDING_SELECT */ + } + + /* Only thread 0 handles shutdown */ + if ( tid ) + return NULL; + + if ( slapd_shutdown == 1 ) { + Debug( LDAP_DEBUG_ANY, + "daemon: shutdown requested and initiated.\n", + 0, 0, 0 ); + + } else if ( slapd_shutdown == 2 ) { +#ifdef HAVE_NT_SERVICE_MANAGER + Debug( LDAP_DEBUG_ANY, + "daemon: shutdown initiated by Service Manager.\n", + 0, 0, 0); +#else /* !HAVE_NT_SERVICE_MANAGER */ + Debug( LDAP_DEBUG_ANY, + "daemon: abnormal condition, shutdown initiated.\n", + 0, 0, 0 ); +#endif /* !HAVE_NT_SERVICE_MANAGER */ + } else { + Debug( LDAP_DEBUG_ANY, + "daemon: no active streams, shutdown initiated.\n", + 0, 0, 0 ); + } + + close_listeners( 0 ); + + if ( !slapd_gentle_shutdown ) { + slapd_abrupt_shutdown = 1; + connections_shutdown(); + } + + if ( LogTest( LDAP_DEBUG_ANY )) { + int t = ldap_pvt_thread_pool_backload( &connection_pool ); + Debug( LDAP_DEBUG_ANY, + "slapd shutdown: waiting for %d operations/tasks to finish\n", + t, 0, 0 ); + } + ldap_pvt_thread_pool_destroy( &connection_pool, 1 ); + + return NULL; +} + + +#ifdef LDAP_CONNECTIONLESS +static int +connectionless_init( void ) +{ + int l; + + for ( l = 0; slap_listeners[l] != NULL; l++ ) { + Listener *lr = slap_listeners[l]; + Connection *c; + + if ( !lr->sl_is_udp ) { + continue; + } + + c = connection_init( lr->sl_sd, lr, "", "", + CONN_IS_UDP, (slap_ssf_t) 0, NULL + LDAP_PF_LOCAL_SENDMSG_ARG(NULL)); + + if ( !c ) { + Debug( LDAP_DEBUG_TRACE, + "connectionless_init: failed on %s (%d)\n", + lr->sl_url.bv_val, lr->sl_sd, 0 ); + return -1; + } + lr->sl_is_udp++; + } + + return 0; +} +#endif /* LDAP_CONNECTIONLESS */ + +int +slapd_daemon( void ) +{ + int i, rc; + +#ifdef LDAP_CONNECTIONLESS + connectionless_init(); +#endif /* LDAP_CONNECTIONLESS */ + + if ( slapd_daemon_threads > SLAPD_MAX_DAEMON_THREADS ) + slapd_daemon_threads = SLAPD_MAX_DAEMON_THREADS; + + listener_tid = ch_malloc(slapd_daemon_threads * sizeof(ldap_pvt_thread_t)); + + /* daemon_init only inits element 0 */ + for ( i=1; i<slapd_daemon_threads; i++ ) + { + ldap_pvt_thread_mutex_init( &slap_daemon[i].sd_mutex ); + + if( (rc = lutil_pair( wake_sds[i] )) < 0 ) { + Debug( LDAP_DEBUG_ANY, + "daemon: lutil_pair() failed rc=%d\n", rc, 0, 0 ); + return rc; + } + ber_pvt_socket_set_nonblock( wake_sds[i][1], 1 ); + + SLAP_SOCK_INIT(i); + } + + for ( i=0; i<slapd_daemon_threads; i++ ) + { + /* listener as a separate THREAD */ + rc = ldap_pvt_thread_create( &listener_tid[i], + 0, slapd_daemon_task, &listener_tid[i] ); + + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "listener ldap_pvt_thread_create failed (%d)\n", rc, 0, 0 ); + return rc; + } + } + + /* wait for the listener threads to complete */ + for ( i=0; i<slapd_daemon_threads; i++ ) + ldap_pvt_thread_join( listener_tid[i], (void *)NULL ); + + destroy_listeners(); + ch_free( listener_tid ); + listener_tid = NULL; + + return 0; +} + +static int +sockinit( void ) +{ +#if defined( HAVE_WINSOCK2 ) + WORD wVersionRequested; + WSADATA wsaData; + int err; + + wVersionRequested = MAKEWORD( 2, 0 ); + + err = WSAStartup( wVersionRequested, &wsaData ); + if ( err != 0 ) { + /* Tell the user that we couldn't find a usable */ + /* WinSock DLL. */ + return -1; + } + + /* Confirm that the WinSock DLL supports 2.0.*/ + /* Note that if the DLL supports versions greater */ + /* than 2.0 in addition to 2.0, it will still return */ + /* 2.0 in wVersion since that is the version we */ + /* requested. */ + + if ( LOBYTE( wsaData.wVersion ) != 2 || + HIBYTE( wsaData.wVersion ) != 0 ) + { + /* Tell the user that we couldn't find a usable */ + /* WinSock DLL. */ + WSACleanup(); + return -1; + } + + /* The WinSock DLL is acceptable. Proceed. */ +#elif defined( HAVE_WINSOCK ) + WSADATA wsaData; + if ( WSAStartup( 0x0101, &wsaData ) != 0 ) return -1; +#endif /* ! HAVE_WINSOCK2 && ! HAVE_WINSOCK */ + + return 0; +} + +static int +sockdestroy( void ) +{ +#if defined( HAVE_WINSOCK2 ) || defined( HAVE_WINSOCK ) + WSACleanup(); +#endif /* HAVE_WINSOCK2 || HAVE_WINSOCK */ + + return 0; +} + +RETSIGTYPE +slap_sig_shutdown( int sig ) +{ + int save_errno = errno; + int i; + +#if 0 + Debug(LDAP_DEBUG_TRACE, "slap_sig_shutdown: signal %d\n", sig, 0, 0); +#endif + + /* + * If the NT Service Manager is controlling the server, we don't + * want SIGBREAK to kill the server. For some strange reason, + * SIGBREAK is generated when a user logs out. + */ + +#if defined(HAVE_NT_SERVICE_MANAGER) && defined(SIGBREAK) + if (is_NT_Service && sig == SIGBREAK) { + /* empty */; + } else +#endif /* HAVE_NT_SERVICE_MANAGER && SIGBREAK */ +#ifdef SIGHUP + if (sig == SIGHUP && global_gentlehup && slapd_gentle_shutdown == 0) { + slapd_gentle_shutdown = 1; + } else +#endif /* SIGHUP */ + { + slapd_shutdown = 1; + } + + for (i=0; i<slapd_daemon_threads; i++) { + WAKE_LISTENER(i,1); + } + + /* reinstall self */ + (void) SIGNAL_REINSTALL( sig, slap_sig_shutdown ); + + errno = save_errno; +} + +RETSIGTYPE +slap_sig_wake( int sig ) +{ + int save_errno = errno; + + WAKE_LISTENER(0,1); + + /* reinstall self */ + (void) SIGNAL_REINSTALL( sig, slap_sig_wake ); + + errno = save_errno; +} + + +void +slapd_add_internal( ber_socket_t s, int isactive ) +{ + if (!isactive) { + SET_CLOSE(s); + } + slapd_add( s, isactive, NULL, -1 ); +} + +Listener ** +slapd_get_listeners( void ) +{ + /* Could return array with no listeners if !listening, but current + * callers mostly look at the URLs. E.g. syncrepl uses this to + * identify the server, which means it wants the startup arguments. + */ + return slap_listeners; +} + +/* Reject all incoming requests */ +void +slap_suspend_listeners( void ) +{ + int i; + for (i=0; slap_listeners[i]; i++) { + slap_listeners[i]->sl_mute = 1; + listen( slap_listeners[i]->sl_sd, 0 ); + } +} + +/* Resume after a suspend */ +void +slap_resume_listeners( void ) +{ + int i; + for (i=0; slap_listeners[i]; i++) { + slap_listeners[i]->sl_mute = 0; + listen( slap_listeners[i]->sl_sd, SLAPD_LISTEN_BACKLOG ); + } +} + +void +slap_wake_listener() +{ + WAKE_LISTENER(0,1); +} diff --git a/servers/slapd/delete.c b/servers/slapd/delete.c new file mode 100644 index 0000000..1c0d8ac --- /dev/null +++ b/servers/slapd/delete.c @@ -0,0 +1,237 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" + +#include "lutil.h" + +int +do_delete( + Operation *op, + SlapReply *rs ) +{ + struct berval dn = BER_BVNULL; + + Debug( LDAP_DEBUG_TRACE, "%s do_delete\n", + op->o_log_prefix, 0, 0 ); + /* + * Parse the delete request. It looks like this: + * + * DelRequest := DistinguishedName + */ + + if ( ber_scanf( op->o_ber, "m", &dn ) == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, "%s do_delete: ber_scanf failed\n", + op->o_log_prefix, 0, 0 ); + send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" ); + return SLAPD_DISCONNECT; + } + + if( get_ctrls( op, rs, 1 ) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s do_delete: get_ctrls failed\n", + op->o_log_prefix, 0, 0 ); + goto cleanup; + } + + rs->sr_err = dnPrettyNormal( NULL, &dn, &op->o_req_dn, &op->o_req_ndn, + op->o_tmpmemctx ); + if( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s do_delete: invalid dn (%s)\n", + op->o_log_prefix, dn.bv_val, 0 ); + send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid DN" ); + goto cleanup; + } + + Statslog( LDAP_DEBUG_STATS, "%s DEL dn=\"%s\"\n", + op->o_log_prefix, op->o_req_dn.bv_val, 0, 0, 0 ); + + if( op->o_req_ndn.bv_len == 0 ) { + Debug( LDAP_DEBUG_ANY, "%s do_delete: root dse!\n", + op->o_log_prefix, 0, 0 ); + /* protocolError would likely be a more appropriate error */ + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "cannot delete the root DSE" ); + goto cleanup; + + } else if ( bvmatch( &op->o_req_ndn, &frontendDB->be_schemandn ) ) { + Debug( LDAP_DEBUG_ANY, "%s do_delete: subschema subentry!\n", + op->o_log_prefix, 0, 0 ); + /* protocolError would likely be a more appropriate error */ + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "cannot delete the root DSE" ); + goto cleanup; + } + + op->o_bd = frontendDB; + rs->sr_err = frontendDB->be_delete( op, rs ); + +#ifdef LDAP_X_TXN + if( rs->sr_err == LDAP_X_TXN_SPECIFY_OKAY ) { + /* skip cleanup */ + return rs->sr_err; + } +#endif + +cleanup:; + op->o_tmpfree( op->o_req_dn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx ); + return rs->sr_err; +} + +int +fe_op_delete( Operation *op, SlapReply *rs ) +{ + struct berval pdn = BER_BVNULL; + BackendDB *op_be, *bd = op->o_bd; + + /* + * We could be serving multiple database backends. Select the + * appropriate one, or send a referral to our "referral server" + * if we don't hold it. + */ + op->o_bd = select_backend( &op->o_req_ndn, 1 ); + if ( op->o_bd == NULL ) { + op->o_bd = bd; + rs->sr_ref = referral_rewrite( default_referral, + NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + + if (!rs->sr_ref) rs->sr_ref = default_referral; + if ( rs->sr_ref != NULL ) { + rs->sr_err = LDAP_REFERRAL; + send_ldap_result( op, rs ); + + if (rs->sr_ref != default_referral) ber_bvarray_free( rs->sr_ref ); + } else { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "no global superior knowledge" ); + } + goto cleanup; + } + + /* If we've got a glued backend, check the real backend */ + op_be = op->o_bd; + if ( SLAP_GLUE_INSTANCE( op->o_bd )) { + op->o_bd = select_backend( &op->o_req_ndn, 0 ); + } + + /* check restrictions */ + if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + /* check for referrals */ + if( backend_check_referrals( op, rs ) != LDAP_SUCCESS ) { + goto cleanup; + } + + /* + * do the delete if 1 && (2 || 3) + * 1) there is a delete function implemented in this backend; + * 2) this backend is the provider for what it holds; + * 3) it's a replica and the dn supplied is the update_ndn. + */ + if ( op->o_bd->be_delete ) { + /* do the update here */ + int repl_user = be_isupdate( op ); + if ( !SLAP_SINGLE_SHADOW(op->o_bd) || repl_user ) { + struct berval org_req_dn = BER_BVNULL; + struct berval org_req_ndn = BER_BVNULL; + struct berval org_dn = BER_BVNULL; + struct berval org_ndn = BER_BVNULL; + int org_managedsait; + + op->o_bd = op_be; + op->o_bd->be_delete( op, rs ); + + org_req_dn = op->o_req_dn; + org_req_ndn = op->o_req_ndn; + org_dn = op->o_dn; + org_ndn = op->o_ndn; + org_managedsait = get_manageDSAit( op ); + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + op->o_managedsait = SLAP_CONTROL_NONCRITICAL; + + while ( rs->sr_err == LDAP_SUCCESS && + op->o_delete_glue_parent ) + { + op->o_delete_glue_parent = 0; + if ( !be_issuffix( op->o_bd, &op->o_req_ndn )) { + slap_callback cb = { NULL, NULL, NULL, NULL }; + cb.sc_response = slap_null_cb; + dnParent( &op->o_req_ndn, &pdn ); + op->o_req_dn = pdn; + op->o_req_ndn = pdn; + op->o_callback = &cb; + op->o_bd->be_delete( op, rs ); + } else { + break; + } + } + + op->o_managedsait = org_managedsait; + op->o_dn = org_dn; + op->o_ndn = org_ndn; + op->o_req_dn = org_req_dn; + op->o_req_ndn = org_req_ndn; + op->o_delete_glue_parent = 0; + + } else { + BerVarray defref = op->o_bd->be_update_refs + ? op->o_bd->be_update_refs : default_referral; + + if ( defref != NULL ) { + rs->sr_ref = referral_rewrite( defref, + NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + if (!rs->sr_ref) rs->sr_ref = defref; + rs->sr_err = LDAP_REFERRAL; + send_ldap_result( op, rs ); + + if (rs->sr_ref != defref) ber_bvarray_free( rs->sr_ref ); + + } else { + send_ldap_error( op, rs, + LDAP_UNWILLING_TO_PERFORM, + "shadow context; no update referral" ); + } + } + + } else { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "operation not supported within namingContext" ); + } + +cleanup:; + op->o_bd = bd; + return rs->sr_err; +} diff --git a/servers/slapd/dn.c b/servers/slapd/dn.c new file mode 100644 index 0000000..07e4965 --- /dev/null +++ b/servers/slapd/dn.c @@ -0,0 +1,1331 @@ +/* dn.c - routines for dealing with distinguished names */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "slap.h" +#include "lutil.h" + +/* + * The DN syntax-related functions take advantage of the dn representation + * handling functions ldap_str2dn/ldap_dn2str. The latter are not schema- + * aware, so the attributes and their values need be validated (and possibly + * normalized). In the current implementation the required validation/nor- + * malization/"pretty"ing are done on newly created DN structural represen- + * tations; however the idea is to move towards DN handling in structural + * representation instead of the current string representation. To this + * purpose, we need to do only the required operations and keep track of + * what has been done to minimize their impact on performances. + * + * Developers are strongly encouraged to use this feature, to speed-up + * its stabilization. + */ + +#define AVA_PRIVATE( ava ) ( ( AttributeDescription * )(ava)->la_private ) + +int slap_DN_strict = SLAP_AD_NOINSERT; + +static int +LDAPRDN_validate( LDAPRDN rdn ) +{ + int iAVA; + int rc; + + assert( rdn != NULL ); + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ]; + AttributeDescription *ad; + slap_syntax_validate_func *validate = NULL; + + assert( ava != NULL ); + + if ( ( ad = AVA_PRIVATE( ava ) ) == NULL ) { + const char *text = NULL; + + rc = slap_bv2ad( &ava->la_attr, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + rc = slap_bv2undef_ad( &ava->la_attr, + &ad, &text, + SLAP_AD_PROXIED|slap_DN_strict ); + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + } + + ava->la_private = ( void * )ad; + } + + /* + * Do not allow X-ORDERED 'VALUES' naming attributes + */ + if ( ad->ad_type->sat_flags & SLAP_AT_ORDERED_VAL ) { + return LDAP_INVALID_SYNTAX; + } + + /* + * Replace attr oid/name with the canonical name + */ + ava->la_attr = ad->ad_cname; + + validate = ad->ad_type->sat_syntax->ssyn_validate; + + if ( validate ) { + /* + * validate value by validate function + */ + rc = ( *validate )( ad->ad_type->sat_syntax, + &ava->la_value ); + + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + } + } + + return LDAP_SUCCESS; +} + +/* + * In-place, schema-aware validation of the + * structural representation of a distinguished name. + */ +static int +LDAPDN_validate( LDAPDN dn ) +{ + int iRDN; + int rc; + + assert( dn != NULL ); + + for ( iRDN = 0; dn[ iRDN ]; iRDN++ ) { + rc = LDAPRDN_validate( dn[ iRDN ] ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + } + + return LDAP_SUCCESS; +} + +/* + * dn validate routine + */ +int +dnValidate( + Syntax *syntax, + struct berval *in ) +{ + int rc; + LDAPDN dn = NULL; + + assert( in != NULL ); + + if ( in->bv_len == 0 ) { + return LDAP_SUCCESS; + + } else if ( in->bv_len > SLAP_LDAPDN_MAXLEN ) { + return LDAP_INVALID_SYNTAX; + } + + rc = ldap_bv2dn( in, &dn, LDAP_DN_FORMAT_LDAP ); + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + + assert( strlen( in->bv_val ) == in->bv_len ); + + /* + * Schema-aware validate + */ + rc = LDAPDN_validate( dn ); + ldap_dnfree( dn ); + + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + + return LDAP_SUCCESS; +} + +int +rdnValidate( + Syntax *syntax, + struct berval *in ) +{ + int rc; + LDAPRDN rdn; + char* p; + + assert( in != NULL ); + if ( in->bv_len == 0 ) { + return LDAP_SUCCESS; + + } else if ( in->bv_len > SLAP_LDAPDN_MAXLEN ) { + return LDAP_INVALID_SYNTAX; + } + + rc = ldap_bv2rdn_x( in , &rdn, (char **) &p, + LDAP_DN_FORMAT_LDAP, NULL); + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + + assert( strlen( in->bv_val ) == in->bv_len ); + + /* + * Schema-aware validate + */ + rc = LDAPRDN_validate( rdn ); + ldap_rdnfree( rdn ); + + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + + return LDAP_SUCCESS; +} + + +/* + * AVA sorting inside a RDN + * + * Rule: sort attributeTypes in alphabetical order. + * + * Note: the sorting can be slightly improved by sorting first + * by attribute type length, then by alphabetical order. + * + * uses an insertion sort; should be fine since the number of AVAs in + * a RDN should be limited. + */ +static int +AVA_Sort( LDAPRDN rdn, int nAVAs ) +{ + LDAPAVA *ava_i; + int i; + int rc = LDAP_SUCCESS; + + assert( rdn != NULL ); + + for ( i = 1; i < nAVAs; i++ ) { + LDAPAVA *ava_j; + int j; + + ava_i = rdn[ i ]; + for ( j = i-1; j >=0; j-- ) { + int a; + + ava_j = rdn[ j ]; + a = strcmp( ava_i->la_attr.bv_val, ava_j->la_attr.bv_val ); + + /* RFC4512 does not allow multiple AVAs + * with the same attribute type in RDN (ITS#5968) */ + if ( a == 0 ) + rc = LDAP_INVALID_DN_SYNTAX; + + if ( a > 0 ) + break; + + rdn[ j+1 ] = rdn[ j ]; + } + rdn[ j+1 ] = ava_i; + } + return rc; +} + +static int +LDAPRDN_rewrite( LDAPRDN rdn, unsigned flags, void *ctx ) +{ + + int rc, iAVA, do_sort = 0; + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ]; + AttributeDescription *ad; + slap_syntax_validate_func *validf = NULL; + slap_mr_normalize_func *normf = NULL; + slap_syntax_transform_func *transf = NULL; + MatchingRule *mr = NULL; + struct berval bv = BER_BVNULL; + + assert( ava != NULL ); + + if ( ( ad = AVA_PRIVATE( ava ) ) == NULL ) { + const char *text = NULL; + + rc = slap_bv2ad( &ava->la_attr, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + rc = slap_bv2undef_ad( &ava->la_attr, + &ad, &text, + SLAP_AD_PROXIED|slap_DN_strict ); + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + } + + ava->la_private = ( void * )ad; + do_sort = 1; + } + + /* + * Replace attr oid/name with the canonical name + */ + ava->la_attr = ad->ad_cname; + + if( ava->la_flags & LDAP_AVA_BINARY ) { + /* AVA is binary encoded, not supported */ + return LDAP_INVALID_SYNTAX; + + /* Do not allow X-ORDERED 'VALUES' naming attributes */ + } else if( ad->ad_type->sat_flags & SLAP_AT_ORDERED_VAL ) { + return LDAP_INVALID_SYNTAX; + + } else if( flags & SLAP_LDAPDN_PRETTY ) { + transf = ad->ad_type->sat_syntax->ssyn_pretty; + if( !transf ) { + validf = ad->ad_type->sat_syntax->ssyn_validate; + } + } else { /* normalization */ + validf = ad->ad_type->sat_syntax->ssyn_validate; + mr = ad->ad_type->sat_equality; + if( mr && (!( mr->smr_usage & SLAP_MR_MUTATION_NORMALIZER ))) { + normf = mr->smr_normalize; + } + } + + if ( validf ) { + /* validate value before normalization */ + rc = ( *validf )( ad->ad_type->sat_syntax, + ava->la_value.bv_len + ? &ava->la_value + : (struct berval *) &slap_empty_bv ); + + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + } + + if ( transf ) { + /* + * transform value by pretty function + * if value is empty, use empty_bv + */ + rc = ( *transf )( ad->ad_type->sat_syntax, + ava->la_value.bv_len + ? &ava->la_value + : (struct berval *) &slap_empty_bv, + &bv, ctx ); + + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + } + + if ( normf ) { + /* + * normalize value + * if value is empty, use empty_bv + */ + rc = ( *normf )( + SLAP_MR_VALUE_OF_ASSERTION_SYNTAX, + ad->ad_type->sat_syntax, + mr, + ava->la_value.bv_len + ? &ava->la_value + : (struct berval *) &slap_empty_bv, + &bv, ctx ); + + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + } + + + if( bv.bv_val ) { + if ( ava->la_flags & LDAP_AVA_FREE_VALUE ) + ber_memfree_x( ava->la_value.bv_val, ctx ); + ava->la_value = bv; + ava->la_flags |= LDAP_AVA_FREE_VALUE; + } + /* reject empty values */ + if (!ava->la_value.bv_len) { + return LDAP_INVALID_SYNTAX; + } + } + rc = LDAP_SUCCESS; + + if ( do_sort ) { + rc = AVA_Sort( rdn, iAVA ); + } + + return rc; +} + +/* + * In-place, schema-aware normalization / "pretty"ing of the + * structural representation of a distinguished name. + */ +static int +LDAPDN_rewrite( LDAPDN dn, unsigned flags, void *ctx ) +{ + int iRDN; + int rc; + + assert( dn != NULL ); + + for ( iRDN = 0; dn[ iRDN ]; iRDN++ ) { + rc = LDAPRDN_rewrite( dn[ iRDN ], flags, ctx ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + } + + return LDAP_SUCCESS; +} + +int +dnNormalize( + slap_mask_t use, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *out, + void *ctx) +{ + assert( val != NULL ); + assert( out != NULL ); + + Debug( LDAP_DEBUG_TRACE, ">>> dnNormalize: <%s>\n", val->bv_val ? val->bv_val : "", 0, 0 ); + + if ( val->bv_len != 0 ) { + LDAPDN dn = NULL; + int rc; + + /* + * Go to structural representation + */ + rc = ldap_bv2dn_x( val, &dn, LDAP_DN_FORMAT_LDAP, ctx ); + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + + assert( strlen( val->bv_val ) == val->bv_len ); + + /* + * Schema-aware rewrite + */ + if ( LDAPDN_rewrite( dn, 0, ctx ) != LDAP_SUCCESS ) { + ldap_dnfree_x( dn, ctx ); + return LDAP_INVALID_SYNTAX; + } + + /* + * Back to string representation + */ + rc = ldap_dn2bv_x( dn, out, + LDAP_DN_FORMAT_LDAPV3 | LDAP_DN_PRETTY, ctx ); + + ldap_dnfree_x( dn, ctx ); + + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + } else { + ber_dupbv_x( out, val, ctx ); + } + + Debug( LDAP_DEBUG_TRACE, "<<< dnNormalize: <%s>\n", out->bv_val ? out->bv_val : "", 0, 0 ); + + return LDAP_SUCCESS; +} + +int +rdnNormalize( + slap_mask_t use, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *out, + void *ctx) +{ + assert( val != NULL ); + assert( out != NULL ); + + Debug( LDAP_DEBUG_TRACE, ">>> dnNormalize: <%s>\n", val->bv_val ? val->bv_val : "", 0, 0 ); + if ( val->bv_len != 0 ) { + LDAPRDN rdn = NULL; + int rc; + char* p; + + /* + * Go to structural representation + */ + rc = ldap_bv2rdn_x( val , &rdn, (char **) &p, + LDAP_DN_FORMAT_LDAP, ctx); + + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + + assert( strlen( val->bv_val ) == val->bv_len ); + + /* + * Schema-aware rewrite + */ + if ( LDAPRDN_rewrite( rdn, 0, ctx ) != LDAP_SUCCESS ) { + ldap_rdnfree_x( rdn, ctx ); + return LDAP_INVALID_SYNTAX; + } + + /* + * Back to string representation + */ + rc = ldap_rdn2bv_x( rdn, out, + LDAP_DN_FORMAT_LDAPV3 | LDAP_DN_PRETTY, ctx ); + + ldap_rdnfree_x( rdn, ctx ); + + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + } else { + ber_dupbv_x( out, val, ctx ); + } + + Debug( LDAP_DEBUG_TRACE, "<<< dnNormalize: <%s>\n", out->bv_val ? out->bv_val : "", 0, 0 ); + + return LDAP_SUCCESS; +} + +int +dnPretty( + Syntax *syntax, + struct berval *val, + struct berval *out, + void *ctx) +{ + assert( val != NULL ); + assert( out != NULL ); + + Debug( LDAP_DEBUG_TRACE, ">>> dnPretty: <%s>\n", val->bv_val ? val->bv_val : "", 0, 0 ); + + if ( val->bv_len == 0 ) { + ber_dupbv_x( out, val, ctx ); + + } else if ( val->bv_len > SLAP_LDAPDN_MAXLEN ) { + return LDAP_INVALID_SYNTAX; + + } else { + LDAPDN dn = NULL; + int rc; + + /* FIXME: should be liberal in what we accept */ + rc = ldap_bv2dn_x( val, &dn, LDAP_DN_FORMAT_LDAP, ctx ); + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + + assert( strlen( val->bv_val ) == val->bv_len ); + + /* + * Schema-aware rewrite + */ + if ( LDAPDN_rewrite( dn, SLAP_LDAPDN_PRETTY, ctx ) != LDAP_SUCCESS ) { + ldap_dnfree_x( dn, ctx ); + return LDAP_INVALID_SYNTAX; + } + + /* FIXME: not sure why the default isn't pretty */ + /* RE: the default is the form that is used as + * an internal representation; the pretty form + * is a variant */ + rc = ldap_dn2bv_x( dn, out, + LDAP_DN_FORMAT_LDAPV3 | LDAP_DN_PRETTY, ctx ); + + ldap_dnfree_x( dn, ctx ); + + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + } + + Debug( LDAP_DEBUG_TRACE, "<<< dnPretty: <%s>\n", out->bv_val ? out->bv_val : "", 0, 0 ); + + return LDAP_SUCCESS; +} + +int +rdnPretty( + Syntax *syntax, + struct berval *val, + struct berval *out, + void *ctx) +{ + assert( val != NULL ); + assert( out != NULL ); + + Debug( LDAP_DEBUG_TRACE, ">>> rdnPretty: <%s>\n", val->bv_val ? val->bv_val : "", 0, 0 ); + + if ( val->bv_len == 0 ) { + ber_dupbv_x( out, val, ctx ); + + } else if ( val->bv_len > SLAP_LDAPDN_MAXLEN ) { + return LDAP_INVALID_SYNTAX; + + } else { + LDAPRDN rdn = NULL; + int rc; + char* p; + + /* FIXME: should be liberal in what we accept */ + rc = ldap_bv2rdn_x( val , &rdn, (char **) &p, + LDAP_DN_FORMAT_LDAP, ctx); + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + + assert( strlen( val->bv_val ) == val->bv_len ); + + /* + * Schema-aware rewrite + */ + if ( LDAPRDN_rewrite( rdn, SLAP_LDAPDN_PRETTY, ctx ) != LDAP_SUCCESS ) { + ldap_rdnfree_x( rdn, ctx ); + return LDAP_INVALID_SYNTAX; + } + + /* FIXME: not sure why the default isn't pretty */ + /* RE: the default is the form that is used as + * an internal representation; the pretty form + * is a variant */ + rc = ldap_rdn2bv_x( rdn, out, + LDAP_DN_FORMAT_LDAPV3 | LDAP_DN_PRETTY, ctx ); + + ldap_rdnfree_x( rdn, ctx ); + + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + } + + Debug( LDAP_DEBUG_TRACE, "<<< dnPretty: <%s>\n", out->bv_val ? out->bv_val : "", 0, 0 ); + + return LDAP_SUCCESS; +} + + +int +dnPrettyNormalDN( + Syntax *syntax, + struct berval *val, + LDAPDN *dn, + int flags, + void *ctx ) +{ + assert( val != NULL ); + assert( dn != NULL ); + + Debug( LDAP_DEBUG_TRACE, ">>> dn%sDN: <%s>\n", + flags == SLAP_LDAPDN_PRETTY ? "Pretty" : "Normal", + val->bv_val ? val->bv_val : "", 0 ); + + if ( val->bv_len == 0 ) { + return LDAP_SUCCESS; + + } else if ( val->bv_len > SLAP_LDAPDN_MAXLEN ) { + return LDAP_INVALID_SYNTAX; + + } else { + int rc; + + /* FIXME: should be liberal in what we accept */ + rc = ldap_bv2dn_x( val, dn, LDAP_DN_FORMAT_LDAP, ctx ); + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + + assert( strlen( val->bv_val ) == val->bv_len ); + + /* + * Schema-aware rewrite + */ + if ( LDAPDN_rewrite( *dn, flags, ctx ) != LDAP_SUCCESS ) { + ldap_dnfree_x( *dn, ctx ); + *dn = NULL; + return LDAP_INVALID_SYNTAX; + } + } + + Debug( LDAP_DEBUG_TRACE, "<<< dn%sDN\n", + flags == SLAP_LDAPDN_PRETTY ? "Pretty" : "Normal", + 0, 0 ); + + return LDAP_SUCCESS; +} + +/* + * Combination of both dnPretty and dnNormalize + */ +int +dnPrettyNormal( + Syntax *syntax, + struct berval *val, + struct berval *pretty, + struct berval *normal, + void *ctx) +{ + assert( val != NULL ); + assert( pretty != NULL ); + assert( normal != NULL ); + Debug( LDAP_DEBUG_TRACE, ">>> dnPrettyNormal: <%s>\n", val->bv_val ? val->bv_val : "", 0, 0 ); + + if ( val->bv_len == 0 ) { + ber_dupbv_x( pretty, val, ctx ); + ber_dupbv_x( normal, val, ctx ); + + } else if ( val->bv_len > SLAP_LDAPDN_MAXLEN ) { + /* too big */ + return LDAP_INVALID_SYNTAX; + + } else { + LDAPDN dn = NULL; + int rc; + + pretty->bv_val = NULL; + normal->bv_val = NULL; + pretty->bv_len = 0; + normal->bv_len = 0; + + /* FIXME: should be liberal in what we accept */ + rc = ldap_bv2dn_x( val, &dn, LDAP_DN_FORMAT_LDAP, ctx ); + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + + assert( strlen( val->bv_val ) == val->bv_len ); + + /* + * Schema-aware rewrite + */ + if ( LDAPDN_rewrite( dn, SLAP_LDAPDN_PRETTY, ctx ) != LDAP_SUCCESS ) { + ldap_dnfree_x( dn, ctx ); + return LDAP_INVALID_SYNTAX; + } + + rc = ldap_dn2bv_x( dn, pretty, + LDAP_DN_FORMAT_LDAPV3 | LDAP_DN_PRETTY, ctx ); + + if ( rc != LDAP_SUCCESS ) { + ldap_dnfree_x( dn, ctx ); + return LDAP_INVALID_SYNTAX; + } + + if ( LDAPDN_rewrite( dn, 0, ctx ) != LDAP_SUCCESS ) { + ldap_dnfree_x( dn, ctx ); + ber_memfree_x( pretty->bv_val, ctx ); + pretty->bv_val = NULL; + pretty->bv_len = 0; + return LDAP_INVALID_SYNTAX; + } + + rc = ldap_dn2bv_x( dn, normal, + LDAP_DN_FORMAT_LDAPV3 | LDAP_DN_PRETTY, ctx ); + + ldap_dnfree_x( dn, ctx ); + if ( rc != LDAP_SUCCESS ) { + ber_memfree_x( pretty->bv_val, ctx ); + pretty->bv_val = NULL; + pretty->bv_len = 0; + return LDAP_INVALID_SYNTAX; + } + } + + Debug( LDAP_DEBUG_TRACE, "<<< dnPrettyNormal: <%s>, <%s>\n", + pretty->bv_val ? pretty->bv_val : "", + normal->bv_val ? normal->bv_val : "", 0 ); + + return LDAP_SUCCESS; +} + +/* + * dnMatch routine + */ +int +dnMatch( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + int match; + struct berval *asserted = (struct berval *) assertedValue; + + assert( matchp != NULL ); + assert( value != NULL ); + assert( assertedValue != NULL ); + assert( !BER_BVISNULL( value ) ); + assert( !BER_BVISNULL( asserted ) ); + + match = value->bv_len - asserted->bv_len; + + if ( match == 0 ) { + match = memcmp( value->bv_val, asserted->bv_val, + value->bv_len ); + } + + Debug( LDAP_DEBUG_ARGS, "dnMatch %d\n\t\"%s\"\n\t\"%s\"\n", + match, value->bv_val, asserted->bv_val ); + + *matchp = match; + return LDAP_SUCCESS; +} + +/* + * dnRelativeMatch routine + */ +int +dnRelativeMatch( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + int match; + struct berval *asserted = (struct berval *) assertedValue; + + assert( matchp != NULL ); + assert( value != NULL ); + assert( assertedValue != NULL ); + assert( !BER_BVISNULL( value ) ); + assert( !BER_BVISNULL( asserted ) ); + + if( mr == slap_schema.si_mr_dnSubtreeMatch ) { + if( asserted->bv_len > value->bv_len ) { + match = -1; + } else if ( asserted->bv_len == value->bv_len ) { + match = memcmp( value->bv_val, asserted->bv_val, + value->bv_len ); + } else { + if( DN_SEPARATOR( + value->bv_val[value->bv_len - asserted->bv_len - 1] )) + { + match = memcmp( + &value->bv_val[value->bv_len - asserted->bv_len], + asserted->bv_val, + asserted->bv_len ); + } else { + match = 1; + } + } + + *matchp = match; + return LDAP_SUCCESS; + } + + if( mr == slap_schema.si_mr_dnSuperiorMatch ) { + asserted = value; + value = (struct berval *) assertedValue; + mr = slap_schema.si_mr_dnSubordinateMatch; + } + + if( mr == slap_schema.si_mr_dnSubordinateMatch ) { + if( asserted->bv_len >= value->bv_len ) { + match = -1; + } else { + if( DN_SEPARATOR( + value->bv_val[value->bv_len - asserted->bv_len - 1] )) + { + match = memcmp( + &value->bv_val[value->bv_len - asserted->bv_len], + asserted->bv_val, + asserted->bv_len ); + } else { + match = 1; + } + } + + *matchp = match; + return LDAP_SUCCESS; + } + + if( mr == slap_schema.si_mr_dnOneLevelMatch ) { + if( asserted->bv_len >= value->bv_len ) { + match = -1; + } else { + if( DN_SEPARATOR( + value->bv_val[value->bv_len - asserted->bv_len - 1] )) + { + match = memcmp( + &value->bv_val[value->bv_len - asserted->bv_len], + asserted->bv_val, + asserted->bv_len ); + + if( !match ) { + struct berval rdn; + rdn.bv_val = value->bv_val; + rdn.bv_len = value->bv_len - asserted->bv_len - 1; + match = dnIsOneLevelRDN( &rdn ) ? 0 : 1; + } + } else { + match = 1; + } + } + + *matchp = match; + return LDAP_SUCCESS; + } + + /* should not be reachable */ + assert( 0 ); + return LDAP_OTHER; +} + +int +rdnMatch( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + int match; + struct berval *asserted = (struct berval *) assertedValue; + + assert( matchp != NULL ); + assert( value != NULL ); + assert( assertedValue != NULL ); + + match = value->bv_len - asserted->bv_len; + + if ( match == 0 ) { + match = memcmp( value->bv_val, asserted->bv_val, + value->bv_len ); + } + + Debug( LDAP_DEBUG_ARGS, "rdnMatch %d\n\t\"%s\"\n\t\"%s\"\n", + match, value->bv_val, asserted->bv_val ); + + *matchp = match; + return LDAP_SUCCESS; +} + + +/* + * dnParent - dn's parent, in-place + * note: the incoming dn is assumed to be normalized/prettyfied, + * so that escaped rdn/ava separators are in '\'+hexpair form + * + * note: "dn" and "pdn" can point to the same berval; + * beware that, in this case, the pointer to the original buffer + * will get lost. + */ +void +dnParent( + struct berval *dn, + struct berval *pdn ) +{ + char *p; + + p = ber_bvchr( dn, ',' ); + + /* one-level dn */ + if ( p == NULL ) { + pdn->bv_val = dn->bv_val + dn->bv_len; + pdn->bv_len = 0; + return; + } + + assert( DN_SEPARATOR( p[ 0 ] ) ); + p++; + + assert( ATTR_LEADCHAR( p[ 0 ] ) ); + pdn->bv_len = dn->bv_len - (p - dn->bv_val); + pdn->bv_val = p; + + return; +} + +/* + * dnRdn - dn's rdn, in-place + * note: the incoming dn is assumed to be normalized/prettyfied, + * so that escaped rdn/ava separators are in '\'+hexpair form + */ +void +dnRdn( + struct berval *dn, + struct berval *rdn ) +{ + char *p; + + *rdn = *dn; + p = ber_bvchr( dn, ',' ); + + /* one-level dn */ + if ( p == NULL ) { + return; + } + + assert( DN_SEPARATOR( p[ 0 ] ) ); + assert( ATTR_LEADCHAR( p[ 1 ] ) ); + rdn->bv_len = p - dn->bv_val; + + return; +} + +int +dnExtractRdn( + struct berval *dn, + struct berval *rdn, + void *ctx ) +{ + LDAPRDN tmpRDN; + const char *p; + int rc; + + assert( dn != NULL ); + assert( rdn != NULL ); + + if( dn->bv_len == 0 ) { + return LDAP_OTHER; + } + + rc = ldap_bv2rdn_x( dn, &tmpRDN, (char **)&p, LDAP_DN_FORMAT_LDAP, ctx ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + rc = ldap_rdn2bv_x( tmpRDN, rdn, LDAP_DN_FORMAT_LDAPV3 | LDAP_DN_PRETTY, + ctx ); + + ldap_rdnfree_x( tmpRDN, ctx ); + return rc; +} + +/* + * We can assume the input is a prettied or normalized DN + */ +ber_len_t +dn_rdnlen( + Backend *be, + struct berval *dn_in ) +{ + const char *p; + + assert( dn_in != NULL ); + + if ( dn_in == NULL ) { + return 0; + } + + if ( !dn_in->bv_len ) { + return 0; + } + + if ( be != NULL && be_issuffix( be, dn_in ) ) { + return 0; + } + + p = ber_bvchr( dn_in, ',' ); + + return p ? (ber_len_t) (p - dn_in->bv_val) : dn_in->bv_len; +} + + +/* rdnValidate: + * + * LDAP_SUCCESS if rdn is a legal rdn; + * LDAP_INVALID_SYNTAX otherwise (including a sequence of rdns) + */ +int +rdn_validate( struct berval *rdn ) +{ +#if 1 + /* Major cheat! + * input is a pretty or normalized DN + * hence, we can just search for ',' + */ + if( rdn == NULL || rdn->bv_len == 0 || + rdn->bv_len > SLAP_LDAPDN_MAXLEN ) + { + return LDAP_INVALID_SYNTAX; + } + return ber_bvchr( rdn, ',' ) == NULL + ? LDAP_SUCCESS : LDAP_INVALID_SYNTAX; + +#else + LDAPRDN *RDN, **DN[ 2 ] = { &RDN, NULL }; + const char *p; + int rc; + + /* + * must be non-empty + */ + if ( rdn == NULL || rdn == '\0' ) { + return 0; + } + + /* + * must be parsable + */ + rc = ldap_bv2rdn( rdn, &RDN, (char **)&p, LDAP_DN_FORMAT_LDAP ); + if ( rc != LDAP_SUCCESS ) { + return 0; + } + + /* + * Must be one-level + */ + if ( p[ 0 ] != '\0' ) { + return 0; + } + + /* + * Schema-aware validate + */ + if ( rc == LDAP_SUCCESS ) { + rc = LDAPDN_validate( DN ); + } + ldap_rdnfree( RDN ); + + /* + * Must validate (there's a repeated parsing ...) + */ + return ( rc == LDAP_SUCCESS ); +#endif +} + + +/* build_new_dn: + * + * Used by back-bdb back_modrdn to create the new dn of entries being + * renamed. + * + * new_dn = parent (p_dn) + separator + rdn (newrdn) + null. + */ + +void +build_new_dn( struct berval * new_dn, + struct berval * parent_dn, + struct berval * newrdn, + void *memctx ) +{ + char *ptr; + + if ( parent_dn == NULL || parent_dn->bv_len == 0 ) { + ber_dupbv_x( new_dn, newrdn, memctx ); + return; + } + + new_dn->bv_len = parent_dn->bv_len + newrdn->bv_len + 1; + new_dn->bv_val = (char *) slap_sl_malloc( new_dn->bv_len + 1, memctx ); + + ptr = lutil_strncopy( new_dn->bv_val, newrdn->bv_val, newrdn->bv_len ); + *ptr++ = ','; + strcpy( ptr, parent_dn->bv_val ); +} + + +/* + * dnIsSuffix - tells whether suffix is a suffix of dn. + * Both dn and suffix must be normalized. + */ +int +dnIsSuffix( + const struct berval *dn, + const struct berval *suffix ) +{ + int d; + + assert( dn != NULL ); + assert( suffix != NULL ); + + d = dn->bv_len - suffix->bv_len; + + /* empty suffix matches any dn */ + if ( suffix->bv_len == 0 ) { + return 1; + } + + /* suffix longer than dn */ + if ( d < 0 ) { + return 0; + } + + /* no rdn separator or escaped rdn separator */ + if ( d > 1 && !DN_SEPARATOR( dn->bv_val[ d - 1 ] ) ) { + return 0; + } + + /* no possible match or malformed dn */ + if ( d == 1 ) { + return 0; + } + + /* compare */ + return( strncmp( dn->bv_val + d, suffix->bv_val, suffix->bv_len ) == 0 ); +} + +/* + * In place; assumes: + * - ndn is normalized + * - nbase is normalized + * - dnIsSuffix( ndn, nbase ) == TRUE + * - LDAP_SCOPE_DEFAULT == LDAP_SCOPE_SUBTREE + */ +int +dnIsWithinScope( struct berval *ndn, struct berval *nbase, int scope ) +{ + assert( ndn != NULL ); + assert( nbase != NULL ); + assert( !BER_BVISNULL( ndn ) ); + assert( !BER_BVISNULL( nbase ) ); + + switch ( scope ) { + case LDAP_SCOPE_DEFAULT: + case LDAP_SCOPE_SUBTREE: + break; + + case LDAP_SCOPE_BASE: + if ( ndn->bv_len != nbase->bv_len ) { + return 0; + } + break; + + case LDAP_SCOPE_ONELEVEL: { + struct berval pndn; + dnParent( ndn, &pndn ); + if ( pndn.bv_len != nbase->bv_len ) { + return 0; + } + } break; + + case LDAP_SCOPE_SUBORDINATE: + if ( ndn->bv_len == nbase->bv_len ) { + return 0; + } + break; + + /* unknown scope */ + default: + return -1; + } + + return 1; +} + +/* + * In place; assumes: + * - ndn is normalized + * - nbase is normalized + * - LDAP_SCOPE_DEFAULT == LDAP_SCOPE_SUBTREE + */ +int +dnIsSuffixScope( struct berval *ndn, struct berval *nbase, int scope ) +{ + if ( !dnIsSuffix( ndn, nbase ) ) { + return 0; + } + + return dnIsWithinScope( ndn, nbase, scope ); +} + +int +dnIsOneLevelRDN( struct berval *rdn ) +{ + ber_len_t len = rdn->bv_len; + for ( ; len--; ) { + if ( DN_SEPARATOR( rdn->bv_val[ len ] ) ) { + return 0; + } + } + + return 1; +} + +#ifdef HAVE_TLS +static SLAP_CERT_MAP_FN *DNX509PeerNormalizeCertMap = NULL; +#endif + +int register_certificate_map_function(SLAP_CERT_MAP_FN *fn) +{ +#ifdef HAVE_TLS + if ( DNX509PeerNormalizeCertMap == NULL ) { + DNX509PeerNormalizeCertMap = fn; + return 0; + } +#endif + + return -1; +} + +/* + * Convert an X.509 DN into a normalized LDAP DN + */ +int +dnX509normalize( void *x509_name, struct berval *out ) +{ + /* Invoke the LDAP library's converter with our schema-rewriter */ + int rc = ldap_X509dn2bv( x509_name, out, LDAPDN_rewrite, 0 ); + + Debug( LDAP_DEBUG_TRACE, + "dnX509Normalize: <%s> (%d)\n", + BER_BVISNULL( out ) ? "(null)" : out->bv_val, rc, 0 ); + + return rc; +} + +#ifdef HAVE_TLS +/* + * Get the TLS session's peer's DN into a normalized LDAP DN + */ +int +dnX509peerNormalize( void *ssl, struct berval *dn ) +{ + int rc = LDAP_INVALID_CREDENTIALS; + + if ( DNX509PeerNormalizeCertMap != NULL ) + rc = (*DNX509PeerNormalizeCertMap)( ssl, dn ); + + if ( rc != LDAP_SUCCESS ) { + rc = ldap_pvt_tls_get_peer_dn( ssl, dn, + (LDAPDN_rewrite_dummy *)LDAPDN_rewrite, 0 ); + } + + return rc; +} +#endif diff --git a/servers/slapd/entry.c b/servers/slapd/entry.c new file mode 100644 index 0000000..42b59cb --- /dev/null +++ b/servers/slapd/entry.c @@ -0,0 +1,1025 @@ +/* entry.c - routines for dealing with entries */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "ldif.h" + +static char *ebuf; /* buf returned by entry2str */ +static char *ecur; /* pointer to end of currently used ebuf */ +static int emaxsize;/* max size of ebuf */ + +/* + * Empty root entry + */ +const Entry slap_entry_root = { + NOID, { 0, "" }, { 0, "" }, NULL, 0, { 0, "" }, NULL +}; + +/* + * these mutexes must be used when calling the entry2str() + * routine since it returns a pointer to static data. + */ +ldap_pvt_thread_mutex_t entry2str_mutex; + +static const struct berval dn_bv = BER_BVC("dn"); + +/* + * Entry free list + * + * Allocate in chunks, minimum of 1000 at a time. + */ +#define CHUNK_SIZE 1000 +typedef struct slap_list { + struct slap_list *next; +} slap_list; +static slap_list *entry_chunks; +static Entry *entry_list; +static ldap_pvt_thread_mutex_t entry_mutex; + +int entry_destroy(void) +{ + slap_list *e; + if ( ebuf ) free( ebuf ); + ebuf = NULL; + ecur = NULL; + emaxsize = 0; + + for ( e=entry_chunks; e; e=entry_chunks ) { + entry_chunks = e->next; + free( e ); + } + + ldap_pvt_thread_mutex_destroy( &entry_mutex ); + ldap_pvt_thread_mutex_destroy( &entry2str_mutex ); + return attr_destroy(); +} + +int +entry_init(void) +{ + ldap_pvt_thread_mutex_init( &entry2str_mutex ); + ldap_pvt_thread_mutex_init( &entry_mutex ); + return attr_init(); +} + +Entry * +str2entry( char *s ) +{ + return str2entry2( s, 1 ); +} + +#define bvcasematch(bv1, bv2) (ber_bvstrcasecmp(bv1, bv2) == 0) + +Entry * +str2entry2( char *s, int checkvals ) +{ + int rc; + Entry *e; + struct berval *type, *vals, *nvals; + char *freeval; + AttributeDescription *ad, *ad_prev; + const char *text; + char *next; + int attr_cnt; + int i, lines; + Attribute ahead, *atail; + + /* + * LDIF is used as the string format. + * An entry looks like this: + * + * dn: <dn>\n + * [<attr>:[:] <value>\n] + * [<tab><continuedvalue>\n]* + * ... + * + * If a double colon is used after a type, it means the + * following value is encoded as a base 64 string. This + * happens if the value contains a non-printing character + * or newline. + */ + + Debug( LDAP_DEBUG_TRACE, "=> str2entry: \"%s\"\n", + s ? s : "NULL", 0, 0 ); + + e = entry_alloc(); + + if( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "<= str2entry NULL (entry allocation failed)\n", + 0, 0, 0 ); + return( NULL ); + } + + /* initialize entry */ + e->e_id = NOID; + + /* dn + attributes */ + atail = &ahead; + ahead.a_next = NULL; + ad = NULL; + ad_prev = NULL; + attr_cnt = 0; + next = s; + + lines = ldif_countlines( s ); + type = ch_calloc( 1, (lines+1)*3*sizeof(struct berval)+lines ); + vals = type+lines+1; + nvals = vals+lines+1; + freeval = (char *)(nvals+lines+1); + i = -1; + + /* parse into individual values, record DN */ + while ( (s = ldif_getline( &next )) != NULL ) { + int freev; + if ( *s == '\n' || *s == '\0' ) { + break; + } + i++; + if (i >= lines) { + Debug( LDAP_DEBUG_TRACE, + "<= str2entry ran past end of entry\n", 0, 0, 0 ); + goto fail; + } + + rc = ldif_parse_line2( s, type+i, vals+i, &freev ); + freeval[i] = freev; + if ( rc ) { + Debug( LDAP_DEBUG_TRACE, + "<= str2entry NULL (parse_line)\n", 0, 0, 0 ); + continue; + } + + if ( bvcasematch( &type[i], &dn_bv ) ) { + if ( e->e_dn != NULL ) { + Debug( LDAP_DEBUG_ANY, "str2entry: " + "entry %ld has multiple DNs \"%s\" and \"%s\"\n", + (long) e->e_id, e->e_dn, vals[i].bv_val ); + goto fail; + } + + rc = dnPrettyNormal( NULL, &vals[i], &e->e_name, &e->e_nname, NULL ); + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "str2entry: " + "entry %ld has invalid DN \"%s\"\n", + (long) e->e_id, vals[i].bv_val, 0 ); + goto fail; + } + if ( freeval[i] ) free( vals[i].bv_val ); + vals[i].bv_val = NULL; + i--; + continue; + } + } + lines = i+1; + + /* check to make sure there was a dn: line */ + if ( BER_BVISNULL( &e->e_name )) { + Debug( LDAP_DEBUG_ANY, "str2entry: entry %ld has no dn\n", + (long) e->e_id, 0, 0 ); + goto fail; + } + + /* Make sure all attributes with multiple values are contiguous */ + if ( checkvals ) { + int j, k; + struct berval bv; + int fv; + + for (i=0; i<lines; i++) { + for ( j=i+1; j<lines; j++ ) { + if ( bvcasematch( type+i, type+j )) { + /* out of order, move intervening attributes down */ + if ( j != i+1 ) { + bv = vals[j]; + fv = freeval[j]; + for ( k=j; k>i; k-- ) { + type[k] = type[k-1]; + vals[k] = vals[k-1]; + freeval[k] = freeval[k-1]; + } + k++; + type[k] = type[i]; + vals[k] = bv; + freeval[k] = fv; + } + i++; + } + } + } + } + + if ( lines > 0 ) { + for ( i=0; i<=lines; i++ ) { + ad_prev = ad; + if ( !ad || ( i<lines && !bvcasematch( type+i, &ad->ad_cname ))) { + ad = NULL; + rc = slap_bv2ad( type+i, &ad, &text ); + + if( rc != LDAP_SUCCESS ) { + int wtool = ( slapMode & (SLAP_TOOL_MODE|SLAP_TOOL_READONLY) ) == SLAP_TOOL_MODE; + Debug( wtool ? LDAP_DEBUG_ANY : LDAP_DEBUG_TRACE, + "<= str2entry: str2ad(%s): %s\n", type[i].bv_val, text, 0 ); + if( wtool ) { + goto fail; + } + + rc = slap_bv2undef_ad( type+i, &ad, &text, 0 ); + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "<= str2entry: slap_str2undef_ad(%s): %s\n", + type[i].bv_val, text, 0 ); + goto fail; + } + } + + /* require ';binary' when appropriate (ITS#5071) */ + if ( slap_syntax_is_binary( ad->ad_type->sat_syntax ) && !slap_ad_is_binary( ad ) ) { + Debug( LDAP_DEBUG_ANY, + "str2entry: attributeType %s #%d: " + "needs ';binary' transfer as per syntax %s\n", + ad->ad_cname.bv_val, 0, + ad->ad_type->sat_syntax->ssyn_oid ); + goto fail; + } + } + + if (( ad_prev && ad != ad_prev ) || ( i == lines )) { + int j, k; + atail->a_next = attr_alloc( NULL ); + atail = atail->a_next; + atail->a_flags = 0; + atail->a_numvals = attr_cnt; + atail->a_desc = ad_prev; + atail->a_vals = ch_malloc( (attr_cnt + 1) * sizeof(struct berval)); + if( ad_prev->ad_type->sat_equality && + ad_prev->ad_type->sat_equality->smr_normalize ) + atail->a_nvals = ch_malloc( (attr_cnt + 1) * sizeof(struct berval)); + else + atail->a_nvals = NULL; + k = i - attr_cnt; + for ( j=0; j<attr_cnt; j++ ) { + if ( freeval[k] ) + atail->a_vals[j] = vals[k]; + else + ber_dupbv( atail->a_vals+j, &vals[k] ); + vals[k].bv_val = NULL; + if ( atail->a_nvals ) { + atail->a_nvals[j] = nvals[k]; + nvals[k].bv_val = NULL; + } + k++; + } + BER_BVZERO( &atail->a_vals[j] ); + if ( atail->a_nvals ) { + BER_BVZERO( &atail->a_nvals[j] ); + } else { + atail->a_nvals = atail->a_vals; + } + attr_cnt = 0; + /* FIXME: we only need this when migrating from an unsorted DB */ + if ( atail->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL ) { + rc = slap_sort_vals( (Modifications *)atail, &text, &j, NULL ); + if ( rc == LDAP_SUCCESS ) { + atail->a_flags |= SLAP_ATTR_SORTED_VALS; + } else if ( rc == LDAP_TYPE_OR_VALUE_EXISTS ) { + Debug( LDAP_DEBUG_ANY, + "str2entry: attributeType %s value #%d provided more than once\n", + atail->a_desc->ad_cname.bv_val, j, 0 ); + goto fail; + } + } + if ( i == lines ) break; + } + + if ( BER_BVISNULL( &vals[i] ) ) { + Debug( LDAP_DEBUG_ANY, + "str2entry: attributeType %s #%d: " + "no value\n", + ad->ad_cname.bv_val, attr_cnt, 0 ); + goto fail; + } + + if ( ad->ad_type->sat_equality && + ad->ad_type->sat_equality->smr_normalize ) + { + rc = ordered_value_normalize( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + ad, + ad->ad_type->sat_equality, + &vals[i], &nvals[i], NULL ); + + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "<= str2entry NULL (smr_normalize %s %d)\n", ad->ad_cname.bv_val, rc, 0 ); + goto fail; + } + } + + attr_cnt++; + } + } + + free( type ); + atail->a_next = NULL; + e->e_attrs = ahead.a_next; + + Debug(LDAP_DEBUG_TRACE, "<= str2entry(%s) -> 0x%lx\n", + e->e_dn, (unsigned long) e, 0 ); + return( e ); + +fail: + for ( i=0; i<lines; i++ ) { + if ( freeval[i] ) free( vals[i].bv_val ); + free( nvals[i].bv_val ); + } + free( type ); + entry_free( e ); + return NULL; +} + + +#define GRABSIZE BUFSIZ + +#define MAKE_SPACE( n ) { \ + while ( ecur + (n) > ebuf + emaxsize ) { \ + ptrdiff_t offset; \ + offset = (int) (ecur - ebuf); \ + ebuf = ch_realloc( ebuf, \ + emaxsize + GRABSIZE ); \ + emaxsize += GRABSIZE; \ + ecur = ebuf + offset; \ + } \ + } + +/* NOTE: only preserved for binary compatibility */ +char * +entry2str( + Entry *e, + int *len ) +{ + return entry2str_wrap( e, len, LDIF_LINE_WIDTH ); +} + +char * +entry2str_wrap( + Entry *e, + int *len, + ber_len_t wrap ) +{ + Attribute *a; + struct berval *bv; + int i; + ber_len_t tmplen; + + assert( e != NULL ); + + /* + * In string format, an entry looks like this: + * dn: <dn>\n + * [<attr>: <value>\n]* + */ + + ecur = ebuf; + + /* put the dn */ + if ( e->e_dn != NULL ) { + /* put "dn: <dn>" */ + tmplen = e->e_name.bv_len; + MAKE_SPACE( LDIF_SIZE_NEEDED( 2, tmplen )); + ldif_sput_wrap( &ecur, LDIF_PUT_VALUE, "dn", e->e_dn, tmplen, wrap ); + } + + /* put the attributes */ + for ( a = e->e_attrs; a != NULL; a = a->a_next ) { + /* put "<type>:[:] <value>" line for each value */ + for ( i = 0; a->a_vals[i].bv_val != NULL; i++ ) { + bv = &a->a_vals[i]; + tmplen = a->a_desc->ad_cname.bv_len; + MAKE_SPACE( LDIF_SIZE_NEEDED( tmplen, bv->bv_len )); + ldif_sput_wrap( &ecur, LDIF_PUT_VALUE, + a->a_desc->ad_cname.bv_val, + bv->bv_val, bv->bv_len, wrap ); + } + } + MAKE_SPACE( 1 ); + *ecur = '\0'; + *len = ecur - ebuf; + + return( ebuf ); +} + +void +entry_clean( Entry *e ) +{ + /* free an entry structure */ + assert( e != NULL ); + + /* e_private must be freed by the caller */ + assert( e->e_private == NULL ); + + e->e_id = 0; + + /* free DNs */ + if ( !BER_BVISNULL( &e->e_name ) ) { + free( e->e_name.bv_val ); + BER_BVZERO( &e->e_name ); + } + if ( !BER_BVISNULL( &e->e_nname ) ) { + free( e->e_nname.bv_val ); + BER_BVZERO( &e->e_nname ); + } + + if ( !BER_BVISNULL( &e->e_bv ) ) { + free( e->e_bv.bv_val ); + BER_BVZERO( &e->e_bv ); + } + + /* free attributes */ + if ( e->e_attrs ) { + attrs_free( e->e_attrs ); + e->e_attrs = NULL; + } + + e->e_ocflags = 0; +} + +void +entry_free( Entry *e ) +{ + entry_clean( e ); + + ldap_pvt_thread_mutex_lock( &entry_mutex ); + e->e_private = entry_list; + entry_list = e; + ldap_pvt_thread_mutex_unlock( &entry_mutex ); +} + +/* These parameters work well on AMD64 */ +#if 0 +#define STRIDE 8 +#define STRIPE 5 +#else +#define STRIDE 1 +#define STRIPE 1 +#endif +#define STRIDE_FACTOR (STRIDE*STRIPE) + +int +entry_prealloc( int num ) +{ + Entry *e, **prev, *tmp; + slap_list *s; + int i, j; + + if (!num) return 0; + +#if STRIDE_FACTOR > 1 + /* Round up to our stride factor */ + num += STRIDE_FACTOR-1; + num /= STRIDE_FACTOR; + num *= STRIDE_FACTOR; +#endif + + s = ch_calloc( 1, sizeof(slap_list) + num * sizeof(Entry)); + s->next = entry_chunks; + entry_chunks = s; + + prev = &tmp; + for (i=0; i<STRIPE; i++) { + e = (Entry *)(s+1); + e += i; + for (j=i; j<num; j+= STRIDE) { + *prev = e; + prev = (Entry **)&e->e_private; + e += STRIDE; + } + } + *prev = entry_list; + entry_list = (Entry *)(s+1); + + return 0; +} + +Entry * +entry_alloc( void ) +{ + Entry *e; + + ldap_pvt_thread_mutex_lock( &entry_mutex ); + if ( !entry_list ) + entry_prealloc( CHUNK_SIZE ); + e = entry_list; + entry_list = e->e_private; + e->e_private = NULL; + ldap_pvt_thread_mutex_unlock( &entry_mutex ); + + return e; +} + + +/* + * These routines are used only by Backend. + * + * the Entry has three entry points (ways to find things): + * + * by entry e.g., if you already have an entry from the cache + * and want to delete it. (really by entry ptr) + * by dn e.g., when looking for the base object of a search + * by id e.g., for search candidates + * + * these correspond to three different avl trees that are maintained. + */ + +int +entry_cmp( Entry *e1, Entry *e2 ) +{ + return SLAP_PTRCMP( e1, e2 ); +} + +int +entry_dn_cmp( const void *v_e1, const void *v_e2 ) +{ + /* compare their normalized UPPERCASED dn's */ + const Entry *e1 = v_e1, *e2 = v_e2; + + return ber_bvcmp( &e1->e_nname, &e2->e_nname ); +} + +int +entry_id_cmp( const void *v_e1, const void *v_e2 ) +{ + const Entry *e1 = v_e1, *e2 = v_e2; + return( e1->e_id < e2->e_id ? -1 : (e1->e_id > e2->e_id ? 1 : 0) ); +} + +/* This is like a ber_len */ +#define entry_lenlen(l) (((l) < 0x80) ? 1 : ((l) < 0x100) ? 2 : \ + ((l) < 0x10000) ? 3 : ((l) < 0x1000000) ? 4 : 5) + +static void +entry_putlen(unsigned char **buf, ber_len_t len) +{ + ber_len_t lenlen = entry_lenlen(len); + + if (lenlen == 1) { + **buf = (unsigned char) len; + } else { + int i; + **buf = 0x80 | ((unsigned char) lenlen - 1); + for (i=lenlen-1; i>0; i--) { + (*buf)[i] = (unsigned char) len; + len >>= 8; + } + } + *buf += lenlen; +} + +static ber_len_t +entry_getlen(unsigned char **buf) +{ + ber_len_t len; + int i; + + len = *(*buf)++; + if (len <= 0x7f) + return len; + i = len & 0x7f; + len = 0; + for (;i > 0; i--) { + len <<= 8; + len |= *(*buf)++; + } + return len; +} + +/* Count up the sizes of the components of an entry */ +void entry_partsize(Entry *e, ber_len_t *plen, + int *pnattrs, int *pnvals, int norm) +{ + ber_len_t len, dnlen, ndnlen; + int i, nat = 0, nval = 0; + Attribute *a; + + dnlen = e->e_name.bv_len; + len = dnlen + 1; /* trailing NUL byte */ + len += entry_lenlen(dnlen); + if (norm) { + ndnlen = e->e_nname.bv_len; + len += ndnlen + 1; + len += entry_lenlen(ndnlen); + } + for (a=e->e_attrs; a; a=a->a_next) { + /* For AttributeDesc, we only store the attr name */ + nat++; + len += a->a_desc->ad_cname.bv_len+1; + len += entry_lenlen(a->a_desc->ad_cname.bv_len); + for (i=0; a->a_vals[i].bv_val; i++) { + nval++; + len += a->a_vals[i].bv_len + 1; + len += entry_lenlen(a->a_vals[i].bv_len); + } + len += entry_lenlen(i); + nval++; /* empty berval at end */ + if (norm && a->a_nvals != a->a_vals) { + for (i=0; a->a_nvals[i].bv_val; i++) { + nval++; + len += a->a_nvals[i].bv_len + 1; + len += entry_lenlen(a->a_nvals[i].bv_len); + } + len += entry_lenlen(i); /* i nvals */ + nval++; + } else { + len += entry_lenlen(0); /* 0 nvals */ + } + } + len += entry_lenlen(nat); + len += entry_lenlen(nval); + *plen = len; + *pnattrs = nat; + *pnvals = nval; +} + +/* Add up the size of the entry for a flattened buffer */ +ber_len_t entry_flatsize(Entry *e, int norm) +{ + ber_len_t len; + int nattrs, nvals; + + entry_partsize(e, &len, &nattrs, &nvals, norm); + len += sizeof(Entry) + (nattrs * sizeof(Attribute)) + + (nvals * sizeof(struct berval)); + return len; +} + +/* Flatten an Entry into a buffer. The buffer is filled with just the + * strings/bervals of all the entry components. Each field is preceded + * by its length, encoded the way ber_put_len works. Every field is NUL + * terminated. The entire buffer size is precomputed so that a single + * malloc can be performed. The entry size is also recorded, + * to aid in entry_decode. + */ +int entry_encode(Entry *e, struct berval *bv) +{ + ber_len_t len, dnlen, ndnlen, i; + int nattrs, nvals; + Attribute *a; + unsigned char *ptr; + + Debug( LDAP_DEBUG_TRACE, "=> entry_encode(0x%08lx): %s\n", + (long) e->e_id, e->e_dn, 0 ); + + dnlen = e->e_name.bv_len; + ndnlen = e->e_nname.bv_len; + + entry_partsize( e, &len, &nattrs, &nvals, 1 ); + + bv->bv_len = len; + bv->bv_val = ch_malloc(len); + ptr = (unsigned char *)bv->bv_val; + entry_putlen(&ptr, nattrs); + entry_putlen(&ptr, nvals); + entry_putlen(&ptr, dnlen); + AC_MEMCPY(ptr, e->e_dn, dnlen); + ptr += dnlen; + *ptr++ = '\0'; + entry_putlen(&ptr, ndnlen); + AC_MEMCPY(ptr, e->e_ndn, ndnlen); + ptr += ndnlen; + *ptr++ = '\0'; + + for (a=e->e_attrs; a; a=a->a_next) { + entry_putlen(&ptr, a->a_desc->ad_cname.bv_len); + AC_MEMCPY(ptr, a->a_desc->ad_cname.bv_val, + a->a_desc->ad_cname.bv_len); + ptr += a->a_desc->ad_cname.bv_len; + *ptr++ = '\0'; + if (a->a_vals) { + for (i=0; a->a_vals[i].bv_val; i++); + assert( i == a->a_numvals ); + entry_putlen(&ptr, i); + for (i=0; a->a_vals[i].bv_val; i++) { + entry_putlen(&ptr, a->a_vals[i].bv_len); + AC_MEMCPY(ptr, a->a_vals[i].bv_val, + a->a_vals[i].bv_len); + ptr += a->a_vals[i].bv_len; + *ptr++ = '\0'; + } + if (a->a_nvals != a->a_vals) { + entry_putlen(&ptr, i); + for (i=0; a->a_nvals[i].bv_val; i++) { + entry_putlen(&ptr, a->a_nvals[i].bv_len); + AC_MEMCPY(ptr, a->a_nvals[i].bv_val, + a->a_nvals[i].bv_len); + ptr += a->a_nvals[i].bv_len; + *ptr++ = '\0'; + } + } else { + entry_putlen(&ptr, 0); + } + } + } + + Debug( LDAP_DEBUG_TRACE, "<= entry_encode(0x%08lx): %s\n", + (long) e->e_id, e->e_dn, 0 ); + + return 0; +} + +/* Retrieve an Entry that was stored using entry_encode above. + * First entry_header must be called to decode the size of the entry. + * Then a single block of memory must be malloc'd to accomodate the + * bervals and the bulk data. Next the bulk data is retrieved from + * the DB and parsed by entry_decode. + * + * Note: everything is stored in a single contiguous block, so + * you can not free individual attributes or names from this + * structure. Attempting to do so will likely corrupt memory. + */ +int entry_header(EntryHeader *eh) +{ + unsigned char *ptr = (unsigned char *)eh->bv.bv_val; + + /* Some overlays can create empty entries + * so don't check for zeros here. + */ + eh->nattrs = entry_getlen(&ptr); + eh->nvals = entry_getlen(&ptr); + eh->data = (char *)ptr; + return LDAP_SUCCESS; +} + +int +entry_decode_dn( EntryHeader *eh, struct berval *dn, struct berval *ndn ) +{ + int i; + unsigned char *ptr = (unsigned char *)eh->bv.bv_val; + + assert( dn != NULL || ndn != NULL ); + + ptr = (unsigned char *)eh->data; + i = entry_getlen(&ptr); + if ( dn != NULL ) { + dn->bv_val = (char *) ptr; + dn->bv_len = i; + } + + if ( ndn != NULL ) { + ptr += i + 1; + i = entry_getlen(&ptr); + ndn->bv_val = (char *) ptr; + ndn->bv_len = i; + } + + Debug( LDAP_DEBUG_TRACE, + "entry_decode_dn: \"%s\"\n", + dn ? dn->bv_val : ndn->bv_val, 0, 0 ); + + return 0; +} + +#ifdef SLAP_ZONE_ALLOC +int entry_decode(EntryHeader *eh, Entry **e, void *ctx) +#else +int entry_decode(EntryHeader *eh, Entry **e) +#endif +{ + int i, j, nattrs, nvals; + int rc; + Attribute *a; + Entry *x; + const char *text; + AttributeDescription *ad; + unsigned char *ptr = (unsigned char *)eh->bv.bv_val; + BerVarray bptr; + + nattrs = eh->nattrs; + nvals = eh->nvals; + x = entry_alloc(); + x->e_attrs = attrs_alloc( nattrs ); + ptr = (unsigned char *)eh->data; + i = entry_getlen(&ptr); + x->e_name.bv_val = (char *) ptr; + x->e_name.bv_len = i; + ptr += i+1; + i = entry_getlen(&ptr); + x->e_nname.bv_val = (char *) ptr; + x->e_nname.bv_len = i; + ptr += i+1; + Debug( LDAP_DEBUG_TRACE, + "entry_decode: \"%s\"\n", + x->e_dn, 0, 0 ); + x->e_bv = eh->bv; + + a = x->e_attrs; + bptr = (BerVarray)eh->bv.bv_val; + + while ((i = entry_getlen(&ptr))) { + struct berval bv; + bv.bv_len = i; + bv.bv_val = (char *) ptr; + ad = NULL; + rc = slap_bv2ad( &bv, &ad, &text ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= entry_decode: str2ad(%s): %s\n", ptr, text, 0 ); + rc = slap_bv2undef_ad( &bv, &ad, &text, 0 ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "<= entry_decode: slap_str2undef_ad(%s): %s\n", + ptr, text, 0 ); + return rc; + } + } + ptr += i + 1; + a->a_desc = ad; + a->a_flags = SLAP_ATTR_DONT_FREE_DATA | SLAP_ATTR_DONT_FREE_VALS; + j = entry_getlen(&ptr); + a->a_numvals = j; + a->a_vals = bptr; + + while (j) { + i = entry_getlen(&ptr); + bptr->bv_len = i; + bptr->bv_val = (char *)ptr; + ptr += i+1; + bptr++; + j--; + } + bptr->bv_val = NULL; + bptr->bv_len = 0; + bptr++; + + j = entry_getlen(&ptr); + if (j) { + a->a_nvals = bptr; + while (j) { + i = entry_getlen(&ptr); + bptr->bv_len = i; + bptr->bv_val = (char *)ptr; + ptr += i+1; + bptr++; + j--; + } + bptr->bv_val = NULL; + bptr->bv_len = 0; + bptr++; + } else { + a->a_nvals = a->a_vals; + } + /* FIXME: This is redundant once a sorted entry is saved into the DB */ + if ( a->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL ) { + rc = slap_sort_vals( (Modifications *)a, &text, &j, NULL ); + if ( rc == LDAP_SUCCESS ) { + a->a_flags |= SLAP_ATTR_SORTED_VALS; + } else if ( rc == LDAP_TYPE_OR_VALUE_EXISTS ) { + /* should never happen */ + Debug( LDAP_DEBUG_ANY, + "entry_decode: attributeType %s value #%d provided more than once\n", + a->a_desc->ad_cname.bv_val, j, 0 ); + return rc; + } + } + a = a->a_next; + nattrs--; + if ( !nattrs ) + break; + } + + Debug(LDAP_DEBUG_TRACE, "<= entry_decode(%s)\n", + x->e_dn, 0, 0 ); + *e = x; + return 0; +} + +Entry * +entry_dup2( Entry *dest, Entry *source ) +{ + assert( dest != NULL ); + assert( source != NULL ); + + assert( dest->e_private == NULL ); + + dest->e_id = source->e_id; + ber_dupbv( &dest->e_name, &source->e_name ); + ber_dupbv( &dest->e_nname, &source->e_nname ); + dest->e_attrs = attrs_dup( source->e_attrs ); + dest->e_ocflags = source->e_ocflags; + + return dest; +} + +Entry * +entry_dup( Entry *e ) +{ + return entry_dup2( entry_alloc(), e ); +} + +#if 1 +/* Duplicates an entry using a single malloc. Saves CPU time, increases + * heap usage because a single large malloc is harder to satisfy than + * lots of small ones, and the freed space isn't as easily reusable. + * + * Probably not worth using this function. + */ +Entry *entry_dup_bv( Entry *e ) +{ + ber_len_t len; + int nattrs, nvals; + Entry *ret; + struct berval *bvl; + char *ptr; + Attribute *src, *dst; + + ret = entry_alloc(); + + entry_partsize(e, &len, &nattrs, &nvals, 1); + ret->e_id = e->e_id; + ret->e_attrs = attrs_alloc( nattrs ); + ret->e_ocflags = e->e_ocflags; + ret->e_bv.bv_len = len + nvals * sizeof(struct berval); + ret->e_bv.bv_val = ch_malloc( ret->e_bv.bv_len ); + + bvl = (struct berval *)ret->e_bv.bv_val; + ptr = (char *)(bvl + nvals); + + ret->e_name.bv_len = e->e_name.bv_len; + ret->e_name.bv_val = ptr; + AC_MEMCPY( ptr, e->e_name.bv_val, e->e_name.bv_len ); + ptr += e->e_name.bv_len; + *ptr++ = '\0'; + + ret->e_nname.bv_len = e->e_nname.bv_len; + ret->e_nname.bv_val = ptr; + AC_MEMCPY( ptr, e->e_nname.bv_val, e->e_nname.bv_len ); + ptr += e->e_name.bv_len; + *ptr++ = '\0'; + + dst = ret->e_attrs; + for (src = e->e_attrs; src; src=src->a_next,dst=dst->a_next ) { + int i; + dst->a_desc = src->a_desc; + dst->a_flags = SLAP_ATTR_DONT_FREE_DATA | SLAP_ATTR_DONT_FREE_VALS; + dst->a_vals = bvl; + dst->a_numvals = src->a_numvals; + for ( i=0; src->a_vals[i].bv_val; i++ ) { + bvl->bv_len = src->a_vals[i].bv_len; + bvl->bv_val = ptr; + AC_MEMCPY( ptr, src->a_vals[i].bv_val, bvl->bv_len ); + ptr += bvl->bv_len; + *ptr++ = '\0'; + bvl++; + } + BER_BVZERO(bvl); + bvl++; + if ( src->a_vals != src->a_nvals ) { + dst->a_nvals = bvl; + for ( i=0; src->a_nvals[i].bv_val; i++ ) { + bvl->bv_len = src->a_nvals[i].bv_len; + bvl->bv_val = ptr; + AC_MEMCPY( ptr, src->a_nvals[i].bv_val, bvl->bv_len ); + ptr += bvl->bv_len; + *ptr++ = '\0'; + bvl++; + } + BER_BVZERO(bvl); + bvl++; + } + } + return ret; +} +#endif diff --git a/servers/slapd/extended.c b/servers/slapd/extended.c new file mode 100644 index 0000000..5efafe1 --- /dev/null +++ b/servers/slapd/extended.c @@ -0,0 +1,406 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +/* + * LDAPv3 Extended Operation Request + * ExtendedRequest ::= [APPLICATION 23] SEQUENCE { + * requestName [0] LDAPOID, + * requestValue [1] OCTET STRING OPTIONAL + * } + * + * LDAPv3 Extended Operation Response + * ExtendedResponse ::= [APPLICATION 24] SEQUENCE { + * COMPONENTS OF LDAPResult, + * responseName [10] LDAPOID OPTIONAL, + * response [11] OCTET STRING OPTIONAL + * } + * + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "lber_pvt.h" + +static struct extop_list { + struct extop_list *next; + struct berval oid; + slap_mask_t flags; + SLAP_EXTOP_MAIN_FN *ext_main; +} *supp_ext_list = NULL; + +static SLAP_EXTOP_MAIN_FN whoami_extop; + +/* This list of built-in extops is for extops that are not part + * of backends or in external modules. Essentially, this is + * just a way to get built-in extops onto the extop list without + * having a separate init routine for each built-in extop. + */ +static struct { + const struct berval *oid; + slap_mask_t flags; + SLAP_EXTOP_MAIN_FN *ext_main; +} builtin_extops[] = { +#ifdef LDAP_X_TXN + { &slap_EXOP_TXN_START, 0, txn_start_extop }, + { &slap_EXOP_TXN_END, 0, txn_end_extop }, +#endif + { &slap_EXOP_CANCEL, 0, cancel_extop }, + { &slap_EXOP_WHOAMI, 0, whoami_extop }, + { &slap_EXOP_MODIFY_PASSWD, SLAP_EXOP_WRITES, passwd_extop }, + { NULL, 0, NULL } +}; + + +static struct extop_list *find_extop( + struct extop_list *list, struct berval *oid ); + +struct berval * +get_supported_extop (int index) +{ + struct extop_list *ext; + + /* linear scan is slow, but this way doesn't force a + * big change on root_dse.c, where this routine is used. + */ + for (ext = supp_ext_list; ext != NULL && --index >= 0; ext = ext->next) { + ; /* empty */ + } + + if (ext == NULL) return NULL; + + return &ext->oid; +} + + +int exop_root_dse_info( Entry *e ) +{ + AttributeDescription *ad_supportedExtension + = slap_schema.si_ad_supportedExtension; + struct berval vals[2]; + struct extop_list *ext; + + vals[1].bv_val = NULL; + vals[1].bv_len = 0; + + for (ext = supp_ext_list; ext != NULL; ext = ext->next) { + if( ext->flags & SLAP_EXOP_HIDE ) continue; + + vals[0] = ext->oid; + + if( attr_merge( e, ad_supportedExtension, vals, NULL ) ) { + return LDAP_OTHER; + } + } + + return LDAP_SUCCESS; +} + +int +do_extended( + Operation *op, + SlapReply *rs +) +{ + struct berval reqdata = {0, NULL}; + ber_len_t len; + + Debug( LDAP_DEBUG_TRACE, "%s do_extended\n", + op->o_log_prefix, 0, 0 ); + + if( op->o_protocol < LDAP_VERSION3 ) { + Debug( LDAP_DEBUG_ANY, "%s do_extended: protocol version (%d) too low\n", + op->o_log_prefix, op->o_protocol, 0 ); + send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "requires LDAPv3" ); + rs->sr_err = SLAPD_DISCONNECT; + goto done; + } + + if ( ber_scanf( op->o_ber, "{m" /*}*/, &op->ore_reqoid ) == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, "%s do_extended: ber_scanf failed\n", + op->o_log_prefix, 0, 0 ); + send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" ); + rs->sr_err = SLAPD_DISCONNECT; + goto done; + } + + if( ber_peek_tag( op->o_ber, &len ) == LDAP_TAG_EXOP_REQ_VALUE ) { + if( ber_scanf( op->o_ber, "m", &reqdata ) == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, "%s do_extended: ber_scanf failed\n", + op->o_log_prefix, 0, 0 ); + send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" ); + rs->sr_err = SLAPD_DISCONNECT; + goto done; + } + } + + if( get_ctrls( op, rs, 1 ) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s do_extended: get_ctrls failed\n", + op->o_log_prefix, 0, 0 ); + return rs->sr_err; + } + + Statslog( LDAP_DEBUG_STATS, "%s EXT oid=%s\n", + op->o_log_prefix, op->ore_reqoid.bv_val, 0, 0, 0 ); + + /* check for controls inappropriate for all extended operations */ + if( get_manageDSAit( op ) == SLAP_CONTROL_CRITICAL ) { + send_ldap_error( op, rs, + LDAP_UNAVAILABLE_CRITICAL_EXTENSION, + "manageDSAit control inappropriate" ); + goto done; + } + + /* FIXME: temporary? */ + if ( reqdata.bv_val ) { + op->ore_reqdata = &reqdata; + } + + op->o_bd = frontendDB; + rs->sr_err = frontendDB->be_extended( op, rs ); + + /* clean up in case some overlay set them? */ + if ( !BER_BVISNULL( &op->o_req_ndn ) ) { + if ( !BER_BVISNULL( &op->o_req_dn ) + && op->o_req_ndn.bv_val != op->o_req_dn.bv_val ) + { + op->o_tmpfree( op->o_req_dn.bv_val, op->o_tmpmemctx ); + } + op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &op->o_req_dn ); + BER_BVZERO( &op->o_req_ndn ); + } + +done: + return rs->sr_err; +} + +int +fe_extended( Operation *op, SlapReply *rs ) +{ + struct extop_list *ext = NULL; + struct berval reqdata = BER_BVNULL; + + if (op->ore_reqdata) { + reqdata = *op->ore_reqdata; + } + + ext = find_extop(supp_ext_list, &op->ore_reqoid ); + if ( ext == NULL ) { + Debug( LDAP_DEBUG_ANY, "%s do_extended: unsupported operation \"%s\"\n", + op->o_log_prefix, op->ore_reqoid.bv_val, 0 ); + send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR, + "unsupported extended operation" ); + goto done; + } + + op->ore_flags = ext->flags; + + Debug( LDAP_DEBUG_ARGS, "do_extended: oid=%s\n", + op->ore_reqoid.bv_val, 0 ,0 ); + + { /* start of OpenLDAP extended operation */ + BackendDB *bd = op->o_bd; + + rs->sr_err = (ext->ext_main)( op, rs ); + + if( rs->sr_err != SLAPD_ABANDON ) { + if ( rs->sr_err == LDAP_REFERRAL && rs->sr_ref == NULL ) { + rs->sr_ref = referral_rewrite( default_referral, + NULL, NULL, LDAP_SCOPE_DEFAULT ); + if ( !rs->sr_ref ) rs->sr_ref = default_referral; + if ( !rs->sr_ref ) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "referral missing"; + } + } + + if ( op->o_bd == NULL ) + op->o_bd = bd; + send_ldap_extended( op, rs ); + + if ( rs->sr_ref != default_referral ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + } + + if ( rs->sr_rspoid != NULL ) { + free( (char *)rs->sr_rspoid ); + rs->sr_rspoid = NULL; + } + + if ( rs->sr_rspdata != NULL ) { + ber_bvfree( rs->sr_rspdata ); + rs->sr_rspdata = NULL; + } + } /* end of OpenLDAP extended operation */ + +done:; + return rs->sr_err; +} + +int +load_extop2( + const struct berval *ext_oid, + slap_mask_t ext_flags, + SLAP_EXTOP_MAIN_FN *ext_main, + unsigned flags ) +{ + struct berval oidm = BER_BVNULL; + struct extop_list *ext; + int insertme = 0; + + if ( !ext_main ) { + return -1; + } + + if ( ext_oid == NULL || BER_BVISNULL( ext_oid ) || + BER_BVISEMPTY( ext_oid ) ) + { + return -1; + } + + if ( numericoidValidate( NULL, (struct berval *)ext_oid ) != + LDAP_SUCCESS ) + { + oidm.bv_val = oidm_find( ext_oid->bv_val ); + if ( oidm.bv_val == NULL ) { + return -1; + } + oidm.bv_len = strlen( oidm.bv_val ); + ext_oid = &oidm; + } + + for ( ext = supp_ext_list; ext; ext = ext->next ) { + if ( bvmatch( ext_oid, &ext->oid ) ) { + if ( flags == 1 ) { + break; + } + return -1; + } + } + + if ( flags == 0 || ext == NULL ) { + ext = ch_calloc( 1, sizeof(struct extop_list) + ext_oid->bv_len + 1 ); + if ( ext == NULL ) { + return(-1); + } + + ext->oid.bv_val = (char *)(ext + 1); + AC_MEMCPY( ext->oid.bv_val, ext_oid->bv_val, ext_oid->bv_len ); + ext->oid.bv_len = ext_oid->bv_len; + ext->oid.bv_val[ext->oid.bv_len] = '\0'; + + insertme = 1; + } + + ext->flags = ext_flags; + ext->ext_main = ext_main; + + if ( insertme ) { + ext->next = supp_ext_list; + supp_ext_list = ext; + } + + return(0); +} + +int +extops_init (void) +{ + int i; + + for ( i = 0; builtin_extops[i].oid != NULL; i++ ) { + load_extop( (struct berval *)builtin_extops[i].oid, + builtin_extops[i].flags, + builtin_extops[i].ext_main ); + } + return(0); +} + +int +extops_kill (void) +{ + struct extop_list *ext; + + /* we allocated the memory, so we have to free it, too. */ + while ((ext = supp_ext_list) != NULL) { + supp_ext_list = ext->next; + ch_free(ext); + } + return(0); +} + +static struct extop_list * +find_extop( struct extop_list *list, struct berval *oid ) +{ + struct extop_list *ext; + + for (ext = list; ext; ext = ext->next) { + if (bvmatch(&ext->oid, oid)) + return(ext); + } + return(NULL); +} + + +const struct berval slap_EXOP_WHOAMI = BER_BVC(LDAP_EXOP_WHO_AM_I); + +static int +whoami_extop ( + Operation *op, + SlapReply *rs ) +{ + struct berval *bv; + + if ( op->ore_reqdata != NULL ) { + /* no request data should be provided */ + rs->sr_text = "no request data expected"; + return LDAP_PROTOCOL_ERROR; + } + + Statslog( LDAP_DEBUG_STATS, "%s WHOAMI\n", + op->o_log_prefix, 0, 0, 0, 0 ); + + op->o_bd = op->o_conn->c_authz_backend; + if( backend_check_restrictions( op, rs, + (struct berval *)&slap_EXOP_WHOAMI ) != LDAP_SUCCESS ) + { + return rs->sr_err; + } + + bv = (struct berval *) ch_malloc( sizeof(struct berval) ); + if( op->o_dn.bv_len ) { + bv->bv_len = op->o_dn.bv_len + STRLENOF( "dn:" ); + bv->bv_val = ch_malloc( bv->bv_len + 1 ); + AC_MEMCPY( bv->bv_val, "dn:", STRLENOF( "dn:" ) ); + AC_MEMCPY( &bv->bv_val[STRLENOF( "dn:" )], op->o_dn.bv_val, + op->o_dn.bv_len ); + bv->bv_val[bv->bv_len] = '\0'; + + } else { + bv->bv_len = 0; + bv->bv_val = NULL; + } + + rs->sr_rspdata = bv; + return LDAP_SUCCESS; +} diff --git a/servers/slapd/filter.c b/servers/slapd/filter.c new file mode 100644 index 0000000..6decbd9 --- /dev/null +++ b/servers/slapd/filter.c @@ -0,0 +1,1454 @@ +/* filter.c - routines for parsing and dealing with filters */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "lutil.h" + +const Filter *slap_filter_objectClass_pres; +const struct berval *slap_filterstr_objectClass_pres; + +#ifndef SLAPD_MAX_FILTER_DEPTH +#define SLAPD_MAX_FILTER_DEPTH 5000 +#endif + +static int get_filter_list( + Operation *op, + BerElement *ber, + Filter **f, + const char **text, + int depth ); + +static int get_ssa( + Operation *op, + BerElement *ber, + Filter *f, + const char **text ); + +static void simple_vrFilter2bv( + Operation *op, + ValuesReturnFilter *f, + struct berval *fstr ); + +static int get_simple_vrFilter( + Operation *op, + BerElement *ber, + ValuesReturnFilter **f, + const char **text ); + +int +filter_init( void ) +{ + static Filter filter_objectClass_pres = { LDAP_FILTER_PRESENT }; + static struct berval filterstr_objectClass_pres = BER_BVC("(objectClass=*)"); + + filter_objectClass_pres.f_desc = slap_schema.si_ad_objectClass; + + slap_filter_objectClass_pres = &filter_objectClass_pres; + slap_filterstr_objectClass_pres = &filterstr_objectClass_pres; + + return 0; +} + +void +filter_destroy( void ) +{ + return; +} + +static int +get_filter0( + Operation *op, + BerElement *ber, + Filter **filt, + const char **text, + int depth ) +{ + ber_tag_t tag; + ber_len_t len; + int err; + Filter f; + + Debug( LDAP_DEBUG_FILTER, "begin get_filter\n", 0, 0, 0 ); + /* + * A filter looks like this coming in: + * Filter ::= CHOICE { + * and [0] SET OF Filter, + * or [1] SET OF Filter, + * not [2] Filter, + * equalityMatch [3] AttributeValueAssertion, + * substrings [4] SubstringFilter, + * greaterOrEqual [5] AttributeValueAssertion, + * lessOrEqual [6] AttributeValueAssertion, + * present [7] AttributeType, + * approxMatch [8] AttributeValueAssertion, + * extensibleMatch [9] MatchingRuleAssertion + * } + * + * SubstringFilter ::= SEQUENCE { + * type AttributeType, + * SEQUENCE OF CHOICE { + * initial [0] IA5String, + * any [1] IA5String, + * final [2] IA5String + * } + * } + * + * MatchingRuleAssertion ::= SEQUENCE { + * matchingRule [1] MatchingRuleId OPTIONAL, + * type [2] AttributeDescription OPTIONAL, + * matchValue [3] AssertionValue, + * dnAttributes [4] BOOLEAN DEFAULT FALSE + * } + * + */ + + if( depth > SLAPD_MAX_FILTER_DEPTH ) { + *text = "filter nested too deeply"; + return SLAPD_DISCONNECT; + } + + tag = ber_peek_tag( ber, &len ); + + if( tag == LBER_ERROR ) { + *text = "error decoding filter"; + return SLAPD_DISCONNECT; + } + + err = LDAP_SUCCESS; + + f.f_next = NULL; + f.f_choice = tag; + + switch ( f.f_choice ) { + case LDAP_FILTER_EQUALITY: + Debug( LDAP_DEBUG_FILTER, "EQUALITY\n", 0, 0, 0 ); + err = get_ava( op, ber, &f, SLAP_MR_EQUALITY, text ); + if ( err != LDAP_SUCCESS ) { + break; + } + + assert( f.f_ava != NULL ); + break; + + case LDAP_FILTER_SUBSTRINGS: + Debug( LDAP_DEBUG_FILTER, "SUBSTRINGS\n", 0, 0, 0 ); + err = get_ssa( op, ber, &f, text ); + if( err != LDAP_SUCCESS ) { + break; + } + assert( f.f_sub != NULL ); + break; + + case LDAP_FILTER_GE: + Debug( LDAP_DEBUG_FILTER, "GE\n", 0, 0, 0 ); + err = get_ava( op, ber, &f, SLAP_MR_ORDERING, text ); + if ( err != LDAP_SUCCESS ) { + break; + } + assert( f.f_ava != NULL ); + break; + + case LDAP_FILTER_LE: + Debug( LDAP_DEBUG_FILTER, "LE\n", 0, 0, 0 ); + err = get_ava( op, ber, &f, SLAP_MR_ORDERING, text ); + if ( err != LDAP_SUCCESS ) { + break; + } + assert( f.f_ava != NULL ); + break; + + case LDAP_FILTER_PRESENT: { + struct berval type; + + Debug( LDAP_DEBUG_FILTER, "PRESENT\n", 0, 0, 0 ); + if ( ber_scanf( ber, "m", &type ) == LBER_ERROR ) { + err = SLAPD_DISCONNECT; + *text = "error decoding filter"; + break; + } + + f.f_desc = NULL; + err = slap_bv2ad( &type, &f.f_desc, text ); + + if( err != LDAP_SUCCESS ) { + f.f_choice |= SLAPD_FILTER_UNDEFINED; + err = slap_bv2undef_ad( &type, &f.f_desc, text, + SLAP_AD_PROXIED|SLAP_AD_NOINSERT ); + + if ( err != LDAP_SUCCESS ) { + /* unrecognized attribute description or other error */ + Debug( LDAP_DEBUG_ANY, + "get_filter: conn %lu unknown attribute " + "type=%s (%d)\n", + op->o_connid, type.bv_val, err ); + + err = LDAP_SUCCESS; + f.f_desc = slap_bv2tmp_ad( &type, op->o_tmpmemctx ); + } + *text = NULL; + } + + assert( f.f_desc != NULL ); + } break; + + case LDAP_FILTER_APPROX: + Debug( LDAP_DEBUG_FILTER, "APPROX\n", 0, 0, 0 ); + err = get_ava( op, ber, &f, SLAP_MR_EQUALITY_APPROX, text ); + if ( err != LDAP_SUCCESS ) { + break; + } + assert( f.f_ava != NULL ); + break; + + case LDAP_FILTER_AND: + Debug( LDAP_DEBUG_FILTER, "AND\n", 0, 0, 0 ); + err = get_filter_list( op, ber, &f.f_and, text, depth+1 ); + if ( err != LDAP_SUCCESS ) { + break; + } + if ( f.f_and == NULL ) { + f.f_choice = SLAPD_FILTER_COMPUTED; + f.f_result = LDAP_COMPARE_TRUE; + } + /* no assert - list could be empty */ + break; + + case LDAP_FILTER_OR: + Debug( LDAP_DEBUG_FILTER, "OR\n", 0, 0, 0 ); + err = get_filter_list( op, ber, &f.f_or, text, depth+1 ); + if ( err != LDAP_SUCCESS ) { + break; + } + if ( f.f_or == NULL ) { + f.f_choice = SLAPD_FILTER_COMPUTED; + f.f_result = LDAP_COMPARE_FALSE; + } + /* no assert - list could be empty */ + break; + + case LDAP_FILTER_NOT: + Debug( LDAP_DEBUG_FILTER, "NOT\n", 0, 0, 0 ); + (void) ber_skip_tag( ber, &len ); + err = get_filter0( op, ber, &f.f_not, text, depth+1 ); + if ( err != LDAP_SUCCESS ) { + break; + } + + assert( f.f_not != NULL ); + if ( f.f_not->f_choice == SLAPD_FILTER_COMPUTED ) { + int fresult = f.f_not->f_result; + f.f_choice = SLAPD_FILTER_COMPUTED; + op->o_tmpfree( f.f_not, op->o_tmpmemctx ); + f.f_not = NULL; + + switch( fresult ) { + case LDAP_COMPARE_TRUE: + f.f_result = LDAP_COMPARE_FALSE; + break; + case LDAP_COMPARE_FALSE: + f.f_result = LDAP_COMPARE_TRUE; + break; + default: ; + /* (!Undefined) is Undefined */ + } + } + break; + + case LDAP_FILTER_EXT: + Debug( LDAP_DEBUG_FILTER, "EXTENSIBLE\n", 0, 0, 0 ); + + err = get_mra( op, ber, &f, text ); + if ( err != LDAP_SUCCESS ) { + break; + } + + assert( f.f_mra != NULL ); + break; + + default: + (void) ber_scanf( ber, "x" ); /* skip the element */ + Debug( LDAP_DEBUG_ANY, "get_filter: unknown filter type=%lu\n", + f.f_choice, 0, 0 ); + f.f_choice = SLAPD_FILTER_COMPUTED; + f.f_result = SLAPD_COMPARE_UNDEFINED; + break; + } + + if( err != LDAP_SUCCESS && err != SLAPD_DISCONNECT ) { + /* ignore error */ + *text = NULL; + f.f_choice = SLAPD_FILTER_COMPUTED; + f.f_result = SLAPD_COMPARE_UNDEFINED; + err = LDAP_SUCCESS; + } + + if ( err == LDAP_SUCCESS ) { + *filt = op->o_tmpalloc( sizeof(f), op->o_tmpmemctx ); + **filt = f; + } + + Debug( LDAP_DEBUG_FILTER, "end get_filter %d\n", err, 0, 0 ); + + return( err ); +} + +int +get_filter( + Operation *op, + BerElement *ber, + Filter **filt, + const char **text ) +{ + return get_filter0( op, ber, filt, text, 0 ); +} + + +static int +get_filter_list( Operation *op, BerElement *ber, + Filter **f, + const char **text, + int depth ) +{ + Filter **new; + int err; + ber_tag_t tag; + ber_len_t len; + char *last; + + Debug( LDAP_DEBUG_FILTER, "begin get_filter_list\n", 0, 0, 0 ); + new = f; + for ( tag = ber_first_element( ber, &len, &last ); + tag != LBER_DEFAULT; + tag = ber_next_element( ber, &len, last ) ) + { + err = get_filter0( op, ber, new, text, depth ); + if ( err != LDAP_SUCCESS ) + return( err ); + new = &(*new)->f_next; + } + *new = NULL; + + Debug( LDAP_DEBUG_FILTER, "end get_filter_list\n", 0, 0, 0 ); + return( LDAP_SUCCESS ); +} + +static int +get_ssa( + Operation *op, + BerElement *ber, + Filter *f, + const char **text ) +{ + ber_tag_t tag; + ber_len_t len; + int rc; + struct berval desc, value, nvalue; + char *last; + SubstringsAssertion ssa; + + *text = "error decoding filter"; + + Debug( LDAP_DEBUG_FILTER, "begin get_ssa\n", 0, 0, 0 ); + if ( ber_scanf( ber, "{m" /*}*/, &desc ) == LBER_ERROR ) { + return SLAPD_DISCONNECT; + } + + *text = NULL; + + ssa.sa_desc = NULL; + ssa.sa_initial.bv_val = NULL; + ssa.sa_any = NULL; + ssa.sa_final.bv_val = NULL; + + rc = slap_bv2ad( &desc, &ssa.sa_desc, text ); + + if( rc != LDAP_SUCCESS ) { + f->f_choice |= SLAPD_FILTER_UNDEFINED; + rc = slap_bv2undef_ad( &desc, &ssa.sa_desc, text, + SLAP_AD_PROXIED|SLAP_AD_NOINSERT ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "get_ssa: conn %lu unknown attribute type=%s (%ld)\n", + op->o_connid, desc.bv_val, (long) rc ); + + ssa.sa_desc = slap_bv2tmp_ad( &desc, op->o_tmpmemctx ); + } + } + + rc = LDAP_PROTOCOL_ERROR; + + /* If there is no substring matching rule, there's nothing + * we can do with this filter. But we continue to parse it + * for logging purposes. + */ + if ( ssa.sa_desc->ad_type->sat_substr == NULL ) { + f->f_choice |= SLAPD_FILTER_UNDEFINED; + Debug( LDAP_DEBUG_FILTER, + "get_ssa: no substring matching rule for attributeType %s\n", + desc.bv_val, 0, 0 ); + } + + for ( tag = ber_first_element( ber, &len, &last ); + tag != LBER_DEFAULT; + tag = ber_next_element( ber, &len, last ) ) + { + unsigned usage; + + if ( ber_scanf( ber, "m", &value ) == LBER_ERROR ) { + rc = SLAPD_DISCONNECT; + goto return_error; + } + + if ( value.bv_val == NULL || value.bv_len == 0 ) { + rc = LDAP_INVALID_SYNTAX; + goto return_error; + } + + switch ( tag ) { + case LDAP_SUBSTRING_INITIAL: + if ( ssa.sa_initial.bv_val != NULL + || ssa.sa_any != NULL + || ssa.sa_final.bv_val != NULL ) + { + rc = LDAP_PROTOCOL_ERROR; + goto return_error; + } + usage = SLAP_MR_SUBSTR_INITIAL; + break; + + case LDAP_SUBSTRING_ANY: + if ( ssa.sa_final.bv_val != NULL ) { + rc = LDAP_PROTOCOL_ERROR; + goto return_error; + } + usage = SLAP_MR_SUBSTR_ANY; + break; + + case LDAP_SUBSTRING_FINAL: + if ( ssa.sa_final.bv_val != NULL ) { + rc = LDAP_PROTOCOL_ERROR; + goto return_error; + } + + usage = SLAP_MR_SUBSTR_FINAL; + break; + + default: + Debug( LDAP_DEBUG_FILTER, + " unknown substring choice=%ld\n", + (long) tag, 0, 0 ); + + rc = LDAP_PROTOCOL_ERROR; + goto return_error; + } + + /* validate/normalize using equality matching rule validator! */ + rc = asserted_value_validate_normalize( + ssa.sa_desc, ssa.sa_desc->ad_type->sat_equality, + usage, &value, &nvalue, text, op->o_tmpmemctx ); + if( rc != LDAP_SUCCESS ) { + f->f_choice |= SLAPD_FILTER_UNDEFINED; + Debug( LDAP_DEBUG_FILTER, + "get_ssa: illegal value for attributeType %s (%d) %s\n", + desc.bv_val, rc, *text ); + ber_dupbv_x( &nvalue, &value, op->o_tmpmemctx ); + } + + switch ( tag ) { + case LDAP_SUBSTRING_INITIAL: + Debug( LDAP_DEBUG_FILTER, " INITIAL\n", 0, 0, 0 ); + ssa.sa_initial = nvalue; + break; + + case LDAP_SUBSTRING_ANY: + Debug( LDAP_DEBUG_FILTER, " ANY\n", 0, 0, 0 ); + ber_bvarray_add_x( &ssa.sa_any, &nvalue, op->o_tmpmemctx ); + break; + + case LDAP_SUBSTRING_FINAL: + Debug( LDAP_DEBUG_FILTER, " FINAL\n", 0, 0, 0 ); + ssa.sa_final = nvalue; + break; + + default: + assert( 0 ); + slap_sl_free( nvalue.bv_val, op->o_tmpmemctx ); + rc = LDAP_PROTOCOL_ERROR; + +return_error: + Debug( LDAP_DEBUG_FILTER, " error=%ld\n", + (long) rc, 0, 0 ); + slap_sl_free( ssa.sa_initial.bv_val, op->o_tmpmemctx ); + ber_bvarray_free_x( ssa.sa_any, op->o_tmpmemctx ); + if ( ssa.sa_desc->ad_flags & SLAP_DESC_TEMPORARY ) + op->o_tmpfree( ssa.sa_desc, op->o_tmpmemctx ); + slap_sl_free( ssa.sa_final.bv_val, op->o_tmpmemctx ); + return rc; + } + + *text = NULL; + rc = LDAP_SUCCESS; + } + + if( rc == LDAP_SUCCESS ) { + f->f_sub = op->o_tmpalloc( sizeof( ssa ), op->o_tmpmemctx ); + *f->f_sub = ssa; + } + + Debug( LDAP_DEBUG_FILTER, "end get_ssa\n", 0, 0, 0 ); + return rc /* LDAP_SUCCESS */ ; +} + +void +filter_free_x( Operation *op, Filter *f, int freeme ) +{ + Filter *p, *next; + + if ( f == NULL ) { + return; + } + + f->f_choice &= SLAPD_FILTER_MASK; + + switch ( f->f_choice ) { + case LDAP_FILTER_PRESENT: + if ( f->f_desc->ad_flags & SLAP_DESC_TEMPORARY ) + op->o_tmpfree( f->f_desc, op->o_tmpmemctx ); + break; + + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + case LDAP_FILTER_APPROX: + ava_free( op, f->f_ava, 1 ); + break; + + case LDAP_FILTER_SUBSTRINGS: + if ( f->f_sub_initial.bv_val != NULL ) { + op->o_tmpfree( f->f_sub_initial.bv_val, op->o_tmpmemctx ); + } + ber_bvarray_free_x( f->f_sub_any, op->o_tmpmemctx ); + if ( f->f_sub_final.bv_val != NULL ) { + op->o_tmpfree( f->f_sub_final.bv_val, op->o_tmpmemctx ); + } + if ( f->f_sub->sa_desc->ad_flags & SLAP_DESC_TEMPORARY ) + op->o_tmpfree( f->f_sub->sa_desc, op->o_tmpmemctx ); + op->o_tmpfree( f->f_sub, op->o_tmpmemctx ); + break; + + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: + for ( p = f->f_list; p != NULL; p = next ) { + next = p->f_next; + filter_free_x( op, p, 1 ); + } + break; + + case LDAP_FILTER_EXT: + mra_free( op, f->f_mra, 1 ); + break; + + case SLAPD_FILTER_COMPUTED: + break; + + default: + Debug( LDAP_DEBUG_ANY, "filter_free: unknown filter type=%lu\n", + f->f_choice, 0, 0 ); + break; + } + + if ( freeme ) { + op->o_tmpfree( f, op->o_tmpmemctx ); + } +} + +void +filter_free( Filter *f ) +{ + Operation op; + Opheader ohdr; + + op.o_hdr = &ohdr; + op.o_tmpmemctx = slap_sl_context( f ); + op.o_tmpmfuncs = &slap_sl_mfuncs; + filter_free_x( &op, f, 1 ); +} + +void +filter2bv_x( Operation *op, Filter *f, struct berval *fstr ) +{ + filter2bv_undef_x( op, f, 0, fstr ); +} + +void +filter2bv_undef_x( Operation *op, Filter *f, int noundef, struct berval *fstr ) +{ + int i; + Filter *p; + struct berval tmp, value; + static struct berval + ber_bvfalse = BER_BVC( "(?=false)" ), + ber_bvtrue = BER_BVC( "(?=true)" ), + ber_bvundefined = BER_BVC( "(?=undefined)" ), + ber_bverror = BER_BVC( "(?=error)" ), + ber_bvunknown = BER_BVC( "(?=unknown)" ), + ber_bvnone = BER_BVC( "(?=none)" ), + ber_bvF = BER_BVC( "(|)" ), + ber_bvT = BER_BVC( "(&)" ); + ber_len_t len; + ber_tag_t choice; + int undef, undef2; + char *sign; + + if ( f == NULL ) { + ber_dupbv_x( fstr, &ber_bvnone, op->o_tmpmemctx ); + return; + } + + undef = f->f_choice & SLAPD_FILTER_UNDEFINED; + undef2 = (undef && !noundef); + choice = f->f_choice & SLAPD_FILTER_MASK; + + switch ( choice ) { + case LDAP_FILTER_EQUALITY: + fstr->bv_len = STRLENOF("(=)"); + sign = "="; + goto simple; + case LDAP_FILTER_GE: + fstr->bv_len = STRLENOF("(>=)"); + sign = ">="; + goto simple; + case LDAP_FILTER_LE: + fstr->bv_len = STRLENOF("(<=)"); + sign = "<="; + goto simple; + case LDAP_FILTER_APPROX: + fstr->bv_len = STRLENOF("(~=)"); + sign = "~="; + +simple: + value = f->f_av_value; + if ( f->f_av_desc->ad_type->sat_equality && + !undef && + ( f->f_av_desc->ad_type->sat_equality->smr_usage & SLAP_MR_MUTATION_NORMALIZER )) + { + f->f_av_desc->ad_type->sat_equality->smr_normalize( + (SLAP_MR_DENORMALIZE|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX), + NULL, NULL, &f->f_av_value, &value, op->o_tmpmemctx ); + } + + filter_escape_value_x( &value, &tmp, op->o_tmpmemctx ); + /* NOTE: tmp can legitimately be NULL (meaning empty) + * since in a Filter values in AVAs are supposed + * to have been normalized, meaning that an empty value + * is legal for that attribute's syntax */ + + fstr->bv_len += f->f_av_desc->ad_cname.bv_len + tmp.bv_len; + if ( undef2 ) + fstr->bv_len++; + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s%s%s)", + undef2 ? "?" : "", + f->f_av_desc->ad_cname.bv_val, sign, + tmp.bv_len ? tmp.bv_val : "" ); + + if ( value.bv_val != f->f_av_value.bv_val ) { + ber_memfree_x( value.bv_val, op->o_tmpmemctx ); + } + + ber_memfree_x( tmp.bv_val, op->o_tmpmemctx ); + break; + + case LDAP_FILTER_SUBSTRINGS: + fstr->bv_len = f->f_sub_desc->ad_cname.bv_len + + STRLENOF("(=*)"); + if ( undef2 ) + fstr->bv_len++; + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 128, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s=*)", + undef2 ? "?" : "", + f->f_sub_desc->ad_cname.bv_val ); + + if ( f->f_sub_initial.bv_val != NULL ) { + ber_len_t tmplen; + + len = fstr->bv_len; + + filter_escape_value_x( &f->f_sub_initial, &tmp, op->o_tmpmemctx ); + tmplen = tmp.bv_len; + + fstr->bv_len += tmplen; + fstr->bv_val = op->o_tmprealloc( fstr->bv_val, + fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( &fstr->bv_val[len - 2], + tmplen + STRLENOF( /*(*/ "*)" ) + 1, + /* "(attr=" */ "%s*)", + tmp.bv_len ? tmp.bv_val : ""); + + ber_memfree_x( tmp.bv_val, op->o_tmpmemctx ); + } + + if ( f->f_sub_any != NULL ) { + for ( i = 0; f->f_sub_any[i].bv_val != NULL; i++ ) { + ber_len_t tmplen; + + len = fstr->bv_len; + filter_escape_value_x( &f->f_sub_any[i], + &tmp, op->o_tmpmemctx ); + tmplen = tmp.bv_len; + + fstr->bv_len += tmplen + STRLENOF( /*(*/ ")" ); + fstr->bv_val = op->o_tmprealloc( fstr->bv_val, + fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( &fstr->bv_val[len - 1], + tmplen + STRLENOF( /*(*/ "*)" ) + 1, + /* "(attr=[init]*[any*]" */ "%s*)", + tmp.bv_len ? tmp.bv_val : ""); + ber_memfree_x( tmp.bv_val, op->o_tmpmemctx ); + } + } + + if ( f->f_sub_final.bv_val != NULL ) { + ber_len_t tmplen; + + len = fstr->bv_len; + + filter_escape_value_x( &f->f_sub_final, &tmp, op->o_tmpmemctx ); + tmplen = tmp.bv_len; + + fstr->bv_len += tmplen; + fstr->bv_val = op->o_tmprealloc( fstr->bv_val, + fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( &fstr->bv_val[len - 1], + tmplen + STRLENOF( /*(*/ ")" ) + 1, + /* "(attr=[init*][any*]" */ "%s)", + tmp.bv_len ? tmp.bv_val : ""); + + ber_memfree_x( tmp.bv_val, op->o_tmpmemctx ); + } + + break; + + case LDAP_FILTER_PRESENT: + fstr->bv_len = f->f_desc->ad_cname.bv_len + + STRLENOF("(=*)"); + if ( undef2 ) + fstr->bv_len++; + + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s=*)", + undef2 ? "?" : "", + f->f_desc->ad_cname.bv_val ); + break; + + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: + fstr->bv_len = STRLENOF("(%)"); + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 128, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%c)", + f->f_choice == LDAP_FILTER_AND ? '&' : + f->f_choice == LDAP_FILTER_OR ? '|' : '!' ); + + for ( p = f->f_list; p != NULL; p = p->f_next ) { + len = fstr->bv_len; + + filter2bv_undef_x( op, p, noundef, &tmp ); + + fstr->bv_len += tmp.bv_len; + fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1, + op->o_tmpmemctx ); + + snprintf( &fstr->bv_val[len-1], + tmp.bv_len + STRLENOF( /*(*/ ")" ) + 1, + /*"("*/ "%s)", tmp.bv_val ); + + op->o_tmpfree( tmp.bv_val, op->o_tmpmemctx ); + } + + break; + + case LDAP_FILTER_EXT: { + struct berval ad; + + filter_escape_value_x( &f->f_mr_value, &tmp, op->o_tmpmemctx ); + /* NOTE: tmp can legitimately be NULL (meaning empty) + * since in a Filter values in MRAs are supposed + * to have been normalized, meaning that an empty value + * is legal for that attribute's syntax */ + + if ( f->f_mr_desc ) { + ad = f->f_mr_desc->ad_cname; + } else { + ad.bv_len = 0; + ad.bv_val = ""; + } + + fstr->bv_len = ad.bv_len + + ( undef2 ? 1 : 0 ) + + ( f->f_mr_dnattrs ? STRLENOF(":dn") : 0 ) + + ( f->f_mr_rule_text.bv_len ? f->f_mr_rule_text.bv_len + STRLENOF(":") : 0 ) + + tmp.bv_len + STRLENOF("(:=)"); + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s%s%s%s:=%s)", + undef2 ? "?" : "", + ad.bv_val, + f->f_mr_dnattrs ? ":dn" : "", + f->f_mr_rule_text.bv_len ? ":" : "", + f->f_mr_rule_text.bv_len ? f->f_mr_rule_text.bv_val : "", + tmp.bv_len ? tmp.bv_val : "" ); + ber_memfree_x( tmp.bv_val, op->o_tmpmemctx ); + } break; + + case SLAPD_FILTER_COMPUTED: + switch ( f->f_result ) { + case LDAP_COMPARE_FALSE: + tmp = ( noundef ? ber_bvF : ber_bvfalse ); + break; + + case LDAP_COMPARE_TRUE: + tmp = ( noundef ? ber_bvT : ber_bvtrue ); + break; + + case SLAPD_COMPARE_UNDEFINED: + tmp = ber_bvundefined; + break; + + default: + tmp = ber_bverror; + break; + } + + ber_dupbv_x( fstr, &tmp, op->o_tmpmemctx ); + break; + + default: + ber_dupbv_x( fstr, &ber_bvunknown, op->o_tmpmemctx ); + break; + } +} + +void +filter2bv( Filter *f, struct berval *fstr ) +{ + filter2bv_undef( f, 0, fstr ); +} + +void +filter2bv_undef( Filter *f, int noundef, struct berval *fstr ) +{ + Operation op; + Opheader ohdr; + + op.o_hdr = &ohdr; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + filter2bv_undef_x( &op, f, noundef, fstr ); +} + +Filter * +filter_dup( Filter *f, void *memctx ) +{ + BerMemoryFunctions *mf = &slap_sl_mfuncs; + Filter *n; + + if ( !f ) + return NULL; + + n = mf->bmf_malloc( sizeof(Filter), memctx ); + n->f_choice = f->f_choice; + n->f_next = NULL; + + switch( f->f_choice & SLAPD_FILTER_MASK ) { + case SLAPD_FILTER_COMPUTED: + n->f_result = f->f_result; + break; + case LDAP_FILTER_PRESENT: + if ( f->f_desc->ad_flags & SLAP_DESC_TEMPORARY ) + n->f_desc = slap_bv2tmp_ad( &f->f_desc->ad_cname, memctx ); + else + n->f_desc = f->f_desc; + break; + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + case LDAP_FILTER_APPROX: + /* Should this be ava_dup() ? */ + n->f_ava = mf->bmf_calloc( 1, sizeof(AttributeAssertion), memctx ); + *n->f_ava = *f->f_ava; + if ( f->f_av_desc->ad_flags & SLAP_DESC_TEMPORARY ) + n->f_av_desc = slap_bv2tmp_ad( &f->f_av_desc->ad_cname, memctx ); + ber_dupbv_x( &n->f_av_value, &f->f_av_value, memctx ); + break; + case LDAP_FILTER_SUBSTRINGS: + n->f_sub = mf->bmf_calloc( 1, sizeof(SubstringsAssertion), memctx ); + if ( f->f_sub_desc->ad_flags & SLAP_DESC_TEMPORARY ) + n->f_sub_desc = slap_bv2tmp_ad( &f->f_sub_desc->ad_cname, memctx ); + else + n->f_sub_desc = f->f_sub_desc; + if ( !BER_BVISNULL( &f->f_sub_initial )) + ber_dupbv_x( &n->f_sub_initial, &f->f_sub_initial, memctx ); + if ( f->f_sub_any ) { + int i; + for ( i = 0; !BER_BVISNULL( &f->f_sub_any[i] ); i++ ); + n->f_sub_any = mf->bmf_malloc(( i+1 )*sizeof( struct berval ), + memctx ); + for ( i = 0; !BER_BVISNULL( &f->f_sub_any[i] ); i++ ) { + ber_dupbv_x( &n->f_sub_any[i], &f->f_sub_any[i], memctx ); + } + BER_BVZERO( &n->f_sub_any[i] ); + } + if ( !BER_BVISNULL( &f->f_sub_final )) + ber_dupbv_x( &n->f_sub_final, &f->f_sub_final, memctx ); + break; + case LDAP_FILTER_EXT: { + /* Should this be mra_dup() ? */ + ber_len_t length; + length = sizeof(MatchingRuleAssertion); + if ( !BER_BVISNULL( &f->f_mr_rule_text )) + length += f->f_mr_rule_text.bv_len + 1; + n->f_mra = mf->bmf_calloc( 1, length, memctx ); + *n->f_mra = *f->f_mra; + if ( f->f_mr_desc && ( f->f_sub_desc->ad_flags & SLAP_DESC_TEMPORARY )) + n->f_mr_desc = slap_bv2tmp_ad( &f->f_mr_desc->ad_cname, memctx ); + ber_dupbv_x( &n->f_mr_value, &f->f_mr_value, memctx ); + if ( !BER_BVISNULL( &f->f_mr_rule_text )) { + n->f_mr_rule_text.bv_val = (char *)(n->f_mra+1); + AC_MEMCPY(n->f_mr_rule_text.bv_val, + f->f_mr_rule_text.bv_val, f->f_mr_rule_text.bv_len ); + } + } break; + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: { + Filter **p; + for ( p = &n->f_list, f = f->f_list; f; f = f->f_next ) { + *p = filter_dup( f, memctx ); + p = &(*p)->f_next; + } + } break; + } + return n; +} + +static int +get_simple_vrFilter( + Operation *op, + BerElement *ber, + ValuesReturnFilter **filt, + const char **text ) +{ + ber_tag_t tag; + ber_len_t len; + int err; + ValuesReturnFilter vrf; + + Debug( LDAP_DEBUG_FILTER, "begin get_simple_vrFilter\n", 0, 0, 0 ); + + tag = ber_peek_tag( ber, &len ); + + if( tag == LBER_ERROR ) { + *text = "error decoding filter"; + return SLAPD_DISCONNECT; + } + + vrf.vrf_next = NULL; + + err = LDAP_SUCCESS; + vrf.vrf_choice = tag; + + switch ( vrf.vrf_choice ) { + case LDAP_FILTER_EQUALITY: + Debug( LDAP_DEBUG_FILTER, "EQUALITY\n", 0, 0, 0 ); + err = get_ava( op, ber, (Filter *)&vrf, SLAP_MR_EQUALITY, text ); + if ( err != LDAP_SUCCESS ) { + break; + } + + assert( vrf.vrf_ava != NULL ); + break; + + case LDAP_FILTER_SUBSTRINGS: + Debug( LDAP_DEBUG_FILTER, "SUBSTRINGS\n", 0, 0, 0 ); + err = get_ssa( op, ber, (Filter *)&vrf, text ); + break; + + case LDAP_FILTER_GE: + Debug( LDAP_DEBUG_FILTER, "GE\n", 0, 0, 0 ); + err = get_ava( op, ber, (Filter *)&vrf, SLAP_MR_ORDERING, text ); + if ( err != LDAP_SUCCESS ) { + break; + } + break; + + case LDAP_FILTER_LE: + Debug( LDAP_DEBUG_FILTER, "LE\n", 0, 0, 0 ); + err = get_ava( op, ber, (Filter *)&vrf, SLAP_MR_ORDERING, text ); + if ( err != LDAP_SUCCESS ) { + break; + } + break; + + case LDAP_FILTER_PRESENT: { + struct berval type; + + Debug( LDAP_DEBUG_FILTER, "PRESENT\n", 0, 0, 0 ); + if ( ber_scanf( ber, "m", &type ) == LBER_ERROR ) { + err = SLAPD_DISCONNECT; + *text = "error decoding filter"; + break; + } + + vrf.vrf_desc = NULL; + err = slap_bv2ad( &type, &vrf.vrf_desc, text ); + + if( err != LDAP_SUCCESS ) { + vrf.vrf_choice |= SLAPD_FILTER_UNDEFINED; + err = slap_bv2undef_ad( &type, &vrf.vrf_desc, text, + SLAP_AD_PROXIED); + + if( err != LDAP_SUCCESS ) { + /* unrecognized attribute description or other error */ + Debug( LDAP_DEBUG_ANY, + "get_simple_vrFilter: conn %lu unknown " + "attribute type=%s (%d)\n", + op->o_connid, type.bv_val, err ); + + vrf.vrf_choice = SLAPD_FILTER_COMPUTED; + vrf.vrf_result = LDAP_COMPARE_FALSE; + err = LDAP_SUCCESS; + break; + } + } + } break; + + case LDAP_FILTER_APPROX: + Debug( LDAP_DEBUG_FILTER, "APPROX\n", 0, 0, 0 ); + err = get_ava( op, ber, (Filter *)&vrf, SLAP_MR_EQUALITY_APPROX, text ); + if ( err != LDAP_SUCCESS ) { + break; + } + break; + + case LDAP_FILTER_EXT: + Debug( LDAP_DEBUG_FILTER, "EXTENSIBLE\n", 0, 0, 0 ); + + err = get_mra( op, ber, (Filter *)&vrf, text ); + if ( err != LDAP_SUCCESS ) { + break; + } + + assert( vrf.vrf_mra != NULL ); + break; + + default: + (void) ber_scanf( ber, "x" ); /* skip the element */ + Debug( LDAP_DEBUG_ANY, "get_simple_vrFilter: unknown filter type=%lu\n", + vrf.vrf_choice, 0, 0 ); + vrf.vrf_choice = SLAPD_FILTER_COMPUTED; + vrf.vrf_result = SLAPD_COMPARE_UNDEFINED; + break; + } + + if ( err != LDAP_SUCCESS && err != SLAPD_DISCONNECT ) { + /* ignore error */ + vrf.vrf_choice = SLAPD_FILTER_COMPUTED; + vrf.vrf_result = SLAPD_COMPARE_UNDEFINED; + err = LDAP_SUCCESS; + } + + if ( err == LDAP_SUCCESS ) { + *filt = op->o_tmpalloc( sizeof vrf, op->o_tmpmemctx ); + **filt = vrf; + } + + Debug( LDAP_DEBUG_FILTER, "end get_simple_vrFilter %d\n", err, 0, 0 ); + + return err; +} + +int +get_vrFilter( Operation *op, BerElement *ber, + ValuesReturnFilter **vrf, + const char **text ) +{ + /* + * A ValuesReturnFilter looks like this: + * + * ValuesReturnFilter ::= SEQUENCE OF SimpleFilterItem + * SimpleFilterItem ::= CHOICE { + * equalityMatch [3] AttributeValueAssertion, + * substrings [4] SubstringFilter, + * greaterOrEqual [5] AttributeValueAssertion, + * lessOrEqual [6] AttributeValueAssertion, + * present [7] AttributeType, + * approxMatch [8] AttributeValueAssertion, + * extensibleMatch [9] SimpleMatchingAssertion -- LDAPv3 + * } + * + * SubstringFilter ::= SEQUENCE { + * type AttributeType, + * SEQUENCE OF CHOICE { + * initial [0] IA5String, + * any [1] IA5String, + * final [2] IA5String + * } + * } + * + * SimpleMatchingAssertion ::= SEQUENCE { -- LDAPv3 + * matchingRule [1] MatchingRuleId OPTIONAL, + * type [2] AttributeDescription OPTIONAL, + * matchValue [3] AssertionValue } + */ + + ValuesReturnFilter **n; + ber_tag_t tag; + ber_len_t len; + char *last; + + Debug( LDAP_DEBUG_FILTER, "begin get_vrFilter\n", 0, 0, 0 ); + + tag = ber_peek_tag( ber, &len ); + + if( tag == LBER_ERROR ) { + *text = "error decoding vrFilter"; + return SLAPD_DISCONNECT; + } + + if( tag != LBER_SEQUENCE ) { + *text = "error decoding vrFilter, expect SEQUENCE tag"; + return SLAPD_DISCONNECT; + } + + n = vrf; + for ( tag = ber_first_element( ber, &len, &last ); + tag != LBER_DEFAULT; + tag = ber_next_element( ber, &len, last ) ) + { + int err = get_simple_vrFilter( op, ber, n, text ); + + if ( err != LDAP_SUCCESS ) return( err ); + + n = &(*n)->vrf_next; + } + *n = NULL; + + Debug( LDAP_DEBUG_FILTER, "end get_vrFilter\n", 0, 0, 0 ); + return( LDAP_SUCCESS ); +} + +void +vrFilter_free( Operation *op, ValuesReturnFilter *vrf ) +{ + ValuesReturnFilter *next; + + for ( ; vrf != NULL; vrf = next ) { + next = vrf->vrf_next; + + switch ( vrf->vrf_choice & SLAPD_FILTER_MASK ) { + case LDAP_FILTER_PRESENT: + break; + + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + case LDAP_FILTER_APPROX: + ava_free( op, vrf->vrf_ava, 1 ); + break; + + case LDAP_FILTER_SUBSTRINGS: + if ( vrf->vrf_sub_initial.bv_val != NULL ) { + op->o_tmpfree( vrf->vrf_sub_initial.bv_val, op->o_tmpmemctx ); + } + ber_bvarray_free_x( vrf->vrf_sub_any, op->o_tmpmemctx ); + if ( vrf->vrf_sub_final.bv_val != NULL ) { + op->o_tmpfree( vrf->vrf_sub_final.bv_val, op->o_tmpmemctx ); + } + op->o_tmpfree( vrf->vrf_sub, op->o_tmpmemctx ); + break; + + case LDAP_FILTER_EXT: + mra_free( op, vrf->vrf_mra, 1 ); + break; + + case SLAPD_FILTER_COMPUTED: + break; + + default: + Debug( LDAP_DEBUG_ANY, "filter_free: unknown filter type=%lu\n", + vrf->vrf_choice, 0, 0 ); + break; + } + + op->o_tmpfree( vrf, op->o_tmpmemctx ); + } +} + +void +vrFilter2bv( Operation *op, ValuesReturnFilter *vrf, struct berval *fstr ) +{ + ValuesReturnFilter *p; + struct berval tmp; + ber_len_t len; + + if ( vrf == NULL ) { + ber_str2bv_x( "No filter!", STRLENOF("No filter!"), + 1, fstr, op->o_tmpmemctx ); + return; + } + + fstr->bv_len = STRLENOF("()"); + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 128, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "()"); + + for ( p = vrf; p != NULL; p = p->vrf_next ) { + len = fstr->bv_len; + + simple_vrFilter2bv( op, p, &tmp ); + + fstr->bv_len += tmp.bv_len; + fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1, + op->o_tmpmemctx ); + + snprintf( &fstr->bv_val[len-1], tmp.bv_len + 2, + /*"("*/ "%s)", tmp.bv_val ); + + op->o_tmpfree( tmp.bv_val, op->o_tmpmemctx ); + } +} + +static void +simple_vrFilter2bv( Operation *op, ValuesReturnFilter *vrf, struct berval *fstr ) +{ + struct berval tmp; + ber_len_t len; + int undef; + + if ( vrf == NULL ) { + ber_str2bv_x( "No filter!", STRLENOF("No filter!"), 1, fstr, + op->o_tmpmemctx ); + return; + } + undef = vrf->vrf_choice & SLAPD_FILTER_UNDEFINED; + + switch ( vrf->vrf_choice & SLAPD_FILTER_MASK ) { + case LDAP_FILTER_EQUALITY: + filter_escape_value_x( &vrf->vrf_av_value, &tmp, op->o_tmpmemctx ); + + fstr->bv_len = vrf->vrf_av_desc->ad_cname.bv_len + + tmp.bv_len + STRLENOF("(=)"); + if ( undef ) fstr->bv_len++; + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=%s)", + vrf->vrf_av_desc->ad_cname.bv_val, + tmp.bv_val ); + + ber_memfree_x( tmp.bv_val, op->o_tmpmemctx ); + break; + + case LDAP_FILTER_GE: + filter_escape_value_x( &vrf->vrf_av_value, &tmp, op->o_tmpmemctx ); + + fstr->bv_len = vrf->vrf_av_desc->ad_cname.bv_len + + tmp.bv_len + STRLENOF("(>=)"); + if ( undef ) fstr->bv_len++; + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s>=%s)", + vrf->vrf_av_desc->ad_cname.bv_val, + tmp.bv_val ); + + ber_memfree_x( tmp.bv_val, op->o_tmpmemctx ); + break; + + case LDAP_FILTER_LE: + filter_escape_value_x( &vrf->vrf_av_value, &tmp, op->o_tmpmemctx ); + + fstr->bv_len = vrf->vrf_av_desc->ad_cname.bv_len + + tmp.bv_len + STRLENOF("(<=)"); + if ( undef ) fstr->bv_len++; + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s<=%s)", + vrf->vrf_av_desc->ad_cname.bv_val, + tmp.bv_val ); + + ber_memfree_x( tmp.bv_val, op->o_tmpmemctx ); + break; + + case LDAP_FILTER_APPROX: + filter_escape_value_x( &vrf->vrf_av_value, &tmp, op->o_tmpmemctx ); + + fstr->bv_len = vrf->vrf_av_desc->ad_cname.bv_len + + tmp.bv_len + STRLENOF("(~=)"); + if ( undef ) fstr->bv_len++; + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s~=%s)", + vrf->vrf_av_desc->ad_cname.bv_val, + tmp.bv_val ); + ber_memfree_x( tmp.bv_val, op->o_tmpmemctx ); + break; + + case LDAP_FILTER_SUBSTRINGS: + fstr->bv_len = vrf->vrf_sub_desc->ad_cname.bv_len + + STRLENOF("(=*)"); + if ( undef ) fstr->bv_len++; + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 128, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)", + vrf->vrf_sub_desc->ad_cname.bv_val ); + + if ( vrf->vrf_sub_initial.bv_val != NULL ) { + len = fstr->bv_len; + + filter_escape_value_x( &vrf->vrf_sub_initial, &tmp, op->o_tmpmemctx ); + + fstr->bv_len += tmp.bv_len; + fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1, + op->o_tmpmemctx ); + + snprintf( &fstr->bv_val[len-2], tmp.bv_len+3, + /* "(attr=" */ "%s*)", + tmp.bv_val ); + + ber_memfree_x( tmp.bv_val, op->o_tmpmemctx ); + } + + if ( vrf->vrf_sub_any != NULL ) { + int i; + for ( i = 0; vrf->vrf_sub_any[i].bv_val != NULL; i++ ) { + len = fstr->bv_len; + filter_escape_value_x( &vrf->vrf_sub_any[i], &tmp, + op->o_tmpmemctx ); + + fstr->bv_len += tmp.bv_len + 1; + fstr->bv_val = op->o_tmprealloc( fstr->bv_val, + fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( &fstr->bv_val[len-1], tmp.bv_len+3, + /* "(attr=[init]*[any*]" */ "%s*)", + tmp.bv_val ); + ber_memfree_x( tmp.bv_val, op->o_tmpmemctx ); + } + } + + if ( vrf->vrf_sub_final.bv_val != NULL ) { + len = fstr->bv_len; + + filter_escape_value_x( &vrf->vrf_sub_final, &tmp, op->o_tmpmemctx ); + + fstr->bv_len += tmp.bv_len; + fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1, + op->o_tmpmemctx ); + + snprintf( &fstr->bv_val[len-1], tmp.bv_len+3, + /* "(attr=[init*][any*]" */ "%s)", + tmp.bv_val ); + + ber_memfree_x( tmp.bv_val, op->o_tmpmemctx ); + } + + break; + + case LDAP_FILTER_PRESENT: + fstr->bv_len = vrf->vrf_desc->ad_cname.bv_len + + STRLENOF("(=*)"); + if ( undef ) fstr->bv_len++; + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)", + vrf->vrf_desc->ad_cname.bv_val ); + break; + + case LDAP_FILTER_EXT: { + struct berval ad; + filter_escape_value_x( &vrf->vrf_mr_value, &tmp, op->o_tmpmemctx ); + + if ( vrf->vrf_mr_desc ) { + ad = vrf->vrf_mr_desc->ad_cname; + } else { + ad.bv_len = 0; + ad.bv_val = ""; + } + + fstr->bv_len = ad.bv_len + + ( vrf->vrf_mr_dnattrs ? STRLENOF(":dn") : 0 ) + + ( vrf->vrf_mr_rule_text.bv_len + ? vrf->vrf_mr_rule_text.bv_len+1 : 0 ) + + tmp.bv_len + STRLENOF("(:=)"); + if ( undef ) fstr->bv_len++; + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s%s%s:=%s)", + ad.bv_val, + vrf->vrf_mr_dnattrs ? ":dn" : "", + vrf->vrf_mr_rule_text.bv_len ? ":" : "", + vrf->vrf_mr_rule_text.bv_len ? vrf->vrf_mr_rule_text.bv_val : "", + tmp.bv_val ); + + ber_memfree_x( tmp.bv_val, op->o_tmpmemctx ); + } break; + + case SLAPD_FILTER_COMPUTED: + ber_str2bv_x( + vrf->vrf_result == LDAP_COMPARE_FALSE ? "(?=false)" : + vrf->vrf_result == LDAP_COMPARE_TRUE ? "(?=true)" : + vrf->vrf_result == SLAPD_COMPARE_UNDEFINED + ? "(?=undefined)" : "(?=error)", + vrf->vrf_result == LDAP_COMPARE_FALSE ? STRLENOF("(?=false)") : + vrf->vrf_result == LDAP_COMPARE_TRUE ? STRLENOF("(?=true)") : + vrf->vrf_result == SLAPD_COMPARE_UNDEFINED + ? STRLENOF("(?=undefined)") : STRLENOF("(?=error)"), + 1, fstr, op->o_tmpmemctx ); + break; + + default: + ber_str2bv_x( "(?=unknown)", STRLENOF("(?=unknown)"), + 1, fstr, op->o_tmpmemctx ); + break; + } +} diff --git a/servers/slapd/filterentry.c b/servers/slapd/filterentry.c new file mode 100644 index 0000000..0121bc5 --- /dev/null +++ b/servers/slapd/filterentry.c @@ -0,0 +1,986 @@ +/* filterentry.c - apply a filter to an entry */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" + +#ifdef LDAP_COMP_MATCH +#include "component.h" +#endif + +static int test_filter_and( Operation *op, Entry *e, Filter *flist ); +static int test_filter_or( Operation *op, Entry *e, Filter *flist ); +static int test_substrings_filter( Operation *op, Entry *e, Filter *f); +static int test_ava_filter( Operation *op, + Entry *e, AttributeAssertion *ava, int type ); +static int test_mra_filter( Operation *op, + Entry *e, MatchingRuleAssertion *mra ); +static int test_presence_filter( Operation *op, + Entry *e, AttributeDescription *desc ); + + +/* + * test_filter - test a filter against a single entry. + * returns: + * LDAP_COMPARE_TRUE filter matched + * LDAP_COMPARE_FALSE filter did not match + * SLAPD_COMPARE_UNDEFINED filter is undefined + * or an ldap result code indicating error + */ + +int +test_filter( + Operation *op, + Entry *e, + Filter *f ) +{ + int rc; + Debug( LDAP_DEBUG_FILTER, "=> test_filter\n", 0, 0, 0 ); + + if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) { + Debug( LDAP_DEBUG_FILTER, " UNDEFINED\n", 0, 0, 0 ); + rc = SLAPD_COMPARE_UNDEFINED; + goto out; + } + + switch ( f->f_choice ) { + case SLAPD_FILTER_COMPUTED: + Debug( LDAP_DEBUG_FILTER, " COMPUTED %s (%d)\n", + f->f_result == LDAP_COMPARE_FALSE ? "false" : + f->f_result == LDAP_COMPARE_TRUE ? "true" : + f->f_result == SLAPD_COMPARE_UNDEFINED ? "undefined" : "error", + f->f_result, 0 ); + + rc = f->f_result; + break; + + case LDAP_FILTER_EQUALITY: + Debug( LDAP_DEBUG_FILTER, " EQUALITY\n", 0, 0, 0 ); + rc = test_ava_filter( op, e, f->f_ava, LDAP_FILTER_EQUALITY ); + break; + + case LDAP_FILTER_SUBSTRINGS: + Debug( LDAP_DEBUG_FILTER, " SUBSTRINGS\n", 0, 0, 0 ); + rc = test_substrings_filter( op, e, f ); + break; + + case LDAP_FILTER_GE: + Debug( LDAP_DEBUG_FILTER, " GE\n", 0, 0, 0 ); + rc = test_ava_filter( op, e, f->f_ava, LDAP_FILTER_GE ); + break; + + case LDAP_FILTER_LE: + Debug( LDAP_DEBUG_FILTER, " LE\n", 0, 0, 0 ); + rc = test_ava_filter( op, e, f->f_ava, LDAP_FILTER_LE ); + break; + + case LDAP_FILTER_PRESENT: + Debug( LDAP_DEBUG_FILTER, " PRESENT\n", 0, 0, 0 ); + rc = test_presence_filter( op, e, f->f_desc ); + break; + + case LDAP_FILTER_APPROX: + Debug( LDAP_DEBUG_FILTER, " APPROX\n", 0, 0, 0 ); + rc = test_ava_filter( op, e, f->f_ava, LDAP_FILTER_APPROX ); + break; + + case LDAP_FILTER_AND: + Debug( LDAP_DEBUG_FILTER, " AND\n", 0, 0, 0 ); + rc = test_filter_and( op, e, f->f_and ); + break; + + case LDAP_FILTER_OR: + Debug( LDAP_DEBUG_FILTER, " OR\n", 0, 0, 0 ); + rc = test_filter_or( op, e, f->f_or ); + break; + + case LDAP_FILTER_NOT: + Debug( LDAP_DEBUG_FILTER, " NOT\n", 0, 0, 0 ); + rc = test_filter( op, e, f->f_not ); + + /* Flip true to false and false to true + * but leave Undefined alone. + */ + switch( rc ) { + case LDAP_COMPARE_TRUE: + rc = LDAP_COMPARE_FALSE; + break; + case LDAP_COMPARE_FALSE: + rc = LDAP_COMPARE_TRUE; + break; + } + break; + + case LDAP_FILTER_EXT: + Debug( LDAP_DEBUG_FILTER, " EXT\n", 0, 0, 0 ); + rc = test_mra_filter( op, e, f->f_mra ); + break; + + default: + Debug( LDAP_DEBUG_ANY, " unknown filter type %lu\n", + f->f_choice, 0, 0 ); + rc = LDAP_PROTOCOL_ERROR; + } +out: + Debug( LDAP_DEBUG_FILTER, "<= test_filter %d\n", rc, 0, 0 ); + return( rc ); +} + +static int test_mra_filter( + Operation *op, + Entry *e, + MatchingRuleAssertion *mra ) +{ + Attribute *a; + void *memctx; + BER_MEMFREE_FN *memfree; +#ifdef LDAP_COMP_MATCH + int i, num_attr_vals = 0; +#endif + + if ( op == NULL ) { + memctx = NULL; + memfree = slap_sl_mfuncs.bmf_free; + } else { + memctx = op->o_tmpmemctx; + memfree = op->o_tmpfree; + } + + if ( mra->ma_desc ) { + /* + * if ma_desc is available, then we're filtering for + * one attribute, and SEARCH permissions can be checked + * directly. + */ + if ( !access_allowed( op, e, + mra->ma_desc, &mra->ma_value, ACL_SEARCH, NULL ) ) + { + return LDAP_INSUFFICIENT_ACCESS; + } + + if ( mra->ma_desc == slap_schema.si_ad_entryDN ) { + int ret, rc; + const char *text; + + rc = value_match( &ret, slap_schema.si_ad_entryDN, mra->ma_rule, + SLAP_MR_EXT, &e->e_nname, &mra->ma_value, &text ); + + + if( rc != LDAP_SUCCESS ) return rc; + if ( ret == 0 ) return LDAP_COMPARE_TRUE; + return LDAP_COMPARE_FALSE; + } + + for ( a = attrs_find( e->e_attrs, mra->ma_desc ); + a != NULL; + a = attrs_find( a->a_next, mra->ma_desc ) ) + { + struct berval *bv; + int normalize_attribute = 0; + +#ifdef LDAP_COMP_MATCH + /* Component Matching */ + if ( mra->ma_cf && mra->ma_rule->smr_usage & SLAP_MR_COMPONENT ) { + num_attr_vals = 0; + if ( !a->a_comp_data ) { + num_attr_vals = a->a_numvals; + if ( num_attr_vals <= 0 ) { + /* no attribute value */ + return LDAP_INAPPROPRIATE_MATCHING; + } + num_attr_vals++; + + /* following malloced will be freed by comp_tree_free () */ + a->a_comp_data = SLAP_MALLOC( sizeof( ComponentData ) + + sizeof( ComponentSyntaxInfo* )*num_attr_vals ); + + if ( !a->a_comp_data ) return LDAP_NO_MEMORY; + a->a_comp_data->cd_tree = (ComponentSyntaxInfo**) + ((char*)a->a_comp_data + sizeof(ComponentData)); + a->a_comp_data->cd_tree[num_attr_vals - 1] = + (ComponentSyntaxInfo*) NULL; + a->a_comp_data->cd_mem_op = + nibble_mem_allocator( 1024*16, 1024 ); + } + } +#endif + + /* If ma_rule is not the same as the attribute's + * normal rule, then we can't use the a_nvals. + */ + if ( mra->ma_rule == a->a_desc->ad_type->sat_equality ) { + bv = a->a_nvals; + + } else { + bv = a->a_vals; + normalize_attribute = 1; + } +#ifdef LDAP_COMP_MATCH + i = 0; +#endif + for ( ; !BER_BVISNULL( bv ); bv++ ) { + int ret; + int rc; + const char *text; + +#ifdef LDAP_COMP_MATCH + if ( mra->ma_cf && + mra->ma_rule->smr_usage & SLAP_MR_COMPONENT ) + { + /* Check if decoded component trees are already linked */ + if ( num_attr_vals ) { + a->a_comp_data->cd_tree[i] = attr_converter( + a, a->a_desc->ad_type->sat_syntax, bv ); + } + /* decoding error */ + if ( !a->a_comp_data->cd_tree[i] ) { + return LDAP_OPERATIONS_ERROR; + } + rc = value_match( &ret, a->a_desc, mra->ma_rule, + SLAP_MR_COMPONENT, + (struct berval*)a->a_comp_data->cd_tree[i++], + (void*)mra, &text ); + } else +#endif + { + struct berval nbv = BER_BVNULL; + + if ( normalize_attribute && mra->ma_rule->smr_normalize ) { + /* + + Document: RFC 4511 + + 4.5.1. Search Request + ... + If the type field is present and the matchingRule is present, + the matchValue is compared against entry attributes of the + specified type. In this case, the matchingRule MUST be one + suitable for use with the specified type (see [RFC4517]), + otherwise the filter item is Undefined. + + + In this case, since the matchingRule requires the assertion + value to be normalized, we normalize the attribute value + according to the syntax of the matchingRule. + + This should likely be done inside value_match(), by passing + the appropriate flags, but this is not done at present. + See ITS#3406. + */ + if ( mra->ma_rule->smr_normalize( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + mra->ma_rule->smr_syntax, + mra->ma_rule, + bv, &nbv, memctx ) != LDAP_SUCCESS ) + { + /* FIXME: stop processing? */ + continue; + } + + } else { + nbv = *bv; + } + + rc = value_match( &ret, a->a_desc, mra->ma_rule, + SLAP_MR_EXT, &nbv, &mra->ma_value, &text ); + + if ( nbv.bv_val != bv->bv_val ) { + memfree( nbv.bv_val, memctx ); + } + } + + if ( rc != LDAP_SUCCESS ) return rc; + if ( ret == 0 ) return LDAP_COMPARE_TRUE; + } + } + + } else { + /* + * No attribute description: test all + */ + for ( a = e->e_attrs; a != NULL; a = a->a_next ) { + struct berval *bv, value; + const char *text = NULL; + int rc; + int normalize_attribute = 0; + + /* check if matching is appropriate */ + if ( !mr_usable_with_at( mra->ma_rule, a->a_desc->ad_type ) ) { + continue; + } + + /* normalize for equality */ + rc = asserted_value_validate_normalize( a->a_desc, mra->ma_rule, + SLAP_MR_EXT|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX, + &mra->ma_value, &value, &text, memctx ); + if ( rc != LDAP_SUCCESS ) continue; + + /* check search access */ + if ( !access_allowed( op, e, + a->a_desc, &value, ACL_SEARCH, NULL ) ) + { + memfree( value.bv_val, memctx ); + continue; + } +#ifdef LDAP_COMP_MATCH + /* Component Matching */ + if ( mra->ma_cf && + mra->ma_rule->smr_usage & SLAP_MR_COMPONENT ) + { + int ret; + + rc = value_match( &ret, a->a_desc, mra->ma_rule, + SLAP_MR_COMPONENT, + (struct berval*)a, (void*)mra, &text ); + if ( rc != LDAP_SUCCESS ) break; + + if ( ret == 0 ) { + rc = LDAP_COMPARE_TRUE; + break; + } + + } +#endif + + /* check match */ + if ( mra->ma_rule == a->a_desc->ad_type->sat_equality ) { + bv = a->a_nvals; + + } else { + bv = a->a_vals; + normalize_attribute = 1; + } + + for ( ; !BER_BVISNULL( bv ); bv++ ) { + int ret; + struct berval nbv = BER_BVNULL; + + if ( normalize_attribute && mra->ma_rule->smr_normalize ) { + /* see comment above */ + if ( mra->ma_rule->smr_normalize( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + mra->ma_rule->smr_syntax, + mra->ma_rule, + bv, &nbv, memctx ) != LDAP_SUCCESS ) + { + /* FIXME: stop processing? */ + continue; + } + + } else { + nbv = *bv; + } + + rc = value_match( &ret, a->a_desc, mra->ma_rule, + SLAP_MR_EXT, &nbv, &value, &text ); + + if ( nbv.bv_val != bv->bv_val ) { + memfree( nbv.bv_val, memctx ); + } + + if ( rc != LDAP_SUCCESS ) break; + + if ( ret == 0 ) { + rc = LDAP_COMPARE_TRUE; + break; + } + } + memfree( value.bv_val, memctx ); + if ( rc != LDAP_SUCCESS ) return rc; + } + } + + /* check attrs in DN AVAs if required */ + if ( mra->ma_dnattrs && !BER_BVISEMPTY( &e->e_nname ) ) { + LDAPDN dn = NULL; + int iRDN, iAVA; + int rc; + + /* parse and pretty the dn */ + rc = dnPrettyDN( NULL, &e->e_name, &dn, memctx ); + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + + /* for each AVA of each RDN ... */ + for ( iRDN = 0; dn[ iRDN ]; iRDN++ ) { + LDAPRDN rdn = dn[ iRDN ]; + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ]; + struct berval *bv = &ava->la_value, + value = BER_BVNULL, + nbv = BER_BVNULL; + AttributeDescription *ad = + (AttributeDescription *)ava->la_private; + int ret; + const char *text; + + assert( ad != NULL ); + + if ( mra->ma_desc ) { + /* have a mra type? check for subtype */ + if ( !is_ad_subtype( ad, mra->ma_desc ) ) { + continue; + } + value = mra->ma_value; + + } else { + const char *text = NULL; + + /* check if matching is appropriate */ + if ( !mr_usable_with_at( mra->ma_rule, ad->ad_type ) ) { + continue; + } + + /* normalize for equality */ + rc = asserted_value_validate_normalize( ad, + mra->ma_rule, + SLAP_MR_EXT|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX, + &mra->ma_value, &value, &text, memctx ); + if ( rc != LDAP_SUCCESS ) continue; + + /* check search access */ + if ( !access_allowed( op, e, + ad, &value, ACL_SEARCH, NULL ) ) + { + memfree( value.bv_val, memctx ); + continue; + } + } + + if ( mra->ma_rule->smr_normalize ) { + /* see comment above */ + if ( mra->ma_rule->smr_normalize( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + mra->ma_rule->smr_syntax, + mra->ma_rule, + bv, &nbv, memctx ) != LDAP_SUCCESS ) + { + /* FIXME: stop processing? */ + rc = LDAP_SUCCESS; + ret = -1; + goto cleanup; + } + + } else { + nbv = *bv; + } + + /* check match */ + rc = value_match( &ret, ad, mra->ma_rule, SLAP_MR_EXT, + &nbv, &value, &text ); + +cleanup:; + if ( !BER_BVISNULL( &value ) && value.bv_val != mra->ma_value.bv_val ) { + memfree( value.bv_val, memctx ); + } + + if ( !BER_BVISNULL( &nbv ) && nbv.bv_val != bv->bv_val ) { + memfree( nbv.bv_val, memctx ); + } + + if ( rc == LDAP_SUCCESS && ret == 0 ) rc = LDAP_COMPARE_TRUE; + + if ( rc != LDAP_SUCCESS ) { + ldap_dnfree_x( dn, memctx ); + return rc; + } + } + } + ldap_dnfree_x( dn, memctx ); + } + + return LDAP_COMPARE_FALSE; +} + +static int +test_ava_filter( + Operation *op, + Entry *e, + AttributeAssertion *ava, + int type ) +{ + int rc; + Attribute *a; +#ifdef LDAP_COMP_MATCH + int i, num_attr_vals = 0; + AttributeAliasing *a_alias = NULL; +#endif + + if ( !access_allowed( op, e, + ava->aa_desc, &ava->aa_value, ACL_SEARCH, NULL ) ) + { + return LDAP_INSUFFICIENT_ACCESS; + } + + if ( ava->aa_desc == slap_schema.si_ad_hasSubordinates + && op && op->o_bd && op->o_bd->be_has_subordinates ) + { + int hasSubordinates = 0; + struct berval hs; + + if( type != LDAP_FILTER_EQUALITY && + type != LDAP_FILTER_APPROX ) + { + /* No other match is allowed */ + return LDAP_INAPPROPRIATE_MATCHING; + } + + if ( op->o_bd->be_has_subordinates( op, e, &hasSubordinates ) != + LDAP_SUCCESS ) + { + return LDAP_OTHER; + } + + if ( hasSubordinates == LDAP_COMPARE_TRUE ) { + hs = slap_true_bv; + + } else if ( hasSubordinates == LDAP_COMPARE_FALSE ) { + hs = slap_false_bv; + + } else { + return LDAP_OTHER; + } + + if ( bvmatch( &ava->aa_value, &hs ) ) return LDAP_COMPARE_TRUE; + return LDAP_COMPARE_FALSE; + } + + if ( ava->aa_desc == slap_schema.si_ad_entryDN ) { + MatchingRule *mr; + int match; + const char *text; + + if( type != LDAP_FILTER_EQUALITY && + type != LDAP_FILTER_APPROX ) + { + /* No other match is allowed */ + return LDAP_INAPPROPRIATE_MATCHING; + } + + mr = slap_schema.si_ad_entryDN->ad_type->sat_equality; + assert( mr != NULL ); + + rc = value_match( &match, slap_schema.si_ad_entryDN, mr, + SLAP_MR_EXT, &e->e_nname, &ava->aa_value, &text ); + + if( rc != LDAP_SUCCESS ) return rc; + if( match == 0 ) return LDAP_COMPARE_TRUE; + return LDAP_COMPARE_FALSE; + } + + rc = LDAP_COMPARE_FALSE; + +#ifdef LDAP_COMP_MATCH + if ( is_aliased_attribute && ava->aa_cf ) + { + a_alias = is_aliased_attribute ( ava->aa_desc ); + if ( a_alias ) + ava->aa_desc = a_alias->aa_aliased_ad; + else + ava->aa_cf = NULL; + } +#endif + + for(a = attrs_find( e->e_attrs, ava->aa_desc ); + a != NULL; + a = attrs_find( a->a_next, ava->aa_desc ) ) + { + int use; + MatchingRule *mr; + struct berval *bv; + + if (( ava->aa_desc != a->a_desc ) && !access_allowed( op, + e, a->a_desc, &ava->aa_value, ACL_SEARCH, NULL )) + { + rc = LDAP_INSUFFICIENT_ACCESS; + continue; + } + + use = SLAP_MR_EQUALITY; + + switch ( type ) { + case LDAP_FILTER_APPROX: + use = SLAP_MR_EQUALITY_APPROX; + mr = a->a_desc->ad_type->sat_approx; + if( mr != NULL ) break; + + /* fallthru: use EQUALITY matching rule if no APPROX rule */ + + case LDAP_FILTER_EQUALITY: + /* use variable set above so fall thru use is not clobbered */ + mr = a->a_desc->ad_type->sat_equality; + break; + + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + use = SLAP_MR_ORDERING; + mr = a->a_desc->ad_type->sat_ordering; + break; + + default: + mr = NULL; + } + + if( mr == NULL ) { + rc = LDAP_INAPPROPRIATE_MATCHING; + continue; + } + + /* We have no Sort optimization for Approx matches */ + if (( a->a_flags & SLAP_ATTR_SORTED_VALS ) && type != LDAP_FILTER_APPROX ) { + unsigned slot; + int ret; + + /* For Ordering matches, we just need to do one comparison with + * either the first (least) or last (greatest) value. + */ + if ( use == SLAP_MR_ORDERING ) { + const char *text; + int match, which; + which = (type == LDAP_FILTER_LE) ? 0 : a->a_numvals-1; + ret = value_match( &match, a->a_desc, mr, use, + &a->a_nvals[which], &ava->aa_value, &text ); + if ( ret != LDAP_SUCCESS ) return ret; + if (( type == LDAP_FILTER_LE && match <= 0 ) || + ( type == LDAP_FILTER_GE && match >= 0 )) + return LDAP_COMPARE_TRUE; + continue; + } + /* Only Equality will get here */ + ret = attr_valfind( a, use | SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH | + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH, + &ava->aa_value, &slot, NULL ); + if ( ret == LDAP_SUCCESS ) + return LDAP_COMPARE_TRUE; + else if ( ret != LDAP_NO_SUCH_ATTRIBUTE ) + return ret; +#if 0 + /* The following is useful if we want to know which values + * matched an ordering test. But here we don't care, we just + * want to know if any value did, and that is checked above. + */ + if ( ret == LDAP_NO_SUCH_ATTRIBUTE ) { + /* If insertion point is not the end of the list, there was + * at least one value greater than the assertion. + */ + if ( type == LDAP_FILTER_GE && slot < a->a_numvals ) + return LDAP_COMPARE_TRUE; + /* Likewise, if insertion point is not the head of the list, + * there was at least one value less than the assertion. + */ + if ( type == LDAP_FILTER_LE && slot > 0 ) + return LDAP_COMPARE_TRUE; + return LDAP_COMPARE_FALSE; + } +#endif + continue; + } + +#ifdef LDAP_COMP_MATCH + if ( nibble_mem_allocator && ava->aa_cf && !a->a_comp_data ) { + /* Component Matching */ + for ( num_attr_vals = 0; a->a_vals[num_attr_vals].bv_val != NULL; num_attr_vals++ ); + if ( num_attr_vals <= 0 )/* no attribute value */ + return LDAP_INAPPROPRIATE_MATCHING; + num_attr_vals++;/* for NULL termination */ + + /* following malloced will be freed by comp_tree_free () */ + a->a_comp_data = SLAP_MALLOC( sizeof( ComponentData ) + sizeof( ComponentSyntaxInfo* )*num_attr_vals ); + + if ( !a->a_comp_data ) { + return LDAP_NO_MEMORY; + } + + a->a_comp_data->cd_tree = (ComponentSyntaxInfo**)((char*)a->a_comp_data + sizeof(ComponentData)); + i = num_attr_vals; + for ( ; i ; i-- ) { + a->a_comp_data->cd_tree[ i-1 ] = (ComponentSyntaxInfo*)NULL; + } + + a->a_comp_data->cd_mem_op = nibble_mem_allocator ( 1024*10*(num_attr_vals-1), 1024 ); + if ( a->a_comp_data->cd_mem_op == NULL ) { + free ( a->a_comp_data ); + a->a_comp_data = NULL; + return LDAP_OPERATIONS_ERROR; + } + } + + i = 0; +#endif + + for ( bv = a->a_nvals; !BER_BVISNULL( bv ); bv++ ) { + int ret, match; + const char *text; + +#ifdef LDAP_COMP_MATCH + if( attr_converter && ava->aa_cf && a->a_comp_data ) { + /* Check if decoded component trees are already linked */ + struct berval cf_bv = { 20, "componentFilterMatch" }; + MatchingRule* cf_mr = mr_bvfind( &cf_bv ); + MatchingRuleAssertion mra; + mra.ma_cf = ava->aa_cf; + + if ( a->a_comp_data->cd_tree[i] == NULL ) + a->a_comp_data->cd_tree[i] = attr_converter (a, a->a_desc->ad_type->sat_syntax, (a->a_vals + i)); + /* decoding error */ + if ( !a->a_comp_data->cd_tree[i] ) { + free_ComponentData ( a ); + return LDAP_OPERATIONS_ERROR; + } + + ret = value_match( &match, a->a_desc, cf_mr, + SLAP_MR_COMPONENT, + (struct berval*)a->a_comp_data->cd_tree[i++], + (void*)&mra, &text ); + if ( ret == LDAP_INAPPROPRIATE_MATCHING ) { + /* cached component tree is broken, just remove it */ + free_ComponentData ( a ); + return ret; + } + if ( a_alias ) + ava->aa_desc = a_alias->aa_aliasing_ad; + + } else +#endif + { + ret = ordered_value_match( &match, a->a_desc, mr, use, + bv, &ava->aa_value, &text ); + } + + if( ret != LDAP_SUCCESS ) { + rc = ret; + break; + } + + switch ( type ) { + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_APPROX: + if ( match == 0 ) return LDAP_COMPARE_TRUE; + break; + + case LDAP_FILTER_GE: + if ( match >= 0 ) return LDAP_COMPARE_TRUE; + break; + + case LDAP_FILTER_LE: + if ( match <= 0 ) return LDAP_COMPARE_TRUE; + break; + } + } + } + +#ifdef LDAP_COMP_MATCH + if ( a_alias ) + ava->aa_desc = a_alias->aa_aliasing_ad; +#endif + + return rc; +} + + +static int +test_presence_filter( + Operation *op, + Entry *e, + AttributeDescription *desc ) +{ + Attribute *a; + int rc; + + if ( !access_allowed( op, e, desc, NULL, ACL_SEARCH, NULL ) ) { + return LDAP_INSUFFICIENT_ACCESS; + } + + if ( desc == slap_schema.si_ad_hasSubordinates ) { + /* + * XXX: fairly optimistic: if the function is defined, + * then PRESENCE must succeed, because hasSubordinate + * is boolean-valued; I think we may live with this + * simplification by now. + */ + if ( op && op->o_bd && op->o_bd->be_has_subordinates ) { + return LDAP_COMPARE_TRUE; + } + + return LDAP_COMPARE_FALSE; + } + + if ( desc == slap_schema.si_ad_entryDN || + desc == slap_schema.si_ad_subschemaSubentry ) + { + /* entryDN and subschemaSubentry are always present */ + return LDAP_COMPARE_TRUE; + } + + rc = LDAP_COMPARE_FALSE; + + for(a = attrs_find( e->e_attrs, desc ); + a != NULL; + a = attrs_find( a->a_next, desc ) ) + { + if (( desc != a->a_desc ) && !access_allowed( op, + e, a->a_desc, NULL, ACL_SEARCH, NULL )) + { + rc = LDAP_INSUFFICIENT_ACCESS; + continue; + } + + rc = LDAP_COMPARE_TRUE; + break; + } + + return rc; +} + + +static int +test_filter_and( + Operation *op, + Entry *e, + Filter *flist ) +{ + Filter *f; + int rtn = LDAP_COMPARE_TRUE; /* True if empty */ + + Debug( LDAP_DEBUG_FILTER, "=> test_filter_and\n", 0, 0, 0 ); + + for ( f = flist; f != NULL; f = f->f_next ) { + int rc = test_filter( op, e, f ); + + if ( rc == LDAP_COMPARE_FALSE ) { + /* filter is False */ + rtn = rc; + break; + } + + if ( rc != LDAP_COMPARE_TRUE ) { + /* filter is Undefined unless later elements are False */ + rtn = rc; + } + } + + Debug( LDAP_DEBUG_FILTER, "<= test_filter_and %d\n", rtn, 0, 0 ); + + return rtn; +} + +static int +test_filter_or( + Operation *op, + Entry *e, + Filter *flist ) +{ + Filter *f; + int rtn = LDAP_COMPARE_FALSE; /* False if empty */ + + Debug( LDAP_DEBUG_FILTER, "=> test_filter_or\n", 0, 0, 0 ); + + for ( f = flist; f != NULL; f = f->f_next ) { + int rc = test_filter( op, e, f ); + + if ( rc == LDAP_COMPARE_TRUE ) { + /* filter is True */ + rtn = rc; + break; + } + + if ( rc != LDAP_COMPARE_FALSE ) { + /* filter is Undefined unless later elements are True */ + rtn = rc; + } + } + + Debug( LDAP_DEBUG_FILTER, "<= test_filter_or %d\n", rtn, 0, 0 ); + return rtn; +} + + +static int +test_substrings_filter( + Operation *op, + Entry *e, + Filter *f ) +{ + Attribute *a; + int rc; + + Debug( LDAP_DEBUG_FILTER, "begin test_substrings_filter\n", 0, 0, 0 ); + + if ( !access_allowed( op, e, + f->f_sub_desc, NULL, ACL_SEARCH, NULL ) ) + { + return LDAP_INSUFFICIENT_ACCESS; + } + + rc = LDAP_COMPARE_FALSE; + + for(a = attrs_find( e->e_attrs, f->f_sub_desc ); + a != NULL; + a = attrs_find( a->a_next, f->f_sub_desc ) ) + { + MatchingRule *mr; + struct berval *bv; + + if (( f->f_sub_desc != a->a_desc ) && !access_allowed( op, + e, a->a_desc, NULL, ACL_SEARCH, NULL )) + { + rc = LDAP_INSUFFICIENT_ACCESS; + continue; + } + + mr = a->a_desc->ad_type->sat_substr; + if( mr == NULL ) { + rc = LDAP_INAPPROPRIATE_MATCHING; + continue; + } + + for ( bv = a->a_nvals; !BER_BVISNULL( bv ); bv++ ) { + int ret, match; + const char *text; + + ret = value_match( &match, a->a_desc, mr, SLAP_MR_SUBSTR, + bv, f->f_sub, &text ); + + if( ret != LDAP_SUCCESS ) { + rc = ret; + break; + } + if ( match == 0 ) return LDAP_COMPARE_TRUE; + } + } + + Debug( LDAP_DEBUG_FILTER, "end test_substrings_filter %d\n", + rc, 0, 0 ); + return rc; +} diff --git a/servers/slapd/frontend.c b/servers/slapd/frontend.c new file mode 100644 index 0000000..6d42b02 --- /dev/null +++ b/servers/slapd/frontend.c @@ -0,0 +1,174 @@ +/* frontend.c - routines for dealing with frontend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> +#include <sys/stat.h> + +#include "slap.h" +#include "lutil.h" +#include "lber_pvt.h" + +#include "ldap_rq.h" + +static BackendInfo slap_frontendInfo; +static BackendDB slap_frontendDB; +BackendDB *frontendDB; + +static int +fe_entry_get_rw( + Operation *op, + struct berval *ndn, + ObjectClass *oc, + AttributeDescription *at, + int rw, + Entry **e ) +{ + BackendDB *bd; + int rc = LDAP_NO_SUCH_OBJECT; + + bd = op->o_bd; + op->o_bd = select_backend( ndn, 0 ); + if ( op->o_bd != NULL ) { + if ( op->o_bd->be_fetch ) { + rc = op->o_bd->be_fetch( op, ndn, oc, at, rw, e ); + } + } + op->o_bd = bd; + + return rc; +} + +static int +fe_entry_release_rw( + Operation *op, + Entry *e, + int rw ) +{ + BackendDB *bd; + int rc = LDAP_NO_SUCH_OBJECT; + + bd = op->o_bd; + op->o_bd = select_backend( &e->e_nname, 0 ); + if ( op->o_bd != NULL ) { + if ( op->o_bd->be_release ) { + rc = op->o_bd->be_release( op, e, rw ); + } + } + op->o_bd = bd; + + return rc; +} + +int +frontend_init( void ) +{ + /* data */ + frontendDB = &slap_frontendDB; + frontendDB->bd_self = frontendDB; + + /* ACLs */ + frontendDB->be_dfltaccess = ACL_READ; + + /* limits */ + frontendDB->be_def_limit.lms_t_soft = SLAPD_DEFAULT_TIMELIMIT; /* backward compatible limits */ + frontendDB->be_def_limit.lms_t_hard = 0; + frontendDB->be_def_limit.lms_s_soft = SLAPD_DEFAULT_SIZELIMIT; /* backward compatible limits */ + frontendDB->be_def_limit.lms_s_hard = 0; + frontendDB->be_def_limit.lms_s_unchecked = -1; /* no limit on unchecked size */ + frontendDB->be_def_limit.lms_s_pr = 0; /* page limit */ + frontendDB->be_def_limit.lms_s_pr_hide = 0; /* don't hide number of entries left */ + frontendDB->be_def_limit.lms_s_pr_total = 0; /* number of total entries returned by pagedResults equal to hard limit */ + + ldap_pvt_thread_mutex_init( &frontendDB->be_pcl_mutex ); + + /* suffix */ + frontendDB->be_suffix = ch_calloc( 2, sizeof( struct berval ) ); + ber_str2bv( "", 0, 1, &frontendDB->be_suffix[0] ); + BER_BVZERO( &frontendDB->be_suffix[1] ); + frontendDB->be_nsuffix = ch_calloc( 2, sizeof( struct berval ) ); + ber_str2bv( "", 0, 1, &frontendDB->be_nsuffix[0] ); + BER_BVZERO( &frontendDB->be_nsuffix[1] ); + + /* info */ + frontendDB->bd_info = &slap_frontendInfo; + + SLAP_BFLAGS(frontendDB) |= SLAP_BFLAG_FRONTEND; + + /* name */ + frontendDB->bd_info->bi_type = "frontend"; + + /* known controls */ + { + int i; + + frontendDB->bd_info->bi_controls = slap_known_controls; + + for ( i = 0; slap_known_controls[ i ]; i++ ) { + int cid; + + if ( slap_find_control_id( slap_known_controls[ i ], &cid ) + == LDAP_CONTROL_NOT_FOUND ) + { + assert( 0 ); + return -1; + } + + frontendDB->bd_info->bi_ctrls[ cid ] = 1; + frontendDB->be_ctrls[ cid ] = 1; + } + } + + /* calls */ + frontendDB->bd_info->bi_op_abandon = fe_op_abandon; + frontendDB->bd_info->bi_op_add = fe_op_add; + frontendDB->bd_info->bi_op_bind = fe_op_bind; + frontendDB->bd_info->bi_op_compare = fe_op_compare; + frontendDB->bd_info->bi_op_delete = fe_op_delete; + frontendDB->bd_info->bi_op_modify = fe_op_modify; + frontendDB->bd_info->bi_op_modrdn = fe_op_modrdn; + frontendDB->bd_info->bi_op_search = fe_op_search; + frontendDB->bd_info->bi_extended = fe_extended; + frontendDB->bd_info->bi_operational = fe_aux_operational; + frontendDB->bd_info->bi_entry_get_rw = fe_entry_get_rw; + frontendDB->bd_info->bi_entry_release_rw = fe_entry_release_rw; + frontendDB->bd_info->bi_access_allowed = fe_access_allowed; + frontendDB->bd_info->bi_acl_group = fe_acl_group; + frontendDB->bd_info->bi_acl_attribute = fe_acl_attribute; + +#if 0 + /* FIXME: is this too early? */ + return backend_startup_one( frontendDB ); +#endif + + return 0; +} + diff --git a/servers/slapd/globals.c b/servers/slapd/globals.c new file mode 100644 index 0000000..6e73512 --- /dev/null +++ b/servers/slapd/globals.c @@ -0,0 +1,38 @@ +/* globals.c - various global variables */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <ac/string.h> +#include "lber_pvt.h" + +#include "slap.h" + + +/* + * Global variables, in general, should be declared in the file + * primarily responsible for its management. Configurable globals + * belong in config.c. Variables declared here have no other + * sensible home. + */ + +const struct berval slap_empty_bv = BER_BVC(""); +const struct berval slap_unknown_bv = BER_BVC("unknown"); + +/* normalized boolean values */ +const struct berval slap_true_bv = BER_BVC("TRUE"); +const struct berval slap_false_bv = BER_BVC("FALSE"); + diff --git a/servers/slapd/index.c b/servers/slapd/index.c new file mode 100644 index 0000000..8f37a10 --- /dev/null +++ b/servers/slapd/index.c @@ -0,0 +1,91 @@ +/* index.c - index utilities */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <lutil.h> + +#include "slap.h" + +static slap_verbmasks idxstr[] = { + { BER_BVC("pres"), SLAP_INDEX_PRESENT }, + { BER_BVC("eq"), SLAP_INDEX_EQUALITY }, + { BER_BVC("approx"), SLAP_INDEX_APPROX }, + { BER_BVC("subinitial"), SLAP_INDEX_SUBSTR_INITIAL }, + { BER_BVC("subany"), SLAP_INDEX_SUBSTR_ANY }, + { BER_BVC("subfinal"), SLAP_INDEX_SUBSTR_FINAL }, + { BER_BVC("sub"), SLAP_INDEX_SUBSTR_DEFAULT }, + { BER_BVC("substr"), 0 }, + { BER_BVC("notags"), SLAP_INDEX_NOTAGS }, + { BER_BVC("nolang"), 0 }, /* backwards compat */ + { BER_BVC("nosubtypes"), SLAP_INDEX_NOSUBTYPES }, + { BER_BVNULL, 0 } +}; + + +int slap_str2index( const char *str, slap_mask_t *idx ) +{ + int i; + + i = verb_to_mask( str, idxstr ); + if ( BER_BVISNULL(&idxstr[i].word) ) return LDAP_OTHER; + while ( !idxstr[i].mask ) i--; + *idx = idxstr[i].mask; + + + return LDAP_SUCCESS; +} + +void slap_index2bvlen( slap_mask_t idx, struct berval *bv ) +{ + int i; + + bv->bv_len = 0; + + for ( i=0; !BER_BVISNULL( &idxstr[i].word ); i++ ) { + if ( !idxstr[i].mask ) continue; + if ( IS_SLAP_INDEX( idx, idxstr[i].mask )) { + if ( (idxstr[i].mask & SLAP_INDEX_SUBSTR) && + ((idx & SLAP_INDEX_SUBSTR_DEFAULT) != idxstr[i].mask)) + continue; + if ( bv->bv_len ) bv->bv_len++; + bv->bv_len += idxstr[i].word.bv_len; + } + } +} + +/* caller must provide buffer space, after calling index2bvlen */ +void slap_index2bv( slap_mask_t idx, struct berval *bv ) +{ + int i; + char *ptr; + + if ( !bv->bv_len ) return; + + ptr = bv->bv_val; + for ( i=0; !BER_BVISNULL( &idxstr[i].word ); i++ ) { + if ( !idxstr[i].mask ) continue; + if ( IS_SLAP_INDEX( idx, idxstr[i].mask )) { + if ( (idxstr[i].mask & SLAP_INDEX_SUBSTR) && + ((idx & SLAP_INDEX_SUBSTR_DEFAULT) != idxstr[i].mask)) + continue; + if ( ptr != bv->bv_val ) *ptr++ = ','; + ptr = lutil_strcopy( ptr, idxstr[i].word.bv_val ); + } + } +} diff --git a/servers/slapd/init.c b/servers/slapd/init.c new file mode 100644 index 0000000..043c3dd --- /dev/null +++ b/servers/slapd/init.c @@ -0,0 +1,323 @@ +/* init.c - initialize various things */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "slap.h" +#include "lber_pvt.h" + +#include "ldap_rq.h" + +/* + * read-only global variables or variables only written by the listener + * thread (after they are initialized) - no need to protect them with a mutex. + */ +int slap_debug = 0; + +#ifdef LDAP_DEBUG +int ldap_syslog = LDAP_DEBUG_STATS; +#else +int ldap_syslog; +#endif + +#ifdef LOG_DEBUG +int ldap_syslog_level = LOG_DEBUG; +#endif + +BerVarray default_referral = NULL; + +/* + * global variables that need mutex protection + */ +ldap_pvt_thread_pool_t connection_pool; +int connection_pool_max = SLAP_MAX_WORKER_THREADS; +int slap_tool_thread_max = 1; + +slap_counters_t slap_counters, *slap_counters_list; + +static const char* slap_name = NULL; +int slapMode = SLAP_UNDEFINED_MODE; + +int +slap_init( int mode, const char *name ) +{ + int rc; + + assert( mode ); + + if ( slapMode != SLAP_UNDEFINED_MODE ) { + /* Make sure we write something to stderr */ + slap_debug |= LDAP_DEBUG_NONE; + Debug( LDAP_DEBUG_ANY, + "%s init: init called twice (old=%d, new=%d)\n", + name, slapMode, mode ); + + return 1; + } + + slapMode = mode; + + slap_op_init(); + +#ifdef SLAPD_MODULES + if ( module_init() != 0 ) { + slap_debug |= LDAP_DEBUG_NONE; + Debug( LDAP_DEBUG_ANY, + "%s: module_init failed\n", + name, 0, 0 ); + return 1; + } +#endif + + if ( slap_schema_init( ) != 0 ) { + slap_debug |= LDAP_DEBUG_NONE; + Debug( LDAP_DEBUG_ANY, + "%s: slap_schema_init failed\n", + name, 0, 0 ); + return 1; + } + + if ( filter_init() != 0 ) { + slap_debug |= LDAP_DEBUG_NONE; + Debug( LDAP_DEBUG_ANY, + "%s: filter_init failed\n", + name, 0, 0 ); + return 1; + } + + if ( entry_init() != 0 ) { + slap_debug |= LDAP_DEBUG_NONE; + Debug( LDAP_DEBUG_ANY, + "%s: entry_init failed\n", + name, 0, 0 ); + return 1; + } + + switch ( slapMode & SLAP_MODE ) { + case SLAP_SERVER_MODE: + root_dse_init(); + + /* FALLTHRU */ + case SLAP_TOOL_MODE: + Debug( LDAP_DEBUG_TRACE, + "%s init: initiated %s.\n", name, + (mode & SLAP_MODE) == SLAP_TOOL_MODE ? "tool" : "server", + 0 ); + + slap_name = name; + + ldap_pvt_thread_pool_init( &connection_pool, + connection_pool_max, 0); + + slap_counters_init( &slap_counters ); + + ldap_pvt_thread_mutex_init( &slapd_rq.rq_mutex ); + LDAP_STAILQ_INIT( &slapd_rq.task_list ); + LDAP_STAILQ_INIT( &slapd_rq.run_list ); + + slap_passwd_init(); + + rc = slap_sasl_init(); + + if( rc == 0 ) { + rc = backend_init( ); + } + if ( rc ) + return rc; + + break; + + default: + slap_debug |= LDAP_DEBUG_NONE; + Debug( LDAP_DEBUG_ANY, + "%s init: undefined mode (%d).\n", name, mode, 0 ); + + rc = 1; + break; + } + + if ( slap_controls_init( ) != 0 ) { + slap_debug |= LDAP_DEBUG_NONE; + Debug( LDAP_DEBUG_ANY, + "%s: slap_controls_init failed\n", + name, 0, 0 ); + return 1; + } + + if ( frontend_init() ) { + slap_debug |= LDAP_DEBUG_NONE; + Debug( LDAP_DEBUG_ANY, + "%s: frontend_init failed\n", + name, 0, 0 ); + return 1; + } + + if ( overlay_init() ) { + slap_debug |= LDAP_DEBUG_NONE; + Debug( LDAP_DEBUG_ANY, + "%s: overlay_init failed\n", + name, 0, 0 ); + return 1; + } + + if ( glue_sub_init() ) { + slap_debug |= LDAP_DEBUG_NONE; + Debug( LDAP_DEBUG_ANY, + "%s: glue/subordinate init failed\n", + name, 0, 0 ); + + return 1; + } + + if ( acl_init() ) { + slap_debug |= LDAP_DEBUG_NONE; + Debug( LDAP_DEBUG_ANY, + "%s: acl_init failed\n", + name, 0, 0 ); + return 1; + } + + return rc; +} + +int slap_startup( Backend *be ) +{ + int rc; + Debug( LDAP_DEBUG_TRACE, + "%s startup: initiated.\n", + slap_name, 0, 0 ); + + rc = backend_startup( be ); + if ( !rc && ( slapMode & SLAP_SERVER_MODE )) + slapMode |= SLAP_SERVER_RUNNING; + return rc; +} + +int slap_shutdown( Backend *be ) +{ + Debug( LDAP_DEBUG_TRACE, + "%s shutdown: initiated\n", + slap_name, 0, 0 ); + + /* let backends do whatever cleanup they need to do */ + return backend_shutdown( be ); +} + +int slap_destroy(void) +{ + int rc; + + Debug( LDAP_DEBUG_TRACE, + "%s destroy: freeing system resources.\n", + slap_name, 0, 0 ); + + if ( default_referral ) { + ber_bvarray_free( default_referral ); + } + + /* clear out any thread-keys for the main thread */ + ldap_pvt_thread_pool_context_reset( ldap_pvt_thread_pool_context()); + + rc = backend_destroy(); + + slap_sasl_destroy(); + + /* rootdse destroy goes before entry_destroy() + * because it may use entry_free() */ + root_dse_destroy(); + entry_destroy(); + + switch ( slapMode & SLAP_MODE ) { + case SLAP_SERVER_MODE: + case SLAP_TOOL_MODE: + slap_counters_destroy( &slap_counters ); + break; + + default: + Debug( LDAP_DEBUG_ANY, + "slap_destroy(): undefined mode (%d).\n", slapMode, 0, 0 ); + + rc = 1; + break; + + } + + slap_op_destroy(); + + ldap_pvt_thread_destroy(); + + /* should destroy the above mutex */ + return rc; +} + +void slap_counters_init( slap_counters_t *sc ) +{ + int i; + + ldap_pvt_thread_mutex_init( &sc->sc_mutex ); + ldap_pvt_mp_init( sc->sc_bytes ); + ldap_pvt_mp_init( sc->sc_pdu ); + ldap_pvt_mp_init( sc->sc_entries ); + ldap_pvt_mp_init( sc->sc_refs ); + + ldap_pvt_mp_init( sc->sc_ops_initiated ); + ldap_pvt_mp_init( sc->sc_ops_completed ); + +#ifdef SLAPD_MONITOR + for ( i = 0; i < SLAP_OP_LAST; i++ ) { + ldap_pvt_mp_init( sc->sc_ops_initiated_[ i ] ); + ldap_pvt_mp_init( sc->sc_ops_completed_[ i ] ); + } +#endif /* SLAPD_MONITOR */ +} + +void slap_counters_destroy( slap_counters_t *sc ) +{ + int i; + + ldap_pvt_thread_mutex_destroy( &sc->sc_mutex ); + ldap_pvt_mp_clear( sc->sc_bytes ); + ldap_pvt_mp_clear( sc->sc_pdu ); + ldap_pvt_mp_clear( sc->sc_entries ); + ldap_pvt_mp_clear( sc->sc_refs ); + + ldap_pvt_mp_clear( sc->sc_ops_initiated ); + ldap_pvt_mp_clear( sc->sc_ops_completed ); + +#ifdef SLAPD_MONITOR + for ( i = 0; i < SLAP_OP_LAST; i++ ) { + ldap_pvt_mp_clear( sc->sc_ops_initiated_[ i ] ); + ldap_pvt_mp_clear( sc->sc_ops_completed_[ i ] ); + } +#endif /* SLAPD_MONITOR */ +} + diff --git a/servers/slapd/ldapsync.c b/servers/slapd/ldapsync.c new file mode 100644 index 0000000..3ba029f --- /dev/null +++ b/servers/slapd/ldapsync.c @@ -0,0 +1,464 @@ +/* ldapsync.c -- LDAP Content Sync Routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * Portions Copyright 2003 IBM Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "lutil.h" +#include "slap.h" +#include "../../libraries/liblber/lber-int.h" /* get ber_strndup() */ +#include "lutil_ldap.h" + +struct slap_sync_cookie_s slap_sync_cookie = + LDAP_STAILQ_HEAD_INITIALIZER( slap_sync_cookie ); + +void +slap_compose_sync_cookie( + Operation *op, + struct berval *cookie, + BerVarray csn, + int rid, + int sid ) +{ + int len, numcsn = 0; + + if ( csn ) { + for (; !BER_BVISNULL( &csn[numcsn] ); numcsn++); + } + + if ( numcsn == 0 || rid == -1 ) { + char cookiestr[ LDAP_PVT_CSNSTR_BUFSIZE + 20 ]; + if ( rid == -1 ) { + cookiestr[0] = '\0'; + len = 0; + } else { + len = snprintf( cookiestr, sizeof( cookiestr ), + "rid=%03d", rid ); + if ( sid >= 0 ) { + len += sprintf( cookiestr+len, ",sid=%03x", sid ); + } + } + ber_str2bv_x( cookiestr, len, 1, cookie, + op ? op->o_tmpmemctx : NULL ); + } else { + char *ptr; + int i; + + len = 0; + for ( i=0; i<numcsn; i++) + len += csn[i].bv_len + 1; + + len += STRLENOF("rid=123,csn="); + if ( sid >= 0 ) + len += STRLENOF("sid=xxx,"); + + cookie->bv_val = slap_sl_malloc( len, op ? op->o_tmpmemctx : NULL ); + + len = sprintf( cookie->bv_val, "rid=%03d,", rid ); + ptr = cookie->bv_val + len; + if ( sid >= 0 ) { + ptr += sprintf( ptr, "sid=%03x,", sid ); + } + ptr = lutil_strcopy( ptr, "csn=" ); + for ( i=0; i<numcsn; i++) { + ptr = lutil_strncopy( ptr, csn[i].bv_val, csn[i].bv_len ); + *ptr++ = ';'; + } + ptr--; + *ptr = '\0'; + cookie->bv_len = ptr - cookie->bv_val; + } +} + +void +slap_sync_cookie_free( + struct sync_cookie *cookie, + int free_cookie +) +{ + if ( cookie == NULL ) + return; + + if ( cookie->sids ) { + ch_free( cookie->sids ); + cookie->sids = NULL; + } + + if ( cookie->ctxcsn ) { + ber_bvarray_free( cookie->ctxcsn ); + cookie->ctxcsn = NULL; + } + cookie->numcsns = 0; + if ( !BER_BVISNULL( &cookie->octet_str )) { + ch_free( cookie->octet_str.bv_val ); + BER_BVZERO( &cookie->octet_str ); + } + + if ( free_cookie ) { + ch_free( cookie ); + } + + return; +} + +int +slap_parse_csn_sid( struct berval *csnp ) +{ + char *p, *q; + struct berval csn = *csnp; + int i; + + p = ber_bvchr( &csn, '#' ); + if ( !p ) + return -1; + p++; + csn.bv_len -= p - csn.bv_val; + csn.bv_val = p; + + p = ber_bvchr( &csn, '#' ); + if ( !p ) + return -1; + p++; + csn.bv_len -= p - csn.bv_val; + csn.bv_val = p; + + q = ber_bvchr( &csn, '#' ); + if ( !q ) + return -1; + + csn.bv_len = q - p; + + i = strtol( p, &q, 16 ); + if ( p == q || q != p + csn.bv_len || i < 0 || i > SLAP_SYNC_SID_MAX ) { + i = -1; + } + + return i; +} + +int * +slap_parse_csn_sids( BerVarray csns, int numcsns, void *memctx ) +{ + int i, *ret; + + ret = slap_sl_malloc( numcsns * sizeof(int), memctx ); + for ( i=0; i<numcsns; i++ ) { + ret[i] = slap_parse_csn_sid( &csns[i] ); + } + return ret; +} + +static slap_mr_match_func sidsort_cmp; + +static const MatchingRule sidsort_mr = { + { 0 }, + NULL, + { 0 }, + { 0 }, + 0, + NULL, NULL, NULL, sidsort_cmp +}; +static const AttributeType sidsort_at = { + { 0 }, + { 0 }, + NULL, NULL, (MatchingRule *)&sidsort_mr, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, SLAP_AT_SORTED_VAL +}; +static const AttributeDescription sidsort_ad = { + NULL, + (AttributeType *)&sidsort_at +}; + +static int +sidsort_cmp( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *b1, + void *v2 ) +{ + struct berval *b2 = v2; + *matchp = b1->bv_len - b2->bv_len; + return LDAP_SUCCESS; +} + +/* sort CSNs by SID. Use a fake Attribute with our own + * syntax and matching rule, which sorts the nvals by + * bv_len order. Stuff our sids into the bv_len. + */ +int +slap_sort_csn_sids( BerVarray csns, int *sids, int numcsns, void *memctx ) +{ + Attribute a; + const char *text; + int i, rc; + + a.a_desc = (AttributeDescription *)&sidsort_ad; + a.a_nvals = slap_sl_malloc( numcsns * sizeof(struct berval), memctx ); + for ( i=0; i<numcsns; i++ ) { + a.a_nvals[i].bv_len = sids[i]; + a.a_nvals[i].bv_val = NULL; + } + a.a_vals = csns; + a.a_numvals = numcsns; + a.a_flags = 0; + rc = slap_sort_vals( (Modifications *)&a, &text, &i, memctx ); + for ( i=0; i<numcsns; i++ ) + sids[i] = a.a_nvals[i].bv_len; + slap_sl_free( a.a_nvals, memctx ); + return rc; +} + +void +slap_insert_csn_sids( + struct sync_cookie *ck, + int pos, + int sid, + struct berval *csn +) +{ + int i; + ck->numcsns++; + ck->ctxcsn = ch_realloc( ck->ctxcsn, + (ck->numcsns+1) * sizeof(struct berval)); + BER_BVZERO( &ck->ctxcsn[ck->numcsns] ); + ck->sids = ch_realloc( ck->sids, ck->numcsns * sizeof(int)); + for ( i = ck->numcsns-1; i > pos; i-- ) { + ck->ctxcsn[i] = ck->ctxcsn[i-1]; + ck->sids[i] = ck->sids[i-1]; + } + ck->sids[i] = sid; + ber_dupbv( &ck->ctxcsn[i], csn ); +} + +int +slap_parse_sync_cookie( + struct sync_cookie *cookie, + void *memctx +) +{ + char *csn_ptr; + char *csn_str; + char *cval; + char *next, *end; + AttributeDescription *ad = slap_schema.si_ad_entryCSN; + + if ( cookie == NULL ) + return -1; + + if ( cookie->octet_str.bv_len <= STRLENOF( "rid=" ) ) + return -1; + + cookie->rid = -1; + cookie->sid = -1; + cookie->ctxcsn = NULL; + cookie->sids = NULL; + cookie->numcsns = 0; + + end = cookie->octet_str.bv_val + cookie->octet_str.bv_len; + + for ( next=cookie->octet_str.bv_val; next < end; ) { + if ( !strncmp( next, "rid=", STRLENOF("rid=") )) { + char *rid_ptr = next; + cookie->rid = strtol( &rid_ptr[ STRLENOF( "rid=" ) ], &next, 10 ); + if ( next == rid_ptr || + next > end || + ( *next && *next != ',' ) || + cookie->rid < 0 || + cookie->rid > SLAP_SYNC_RID_MAX ) + { + return -1; + } + if ( *next == ',' ) { + next++; + } + if ( !ad ) { + break; + } + continue; + } + if ( !strncmp( next, "sid=", STRLENOF("sid=") )) { + char *sid_ptr = next; + sid_ptr = next; + cookie->sid = strtol( &sid_ptr[ STRLENOF( "sid=" ) ], &next, 16 ); + if ( next == sid_ptr || + next > end || + ( *next && *next != ',' ) || + cookie->sid < 0 || + cookie->sid > SLAP_SYNC_SID_MAX ) + { + return -1; + } + if ( *next == ',' ) { + next++; + } + continue; + } + if ( !strncmp( next, "csn=", STRLENOF("csn=") )) { + struct berval stamp; + + next += STRLENOF("csn="); + while ( next < end ) { + csn_str = next; + csn_ptr = strchr( csn_str, '#' ); + if ( !csn_ptr || csn_ptr > end ) + break; + /* ad will be NULL when called from main. we just + * want to parse the rid then. But we still iterate + * through the string to find the end. + */ + cval = strchr( csn_ptr, ';' ); + if ( !cval ) + cval = strchr(csn_ptr, ',' ); + if ( cval ) + stamp.bv_len = cval - csn_str; + else + stamp.bv_len = end - csn_str; + if ( ad ) { + struct berval bv; + stamp.bv_val = csn_str; + if ( ad->ad_type->sat_syntax->ssyn_validate( + ad->ad_type->sat_syntax, &stamp ) != LDAP_SUCCESS ) + break; + if ( ad->ad_type->sat_equality->smr_normalize( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + ad->ad_type->sat_syntax, + ad->ad_type->sat_equality, + &stamp, &bv, memctx ) != LDAP_SUCCESS ) + break; + ber_bvarray_add_x( &cookie->ctxcsn, &bv, memctx ); + cookie->numcsns++; + } + if ( cval ) { + next = cval + 1; + if ( *cval != ';' ) + break; + } else { + next = end; + break; + } + } + continue; + } + next++; + } + if ( cookie->numcsns ) { + cookie->sids = slap_parse_csn_sids( cookie->ctxcsn, cookie->numcsns, + memctx ); + if ( cookie->numcsns > 1 ) + slap_sort_csn_sids( cookie->ctxcsn, cookie->sids, cookie->numcsns, memctx ); + } + return 0; +} + +/* count the numcsns and regenerate the list of SIDs in a recomposed cookie */ +void +slap_reparse_sync_cookie( + struct sync_cookie *cookie, + void *memctx ) +{ + if ( cookie->ctxcsn ) { + for (; !BER_BVISNULL( &cookie->ctxcsn[cookie->numcsns] ); cookie->numcsns++); + } + if ( cookie->numcsns ) { + cookie->sids = slap_parse_csn_sids( cookie->ctxcsn, cookie->numcsns, NULL ); + if ( cookie->numcsns > 1 ) + slap_sort_csn_sids( cookie->ctxcsn, cookie->sids, cookie->numcsns, memctx ); + } +} + +int +slap_init_sync_cookie_ctxcsn( + struct sync_cookie *cookie +) +{ + char csnbuf[ LDAP_PVT_CSNSTR_BUFSIZE + 4 ]; + struct berval octet_str = BER_BVNULL; + struct berval ctxcsn = BER_BVNULL; + + if ( cookie == NULL ) + return -1; + + octet_str.bv_len = snprintf( csnbuf, LDAP_PVT_CSNSTR_BUFSIZE + 4, + "csn=%4d%02d%02d%02d%02d%02dZ#%06x#%02x#%06x", + 1900, 1, 1, 0, 0, 0, 0, 0, 0 ); + octet_str.bv_val = csnbuf; + ch_free( cookie->octet_str.bv_val ); + ber_dupbv( &cookie->octet_str, &octet_str ); + + ctxcsn.bv_val = octet_str.bv_val + 4; + ctxcsn.bv_len = octet_str.bv_len - 4; + cookie->ctxcsn = NULL; + value_add_one( &cookie->ctxcsn, &ctxcsn ); + cookie->numcsns = 1; + cookie->sid = -1; + + return 0; +} + +struct sync_cookie * +slap_dup_sync_cookie( + struct sync_cookie *dst, + struct sync_cookie *src +) +{ + struct sync_cookie *new; + int i; + + if ( src == NULL ) + return NULL; + + if ( dst ) { + ber_bvarray_free( dst->ctxcsn ); + dst->ctxcsn = NULL; + dst->sids = NULL; + ch_free( dst->octet_str.bv_val ); + BER_BVZERO( &dst->octet_str ); + new = dst; + } else { + new = ( struct sync_cookie * ) + ch_calloc( 1, sizeof( struct sync_cookie )); + } + + new->rid = src->rid; + new->sid = src->sid; + new->numcsns = src->numcsns; + + if ( src->numcsns ) { + if ( ber_bvarray_dup_x( &new->ctxcsn, src->ctxcsn, NULL )) { + if ( !dst ) { + ch_free( new ); + } + return NULL; + } + new->sids = ch_malloc( src->numcsns * sizeof(int) ); + for (i=0; i<src->numcsns; i++) + new->sids[i] = src->sids[i]; + } + + if ( !BER_BVISNULL( &src->octet_str )) { + ber_dupbv( &new->octet_str, &src->octet_str ); + } + + return new; +} + diff --git a/servers/slapd/limits.c b/servers/slapd/limits.c new file mode 100644 index 0000000..5812517 --- /dev/null +++ b/servers/slapd/limits.c @@ -0,0 +1,1355 @@ +/* limits.c - routines to handle regex-based size and time limits */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/regex.h> +#include <ac/string.h> + +#include "slap.h" +#include "lutil.h" + +/* define to get an error if requesting limit higher than hard */ +#undef ABOVE_HARD_LIMIT_IS_ERROR + +static const struct berval lmpats[] = { + BER_BVC( "base" ), + BER_BVC( "base" ), + BER_BVC( "onelevel" ), + BER_BVC( "subtree" ), + BER_BVC( "children" ), + BER_BVC( "regex" ), + BER_BVC( "anonymous" ), + BER_BVC( "users" ), + BER_BVC( "*" ) +}; + +#ifdef LDAP_DEBUG +static const char *const dn_source[2] = { "DN", "DN.THIS" }; +static const char *const lmpats_out[] = { + "UNDEFINED", + "EXACT", + "ONELEVEL", + "SUBTREE", + "CHILDREN", + "REGEX", + "ANONYMOUS", + "USERS", + "ANY" +}; + +static const char * +limits2str( unsigned i ) +{ + return i < (sizeof( lmpats_out ) / sizeof( lmpats_out[0] )) + ? lmpats_out[i] : "UNKNOWN"; +} +#endif /* LDAP_DEBUG */ + +static int +limits_get( + Operation *op, + struct slap_limits_set **limit +) +{ + static struct berval empty_dn = BER_BVC( "" ); + struct slap_limits **lm; + struct berval *ndns[2]; + + assert( op != NULL ); + assert( limit != NULL ); + + ndns[0] = &op->o_ndn; + ndns[1] = &op->o_req_ndn; + + Debug( LDAP_DEBUG_TRACE, "==> limits_get: %s self=\"%s\" this=\"%s\"\n", + op->o_log_prefix, + BER_BVISNULL( ndns[0] ) ? "[anonymous]" : ndns[0]->bv_val, + BER_BVISNULL( ndns[1] ) ? "" : ndns[1]->bv_val ); + /* + * default values + */ + *limit = &op->o_bd->be_def_limit; + + if ( op->o_bd->be_limits == NULL ) { + return( 0 ); + } + + for ( lm = op->o_bd->be_limits; lm[0] != NULL; lm++ ) { + unsigned style = lm[0]->lm_flags & SLAP_LIMITS_MASK; + unsigned type = lm[0]->lm_flags & SLAP_LIMITS_TYPE_MASK; + unsigned isthis = type == SLAP_LIMITS_TYPE_THIS; + struct berval *ndn = ndns[isthis]; + + if ( style == SLAP_LIMITS_ANY ) + goto found_any; + + if ( BER_BVISEMPTY( ndn ) ) { + if ( style == SLAP_LIMITS_ANONYMOUS ) + goto found_nodn; + if ( !isthis ) + continue; + ndn = &empty_dn; + } + + switch ( style ) { + case SLAP_LIMITS_EXACT: + if ( type == SLAP_LIMITS_TYPE_GROUP ) { + int rc = backend_group( op, NULL, + &lm[0]->lm_pat, ndn, + lm[0]->lm_group_oc, + lm[0]->lm_group_ad ); + if ( rc == 0 ) { + goto found_group; + } + } else { + if ( dn_match( &lm[0]->lm_pat, ndn ) ) { + goto found_dn; + } + } + break; + + case SLAP_LIMITS_ONE: + case SLAP_LIMITS_SUBTREE: + case SLAP_LIMITS_CHILDREN: { + ber_len_t d; + + /* ndn shorter than lm_pat */ + if ( ndn->bv_len < lm[0]->lm_pat.bv_len ) { + break; + } + d = ndn->bv_len - lm[0]->lm_pat.bv_len; + + if ( d == 0 ) { + /* allow exact match for SUBTREE only */ + if ( style != SLAP_LIMITS_SUBTREE ) { + break; + } + } else { + /* check for unescaped rdn separator */ + if ( !DN_SEPARATOR( ndn->bv_val[d - 1] ) ) { + break; + } + } + + /* check that ndn ends with lm_pat */ + if ( strcmp( lm[0]->lm_pat.bv_val, &ndn->bv_val[d] ) != 0 ) { + break; + } + + /* in case of ONE, require exactly one rdn below lm_pat */ + if ( style == SLAP_LIMITS_ONE ) { + if ( dn_rdnlen( NULL, ndn ) != d - 1 ) { + break; + } + } + + goto found_dn; + } + + case SLAP_LIMITS_REGEX: + if ( regexec( &lm[0]->lm_regex, ndn->bv_val, 0, NULL, 0 ) == 0 ) { + goto found_dn; + } + break; + + case SLAP_LIMITS_ANONYMOUS: + break; + + case SLAP_LIMITS_USERS: + found_nodn: + Debug( LDAP_DEBUG_TRACE, "<== limits_get: type=%s match=%s\n", + dn_source[isthis], limits2str( style ), 0 ); + found_any: + *limit = &lm[0]->lm_limits; + return( 0 ); + + found_dn: + Debug( LDAP_DEBUG_TRACE, + "<== limits_get: type=%s match=%s dn=\"%s\"\n", + dn_source[isthis], limits2str( style ), lm[0]->lm_pat.bv_val ); + *limit = &lm[0]->lm_limits; + return( 0 ); + + found_group: + Debug( LDAP_DEBUG_TRACE, "<== limits_get: type=GROUP match=EXACT " + "dn=\"%s\" oc=\"%s\" ad=\"%s\"\n", + lm[0]->lm_pat.bv_val, + lm[0]->lm_group_oc->soc_cname.bv_val, + lm[0]->lm_group_ad->ad_cname.bv_val ); + *limit = &lm[0]->lm_limits; + return( 0 ); + + default: + assert( 0 ); /* unreachable */ + return( -1 ); + } + } + + return( 0 ); +} + +static int +limits_add( + Backend *be, + unsigned flags, + const char *pattern, + ObjectClass *group_oc, + AttributeDescription *group_ad, + struct slap_limits_set *limit +) +{ + int i; + struct slap_limits *lm; + unsigned type, style; + + assert( be != NULL ); + assert( limit != NULL ); + + type = flags & SLAP_LIMITS_TYPE_MASK; + style = flags & SLAP_LIMITS_MASK; + + switch ( style ) { + case SLAP_LIMITS_ANONYMOUS: + case SLAP_LIMITS_USERS: + case SLAP_LIMITS_ANY: + /* For these styles, type == 0 (SLAP_LIMITS_TYPE_SELF). */ + for ( i = 0; be->be_limits && be->be_limits[ i ]; i++ ) { + if ( be->be_limits[ i ]->lm_flags == style ) { + return( -1 ); + } + } + break; + } + + + lm = ( struct slap_limits * )ch_calloc( sizeof( struct slap_limits ), 1 ); + + switch ( style ) { + case SLAP_LIMITS_UNDEFINED: + style = SLAP_LIMITS_EXACT; + /* continue to next cases */ + case SLAP_LIMITS_EXACT: + case SLAP_LIMITS_ONE: + case SLAP_LIMITS_SUBTREE: + case SLAP_LIMITS_CHILDREN: + { + int rc; + struct berval bv; + + ber_str2bv( pattern, 0, 0, &bv ); + + rc = dnNormalize( 0, NULL, NULL, &bv, &lm->lm_pat, NULL ); + if ( rc != LDAP_SUCCESS ) { + ch_free( lm ); + return( -1 ); + } + } + break; + + case SLAP_LIMITS_REGEX: + ber_str2bv( pattern, 0, 1, &lm->lm_pat ); + if ( regcomp( &lm->lm_regex, lm->lm_pat.bv_val, + REG_EXTENDED | REG_ICASE ) ) { + free( lm->lm_pat.bv_val ); + ch_free( lm ); + return( -1 ); + } + break; + + case SLAP_LIMITS_ANONYMOUS: + case SLAP_LIMITS_USERS: + case SLAP_LIMITS_ANY: + BER_BVZERO( &lm->lm_pat ); + break; + } + + switch ( type ) { + case SLAP_LIMITS_TYPE_GROUP: + assert( group_oc != NULL ); + assert( group_ad != NULL ); + lm->lm_group_oc = group_oc; + lm->lm_group_ad = group_ad; + break; + } + + lm->lm_flags = style | type; + lm->lm_limits = *limit; + + i = 0; + if ( be->be_limits != NULL ) { + for ( ; be->be_limits[i]; i++ ); + } + + be->be_limits = ( struct slap_limits ** )ch_realloc( be->be_limits, + sizeof( struct slap_limits * ) * ( i + 2 ) ); + be->be_limits[i] = lm; + be->be_limits[i+1] = NULL; + + return( 0 ); +} + +#define STRSTART( s, m ) (strncasecmp( s, m, STRLENOF( "" m "" )) == 0) + +int +limits_parse( + Backend *be, + const char *fname, + int lineno, + int argc, + char **argv +) +{ + int flags = SLAP_LIMITS_UNDEFINED; + char *pattern; + struct slap_limits_set limit; + int i, rc = 0; + ObjectClass *group_oc = NULL; + AttributeDescription *group_ad = NULL; + + assert( be != NULL ); + + if ( argc < 3 ) { + Debug( LDAP_DEBUG_ANY, + "%s : line %d: missing arg(s) in " + "\"limits <pattern> <limits>\" line.\n%s", + fname, lineno, "" ); + return( -1 ); + } + + limit = be->be_def_limit; + + /* + * syntax: + * + * "limits" <pattern> <limit> [ ... ] + * + * + * <pattern>: + * + * "anonymous" + * "users" + * [ "dn" [ "." { "this" | "self" } ] [ "." { "exact" | "base" | + * "onelevel" | "subtree" | "children" | "regex" | "anonymous" } ] + * "=" ] <dn pattern> + * + * Note: + * "this" is the baseobject, "self" (the default) is the bound DN + * "exact" and "base" are the same (exact match); + * "onelevel" means exactly one rdn below, NOT including pattern + * "subtree" means any rdn below, including pattern + * "children" means any rdn below, NOT including pattern + * + * "anonymous" may be deprecated in favour + * of the pattern = "anonymous" form + * + * "group[/objectClass[/attributeType]]" "=" "<dn pattern>" + * + * <limit>: + * + * "time" [ "." { "soft" | "hard" } ] "=" <integer> + * + * "size" [ "." { "soft" | "hard" | "unchecked" } ] "=" <integer> + */ + + pattern = argv[1]; + if ( strcmp( pattern, "*" ) == 0) { + flags = SLAP_LIMITS_ANY; + + } else if ( strcasecmp( pattern, "anonymous" ) == 0 ) { + flags = SLAP_LIMITS_ANONYMOUS; + + } else if ( strcasecmp( pattern, "users" ) == 0 ) { + flags = SLAP_LIMITS_USERS; + + } else if ( STRSTART( pattern, "dn" ) ) { + pattern += STRLENOF( "dn" ); + flags = SLAP_LIMITS_TYPE_SELF; + if ( pattern[0] == '.' ) { + pattern++; + if ( STRSTART( pattern, "this" ) ) { + flags = SLAP_LIMITS_TYPE_THIS; + pattern += STRLENOF( "this" ); + } else if ( STRSTART( pattern, "self" ) ) { + pattern += STRLENOF( "self" ); + } else { + goto got_dn_dot; + } + } + if ( pattern[0] == '.' ) { + pattern++; + got_dn_dot: + if ( STRSTART( pattern, "exact" ) ) { + flags |= SLAP_LIMITS_EXACT; + pattern += STRLENOF( "exact" ); + + } else if ( STRSTART( pattern, "base" ) ) { + flags |= SLAP_LIMITS_BASE; + pattern += STRLENOF( "base" ); + + } else if ( STRSTART( pattern, "one" ) ) { + flags |= SLAP_LIMITS_ONE; + pattern += STRLENOF( "one" ); + if ( STRSTART( pattern, "level" ) ) { + pattern += STRLENOF( "level" ); + + } else { + Debug( LDAP_DEBUG_ANY, + "%s : line %d: deprecated \"one\" style " + "\"limits <pattern> <limits>\" line; " + "use \"onelevel\" instead.\n", fname, lineno, 0 ); + } + + } else if ( STRSTART( pattern, "sub" ) ) { + flags |= SLAP_LIMITS_SUBTREE; + pattern += STRLENOF( "sub" ); + if ( STRSTART( pattern, "tree" ) ) { + pattern += STRLENOF( "tree" ); + + } else { + Debug( LDAP_DEBUG_ANY, + "%s : line %d: deprecated \"sub\" style " + "\"limits <pattern> <limits>\" line; " + "use \"subtree\" instead.\n", fname, lineno, 0 ); + } + + } else if ( STRSTART( pattern, "children" ) ) { + flags |= SLAP_LIMITS_CHILDREN; + pattern += STRLENOF( "children" ); + + } else if ( STRSTART( pattern, "regex" ) ) { + flags |= SLAP_LIMITS_REGEX; + pattern += STRLENOF( "regex" ); + + /* + * this could be deprecated in favour + * of the pattern = "anonymous" form + */ + } else if ( STRSTART( pattern, "anonymous" ) + && flags == SLAP_LIMITS_TYPE_SELF ) + { + flags = SLAP_LIMITS_ANONYMOUS; + pattern = NULL; + + } else { + /* force error below */ + if ( *pattern == '=' ) + --pattern; + } + } + + /* pre-check the data */ + if ( pattern != NULL ) { + if ( pattern[0] != '=' ) { + Debug( LDAP_DEBUG_ANY, + "%s : line %d: %s in " + "\"dn[.{this|self}][.{exact|base" + "|onelevel|subtree|children|regex" + "|anonymous}]=<pattern>\" in " + "\"limits <pattern> <limits>\" line.\n", + fname, lineno, + isalnum( (unsigned char)pattern[0] ) + ? "unknown DN modifier" : "missing '='" ); + return( -1 ); + } + + /* skip '=' (required) */ + pattern++; + + /* trim obvious cases */ + if ( strcmp( pattern, "*" ) == 0 ) { + flags = SLAP_LIMITS_ANY; + pattern = NULL; + + } else if ( (flags & SLAP_LIMITS_MASK) == SLAP_LIMITS_REGEX + && strcmp( pattern, ".*" ) == 0 ) { + flags = SLAP_LIMITS_ANY; + pattern = NULL; + } + } + + } else if (STRSTART( pattern, "group" ) ) { + pattern += STRLENOF( "group" ); + + if ( pattern[0] == '/' ) { + struct berval oc, ad; + + oc.bv_val = pattern + 1; + pattern = strchr( pattern, '=' ); + if ( pattern == NULL ) { + return -1; + } + + ad.bv_val = strchr( oc.bv_val, '/' ); + if ( ad.bv_val != NULL ) { + const char *text = NULL; + + oc.bv_len = ad.bv_val - oc.bv_val; + + ad.bv_val++; + ad.bv_len = pattern - ad.bv_val; + rc = slap_bv2ad( &ad, &group_ad, &text ); + if ( rc != LDAP_SUCCESS ) { + goto no_ad; + } + + } else { + oc.bv_len = pattern - oc.bv_val; + } + + group_oc = oc_bvfind( &oc ); + if ( group_oc == NULL ) { + goto no_oc; + } + } + + if ( group_oc == NULL ) { + group_oc = oc_find( SLAPD_GROUP_CLASS ); + if ( group_oc == NULL ) { +no_oc:; + return( -1 ); + } + } + + if ( group_ad == NULL ) { + const char *text = NULL; + + rc = slap_str2ad( SLAPD_GROUP_ATTR, &group_ad, &text ); + + if ( rc != LDAP_SUCCESS ) { +no_ad:; + return( -1 ); + } + } + + flags = SLAP_LIMITS_TYPE_GROUP | SLAP_LIMITS_EXACT; + + if ( pattern[0] != '=' ) { + Debug( LDAP_DEBUG_ANY, + "%s : line %d: missing '=' in " + "\"group[/objectClass[/attributeType]]" + "=<pattern>\" in " + "\"limits <pattern> <limits>\" line.\n", + fname, lineno, 0 ); + return( -1 ); + } + + /* skip '=' (required) */ + pattern++; + } + + /* get the limits */ + for ( i = 2; i < argc; i++ ) { + if ( limits_parse_one( argv[i], &limit ) ) { + + Debug( LDAP_DEBUG_ANY, + "%s : line %d: unknown limit values \"%s\" in " + "\"limits <pattern> <limits>\" line.\n", + fname, lineno, argv[i] ); + + return( 1 ); + } + } + + /* + * sanity checks ... + * + * FIXME: add warnings? + */ + if ( limit.lms_t_hard > 0 && + ( limit.lms_t_hard < limit.lms_t_soft + || limit.lms_t_soft == -1 ) ) { + limit.lms_t_hard = limit.lms_t_soft; + } + + if ( limit.lms_s_hard > 0 && + ( limit.lms_s_hard < limit.lms_s_soft + || limit.lms_s_soft == -1 ) ) { + limit.lms_s_hard = limit.lms_s_soft; + } + + /* + * defaults ... + * + * lms_t_hard: + * -1 => no limits + * 0 => same as soft + * > 0 => limit (in seconds) + * + * lms_s_hard: + * -1 => no limits + * 0 0> same as soft + * > 0 => limit (in entries) + * + * lms_s_pr_total: + * -2 => disable the control + * -1 => no limits + * 0 => same as soft + * > 0 => limit (in entries) + * + * lms_s_pr: + * -1 => no limits + * 0 => no limits? + * > 0 => limit size (in entries) + */ + if ( limit.lms_s_pr_total > 0 && + limit.lms_s_pr > limit.lms_s_pr_total ) { + limit.lms_s_pr = limit.lms_s_pr_total; + } + + rc = limits_add( be, flags, pattern, group_oc, group_ad, &limit ); + if ( rc ) { + + Debug( LDAP_DEBUG_ANY, + "%s : line %d: unable to add limit in " + "\"limits <pattern> <limits>\" line.\n", + fname, lineno, 0 ); + } + + return( rc ); +} + +int +limits_parse_one( + const char *arg, + struct slap_limits_set *limit +) +{ + assert( arg != NULL ); + assert( limit != NULL ); + + if ( STRSTART( arg, "time" ) ) { + arg += STRLENOF( "time" ); + + if ( arg[0] == '.' ) { + arg++; + if ( STRSTART( arg, "soft=" ) ) { + arg += STRLENOF( "soft=" ); + if ( strcasecmp( arg, "unlimited" ) == 0 + || strcasecmp( arg, "none" ) == 0 ) + { + limit->lms_t_soft = -1; + + } else { + int soft; + + if ( lutil_atoi( &soft, arg ) != 0 || soft < -1 ) { + return( 1 ); + } + + if ( soft == -1 ) { + /* FIXME: use "unlimited" instead; issue warning? */ + } + + limit->lms_t_soft = soft; + } + + } else if ( STRSTART( arg, "hard=" ) ) { + arg += STRLENOF( "hard=" ); + if ( strcasecmp( arg, "soft" ) == 0 ) { + limit->lms_t_hard = 0; + + } else if ( strcasecmp( arg, "unlimited" ) == 0 + || strcasecmp( arg, "none" ) == 0 ) + { + limit->lms_t_hard = -1; + + } else { + int hard; + + if ( lutil_atoi( &hard, arg ) != 0 || hard < -1 ) { + return( 1 ); + } + + if ( hard == -1 ) { + /* FIXME: use "unlimited" instead */ + } + + if ( hard == 0 ) { + /* FIXME: use "soft" instead */ + } + + limit->lms_t_hard = hard; + } + + } else { + return( 1 ); + } + + } else if ( arg[0] == '=' ) { + arg++; + if ( strcasecmp( arg, "unlimited" ) == 0 + || strcasecmp( arg, "none" ) == 0 ) + { + limit->lms_t_soft = -1; + + } else { + if ( lutil_atoi( &limit->lms_t_soft, arg ) != 0 + || limit->lms_t_soft < -1 ) + { + return( 1 ); + } + } + limit->lms_t_hard = 0; + + } else { + return( 1 ); + } + + } else if ( STRSTART( arg, "size" ) ) { + arg += STRLENOF( "size" ); + + if ( arg[0] == '.' ) { + arg++; + if ( STRSTART( arg, "soft=" ) ) { + arg += STRLENOF( "soft=" ); + if ( strcasecmp( arg, "unlimited" ) == 0 + || strcasecmp( arg, "none" ) == 0 ) + { + limit->lms_s_soft = -1; + + } else { + int soft; + + if ( lutil_atoi( &soft, arg ) != 0 || soft < -1 ) { + return( 1 ); + } + + if ( soft == -1 ) { + /* FIXME: use "unlimited" instead */ + } + + limit->lms_s_soft = soft; + } + + } else if ( STRSTART( arg, "hard=" ) ) { + arg += STRLENOF( "hard=" ); + if ( strcasecmp( arg, "soft" ) == 0 ) { + limit->lms_s_hard = 0; + + } else if ( strcasecmp( arg, "unlimited" ) == 0 + || strcasecmp( arg, "none" ) == 0 ) + { + limit->lms_s_hard = -1; + + } else { + int hard; + + if ( lutil_atoi( &hard, arg ) != 0 || hard < -1 ) { + return( 1 ); + } + + if ( hard == -1 ) { + /* FIXME: use "unlimited" instead */ + } + + if ( hard == 0 ) { + /* FIXME: use "soft" instead */ + } + + limit->lms_s_hard = hard; + } + + } else if ( STRSTART( arg, "unchecked=" ) ) { + arg += STRLENOF( "unchecked=" ); + if ( strcasecmp( arg, "unlimited" ) == 0 + || strcasecmp( arg, "none" ) == 0 ) + { + limit->lms_s_unchecked = -1; + + } else if ( strcasecmp( arg, "disabled" ) == 0 ) { + limit->lms_s_unchecked = 0; + + } else { + int unchecked; + + if ( lutil_atoi( &unchecked, arg ) != 0 || unchecked < -1 ) { + return( 1 ); + } + + if ( unchecked == -1 ) { + /* FIXME: use "unlimited" instead */ + } + + limit->lms_s_unchecked = unchecked; + } + + } else if ( STRSTART( arg, "pr=" ) ) { + arg += STRLENOF( "pr=" ); + if ( strcasecmp( arg, "noEstimate" ) == 0 ) { + limit->lms_s_pr_hide = 1; + + } else if ( strcasecmp( arg, "unlimited" ) == 0 + || strcasecmp( arg, "none" ) == 0 ) + { + limit->lms_s_pr = -1; + + } else { + int pr; + + if ( lutil_atoi( &pr, arg ) != 0 || pr < -1 ) { + return( 1 ); + } + + if ( pr == -1 ) { + /* FIXME: use "unlimited" instead */ + } + + limit->lms_s_pr = pr; + } + + } else if ( STRSTART( arg, "prtotal=" ) ) { + arg += STRLENOF( "prtotal=" ); + + if ( strcasecmp( arg, "unlimited" ) == 0 + || strcasecmp( arg, "none" ) == 0 ) + { + limit->lms_s_pr_total = -1; + + } else if ( strcasecmp( arg, "disabled" ) == 0 ) { + limit->lms_s_pr_total = -2; + + } else if ( strcasecmp( arg, "hard" ) == 0 ) { + limit->lms_s_pr_total = 0; + + } else { + int total; + + if ( lutil_atoi( &total, arg ) != 0 || total < -1 ) { + return( 1 ); + } + + if ( total == -1 ) { + /* FIXME: use "unlimited" instead */ + } + + if ( total == 0 ) { + /* FIXME: use "pr=disable" instead */ + } + + limit->lms_s_pr_total = total; + } + + } else { + return( 1 ); + } + + } else if ( arg[0] == '=' ) { + arg++; + if ( strcasecmp( arg, "unlimited" ) == 0 + || strcasecmp( arg, "none" ) == 0 ) + { + limit->lms_s_soft = -1; + + } else { + if ( lutil_atoi( &limit->lms_s_soft, arg ) != 0 + || limit->lms_s_soft < -1 ) + { + return( 1 ); + } + } + limit->lms_s_hard = 0; + + } else { + return( 1 ); + } + } + + return 0; +} + +/* Helper macros for limits_unparse() and limits_unparse_one(): + * Write to ptr, but not past bufEnd. Move ptr past the new text. + * Return (success && enough room ? 0 : -1). + */ +#define ptr_APPEND_BV(bv) /* Append a \0-terminated berval */ \ + (WHATSLEFT <= (bv).bv_len ? -1 : \ + ((void) (ptr = lutil_strcopy( ptr, (bv).bv_val )), 0)) +#define ptr_APPEND_LIT(str) /* Append a string literal */ \ + (WHATSLEFT <= STRLENOF( "" str "" ) ? -1 : \ + ((void) (ptr = lutil_strcopy( ptr, str )), 0)) +#define ptr_APPEND_FMT(args) /* Append formatted text */ \ + (WHATSLEFT <= (tmpLen = snprintf args) ? -1 : ((void) (ptr += tmpLen), 0)) +#define ptr_APPEND_FMT1(fmt, arg) ptr_APPEND_FMT(( ptr, WHATSLEFT, fmt, arg )) +#define WHATSLEFT ((ber_len_t) (bufEnd - ptr)) + +/* Caller must provide an adequately sized buffer in bv */ +int +limits_unparse( struct slap_limits *lim, struct berval *bv, ber_len_t buflen ) +{ + struct berval btmp; + char *ptr, *bufEnd; /* Updated/used by ptr_APPEND_*()/WHATSLEFT */ + ber_len_t tmpLen; /* Used by ptr_APPEND_FMT*() */ + unsigned type, style; + int rc = 0; + + if ( !bv || !bv->bv_val ) return -1; + + ptr = bv->bv_val; + bufEnd = ptr + buflen; + type = lim->lm_flags & SLAP_LIMITS_TYPE_MASK; + + if ( type == SLAP_LIMITS_TYPE_GROUP ) { + rc = ptr_APPEND_FMT(( ptr, WHATSLEFT, "group/%s/%s=\"%s\"", + lim->lm_group_oc->soc_cname.bv_val, + lim->lm_group_ad->ad_cname.bv_val, + lim->lm_pat.bv_val )); + } else { + style = lim->lm_flags & SLAP_LIMITS_MASK; + switch( style ) { + case SLAP_LIMITS_ANONYMOUS: + case SLAP_LIMITS_USERS: + case SLAP_LIMITS_ANY: + rc = ptr_APPEND_BV( lmpats[style] ); + break; + case SLAP_LIMITS_UNDEFINED: + case SLAP_LIMITS_EXACT: + case SLAP_LIMITS_ONE: + case SLAP_LIMITS_SUBTREE: + case SLAP_LIMITS_CHILDREN: + case SLAP_LIMITS_REGEX: + rc = ptr_APPEND_FMT(( ptr, WHATSLEFT, "dn.%s%s=\"%s\"", + type == SLAP_LIMITS_TYPE_SELF ? "" : "this.", + lmpats[style].bv_val, lim->lm_pat.bv_val )); + break; + } + } + if ( rc == 0 ) { + bv->bv_len = ptr - bv->bv_val; + btmp.bv_val = ptr; + btmp.bv_len = 0; + rc = limits_unparse_one( &lim->lm_limits, + SLAP_LIMIT_SIZE | SLAP_LIMIT_TIME, + &btmp, WHATSLEFT ); + if ( rc == 0 ) + bv->bv_len += btmp.bv_len; + } + return rc; +} + +/* Caller must provide an adequately sized buffer in bv */ +int +limits_unparse_one( + struct slap_limits_set *lim, + int which, + struct berval *bv, + ber_len_t buflen ) +{ + char *ptr, *bufEnd; /* Updated/used by ptr_APPEND_*()/WHATSLEFT */ + ber_len_t tmpLen; /* Used by ptr_APPEND_FMT*() */ + + if ( !bv || !bv->bv_val ) return -1; + + ptr = bv->bv_val; + bufEnd = ptr + buflen; + + if ( which & SLAP_LIMIT_SIZE ) { + if ( lim->lms_s_soft != SLAPD_DEFAULT_SIZELIMIT ) { + + /* If same as global limit, drop it */ + if ( lim != &frontendDB->be_def_limit && + lim->lms_s_soft == frontendDB->be_def_limit.lms_s_soft ) + { + goto s_hard; + /* If there's also a hard limit, fully qualify this one */ + } else if ( lim->lms_s_hard ) { + if ( ptr_APPEND_LIT( " size.soft=" ) ) return -1; + + /* If doing both size & time, qualify this */ + } else if ( which & SLAP_LIMIT_TIME ) { + if ( ptr_APPEND_LIT( " size=" ) ) return -1; + } + + if ( lim->lms_s_soft == -1 + ? ptr_APPEND_LIT( "unlimited " ) + : ptr_APPEND_FMT1( "%d ", lim->lms_s_soft ) ) + return -1; + } +s_hard: + if ( lim->lms_s_hard ) { + if ( ptr_APPEND_LIT( " size.hard=" ) ) return -1; + if ( lim->lms_s_hard == -1 + ? ptr_APPEND_LIT( "unlimited " ) + : ptr_APPEND_FMT1( "%d ", lim->lms_s_hard ) ) + return -1; + } + if ( lim->lms_s_unchecked != -1 ) { + if ( ptr_APPEND_LIT( " size.unchecked=" ) ) return -1; + if ( lim->lms_s_unchecked == 0 + ? ptr_APPEND_LIT( "disabled " ) + : ptr_APPEND_FMT1( "%d ", lim->lms_s_unchecked ) ) + return -1; + } + if ( lim->lms_s_pr_hide ) { + if ( ptr_APPEND_LIT( " size.pr=noEstimate " ) ) return -1; + } + if ( lim->lms_s_pr ) { + if ( ptr_APPEND_LIT( " size.pr=" ) ) return -1; + if ( lim->lms_s_pr == -1 + ? ptr_APPEND_LIT( "unlimited " ) + : ptr_APPEND_FMT1( "%d ", lim->lms_s_pr ) ) + return -1; + } + if ( lim->lms_s_pr_total ) { + if ( ptr_APPEND_LIT( " size.prtotal=" ) ) return -1; + if ( lim->lms_s_pr_total == -1 ? ptr_APPEND_LIT( "unlimited " ) + : lim->lms_s_pr_total == -2 ? ptr_APPEND_LIT( "disabled " ) + : ptr_APPEND_FMT1( "%d ", lim->lms_s_pr_total ) ) + return -1; + } + } + + if ( which & SLAP_LIMIT_TIME ) { + if ( lim->lms_t_soft != SLAPD_DEFAULT_TIMELIMIT ) { + + /* If same as global limit, drop it */ + if ( lim != &frontendDB->be_def_limit && + lim->lms_t_soft == frontendDB->be_def_limit.lms_t_soft ) + { + goto t_hard; + + /* If there's also a hard limit, fully qualify this one */ + } else if ( lim->lms_t_hard ) { + if ( ptr_APPEND_LIT( " time.soft=" ) ) return -1; + + /* If doing both size & time, qualify this */ + } else if ( which & SLAP_LIMIT_SIZE ) { + if ( ptr_APPEND_LIT( " time=" ) ) return -1; + } + + if ( lim->lms_t_soft == -1 + ? ptr_APPEND_LIT( "unlimited " ) + : ptr_APPEND_FMT1( "%d ", lim->lms_t_soft ) ) + return -1; + } +t_hard: + if ( lim->lms_t_hard ) { + if ( ptr_APPEND_LIT( " time.hard=" ) ) return -1; + if ( lim->lms_t_hard == -1 + ? ptr_APPEND_LIT( "unlimited " ) + : ptr_APPEND_FMT1( "%d ", lim->lms_t_hard ) ) + return -1; + } + } + if ( ptr != bv->bv_val ) { + ptr--; + *ptr = '\0'; + bv->bv_len = ptr - bv->bv_val; + } + + return 0; +} + +int +limits_check( Operation *op, SlapReply *rs ) +{ + assert( op != NULL ); + assert( rs != NULL ); + /* FIXME: should this be always true? */ + assert( op->o_tag == LDAP_REQ_SEARCH); + + /* protocol only allows 0..maxInt; + * + * internal searches: + * - may use SLAP_NO_LIMIT ( = -1 ) to indicate no limits; + * - should use slimit = N and tlimit = SLAP_NO_LIMIT to + * indicate searches that should return exactly N matches, + * and handle errors thru a callback (see for instance + * slap_sasl_match() and slap_sasl2dn()) + */ + if ( op->ors_tlimit == SLAP_NO_LIMIT && op->ors_slimit == SLAP_NO_LIMIT ) { + return 0; + } + + /* allow root to set no limit */ + if ( be_isroot( op ) ) { + op->ors_limit = NULL; + + if ( op->ors_tlimit == 0 ) { + op->ors_tlimit = SLAP_NO_LIMIT; + } + + if ( op->ors_slimit == 0 ) { + op->ors_slimit = SLAP_NO_LIMIT; + } + + /* if paged results and slimit are requested */ + if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED && + op->ors_slimit != SLAP_NO_LIMIT ) { + PagedResultsState *ps = op->o_pagedresults_state; + int total = op->ors_slimit - ps->ps_count; + if ( total > 0 ) { + op->ors_slimit = total; + } else { + op->ors_slimit = 0; + } + } + + /* if not root, get appropriate limits */ + } else { + ( void ) limits_get( op, &op->ors_limit ); + + assert( op->ors_limit != NULL ); + + /* if no limit is required, use soft limit */ + if ( op->ors_tlimit == 0 ) { + op->ors_tlimit = op->ors_limit->lms_t_soft; + + /* limit required: check if legal */ + } else { + if ( op->ors_limit->lms_t_hard == 0 ) { + if ( op->ors_limit->lms_t_soft > 0 + && ( op->ors_tlimit > op->ors_limit->lms_t_soft ) ) { + op->ors_tlimit = op->ors_limit->lms_t_soft; + } + + } else if ( op->ors_limit->lms_t_hard > 0 ) { +#ifdef ABOVE_HARD_LIMIT_IS_ERROR + if ( op->ors_tlimit == SLAP_MAX_LIMIT ) { + op->ors_tlimit = op->ors_limit->lms_t_hard; + + } else if ( op->ors_tlimit > op->ors_limit->lms_t_hard ) { + /* error if exceeding hard limit */ + rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED; + send_ldap_result( op, rs ); + rs->sr_err = LDAP_SUCCESS; + return -1; + } +#else /* ! ABOVE_HARD_LIMIT_IS_ERROR */ + if ( op->ors_tlimit > op->ors_limit->lms_t_hard ) { + op->ors_tlimit = op->ors_limit->lms_t_hard; + } +#endif /* ! ABOVE_HARD_LIMIT_IS_ERROR */ + } + } + + /* else leave as is */ + + /* don't even get to backend if candidate check is disabled */ + if ( op->ors_limit->lms_s_unchecked == 0 ) { + rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED; + send_ldap_result( op, rs ); + rs->sr_err = LDAP_SUCCESS; + return -1; + } + + /* if paged results is requested */ + if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) { + int slimit = -2; + int pr_total; + PagedResultsState *ps = op->o_pagedresults_state; + + /* paged results is not allowed */ + if ( op->ors_limit->lms_s_pr_total == -2 ) { + rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED; + rs->sr_text = "pagedResults control not allowed"; + send_ldap_result( op, rs ); + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + return -1; + } + + if ( op->ors_limit->lms_s_pr > 0 + && ps->ps_size > op->ors_limit->lms_s_pr ) + { + rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED; + rs->sr_text = "illegal pagedResults page size"; + send_ldap_result( op, rs ); + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + return -1; + } + + if ( op->ors_limit->lms_s_pr_total == 0 ) { + if ( op->ors_limit->lms_s_hard == 0 ) { + pr_total = op->ors_limit->lms_s_soft; + } else { + pr_total = op->ors_limit->lms_s_hard; + } + } else { + pr_total = op->ors_limit->lms_s_pr_total; + } + + if ( pr_total == -1 ) { + if ( op->ors_slimit == 0 || op->ors_slimit == SLAP_MAX_LIMIT ) { + slimit = -1; + + } else { + slimit = op->ors_slimit - ps->ps_count; + } + +#ifdef ABOVE_HARD_LIMIT_IS_ERROR + } else if ( pr_total > 0 && op->ors_slimit != SLAP_MAX_LIMIT + && ( op->ors_slimit == SLAP_NO_LIMIT + || op->ors_slimit > pr_total ) ) + { + rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED; + send_ldap_result( op, rs ); + rs->sr_err = LDAP_SUCCESS; + return -1; +#endif /* ! ABOVE_HARD_LIMIT_IS_ERROR */ + + } else { + /* if no limit is required, use soft limit */ + int total; + int slimit2; + + /* first round of pagedResults: + * set count to any appropriate limit */ + + /* if the limit is set, check that it does + * not violate any server-side limit */ +#ifdef ABOVE_HARD_LIMIT_IS_ERROR + if ( op->ors_slimit == SLAP_MAX_LIMIT ) +#else /* ! ABOVE_HARD_LIMIT_IS_ERROR */ + if ( op->ors_slimit == SLAP_MAX_LIMIT + || op->ors_slimit > pr_total ) +#endif /* ! ABOVE_HARD_LIMIT_IS_ERROR */ + { + slimit2 = op->ors_slimit = pr_total; + + } else if ( op->ors_slimit == 0 ) { + slimit2 = pr_total; + + } else { + slimit2 = op->ors_slimit; + } + + total = slimit2 - ps->ps_count; + + if ( total >= 0 ) { + if ( op->ors_limit->lms_s_pr > 0 ) { + /* use the smallest limit set by total/per page */ + if ( total < op->ors_limit->lms_s_pr ) { + slimit = total; + + } else { + /* use the perpage limit if any + * NOTE: + 1 because given value must be legal */ + slimit = op->ors_limit->lms_s_pr + 1; + } + + } else { + /* use the total limit if any */ + slimit = total; + } + + } else if ( op->ors_limit->lms_s_pr > 0 ) { + /* use the perpage limit if any + * NOTE: + 1 because the given value must be legal */ + slimit = op->ors_limit->lms_s_pr + 1; + + } else { + /* use the standard hard/soft limit if any */ + slimit = op->ors_limit->lms_s_hard; + } + } + + /* if got any limit, use it */ + if ( slimit != -2 ) { + if ( op->ors_slimit == 0 ) { + op->ors_slimit = slimit; + + } else if ( slimit > 0 ) { + if ( op->ors_slimit - ps->ps_count > slimit ) { + rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED; + send_ldap_result( op, rs ); + rs->sr_err = LDAP_SUCCESS; + return -1; + } + op->ors_slimit = slimit; + + } else if ( slimit == 0 ) { + op->ors_slimit = 0; + } + + } else { + /* use the standard hard/soft limit if any */ + op->ors_slimit = pr_total; + } + + /* no limit requested: use soft, whatever it is */ + } else if ( op->ors_slimit == 0 ) { + op->ors_slimit = op->ors_limit->lms_s_soft; + + /* limit requested: check if legal */ + } else { + /* hard limit as soft (traditional behavior) */ + if ( op->ors_limit->lms_s_hard == 0 ) { + if ( op->ors_limit->lms_s_soft > 0 + && op->ors_slimit > op->ors_limit->lms_s_soft ) { + op->ors_slimit = op->ors_limit->lms_s_soft; + } + + /* explicit hard limit: error if violated */ + } else if ( op->ors_limit->lms_s_hard > 0 ) { +#ifdef ABOVE_HARD_LIMIT_IS_ERROR + if ( op->ors_slimit == SLAP_MAX_LIMIT ) { + op->ors_slimit = op->ors_limit->lms_s_hard; + + } else if ( op->ors_slimit > op->ors_limit->lms_s_hard ) { + /* if limit exceeds hard, error */ + rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED; + send_ldap_result( op, rs ); + rs->sr_err = LDAP_SUCCESS; + return -1; + } +#else /* ! ABOVE_HARD_LIMIT_IS_ERROR */ + if ( op->ors_slimit > op->ors_limit->lms_s_hard ) { + op->ors_slimit = op->ors_limit->lms_s_hard; + } +#endif /* ! ABOVE_HARD_LIMIT_IS_ERROR */ + } + } + + /* else leave as is */ + } + + return 0; +} + +void +limits_free_one( + struct slap_limits *lm ) +{ + if ( ( lm->lm_flags & SLAP_LIMITS_MASK ) == SLAP_LIMITS_REGEX ) + regfree( &lm->lm_regex ); + + if ( !BER_BVISNULL( &lm->lm_pat ) ) + ch_free( lm->lm_pat.bv_val ); + + ch_free( lm ); +} + +void +limits_destroy( + struct slap_limits **lm ) +{ + int i; + + if ( lm == NULL ) { + return; + } + + for ( i = 0; lm[ i ]; i++ ) { + limits_free_one( lm[ i ] ); + } + + ch_free( lm ); +} diff --git a/servers/slapd/lock.c b/servers/slapd/lock.c new file mode 100644 index 0000000..b8c9b32 --- /dev/null +++ b/servers/slapd/lock.c @@ -0,0 +1,83 @@ +/* lock.c - routines to open and apply an advisory lock to a file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> +#include <ac/time.h> +#include <ac/unistd.h> + +#ifdef HAVE_SYS_FILE_H +#include <sys/file.h> +#endif + +#include "slap.h" +#include <lutil.h> + +FILE * +lock_fopen( const char *fname, const char *type, FILE **lfp ) +{ + FILE *fp; + char buf[MAXPATHLEN]; + + /* open the lock file */ + snprintf( buf, sizeof buf, "%s.lock", fname ); + + if ( (*lfp = fopen( buf, "w" )) == NULL ) { + Debug( LDAP_DEBUG_ANY, "could not open \"%s\"\n", buf, 0, 0 ); + + return( NULL ); + } + + /* acquire the lock */ + ldap_lockf( fileno(*lfp) ); + + /* open the log file */ + if ( (fp = fopen( fname, type )) == NULL ) { + Debug( LDAP_DEBUG_ANY, "could not open \"%s\"\n", fname, 0, 0 ); + + ldap_unlockf( fileno(*lfp) ); + fclose( *lfp ); + *lfp = NULL; + return( NULL ); + } + + return( fp ); +} + +int +lock_fclose( FILE *fp, FILE *lfp ) +{ + int rc = fclose( fp ); + /* unlock */ + ldap_unlockf( fileno(lfp) ); + fclose( lfp ); + + return( rc ); +} diff --git a/servers/slapd/main.c b/servers/slapd/main.c new file mode 100644 index 0000000..fff83b7 --- /dev/null +++ b/servers/slapd/main.c @@ -0,0 +1,1151 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> +#include <ac/unistd.h> +#include <ac/wait.h> +#include <ac/errno.h> + +#include "slap.h" +#include "lutil.h" +#include "ldif.h" + +#ifdef LDAP_SLAPI +#include "slapi/slapi.h" +#endif + +#ifdef LDAP_SIGCHLD +static RETSIGTYPE wait4child( int sig ); +#endif + +#ifdef HAVE_NT_SERVICE_MANAGER +#define MAIN_RETURN(x) return +static struct sockaddr_in bind_addr; + +#define SERVICE_EXIT( e, n ) do { \ + if ( is_NT_Service ) { \ + lutil_ServiceStatus.dwWin32ExitCode = (e); \ + lutil_ServiceStatus.dwServiceSpecificExitCode = (n); \ + } \ +} while ( 0 ) + +#else +#define SERVICE_EXIT( e, n ) +#define MAIN_RETURN(x) return(x) +#endif + +typedef int (MainFunc) LDAP_P(( int argc, char *argv[] )); +extern MainFunc slapadd, slapcat, slapdn, slapindex, slappasswd, + slaptest, slapauth, slapacl, slapschema; + +static struct { + char *name; + MainFunc *func; +} tools[] = { + {"slapadd", slapadd}, + {"slapcat", slapcat}, + {"slapdn", slapdn}, + {"slapindex", slapindex}, + {"slappasswd", slappasswd}, + {"slapschema", slapschema}, + {"slaptest", slaptest}, + {"slapauth", slapauth}, + {"slapacl", slapacl}, + /* NOTE: new tools must be added in chronological order, + * not in alphabetical order, because for backwards + * compatibility name[4] is used to identify the + * tools; so name[4]=='a' must refer to "slapadd" and + * not to "slapauth". Alphabetical order can be used + * for tools whose name[4] is not used yet */ + {NULL, NULL} +}; + +/* + * when more than one slapd is running on one machine, each one might have + * it's own LOCAL for syslogging and must have its own pid/args files + */ + +#ifndef HAVE_MKVERSION +const char Versionstr[] = + OPENLDAP_PACKAGE " " OPENLDAP_VERSION " Standalone LDAP Server (slapd)"; +#endif + +extern OverlayInit slap_oinfo[]; +extern BackendInfo slap_binfo[]; + +#define CHECK_NONE 0x00 +#define CHECK_CONFIG 0x01 +#define CHECK_LOGLEVEL 0x02 +static int check = CHECK_NONE; +static int version = 0; + +void *slap_tls_ctx; +LDAP *slap_tls_ld; + +static int +slapd_opt_slp( const char *val, void *arg ) +{ +#ifdef HAVE_SLP + /* NULL is default */ + if ( val == NULL || *val == '(' || strcasecmp( val, "on" ) == 0 ) { + slapd_register_slp = 1; + slapd_slp_attrs = (val != NULL && *val == '(') ? val : NULL; + + } else if ( strcasecmp( val, "off" ) == 0 ) { + slapd_register_slp = 0; + + /* NOTE: add support for URL specification? */ + + } else { + fprintf(stderr, "unrecognized value \"%s\" for SLP option\n", val ); + return -1; + } + + return 0; + +#else + fputs( "slapd: SLP support is not available\n", stderr ); + return 0; +#endif +} + +/* + * Option helper structure: + * + * oh_nam is left-hand part of <option>[=<value>] + * oh_fnc is handler function + * oh_arg is an optional arg to oh_fnc + * oh_usage is the one-line usage string related to the option, + * which is assumed to start with <option>[=<value>] + * + * please leave valid options in the structure, and optionally #ifdef + * their processing inside the helper, so that reasonable and helpful + * error messages can be generated if a disabled option is requested. + */ +struct option_helper { + struct berval oh_name; + int (*oh_fnc)(const char *val, void *arg); + void *oh_arg; + const char *oh_usage; +} option_helpers[] = { + { BER_BVC("slp"), slapd_opt_slp, NULL, "slp[={on|off|(attrs)}] enable/disable SLP using (attrs)" }, + { BER_BVNULL, 0, NULL, NULL } +}; + +#if defined(LDAP_DEBUG) && defined(LDAP_SYSLOG) +#ifdef LOG_LOCAL4 +int +parse_syslog_user( const char *arg, int *syslogUser ) +{ + static slap_verbmasks syslogUsers[] = { + { BER_BVC( "LOCAL0" ), LOG_LOCAL0 }, + { BER_BVC( "LOCAL1" ), LOG_LOCAL1 }, + { BER_BVC( "LOCAL2" ), LOG_LOCAL2 }, + { BER_BVC( "LOCAL3" ), LOG_LOCAL3 }, + { BER_BVC( "LOCAL4" ), LOG_LOCAL4 }, + { BER_BVC( "LOCAL5" ), LOG_LOCAL5 }, + { BER_BVC( "LOCAL6" ), LOG_LOCAL6 }, + { BER_BVC( "LOCAL7" ), LOG_LOCAL7 }, +#ifdef LOG_USER + { BER_BVC( "USER" ), LOG_USER }, +#endif /* LOG_USER */ +#ifdef LOG_DAEMON + { BER_BVC( "DAEMON" ), LOG_DAEMON }, +#endif /* LOG_DAEMON */ + { BER_BVNULL, 0 } + }; + int i = verb_to_mask( arg, syslogUsers ); + + if ( BER_BVISNULL( &syslogUsers[ i ].word ) ) { + Debug( LDAP_DEBUG_ANY, + "unrecognized syslog user \"%s\".\n", + arg, 0, 0 ); + return 1; + } + + *syslogUser = syslogUsers[ i ].mask; + + return 0; +} +#endif /* LOG_LOCAL4 */ + +int +parse_syslog_level( const char *arg, int *levelp ) +{ + static slap_verbmasks str2syslog_level[] = { + { BER_BVC( "EMERG" ), LOG_EMERG }, + { BER_BVC( "ALERT" ), LOG_ALERT }, + { BER_BVC( "CRIT" ), LOG_CRIT }, + { BER_BVC( "ERR" ), LOG_ERR }, + { BER_BVC( "WARNING" ), LOG_WARNING }, + { BER_BVC( "NOTICE" ), LOG_NOTICE }, + { BER_BVC( "INFO" ), LOG_INFO }, + { BER_BVC( "DEBUG" ), LOG_DEBUG }, + { BER_BVNULL, 0 } + }; + int i = verb_to_mask( arg, str2syslog_level ); + if ( BER_BVISNULL( &str2syslog_level[ i ].word ) ) { + Debug( LDAP_DEBUG_ANY, + "unknown syslog level \"%s\".\n", + arg, 0, 0 ); + return 1; + } + + *levelp = str2syslog_level[ i ].mask; + + return 0; +} +#endif /* LDAP_DEBUG && LDAP_SYSLOG */ + +int +parse_debug_unknowns( char **unknowns, int *levelp ) +{ + int i, level, rc = 0; + + for ( i = 0; unknowns[ i ] != NULL; i++ ) { + level = 0; + if ( str2loglevel( unknowns[ i ], &level )) { + fprintf( stderr, + "unrecognized log level \"%s\"\n", unknowns[ i ] ); + rc = 1; + } else { + *levelp |= level; + } + } + return rc; +} + +int +parse_debug_level( const char *arg, int *levelp, char ***unknowns ) +{ + int level; + + if ( arg && arg[ 0 ] != '-' && !isdigit( (unsigned char) arg[ 0 ] ) ) + { + int i; + char **levels; + + levels = ldap_str2charray( arg, "," ); + + for ( i = 0; levels[ i ] != NULL; i++ ) { + level = 0; + + if ( str2loglevel( levels[ i ], &level ) ) { + /* remember this for later */ + ldap_charray_add( unknowns, levels[ i ] ); + fprintf( stderr, + "unrecognized log level \"%s\" (deferred)\n", + levels[ i ] ); + } else { + *levelp |= level; + } + } + + ldap_charray_free( levels ); + + } else { + int rc; + + if ( arg[0] == '-' ) { + rc = lutil_atoix( &level, arg, 0 ); + } else { + unsigned ulevel; + + rc = lutil_atoux( &ulevel, arg, 0 ); + level = (int)ulevel; + } + + if ( rc ) { + fprintf( stderr, + "unrecognized log level " + "\"%s\"\n", arg ); + return 1; + } + + if ( level == 0 ) { + *levelp = 0; + + } else { + *levelp |= level; + } + } + + return 0; +} + +static void +usage( char *name ) +{ + fprintf( stderr, + "usage: %s options\n", name ); + fprintf( stderr, + "\t-4\t\tIPv4 only\n" + "\t-6\t\tIPv6 only\n" + "\t-T {acl|add|auth|cat|dn|index|passwd|test}\n" + "\t\t\tRun in Tool mode\n" + "\t-c cookie\tSync cookie of consumer\n" + "\t-d level\tDebug level" "\n" + "\t-f filename\tConfiguration file\n" + "\t-F dir\tConfiguration directory\n" +#if defined(HAVE_SETUID) && defined(HAVE_SETGID) + "\t-g group\tGroup (id or name) to run as\n" +#endif + "\t-h URLs\t\tList of URLs to serve\n" +#ifdef SLAP_DEFAULT_SYSLOG_USER + "\t-l facility\tSyslog facility (default: LOCAL4)\n" +#endif + "\t-n serverName\tService name\n" + "\t-o <opt>[=val] generic means to specify options" ); + if ( !BER_BVISNULL( &option_helpers[0].oh_name ) ) { + int i; + + fprintf( stderr, "; supported options:\n" ); + for ( i = 0; !BER_BVISNULL( &option_helpers[i].oh_name ); i++) { + fprintf( stderr, "\t\t%s\n", option_helpers[i].oh_usage ); + } + } else { + fprintf( stderr, "\n" ); + } + fprintf( stderr, +#ifdef HAVE_CHROOT + "\t-r directory\tSandbox directory to chroot to\n" +#endif + "\t-s level\tSyslog level\n" +#if defined(HAVE_SETUID) && defined(HAVE_SETGID) + "\t-u user\t\tUser (id or name) to run as\n" +#endif + "\t-V\t\tprint version info (-VV exit afterwards, -VVV print\n" + "\t\t\tinfo about static overlays and backends)\n" + ); +} + +#ifdef HAVE_NT_SERVICE_MANAGER +void WINAPI ServiceMain( DWORD argc, LPTSTR *argv ) +#else +int main( int argc, char **argv ) +#endif +{ + int i, no_detach = 0; + int rc = 1; + char *urls = NULL; +#if defined(HAVE_SETUID) && defined(HAVE_SETGID) + char *username = NULL; + char *groupname = NULL; +#endif +#if defined(HAVE_CHROOT) + char *sandbox = NULL; +#endif +#ifdef SLAP_DEFAULT_SYSLOG_USER + int syslogUser = SLAP_DEFAULT_SYSLOG_USER; +#endif + +#ifndef HAVE_WINSOCK + int pid, waitfds[2]; +#endif + int g_argc = argc; + char **g_argv = argv; + + char *configfile = NULL; + char *configdir = NULL; + char *serverName; + int serverMode = SLAP_SERVER_MODE; + + struct sync_cookie *scp = NULL; + struct sync_cookie *scp_entry = NULL; + + char **debug_unknowns = NULL; + char **syslog_unknowns = NULL; + + char *serverNamePrefix = ""; + size_t l; + + int slapd_pid_file_unlink = 0, slapd_args_file_unlink = 0; + int firstopt = 1; + +#ifdef CSRIMALLOC + FILE *leakfile; + if( ( leakfile = fopen( "slapd.leak", "w" )) == NULL ) { + leakfile = stderr; + } +#endif + + slap_sl_mem_init(); + + + (void) ldap_pvt_thread_initialize(); + + serverName = lutil_progname( "slapd", argc, argv ); + + if ( strcmp( serverName, "slapd" ) ) { +#ifdef DEBUG_CLOSE + extern void slapd_debug_close(); + slapd_debug_close(); +#endif + for (i=0; tools[i].name; i++) { + if ( !strcmp( serverName, tools[i].name ) ) { + rc = tools[i].func(argc, argv); + MAIN_RETURN(rc); + } + } + } + +#ifdef HAVE_NT_SERVICE_MANAGER + { + int *ip; + char *newConfigFile; + char *newConfigDir; + char *newUrls; + char *regService = NULL; + + if ( is_NT_Service ) { + lutil_CommenceStartupProcessing( serverName, slap_sig_shutdown ); + if ( strcmp(serverName, SERVICE_NAME) ) + regService = serverName; + } + + ip = (int*)lutil_getRegParam( regService, "DebugLevel" ); + if ( ip != NULL ) { + slap_debug = *ip; + Debug( LDAP_DEBUG_ANY, + "new debug level from registry is: %d\n", slap_debug, 0, 0 ); + } + + newUrls = (char *) lutil_getRegParam(regService, "Urls"); + if (newUrls) { + if (urls) + ch_free(urls); + + urls = ch_strdup(newUrls); + Debug(LDAP_DEBUG_ANY, "new urls from registry: %s\n", + urls, 0, 0); + } + + newConfigFile = (char*)lutil_getRegParam( regService, "ConfigFile" ); + if ( newConfigFile != NULL ) { + configfile = ch_strdup(newConfigFile); + Debug ( LDAP_DEBUG_ANY, "new config file from registry is: %s\n", configfile, 0, 0 ); + } + + newConfigDir = (char*)lutil_getRegParam( regService, "ConfigDir" ); + if ( newConfigDir != NULL ) { + configdir = ch_strdup(newConfigDir); + Debug ( LDAP_DEBUG_ANY, "new config dir from registry is: %s\n", configdir, 0, 0 ); + } + } +#endif + + while ( (i = getopt( argc, argv, + "c:d:f:F:h:n:o:s:tT:V" +#ifdef LDAP_PF_INET6 + "46" +#endif +#ifdef HAVE_CHROOT + "r:" +#endif +#if defined(LDAP_DEBUG) && defined(LDAP_SYSLOG) + "S:" +#ifdef LOG_LOCAL4 + "l:" +#endif +#endif +#if defined(HAVE_SETUID) && defined(HAVE_SETGID) + "u:g:" +#endif + )) != EOF ) { + switch ( i ) { +#ifdef LDAP_PF_INET6 + case '4': + slap_inet4or6 = AF_INET; + break; + case '6': + slap_inet4or6 = AF_INET6; + break; +#endif + + case 'h': /* listen URLs */ + if ( urls != NULL ) free( urls ); + urls = ch_strdup( optarg ); + break; + + case 'c': /* provide sync cookie, override if exist in consumer */ + scp = (struct sync_cookie *) ch_calloc( 1, + sizeof( struct sync_cookie )); + ber_str2bv( optarg, 0, 1, &scp->octet_str ); + + /* This only parses out the rid at this point */ + slap_parse_sync_cookie( scp, NULL ); + + if ( scp->rid == -1 ) { + Debug( LDAP_DEBUG_ANY, + "main: invalid cookie \"%s\"\n", + optarg, 0, 0 ); + slap_sync_cookie_free( scp, 1 ); + goto destroy; + } + + LDAP_STAILQ_FOREACH( scp_entry, &slap_sync_cookie, sc_next ) { + if ( scp->rid == scp_entry->rid ) { + Debug( LDAP_DEBUG_ANY, + "main: duplicated replica id in cookies\n", + 0, 0, 0 ); + slap_sync_cookie_free( scp, 1 ); + goto destroy; + } + } + LDAP_STAILQ_INSERT_TAIL( &slap_sync_cookie, scp, sc_next ); + break; + + case 'd': { /* set debug level and 'do not detach' flag */ + int level = 0; + + if ( strcmp( optarg, "?" ) == 0 ) { + check |= CHECK_LOGLEVEL; + break; + } + + no_detach = 1; + if ( parse_debug_level( optarg, &level, &debug_unknowns ) ) { + goto destroy; + } +#ifdef LDAP_DEBUG + slap_debug |= level; +#else + if ( level != 0 ) + fputs( "must compile with LDAP_DEBUG for debugging\n", + stderr ); +#endif + } break; + + case 'f': /* read config file */ + configfile = ch_strdup( optarg ); + break; + + case 'F': /* use config dir */ + configdir = ch_strdup( optarg ); + break; + + case 'o': { + char *val = strchr( optarg, '=' ); + struct berval opt; + + opt.bv_val = optarg; + + if ( val ) { + opt.bv_len = ( val - optarg ); + val++; + + } else { + opt.bv_len = strlen( optarg ); + } + + for ( i = 0; !BER_BVISNULL( &option_helpers[i].oh_name ); i++ ) { + if ( ber_bvstrcasecmp( &option_helpers[i].oh_name, &opt ) == 0 ) { + assert( option_helpers[i].oh_fnc != NULL ); + if ( (*option_helpers[i].oh_fnc)( val, option_helpers[i].oh_arg ) == -1 ) { + /* we assume the option parsing helper + * issues appropriate and self-explanatory + * error messages... */ + goto stop; + } + break; + } + } + + if ( BER_BVISNULL( &option_helpers[i].oh_name ) ) { + goto unhandled_option; + } + break; + } + + case 's': /* set syslog level */ + if ( strcmp( optarg, "?" ) == 0 ) { + check |= CHECK_LOGLEVEL; + break; + } + + if ( parse_debug_level( optarg, &ldap_syslog, &syslog_unknowns ) ) { + goto destroy; + } + break; + +#if defined(LDAP_DEBUG) && defined(LDAP_SYSLOG) + case 'S': + if ( parse_syslog_level( optarg, &ldap_syslog_level ) ) { + goto destroy; + } + break; + +#ifdef LOG_LOCAL4 + case 'l': /* set syslog local user */ + if ( parse_syslog_user( optarg, &syslogUser ) ) { + goto destroy; + } + break; +#endif +#endif /* LDAP_DEBUG && LDAP_SYSLOG */ + +#ifdef HAVE_CHROOT + case 'r': + if( sandbox ) free(sandbox); + sandbox = ch_strdup( optarg ); + break; +#endif + +#if defined(HAVE_SETUID) && defined(HAVE_SETGID) + case 'u': /* user name */ + if( username ) free(username); + username = ch_strdup( optarg ); + break; + + case 'g': /* group name */ + if( groupname ) free(groupname); + groupname = ch_strdup( optarg ); + break; +#endif /* SETUID && GETUID */ + + case 'n': /* NT service name */ + serverName = ch_strdup( optarg ); + break; + + case 't': + /* deprecated; use slaptest instead */ + fprintf( stderr, "option -t deprecated; " + "use slaptest command instead\n" ); + check |= CHECK_CONFIG; + break; + + case 'V': + version++; + break; + + case 'T': + if ( firstopt == 0 ) { + fprintf( stderr, "warning: \"-T %s\" " + "should be the first option.\n", + optarg ); + } + +#ifdef DEBUG_CLOSE + extern void slapd_debug_close(); + slapd_debug_close(); +#endif + /* try full option string first */ + for ( i = 0; tools[i].name; i++ ) { + if ( strcmp( optarg, &tools[i].name[4] ) == 0 ) { + rc = tools[i].func( argc, argv ); + MAIN_RETURN( rc ); + } + } + + /* try bits of option string (backward compatibility for single char) */ + l = strlen( optarg ); + for ( i = 0; tools[i].name; i++ ) { + if ( strncmp( optarg, &tools[i].name[4], l ) == 0 ) { + rc = tools[i].func( argc, argv ); + MAIN_RETURN( rc ); + } + } + + /* issue error */ + serverName = optarg; + serverNamePrefix = "slap"; + fprintf( stderr, "program name \"%s%s\" unrecognized; " + "aborting...\n", serverNamePrefix, serverName ); + /* FALLTHRU */ + default: +unhandled_option:; + usage( argv[0] ); + rc = 1; + SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 15 ); + goto stop; + } + + if ( firstopt ) { + firstopt = 0; + } + } + + if ( optind != argc ) + goto unhandled_option; + + ber_set_option(NULL, LBER_OPT_DEBUG_LEVEL, &slap_debug); + ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &slap_debug); + ldif_debug = slap_debug; + + if ( version ) { + fprintf( stderr, "%s\n", Versionstr ); + if ( version > 2 ) { + if ( slap_oinfo[0].ov_type ) { + fprintf( stderr, "Included static overlays:\n"); + for ( i= 0 ; slap_oinfo[i].ov_type; i++ ) { + fprintf( stderr, " %s\n", slap_oinfo[i].ov_type ); + } + } + if ( slap_binfo[0].bi_type ) { + fprintf( stderr, "Included static backends:\n"); + for ( i= 0 ; slap_binfo[i].bi_type; i++ ) { + fprintf( stderr, " %s\n", slap_binfo[i].bi_type ); + } + } + } + + if ( version > 1 ) goto stop; + } + +#if defined(LDAP_DEBUG) && defined(LDAP_SYSLOG) + { + char *logName; +#ifdef HAVE_EBCDIC + logName = ch_strdup( serverName ); + __atoe( logName ); +#else + logName = serverName; +#endif + +#ifdef LOG_LOCAL4 + openlog( logName, OPENLOG_OPTIONS, syslogUser ); +#elif defined LOG_DEBUG + openlog( logName, OPENLOG_OPTIONS ); +#endif +#ifdef HAVE_EBCDIC + free( logName ); +#endif + } +#endif /* LDAP_DEBUG && LDAP_SYSLOG */ + + Debug( LDAP_DEBUG_ANY, "%s", Versionstr, 0, 0 ); + + global_host = ldap_pvt_get_fqdn( NULL ); + ber_str2bv( global_host, 0, 0, &global_host_bv ); + + if( check == CHECK_NONE && slapd_daemon_init( urls ) != 0 ) { + rc = 1; + SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 16 ); + goto stop; + } + +#if defined(HAVE_CHROOT) + if ( sandbox ) { + if ( chdir( sandbox ) ) { + perror("chdir"); + rc = 1; + goto stop; + } + if ( chroot( sandbox ) ) { + perror("chroot"); + rc = 1; + goto stop; + } + if ( chdir( "/" ) ) { + perror("chdir"); + rc = 1; + goto stop; + } + } +#endif + +#if defined(HAVE_SETUID) && defined(HAVE_SETGID) + if ( username != NULL || groupname != NULL ) { + slap_init_user( username, groupname ); + } +#endif + + extops_init(); + lutil_passwd_init(); + +#ifdef HAVE_TLS + rc = ldap_create( &slap_tls_ld ); + if ( rc ) { + SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 20 ); + goto destroy; + } + /* Library defaults to full certificate checking. This is correct when + * a client is verifying a server because all servers should have a + * valid cert. But few clients have valid certs, so we want our default + * to be no checking. The config file can override this as usual. + */ + rc = LDAP_OPT_X_TLS_NEVER; + (void) ldap_pvt_tls_set_option( slap_tls_ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &rc ); +#endif + + rc = slap_init( serverMode, serverName ); + if ( rc ) { + SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 18 ); + goto destroy; + } + + if ( read_config( configfile, configdir ) != 0 ) { + rc = 1; + SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 19 ); + + if ( check & CHECK_CONFIG ) { + fprintf( stderr, "config check failed\n" ); + } + + goto destroy; + } + + if ( debug_unknowns ) { + rc = parse_debug_unknowns( debug_unknowns, &slap_debug ); + ldap_charray_free( debug_unknowns ); + debug_unknowns = NULL; + if ( rc ) + goto destroy; + } + if ( syslog_unknowns ) { + rc = parse_debug_unknowns( syslog_unknowns, &ldap_syslog ); + ldap_charray_free( syslog_unknowns ); + syslog_unknowns = NULL; + if ( rc ) + goto destroy; + } + + if ( check & CHECK_LOGLEVEL ) { + rc = 0; + goto destroy; + } + + if ( check & CHECK_CONFIG ) { + fprintf( stderr, "config check succeeded\n" ); + + check &= ~CHECK_CONFIG; + if ( check == CHECK_NONE ) { + rc = 0; + goto destroy; + } + } + + if ( glue_sub_attach( 0 ) != 0 ) { + Debug( LDAP_DEBUG_ANY, + "subordinate config error\n", + 0, 0, 0 ); + + goto destroy; + } + + if ( slap_schema_check( ) != 0 ) { + Debug( LDAP_DEBUG_ANY, + "schema prep error\n", + 0, 0, 0 ); + + goto destroy; + } + +#ifdef HAVE_TLS + rc = ldap_pvt_tls_init(); + if( rc != 0) { + Debug( LDAP_DEBUG_ANY, + "main: TLS init failed: %d\n", + rc, 0, 0 ); + rc = 1; + SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 20 ); + goto destroy; + } + + { + int opt = 1; + + /* Force new ctx to be created */ + rc = ldap_pvt_tls_set_option( slap_tls_ld, LDAP_OPT_X_TLS_NEWCTX, &opt ); + if( rc == 0 ) { + /* The ctx's refcount is bumped up here */ + ldap_pvt_tls_get_option( slap_tls_ld, LDAP_OPT_X_TLS_CTX, &slap_tls_ctx ); + load_extop( &slap_EXOP_START_TLS, 0, starttls_extop ); + } else if ( rc != LDAP_NOT_SUPPORTED ) { + Debug( LDAP_DEBUG_ANY, + "main: TLS init def ctx failed: %d\n", + rc, 0, 0 ); + rc = 1; + SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 20 ); + goto destroy; + } + } +#endif + +#ifdef HAVE_CYRUS_SASL + if( sasl_host == NULL ) { + sasl_host = ch_strdup( global_host ); + } +#endif + + (void) SIGNAL( LDAP_SIGUSR1, slap_sig_wake ); + (void) SIGNAL( LDAP_SIGUSR2, slap_sig_shutdown ); + +#ifdef SIGPIPE + (void) SIGNAL( SIGPIPE, SIG_IGN ); +#endif +#ifdef SIGHUP + (void) SIGNAL( SIGHUP, slap_sig_shutdown ); +#endif + (void) SIGNAL( SIGINT, slap_sig_shutdown ); + (void) SIGNAL( SIGTERM, slap_sig_shutdown ); +#ifdef SIGTRAP + (void) SIGNAL( SIGTRAP, slap_sig_shutdown ); +#endif +#ifdef LDAP_SIGCHLD + (void) SIGNAL( LDAP_SIGCHLD, wait4child ); +#endif +#ifdef SIGBREAK + /* SIGBREAK is generated when Ctrl-Break is pressed. */ + (void) SIGNAL( SIGBREAK, slap_sig_shutdown ); +#endif + +#ifndef HAVE_WINSOCK + if ( !no_detach ) { + if ( lutil_pair( waitfds ) < 0 ) { + Debug( LDAP_DEBUG_ANY, + "main: lutil_pair failed: %d\n", + 0, 0, 0 ); + rc = 1; + goto destroy; + } + pid = lutil_detach( no_detach, 0 ); + if ( pid ) { + char buf[4]; + rc = EXIT_SUCCESS; + close( waitfds[1] ); + if ( read( waitfds[0], buf, 1 ) != 1 ) + rc = EXIT_FAILURE; + _exit( rc ); + } else { + close( waitfds[0] ); + } + } +#endif /* HAVE_WINSOCK */ + +#ifdef CSRIMALLOC + mal_leaktrace(1); +#endif + + if ( slapd_pid_file != NULL ) { + FILE *fp = fopen( slapd_pid_file, "w" ); + + if ( fp == NULL ) { + int save_errno = errno; + + Debug( LDAP_DEBUG_ANY, "unable to open pid file " + "\"%s\": %d (%s)\n", + slapd_pid_file, + save_errno, strerror( save_errno ) ); + + free( slapd_pid_file ); + slapd_pid_file = NULL; + + rc = 1; + goto destroy; + } + fprintf( fp, "%d\n", (int) getpid() ); + fclose( fp ); + slapd_pid_file_unlink = 1; + } + + if ( slapd_args_file != NULL ) { + FILE *fp = fopen( slapd_args_file, "w" ); + + if ( fp == NULL ) { + int save_errno = errno; + + Debug( LDAP_DEBUG_ANY, "unable to open args file " + "\"%s\": %d (%s)\n", + slapd_args_file, + save_errno, strerror( save_errno ) ); + + free( slapd_args_file ); + slapd_args_file = NULL; + + rc = 1; + goto destroy; + } + + for ( i = 0; i < g_argc; i++ ) { + fprintf( fp, "%s ", g_argv[i] ); + } + fprintf( fp, "\n" ); + fclose( fp ); + slapd_args_file_unlink = 1; + } + + /* + * FIXME: moved here from slapd_daemon_task() + * because back-monitor db_open() needs it + */ + time( &starttime ); + + connections_init(); + + if ( slap_startup( NULL ) != 0 ) { + rc = 1; + SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 21 ); + goto shutdown; + } + + Debug( LDAP_DEBUG_ANY, "slapd starting\n", 0, 0, 0 ); + +#ifndef HAVE_WINSOCK + if ( !no_detach ) { + write( waitfds[1], "1", 1 ); + close( waitfds[1] ); + } +#endif + +#ifdef HAVE_NT_EVENT_LOG + if (is_NT_Service) + lutil_LogStartedEvent( serverName, slap_debug, configfile ? + configfile : SLAPD_DEFAULT_CONFIGFILE , urls ); +#endif + + rc = slapd_daemon(); + +#ifdef HAVE_NT_SERVICE_MANAGER + /* Throw away the event that we used during the startup process. */ + if ( is_NT_Service ) + ldap_pvt_thread_cond_destroy( &started_event ); +#endif + +shutdown: + /* remember an error during shutdown */ + rc |= slap_shutdown( NULL ); + +destroy: + if ( check & CHECK_LOGLEVEL ) { + (void)loglevel_print( stdout ); + } + /* remember an error during destroy */ + rc |= slap_destroy(); + + while ( !LDAP_STAILQ_EMPTY( &slap_sync_cookie )) { + scp = LDAP_STAILQ_FIRST( &slap_sync_cookie ); + LDAP_STAILQ_REMOVE_HEAD( &slap_sync_cookie, sc_next ); + ch_free( scp ); + } + +#ifdef SLAPD_MODULES + module_kill(); +#endif + + extops_kill(); + + supported_feature_destroy(); + entry_info_destroy(); + +stop: +#ifdef HAVE_NT_EVENT_LOG + if (is_NT_Service) + lutil_LogStoppedEvent( serverName ); +#endif + + Debug( LDAP_DEBUG_ANY, "slapd stopped.\n", 0, 0, 0 ); + + +#ifdef HAVE_NT_SERVICE_MANAGER + lutil_ReportShutdownComplete(); +#endif + +#ifdef LOG_DEBUG + closelog(); +#endif + slapd_daemon_destroy(); + + controls_destroy(); + + filter_destroy(); + + schema_destroy(); + + lutil_passwd_destroy(); + +#ifdef HAVE_TLS + if ( slap_tls_ld ) { + ldap_pvt_tls_ctx_free( slap_tls_ctx ); + ldap_unbind_ext( slap_tls_ld, NULL, NULL ); + } + ldap_pvt_tls_destroy(); +#endif + + slap_sasl_regexp_destroy(); + + if ( slapd_pid_file_unlink ) { + unlink( slapd_pid_file ); + } + if ( slapd_args_file_unlink ) { + unlink( slapd_args_file ); + } + + config_destroy(); + + if ( configfile ) + ch_free( configfile ); + if ( configdir ) + ch_free( configdir ); + if ( urls ) + ch_free( urls ); + if ( global_host ) + ch_free( global_host ); + + /* kludge, get symbols referenced */ + tavl_free( NULL, NULL ); + +#ifdef CSRIMALLOC + mal_dumpleaktrace( leakfile ); +#endif + + MAIN_RETURN(rc); +} + + +#ifdef LDAP_SIGCHLD + +/* + * Catch and discard terminated child processes, to avoid zombies. + */ + +static RETSIGTYPE +wait4child( int sig ) +{ + int save_errno = errno; + +#ifdef WNOHANG + do + errno = 0; +#ifdef HAVE_WAITPID + while ( waitpid( (pid_t)-1, NULL, WNOHANG ) > 0 || errno == EINTR ); +#else + while ( wait3( NULL, WNOHANG, NULL ) > 0 || errno == EINTR ); +#endif +#else + (void) wait( NULL ); +#endif + (void) SIGNAL_REINSTALL( sig, wait4child ); + errno = save_errno; +} + +#endif /* LDAP_SIGCHLD */ diff --git a/servers/slapd/matchedValues.c b/servers/slapd/matchedValues.c new file mode 100644 index 0000000..e068c0d --- /dev/null +++ b/servers/slapd/matchedValues.c @@ -0,0 +1,348 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" + +static int +test_mra_vrFilter( + Operation *op, + Attribute *a, + MatchingRuleAssertion *mra, + char ***e_flags +); + +static int +test_substrings_vrFilter( + Operation *op, + Attribute *a, + ValuesReturnFilter *f, + char ***e_flags +); + +static int +test_presence_vrFilter( + Operation *op, + Attribute *a, + AttributeDescription *desc, + char ***e_flags +); + +static int +test_ava_vrFilter( + Operation *op, + Attribute *a, + AttributeAssertion *ava, + int type, + char ***e_flags +); + + +int +filter_matched_values( + Operation *op, + Attribute *a, + char ***e_flags ) +{ + ValuesReturnFilter *vrf; + int rc = LDAP_SUCCESS; + + Debug( LDAP_DEBUG_FILTER, "=> filter_matched_values\n", 0, 0, 0 ); + + for ( vrf = op->o_vrFilter; vrf != NULL; vrf = vrf->vrf_next ) { + switch ( vrf->vrf_choice ) { + case SLAPD_FILTER_COMPUTED: + Debug( LDAP_DEBUG_FILTER, " COMPUTED %s (%d)\n", + vrf->vrf_result == LDAP_COMPARE_FALSE ? "false" + : vrf->vrf_result == LDAP_COMPARE_TRUE ? "true" + : vrf->vrf_result == SLAPD_COMPARE_UNDEFINED ? "undefined" + : "error", + vrf->vrf_result, 0 ); + /*This type of filter does not affect the result */ + rc = LDAP_SUCCESS; + break; + + case LDAP_FILTER_EQUALITY: + Debug( LDAP_DEBUG_FILTER, " EQUALITY\n", 0, 0, 0 ); + rc = test_ava_vrFilter( op, a, vrf->vrf_ava, + LDAP_FILTER_EQUALITY, e_flags ); + if( rc == -1 ) return rc; + break; + + case LDAP_FILTER_SUBSTRINGS: + Debug( LDAP_DEBUG_FILTER, " SUBSTRINGS\n", 0, 0, 0 ); + rc = test_substrings_vrFilter( op, a, + vrf, e_flags ); + if( rc == -1 ) return rc; + break; + + case LDAP_FILTER_PRESENT: + Debug( LDAP_DEBUG_FILTER, " PRESENT\n", 0, 0, 0 ); + rc = test_presence_vrFilter( op, a, + vrf->vrf_desc, e_flags ); + if( rc == -1 ) return rc; + break; + + case LDAP_FILTER_GE: + rc = test_ava_vrFilter( op, a, vrf->vrf_ava, + LDAP_FILTER_GE, e_flags ); + if( rc == -1 ) return rc; + break; + + case LDAP_FILTER_LE: + rc = test_ava_vrFilter( op, a, vrf->vrf_ava, + LDAP_FILTER_LE, e_flags ); + if( rc == -1 ) return rc; + break; + + case LDAP_FILTER_EXT: + Debug( LDAP_DEBUG_FILTER, " EXT\n", 0, 0, 0 ); + rc = test_mra_vrFilter( op, a, + vrf->vrf_mra, e_flags ); + if( rc == -1 ) return rc; + break; + + default: + Debug( LDAP_DEBUG_ANY, " unknown filter type %lu\n", + vrf->vrf_choice, 0, 0 ); + rc = LDAP_PROTOCOL_ERROR; + } + } + + Debug( LDAP_DEBUG_FILTER, "<= filter_matched_values %d\n", rc, 0, 0 ); + return( rc ); +} + +static int +test_ava_vrFilter( + Operation *op, + Attribute *a, + AttributeAssertion *ava, + int type, + char ***e_flags ) +{ + int i, j; + + for ( i=0; a != NULL; a = a->a_next, i++ ) { + MatchingRule *mr; + struct berval *bv; + + if ( !is_ad_subtype( a->a_desc, ava->aa_desc ) ) { + continue; + } + + switch ( type ) { + case LDAP_FILTER_APPROX: + mr = a->a_desc->ad_type->sat_approx; + if( mr != NULL ) break; + /* use EQUALITY matching rule if no APPROX rule */ + + case LDAP_FILTER_EQUALITY: + mr = a->a_desc->ad_type->sat_equality; + break; + + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + mr = a->a_desc->ad_type->sat_ordering; + break; + + default: + mr = NULL; + } + + if( mr == NULL ) continue; + + bv = a->a_nvals; + for ( j=0; !BER_BVISNULL( bv ); bv++, j++ ) { + int rc, match; + const char *text; + + rc = value_match( &match, a->a_desc, mr, 0, + bv, &ava->aa_value, &text ); + if( rc != LDAP_SUCCESS ) return rc; + + switch ( type ) { + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_APPROX: + if ( match == 0 ) { + (*e_flags)[i][j] = 1; + } + break; + + case LDAP_FILTER_GE: + if ( match >= 0 ) { + (*e_flags)[i][j] = 1; + } + break; + + case LDAP_FILTER_LE: + if ( match <= 0 ) { + (*e_flags)[i][j] = 1; + } + break; + } + } + } + return LDAP_SUCCESS; +} + +static int +test_presence_vrFilter( + Operation *op, + Attribute *a, + AttributeDescription *desc, + char ***e_flags ) +{ + int i, j; + + for ( i=0; a != NULL; a = a->a_next, i++ ) { + struct berval *bv; + + if ( !is_ad_subtype( a->a_desc, desc ) ) continue; + + for ( bv = a->a_vals, j = 0; !BER_BVISNULL( bv ); bv++, j++ ); + memset( (*e_flags)[i], 1, j); + } + + return( LDAP_SUCCESS ); +} + +static int +test_substrings_vrFilter( + Operation *op, + Attribute *a, + ValuesReturnFilter *vrf, + char ***e_flags ) +{ + int i, j; + + for ( i=0; a != NULL; a = a->a_next, i++ ) { + MatchingRule *mr = a->a_desc->ad_type->sat_substr; + struct berval *bv; + + if ( !is_ad_subtype( a->a_desc, vrf->vrf_sub_desc ) ) { + continue; + } + + if( mr == NULL ) continue; + + bv = a->a_nvals; + for ( j = 0; !BER_BVISNULL( bv ); bv++, j++ ) { + int rc, match; + const char *text; + + rc = value_match( &match, a->a_desc, mr, 0, + bv, vrf->vrf_sub, &text ); + + if( rc != LDAP_SUCCESS ) return rc; + + if ( match == 0 ) { + (*e_flags)[i][j] = 1; + } + } + } + + return LDAP_SUCCESS; +} + +static int +test_mra_vrFilter( + Operation *op, + Attribute *a, + MatchingRuleAssertion *mra, + char ***e_flags ) +{ + int i, j; + + for ( i = 0; a != NULL; a = a->a_next, i++ ) { + struct berval *bv, assertedValue; + int normalize_attribute = 0; + + if ( mra->ma_desc ) { + if ( !is_ad_subtype( a->a_desc, mra->ma_desc ) ) { + continue; + } + assertedValue = mra->ma_value; + + } else { + int rc; + const char *text = NULL; + + /* check if matching is appropriate */ + if ( !mr_usable_with_at( mra->ma_rule, a->a_desc->ad_type ) ) { + continue; + } + + rc = asserted_value_validate_normalize( a->a_desc, mra->ma_rule, + SLAP_MR_EXT|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX, + &mra->ma_value, &assertedValue, &text, op->o_tmpmemctx ); + + if ( rc != LDAP_SUCCESS ) continue; + } + + /* check match */ + if ( mra->ma_rule == a->a_desc->ad_type->sat_equality ) { + bv = a->a_nvals; + + } else { + bv = a->a_vals; + normalize_attribute = 1; + } + + for ( j = 0; !BER_BVISNULL( bv ); bv++, j++ ) { + int rc, match; + const char *text; + struct berval nbv = BER_BVNULL; + + if ( normalize_attribute && mra->ma_rule->smr_normalize ) { + /* see comment in filterentry.c */ + if ( mra->ma_rule->smr_normalize( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + mra->ma_rule->smr_syntax, + mra->ma_rule, + bv, &nbv, op->o_tmpmemctx ) != LDAP_SUCCESS ) + { + /* FIXME: stop processing? */ + continue; + } + + } else { + nbv = *bv; + } + + rc = value_match( &match, a->a_desc, mra->ma_rule, 0, + &nbv, &assertedValue, &text ); + + if ( nbv.bv_val != bv->bv_val ) { + op->o_tmpfree( nbv.bv_val, op->o_tmpmemctx ); + } + + if ( rc != LDAP_SUCCESS ) return rc; + + if ( match == 0 ) { + (*e_flags)[i][j] = 1; + } + } + } + + return LDAP_SUCCESS; +} + diff --git a/servers/slapd/modify.c b/servers/slapd/modify.c new file mode 100644 index 0000000..3650fbf --- /dev/null +++ b/servers/slapd/modify.c @@ -0,0 +1,1097 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "slap.h" +#include "lutil.h" + + +int +do_modify( + Operation *op, + SlapReply *rs ) +{ + struct berval dn = BER_BVNULL; + char textbuf[ SLAP_TEXT_BUFLEN ]; + size_t textlen = sizeof( textbuf ); +#ifdef LDAP_DEBUG + Modifications *tmp; +#endif + + Debug( LDAP_DEBUG_TRACE, "%s do_modify\n", + op->o_log_prefix, 0, 0 ); + /* + * Parse the modify request. It looks like this: + * + * ModifyRequest := [APPLICATION 6] SEQUENCE { + * name DistinguishedName, + * mods SEQUENCE OF SEQUENCE { + * operation ENUMERATED { + * add (0), + * delete (1), + * replace (2) + * }, + * modification SEQUENCE { + * type AttributeType, + * values SET OF AttributeValue + * } + * } + * } + */ + + if ( ber_scanf( op->o_ber, "{m" /*}*/, &dn ) == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, "%s do_modify: ber_scanf failed\n", + op->o_log_prefix, 0, 0 ); + send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" ); + return SLAPD_DISCONNECT; + } + + Debug( LDAP_DEBUG_ARGS, "%s do_modify: dn (%s)\n", + op->o_log_prefix, dn.bv_val, 0 ); + + rs->sr_err = slap_parse_modlist( op, rs, op->o_ber, &op->oq_modify ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s do_modify: slap_parse_modlist failed err=%d msg=%s\n", + op->o_log_prefix, rs->sr_err, rs->sr_text ); + send_ldap_result( op, rs ); + goto cleanup; + } + + if( get_ctrls( op, rs, 1 ) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s do_modify: get_ctrls failed\n", + op->o_log_prefix, 0, 0 ); + /* get_ctrls has sent results. Now clean up. */ + goto cleanup; + } + + rs->sr_err = dnPrettyNormal( NULL, &dn, &op->o_req_dn, &op->o_req_ndn, + op->o_tmpmemctx ); + if( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s do_modify: invalid dn (%s)\n", + op->o_log_prefix, dn.bv_val, 0 ); + send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid DN" ); + goto cleanup; + } + + op->orm_no_opattrs = 0; + +#ifdef LDAP_DEBUG + Debug( LDAP_DEBUG_ARGS, "%s modifications:\n", + op->o_log_prefix, 0, 0 ); + + for ( tmp = op->orm_modlist; tmp != NULL; tmp = tmp->sml_next ) { + Debug( LDAP_DEBUG_ARGS, "\t%s: %s\n", + tmp->sml_op == LDAP_MOD_ADD ? "add" : + (tmp->sml_op == LDAP_MOD_INCREMENT ? "increment" : + (tmp->sml_op == LDAP_MOD_DELETE ? "delete" : + "replace")), tmp->sml_type.bv_val, 0 ); + + if ( tmp->sml_values == NULL ) { + Debug( LDAP_DEBUG_ARGS, "%s\n", + "\t\tno values", NULL, NULL ); + } else if ( BER_BVISNULL( &tmp->sml_values[ 0 ] ) ) { + Debug( LDAP_DEBUG_ARGS, "%s\n", + "\t\tzero values", NULL, NULL ); + } else if ( BER_BVISNULL( &tmp->sml_values[ 1 ] ) ) { + Debug( LDAP_DEBUG_ARGS, "%s, length %ld\n", + "\t\tone value", (long) tmp->sml_values[0].bv_len, NULL ); + } else { + Debug( LDAP_DEBUG_ARGS, "%s\n", + "\t\tmultiple values", NULL, NULL ); + } + } + + if ( StatslogTest( LDAP_DEBUG_STATS ) ) { + char abuf[BUFSIZ/2], *ptr = abuf; + int len = 0; + + Statslog( LDAP_DEBUG_STATS, "%s MOD dn=\"%s\"\n", + op->o_log_prefix, op->o_req_dn.bv_val, 0, 0, 0 ); + + for ( tmp = op->orm_modlist; tmp != NULL; tmp = tmp->sml_next ) { + if (len + 1 + tmp->sml_type.bv_len > sizeof(abuf)) { + Statslog( LDAP_DEBUG_STATS, "%s MOD attr=%s\n", + op->o_log_prefix, abuf, 0, 0, 0 ); + + len = 0; + ptr = abuf; + + if( 1 + tmp->sml_type.bv_len > sizeof(abuf)) { + Statslog( LDAP_DEBUG_STATS, "%s MOD attr=%s\n", + op->o_log_prefix, tmp->sml_type.bv_val, 0, 0, 0 ); + continue; + } + } + if (len) { + *ptr++ = ' '; + len++; + } + ptr = lutil_strcopy(ptr, tmp->sml_type.bv_val); + len += tmp->sml_type.bv_len; + } + if (len) { + Statslog( LDAP_DEBUG_STATS, "%s MOD attr=%s\n", + op->o_log_prefix, abuf, 0, 0, 0 ); + } + } +#endif /* LDAP_DEBUG */ + + rs->sr_err = slap_mods_check( op, op->orm_modlist, + &rs->sr_text, textbuf, textlen, NULL ); + + if ( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + op->o_bd = frontendDB; + rs->sr_err = frontendDB->be_modify( op, rs ); + +#ifdef LDAP_X_TXN + if( rs->sr_err == LDAP_X_TXN_SPECIFY_OKAY ) { + /* skip cleanup */ + return rs->sr_err; + } +#endif + +cleanup: + op->o_tmpfree( op->o_req_dn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx ); + if ( op->orm_modlist != NULL ) slap_mods_free( op->orm_modlist, 1 ); + + return rs->sr_err; +} + +int +fe_op_modify( Operation *op, SlapReply *rs ) +{ + BackendDB *op_be, *bd = op->o_bd; + char textbuf[ SLAP_TEXT_BUFLEN ]; + size_t textlen = sizeof( textbuf ); + + if ( BER_BVISEMPTY( &op->o_req_ndn ) ) { + Debug( LDAP_DEBUG_ANY, "%s do_modify: root dse!\n", + op->o_log_prefix, 0, 0 ); + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "modify upon the root DSE not supported" ); + goto cleanup; + + } else if ( bvmatch( &op->o_req_ndn, &frontendDB->be_schemandn ) ) { + Debug( LDAP_DEBUG_ANY, "%s do_modify: subschema subentry!\n", + op->o_log_prefix, 0, 0 ); + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "modification of subschema subentry not supported" ); + goto cleanup; + } + + /* + * We could be serving multiple database backends. Select the + * appropriate one, or send a referral to our "referral server" + * if we don't hold it. + */ + op->o_bd = select_backend( &op->o_req_ndn, 1 ); + if ( op->o_bd == NULL ) { + op->o_bd = bd; + rs->sr_ref = referral_rewrite( default_referral, + NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + if ( !rs->sr_ref ) { + rs->sr_ref = default_referral; + } + + if ( rs->sr_ref != NULL ) { + rs->sr_err = LDAP_REFERRAL; + send_ldap_result( op, rs ); + + if ( rs->sr_ref != default_referral ) { + ber_bvarray_free( rs->sr_ref ); + } + + } else { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "no global superior knowledge" ); + } + goto cleanup; + } + + /* If we've got a glued backend, check the real backend */ + op_be = op->o_bd; + if ( SLAP_GLUE_INSTANCE( op->o_bd )) { + op->o_bd = select_backend( &op->o_req_ndn, 0 ); + } + + /* check restrictions */ + if ( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + /* check for referrals */ + if ( backend_check_referrals( op, rs ) != LDAP_SUCCESS ) { + goto cleanup; + } + + rs->sr_err = slap_mods_obsolete_check( op, op->orm_modlist, + &rs->sr_text, textbuf, textlen ); + if ( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + /* check for modify/increment support */ + if ( op->orm_increment && !SLAP_INCREMENT( op->o_bd ) ) { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "modify/increment not supported in context" ); + goto cleanup; + } + + /* + * do the modify if 1 && (2 || 3) + * 1) there is a modify function implemented in this backend; + * 2) this backend is the provider for what it holds; + * 3) it's a replica and the dn supplied is the update_ndn. + */ + if ( op->o_bd->be_modify ) { + /* do the update here */ + int repl_user = be_isupdate( op ); + + /* + * Multimaster slapd does not have to check for replicator dn + * because it accepts each modify request + */ + if ( !SLAP_SINGLE_SHADOW(op->o_bd) || repl_user ) { + int update = !BER_BVISEMPTY( &op->o_bd->be_update_ndn ); + + op->o_bd = op_be; + + if ( !update ) { + rs->sr_err = slap_mods_no_user_mod_check( op, op->orm_modlist, + &rs->sr_text, textbuf, textlen ); + if ( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + } + op->o_bd->be_modify( op, rs ); + + } else { /* send a referral */ + BerVarray defref = op->o_bd->be_update_refs + ? op->o_bd->be_update_refs : default_referral; + if ( defref != NULL ) { + rs->sr_ref = referral_rewrite( defref, + NULL, &op->o_req_dn, + LDAP_SCOPE_DEFAULT ); + if ( rs->sr_ref == NULL ) { + /* FIXME: must duplicate, because + * overlays may muck with it */ + rs->sr_ref = defref; + } + rs->sr_err = LDAP_REFERRAL; + send_ldap_result( op, rs ); + if ( rs->sr_ref != defref ) { + ber_bvarray_free( rs->sr_ref ); + } + + } else { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "shadow context; no update referral" ); + } + } + + } else { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "operation not supported within namingContext" ); + } + +cleanup:; + op->o_bd = bd; + return rs->sr_err; +} + +/* + * Obsolete constraint checking. + */ +int +slap_mods_obsolete_check( + Operation *op, + Modifications *ml, + const char **text, + char *textbuf, + size_t textlen ) +{ + if( get_relax( op ) ) return LDAP_SUCCESS; + + for ( ; ml != NULL; ml = ml->sml_next ) { + if ( is_at_obsolete( ml->sml_desc->ad_type ) && + (( ml->sml_op != LDAP_MOD_REPLACE && + ml->sml_op != LDAP_MOD_DELETE ) || + ml->sml_values != NULL )) + { + /* + * attribute is obsolete, + * only allow replace/delete with no values + */ + snprintf( textbuf, textlen, + "%s: attribute is obsolete", + ml->sml_type.bv_val ); + *text = textbuf; + return LDAP_CONSTRAINT_VIOLATION; + } + } + + return LDAP_SUCCESS; +} + +/* + * No-user-modification constraint checking. + */ +int +slap_mods_no_user_mod_check( + Operation *op, + Modifications *ml, + const char **text, + char *textbuf, + size_t textlen ) +{ + for ( ; ml != NULL; ml = ml->sml_next ) { + if ( !is_at_no_user_mod( ml->sml_desc->ad_type ) ) { + continue; + } + + if ( ml->sml_flags & SLAP_MOD_INTERNAL ) { + continue; + } + + if ( get_relax( op ) ) { + if ( ml->sml_desc->ad_type->sat_flags & SLAP_AT_MANAGEABLE ) { + ml->sml_flags |= SLAP_MOD_MANAGING; + continue; + } + + /* attribute not manageable */ + snprintf( textbuf, textlen, + "%s: no-user-modification attribute not manageable", + ml->sml_type.bv_val ); + + } else { + /* user modification disallowed */ + snprintf( textbuf, textlen, + "%s: no user modification allowed", + ml->sml_type.bv_val ); + } + + *text = textbuf; + return LDAP_CONSTRAINT_VIOLATION; + } + + return LDAP_SUCCESS; +} + +int +slap_mods_no_repl_user_mod_check( + Operation *op, + Modifications *ml, + const char **text, + char *textbuf, + size_t textlen ) +{ + Modifications *mods; + Modifications *modp; + + for ( mods = ml; mods != NULL; mods = mods->sml_next ) { + assert( mods->sml_op == LDAP_MOD_ADD ); + + /* check doesn't already appear */ + for ( modp = ml; modp != NULL; modp = modp->sml_next ) { + if ( mods->sml_desc == modp->sml_desc && mods != modp ) { + snprintf( textbuf, textlen, + "attribute '%s' provided more than once", + mods->sml_desc->ad_cname.bv_val ); + *text = textbuf; + return LDAP_TYPE_OR_VALUE_EXISTS; + } + } + } + + return LDAP_SUCCESS; +} + +/* + * Do basic attribute type checking and syntax validation. + */ +int slap_mods_check( + Operation *op, + Modifications *ml, + const char **text, + char *textbuf, + size_t textlen, + void *ctx ) +{ + int rc; + + for( ; ml != NULL; ml = ml->sml_next ) { + AttributeDescription *ad = NULL; + + /* convert to attribute description */ + if ( ml->sml_desc == NULL ) { + rc = slap_bv2ad( &ml->sml_type, &ml->sml_desc, text ); + if( rc != LDAP_SUCCESS ) { + if ( get_no_schema_check( op )) { + rc = slap_bv2undef_ad( &ml->sml_type, &ml->sml_desc, + text, 0 ); + } + } + if( rc != LDAP_SUCCESS ) { + snprintf( textbuf, textlen, "%s: %s", + ml->sml_type.bv_val, *text ); + *text = textbuf; + return rc; + } + } + + ad = ml->sml_desc; + + if( slap_syntax_is_binary( ad->ad_type->sat_syntax ) + && !slap_ad_is_binary( ad )) + { + /* attribute requires binary transfer */ + snprintf( textbuf, textlen, + "%s: requires ;binary transfer", + ml->sml_type.bv_val ); + *text = textbuf; + return LDAP_UNDEFINED_TYPE; + } + + if( !slap_syntax_is_binary( ad->ad_type->sat_syntax ) + && slap_ad_is_binary( ad )) + { + /* attribute does not require binary transfer */ + snprintf( textbuf, textlen, + "%s: disallows ;binary transfer", + ml->sml_type.bv_val ); + *text = textbuf; + return LDAP_UNDEFINED_TYPE; + } + + if( slap_ad_is_tag_range( ad )) { + /* attribute requires binary transfer */ + snprintf( textbuf, textlen, + "%s: inappropriate use of tag range option", + ml->sml_type.bv_val ); + *text = textbuf; + return LDAP_UNDEFINED_TYPE; + } + +#if 0 + if ( is_at_obsolete( ad->ad_type ) && + (( ml->sml_op != LDAP_MOD_REPLACE && + ml->sml_op != LDAP_MOD_DELETE ) || + ml->sml_values != NULL )) + { + /* + * attribute is obsolete, + * only allow replace/delete with no values + */ + snprintf( textbuf, textlen, + "%s: attribute is obsolete", + ml->sml_type.bv_val ); + *text = textbuf; + return LDAP_CONSTRAINT_VIOLATION; + } +#endif + + if ( ml->sml_op == LDAP_MOD_INCREMENT && +#ifdef SLAPD_REAL_SYNTAX + !is_at_syntax( ad->ad_type, SLAPD_REAL_SYNTAX ) && +#endif + !is_at_syntax( ad->ad_type, SLAPD_INTEGER_SYNTAX ) ) + { + /* + * attribute values must be INTEGER or REAL + */ + snprintf( textbuf, textlen, + "%s: attribute syntax inappropriate for increment", + ml->sml_type.bv_val ); + *text = textbuf; + return LDAP_CONSTRAINT_VIOLATION; + } + + /* + * check values + */ + if( ml->sml_values != NULL ) { + ber_len_t nvals; + slap_syntax_validate_func *validate = + ad->ad_type->sat_syntax->ssyn_validate; + slap_syntax_transform_func *pretty = + ad->ad_type->sat_syntax->ssyn_pretty; + + if( !pretty && !validate ) { + *text = "no validator for syntax"; + snprintf( textbuf, textlen, + "%s: no validator for syntax %s", + ml->sml_type.bv_val, + ad->ad_type->sat_syntax->ssyn_oid ); + *text = textbuf; + return LDAP_INVALID_SYNTAX; + } + + /* + * check that each value is valid per syntax + * and pretty if appropriate + */ + for ( nvals = 0; !BER_BVISNULL( &ml->sml_values[nvals] ); nvals++ ) { + struct berval pval; + + if ( pretty ) { + rc = ordered_value_pretty( ad, + &ml->sml_values[nvals], &pval, ctx ); + } else { + rc = ordered_value_validate( ad, + &ml->sml_values[nvals], ml->sml_op ); + } + + if( rc != 0 ) { + snprintf( textbuf, textlen, + "%s: value #%ld invalid per syntax", + ml->sml_type.bv_val, (long) nvals ); + *text = textbuf; + return LDAP_INVALID_SYNTAX; + } + + if( pretty ) { + ber_memfree_x( ml->sml_values[nvals].bv_val, ctx ); + ml->sml_values[nvals] = pval; + } + } + ml->sml_values[nvals].bv_len = 0; + ml->sml_numvals = nvals; + + /* + * a rough single value check... an additional check is needed + * to catch add of single value to existing single valued attribute + */ + if ((ml->sml_op == LDAP_MOD_ADD || ml->sml_op == LDAP_MOD_REPLACE) + && nvals > 1 && is_at_single_value( ad->ad_type )) + { + snprintf( textbuf, textlen, + "%s: multiple values provided", + ml->sml_type.bv_val ); + *text = textbuf; + return LDAP_CONSTRAINT_VIOLATION; + } + + /* if the type has a normalizer, generate the + * normalized values. otherwise leave them NULL. + * + * this is different from the rule for attributes + * in an entry - in an attribute list, the normalized + * value is set equal to the non-normalized value + * when there is no normalizer. + */ + if( nvals && ad->ad_type->sat_equality && + ad->ad_type->sat_equality->smr_normalize ) + { + ml->sml_nvalues = ber_memalloc_x( + (nvals+1)*sizeof(struct berval), ctx ); + + for ( nvals = 0; !BER_BVISNULL( &ml->sml_values[nvals] ); nvals++ ) { + rc = ordered_value_normalize( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + ad, + ad->ad_type->sat_equality, + &ml->sml_values[nvals], &ml->sml_nvalues[nvals], ctx ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "<= str2entry NULL (ssyn_normalize %d)\n", + rc, 0, 0 ); + snprintf( textbuf, textlen, + "%s: value #%ld normalization failed", + ml->sml_type.bv_val, (long) nvals ); + *text = textbuf; + BER_BVZERO( &ml->sml_nvalues[nvals] ); + return rc; + } + } + + BER_BVZERO( &ml->sml_nvalues[nvals] ); + } + + /* check for duplicates, but ignore Deletes. + */ + if( nvals > 1 && ml->sml_op != LDAP_MOD_DELETE ) { + int i; + rc = slap_sort_vals( ml, text, &i, ctx ); + if ( rc == LDAP_TYPE_OR_VALUE_EXISTS ) { + /* value exists already */ + snprintf( textbuf, textlen, + "%s: value #%d provided more than once", + ml->sml_desc->ad_cname.bv_val, i ); + *text = textbuf; + } + if ( rc ) + return rc; + } + } else { + ml->sml_numvals = 0; + } + } + + return LDAP_SUCCESS; +} + +/* Sort a set of values. An (Attribute *) may be used interchangeably here + * instead of a (Modifications *) structure. + * + * Uses Quicksort + Insertion sort for small arrays + */ + +int +slap_sort_vals( + Modifications *ml, + const char **text, + int *dup, + void *ctx ) +{ + AttributeDescription *ad; + MatchingRule *mr; + int istack[sizeof(int)*16]; + int i, j, k, l, ir, jstack, match, *ix, itmp, nvals, rc = LDAP_SUCCESS; + int is_norm; + struct berval a, *cv; + +#define SMALL 8 +#define SWAP(a,b,tmp) tmp=(a);(a)=(b);(b)=tmp +#define COMP(a,b) match=0; rc = ordered_value_match( &match, \ + ad, mr, SLAP_MR_EQUALITY \ + | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX \ + | SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH \ + | SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH, \ + &(a), &(b), text ); + +#define IX(x) ix[x] +#define EXCH(x,y) SWAP(ix[x],ix[y],itmp) +#define SETA(x) itmp = ix[x]; a = cv[itmp] +#define GETA(x) ix[x] = itmp; +#define SET(x,y) ix[x] = ix[y] + + ad = ml->sml_desc; + nvals = ml->sml_numvals; + if ( nvals <= 1 ) + goto ret; + + /* For Modifications, sml_nvalues is NULL if normalization wasn't needed. + * For Attributes, sml_nvalues == sml_values when normalization isn't needed. + */ + if ( ml->sml_nvalues && ml->sml_nvalues != ml->sml_values ) { + cv = ml->sml_nvalues; + is_norm = 1; + } else { + cv = ml->sml_values; + is_norm = 0; + } + + if ( ad == slap_schema.si_ad_objectClass ) + mr = NULL; /* shortcut matching */ + else + mr = ad->ad_type->sat_equality; + + /* record indices to preserve input ordering */ + ix = slap_sl_malloc( nvals * sizeof(int), ctx ); + for (i=0; i<nvals; i++) ix[i] = i; + + ir = nvals-1; + l = 0; + jstack = 0; + + for(;;) { + if (ir - l < SMALL) { /* Insertion sort */ + match=1; + for (j=l+1;j<=ir;j++) { + SETA(j); + for (i=j-1;i>=0;i--) { + COMP(cv[IX(i)], a); + if ( match <= 0 ) + break; + SET(i+1,i); + } + GETA(i+1); + if ( match == 0 ) goto done; + } + if ( jstack == 0 ) break; + ir = istack[jstack--]; + l = istack[jstack--]; + } else { + k = (l + ir) >> 1; /* Choose median of left, center, right */ + EXCH(k, l+1); + COMP( cv[IX(l)], cv[IX(ir)] ); + if ( match > 0 ) { + EXCH(l, ir); + } else if ( match == 0 ) { + i = ir; + break; + } + COMP( cv[IX(l+1)], cv[IX(ir)] ); + if ( match > 0 ) { + EXCH(l+1, ir); + } else if ( match == 0 ) { + i = ir; + break; + } + COMP( cv[IX(l)], cv[IX(l+1)] ); + if ( match > 0 ) { + EXCH(l, l+1); + } else if ( match == 0 ) { + i = l; + break; + } + i = l+1; + j = ir; + a = cv[IX(i)]; + for(;;) { + do { + i++; + COMP( cv[IX(i)], a ); + } while( match < 0 ); + while( match > 0 ) { + j--; + COMP( cv[IX(j)], a ); + } + if (j < i) { + match = 1; + break; + } + if ( match == 0 ) { + i = l+1; + break; + } + EXCH(i,j); + } + if ( match == 0 ) + break; + EXCH(l+1,j); + jstack += 2; + if (ir-i+1 > j-l) { + istack[jstack] = ir; + istack[jstack-1] = i; + ir = j; + } else { + istack[jstack] = j; + istack[jstack-1] = l; + l = i; + } + } + } + done: + if ( match == 0 && i >= 0 ) + *dup = ix[i]; + + /* For sorted attributes, put the values in index order */ + if ( rc == LDAP_SUCCESS && match && + ( ad->ad_type->sat_flags & SLAP_AT_SORTED_VAL )) { + BerVarray tmpv = slap_sl_malloc( sizeof( struct berval ) * nvals, ctx ); + for ( i = 0; i<nvals; i++ ) + tmpv[i] = cv[ix[i]]; + for ( i = 0; i<nvals; i++ ) + cv[i] = tmpv[i]; + /* Check if the non-normalized array needs to move too */ + if ( is_norm ) { + cv = ml->sml_values; + for ( i = 0; i<nvals; i++ ) + tmpv[i] = cv[ix[i]]; + for ( i = 0; i<nvals; i++ ) + cv[i] = tmpv[i]; + } + slap_sl_free( tmpv, ctx ); + } + + slap_sl_free( ix, ctx ); + + if ( rc == LDAP_SUCCESS && match == 0 ) { + /* value exists already */ + assert( i >= 0 ); + assert( i < nvals ); + rc = LDAP_TYPE_OR_VALUE_EXISTS; + } + ret: + return rc; +} + +/* Enter with bv->bv_len = sizeof buffer, returns with + * actual length of string + */ +void slap_timestamp( time_t *tm, struct berval *bv ) +{ + struct tm ltm; + + ldap_pvt_gmtime( tm, <m ); + + bv->bv_len = lutil_gentime( bv->bv_val, bv->bv_len, <m ); +} + +/* Called for all modify and modrdn ops. If the current op was replicated + * from elsewhere, all of the attrs should already be present. + */ +void slap_mods_opattrs( + Operation *op, + Modifications **modsp, + int manage_ctxcsn ) +{ + struct berval name, timestamp, csn = BER_BVNULL; + struct berval nname; + char timebuf[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + char csnbuf[ LDAP_PVT_CSNSTR_BUFSIZE ]; + Modifications *mod, **modtail, *modlast; + int gotcsn = 0, gotmname = 0, gotmtime = 0; + + if ( SLAP_LASTMOD( op->o_bd ) && !op->orm_no_opattrs ) { + char *ptr; + timestamp.bv_val = timebuf; + for ( modtail = modsp; *modtail; modtail = &(*modtail)->sml_next ) { + if ( (*modtail)->sml_op != LDAP_MOD_ADD && + (*modtail)->sml_op != SLAP_MOD_SOFTADD && + (*modtail)->sml_op != SLAP_MOD_ADD_IF_NOT_PRESENT && + (*modtail)->sml_op != LDAP_MOD_REPLACE ) + { + continue; + } + + if ( (*modtail)->sml_desc == slap_schema.si_ad_entryCSN ) + { + csn = (*modtail)->sml_values[0]; + gotcsn = 1; + + } else if ( (*modtail)->sml_desc == slap_schema.si_ad_modifiersName ) + { + gotmname = 1; + + } else if ( (*modtail)->sml_desc == slap_schema.si_ad_modifyTimestamp ) + { + gotmtime = 1; + } + } + + if ( BER_BVISEMPTY( &op->o_csn )) { + if ( !gotcsn ) { + csn.bv_val = csnbuf; + csn.bv_len = sizeof( csnbuf ); + slap_get_csn( op, &csn, manage_ctxcsn ); + + } else { + if ( manage_ctxcsn ) { + slap_queue_csn( op, &csn ); + } + } + + } else { + csn = op->o_csn; + } + + ptr = ber_bvchr( &csn, '#' ); + if ( ptr ) { + timestamp.bv_len = STRLENOF("YYYYMMDDHHMMSSZ"); + AC_MEMCPY( timebuf, csn.bv_val, timestamp.bv_len ); + timebuf[timestamp.bv_len-1] = 'Z'; + timebuf[timestamp.bv_len] = '\0'; + + } else { + time_t now = slap_get_time(); + + timestamp.bv_len = sizeof(timebuf); + + slap_timestamp( &now, ×tamp ); + } + + if ( BER_BVISEMPTY( &op->o_dn ) ) { + BER_BVSTR( &name, SLAPD_ANONYMOUS ); + nname = name; + + } else { + name = op->o_dn; + nname = op->o_ndn; + } + + if ( !gotcsn ) { + mod = (Modifications *) ch_malloc( sizeof( Modifications ) ); + mod->sml_op = LDAP_MOD_REPLACE; + mod->sml_flags = SLAP_MOD_INTERNAL; + mod->sml_next = NULL; + BER_BVZERO( &mod->sml_type ); + mod->sml_desc = slap_schema.si_ad_entryCSN; + mod->sml_numvals = 1; + mod->sml_values = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) ); + ber_dupbv( &mod->sml_values[0], &csn ); + BER_BVZERO( &mod->sml_values[1] ); + assert( !BER_BVISNULL( &mod->sml_values[0] ) ); + mod->sml_nvalues = NULL; + *modtail = mod; + modlast = mod; + modtail = &mod->sml_next; + } + + if ( !gotmname ) { + mod = (Modifications *) ch_malloc( sizeof( Modifications ) ); + mod->sml_op = LDAP_MOD_REPLACE; + mod->sml_flags = SLAP_MOD_INTERNAL; + mod->sml_next = NULL; + BER_BVZERO( &mod->sml_type ); + mod->sml_desc = slap_schema.si_ad_modifiersName; + mod->sml_numvals = 1; + mod->sml_values = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) ); + ber_dupbv( &mod->sml_values[0], &name ); + BER_BVZERO( &mod->sml_values[1] ); + assert( !BER_BVISNULL( &mod->sml_values[0] ) ); + mod->sml_nvalues = + (BerVarray) ch_malloc( 2 * sizeof( struct berval ) ); + ber_dupbv( &mod->sml_nvalues[0], &nname ); + BER_BVZERO( &mod->sml_nvalues[1] ); + assert( !BER_BVISNULL( &mod->sml_nvalues[0] ) ); + *modtail = mod; + modtail = &mod->sml_next; + } + + if ( !gotmtime ) { + mod = (Modifications *) ch_malloc( sizeof( Modifications ) ); + mod->sml_op = LDAP_MOD_REPLACE; + mod->sml_flags = SLAP_MOD_INTERNAL; + mod->sml_next = NULL; + BER_BVZERO( &mod->sml_type ); + mod->sml_desc = slap_schema.si_ad_modifyTimestamp; + mod->sml_numvals = 1; + mod->sml_values = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) ); + ber_dupbv( &mod->sml_values[0], ×tamp ); + BER_BVZERO( &mod->sml_values[1] ); + assert( !BER_BVISNULL( &mod->sml_values[0] ) ); + mod->sml_nvalues = NULL; + *modtail = mod; + modtail = &mod->sml_next; + } + } +} + +int +slap_parse_modlist( + Operation *op, + SlapReply *rs, + BerElement *ber, + req_modify_s *ms ) +{ + ber_tag_t tag; + ber_len_t len; + char *last; + Modifications **modtail = &ms->rs_mods.rs_modlist; + + ms->rs_mods.rs_modlist = NULL; + ms->rs_increment = 0; + + rs->sr_err = LDAP_SUCCESS; + + /* collect modifications & save for later */ + for ( tag = ber_first_element( ber, &len, &last ); + tag != LBER_DEFAULT; + tag = ber_next_element( ber, &len, last ) ) + { + ber_int_t mop; + Modifications tmp, *mod; + + tmp.sml_nvalues = NULL; + + if ( ber_scanf( ber, "{e{m[W]}}", &mop, + &tmp.sml_type, &tmp.sml_values ) == LBER_ERROR ) + { + rs->sr_text = "decoding modlist error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + + mod = (Modifications *) ch_malloc( sizeof(Modifications) ); + mod->sml_op = mop; + mod->sml_flags = 0; + mod->sml_type = tmp.sml_type; + mod->sml_values = tmp.sml_values; + mod->sml_nvalues = NULL; + mod->sml_desc = NULL; + mod->sml_next = NULL; + *modtail = mod; + + switch( mop ) { + case LDAP_MOD_ADD: + if ( mod->sml_values == NULL ) { + rs->sr_text = "modify/add operation requires values"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + + /* fall through */ + + case LDAP_MOD_DELETE: + case LDAP_MOD_REPLACE: + break; + + case LDAP_MOD_INCREMENT: + if( op->o_protocol >= LDAP_VERSION3 ) { + ms->rs_increment++; + if ( mod->sml_values == NULL ) { + rs->sr_text = "modify/increment operation requires value"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + + if ( !BER_BVISNULL( &mod->sml_values[ 1 ] ) ) { + rs->sr_text = "modify/increment operation requires single value"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + + break; + } + /* fall thru */ + + default: + rs->sr_text = "unrecognized modify operation"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + + modtail = &mod->sml_next; + } + *modtail = NULL; + +done: + if ( rs->sr_err != LDAP_SUCCESS ) { + slap_mods_free( ms->rs_mods.rs_modlist, 1 ); + ms->rs_mods.rs_modlist = NULL; + ms->rs_increment = 0; + } + + return rs->sr_err; +} + diff --git a/servers/slapd/modrdn.c b/servers/slapd/modrdn.c new file mode 100644 index 0000000..1603fa0 --- /dev/null +++ b/servers/slapd/modrdn.c @@ -0,0 +1,539 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright 1999, Juan C. Gomez, All rights reserved. + * This software is not subject to any license of Silicon Graphics + * Inc. or Purdue University. + * + * Redistribution and use in source and binary forms are permitted + * without restriction or fee of any kind as long as this notice + * is preserved. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" + +int +do_modrdn( + Operation *op, + SlapReply *rs +) +{ + struct berval dn = BER_BVNULL; + struct berval newrdn = BER_BVNULL; + struct berval newSuperior = BER_BVNULL; + ber_int_t deloldrdn; + + struct berval pnewSuperior = BER_BVNULL; + + struct berval nnewSuperior = BER_BVNULL; + + ber_len_t length; + + Debug( LDAP_DEBUG_TRACE, "%s do_modrdn\n", + op->o_log_prefix, 0, 0 ); + /* + * Parse the modrdn request. It looks like this: + * + * ModifyRDNRequest := SEQUENCE { + * entry DistinguishedName, + * newrdn RelativeDistinguishedName + * deleteoldrdn BOOLEAN, + * newSuperior [0] LDAPDN OPTIONAL (v3 Only!) + * } + */ + + if ( ber_scanf( op->o_ber, "{mmb", &dn, &newrdn, &deloldrdn ) + == LBER_ERROR ) + { + Debug( LDAP_DEBUG_ANY, "%s do_modrdn: ber_scanf failed\n", + op->o_log_prefix, 0, 0 ); + send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" ); + return SLAPD_DISCONNECT; + } + + /* Check for newSuperior parameter, if present scan it */ + + if ( ber_peek_tag( op->o_ber, &length ) == LDAP_TAG_NEWSUPERIOR ) { + if ( op->o_protocol < LDAP_VERSION3 ) { + /* Connection record indicates v2 but field + * newSuperior is present: report error. + */ + Debug( LDAP_DEBUG_ANY, + "%s do_modrdn: newSuperior requires LDAPv3\n", + op->o_log_prefix, 0, 0 ); + + send_ldap_discon( op, rs, + LDAP_PROTOCOL_ERROR, "newSuperior requires LDAPv3" ); + rs->sr_err = SLAPD_DISCONNECT; + goto cleanup; + } + + if ( ber_scanf( op->o_ber, "m", &newSuperior ) + == LBER_ERROR ) { + + Debug( LDAP_DEBUG_ANY, "%s do_modrdn: ber_scanf(\"m\") failed\n", + op->o_log_prefix, 0, 0 ); + + send_ldap_discon( op, rs, + LDAP_PROTOCOL_ERROR, "decoding error" ); + rs->sr_err = SLAPD_DISCONNECT; + goto cleanup; + } + op->orr_newSup = &pnewSuperior; + op->orr_nnewSup = &nnewSuperior; + } + + Debug( LDAP_DEBUG_ARGS, + "do_modrdn: dn (%s) newrdn (%s) newsuperior (%s)\n", + dn.bv_val, newrdn.bv_val, + newSuperior.bv_len ? newSuperior.bv_val : "" ); + + if ( ber_scanf( op->o_ber, /*{*/ "}") == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, "%s do_modrdn: ber_scanf failed\n", + op->o_log_prefix, 0, 0 ); + send_ldap_discon( op, rs, + LDAP_PROTOCOL_ERROR, "decoding error" ); + rs->sr_err = SLAPD_DISCONNECT; + goto cleanup; + } + + if( get_ctrls( op, rs, 1 ) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s do_modrdn: get_ctrls failed\n", + op->o_log_prefix, 0, 0 ); + /* get_ctrls has sent results. Now clean up. */ + goto cleanup; + } + + rs->sr_err = dnPrettyNormal( NULL, &dn, &op->o_req_dn, &op->o_req_ndn, op->o_tmpmemctx ); + if( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s do_modrdn: invalid dn (%s)\n", + op->o_log_prefix, dn.bv_val, 0 ); + send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid DN" ); + goto cleanup; + } + + /* FIXME: should have/use rdnPretty / rdnNormalize routines */ + + rs->sr_err = dnPrettyNormal( NULL, &newrdn, &op->orr_newrdn, &op->orr_nnewrdn, op->o_tmpmemctx ); + if( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s do_modrdn: invalid newrdn (%s)\n", + op->o_log_prefix, newrdn.bv_val, 0 ); + send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid new RDN" ); + goto cleanup; + } + + if( rdn_validate( &op->orr_newrdn ) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s do_modrdn: invalid rdn (%s)\n", + op->o_log_prefix, op->orr_newrdn.bv_val, 0 ); + send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid new RDN" ); + goto cleanup; + } + + if( op->orr_newSup ) { + rs->sr_err = dnPrettyNormal( NULL, &newSuperior, &pnewSuperior, + &nnewSuperior, op->o_tmpmemctx ); + if( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "%s do_modrdn: invalid newSuperior (%s)\n", + op->o_log_prefix, newSuperior.bv_val, 0 ); + send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid newSuperior" ); + goto cleanup; + } + } + + Statslog( LDAP_DEBUG_STATS, "%s MODRDN dn=\"%s\"\n", + op->o_log_prefix, op->o_req_dn.bv_val, 0, 0, 0 ); + + op->orr_deleteoldrdn = deloldrdn; + op->orr_modlist = NULL; + + /* prepare modlist of modifications from old/new RDN */ + rs->sr_err = slap_modrdn2mods( op, rs ); + if ( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + op->o_bd = frontendDB; + rs->sr_err = frontendDB->be_modrdn( op, rs ); + +#ifdef LDAP_X_TXN + if( rs->sr_err == LDAP_X_TXN_SPECIFY_OKAY ) { + /* skip cleanup */ + } +#endif + +cleanup: + op->o_tmpfree( op->o_req_dn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx ); + + op->o_tmpfree( op->orr_newrdn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( op->orr_nnewrdn.bv_val, op->o_tmpmemctx ); + + if ( op->orr_modlist != NULL ) + slap_mods_free( op->orr_modlist, 1 ); + + if ( !BER_BVISNULL( &pnewSuperior ) ) { + op->o_tmpfree( pnewSuperior.bv_val, op->o_tmpmemctx ); + } + if ( !BER_BVISNULL( &nnewSuperior ) ) { + op->o_tmpfree( nnewSuperior.bv_val, op->o_tmpmemctx ); + } + + return rs->sr_err; +} + +int +fe_op_modrdn( Operation *op, SlapReply *rs ) +{ + struct berval dest_ndn = BER_BVNULL, dest_pndn, pdn = BER_BVNULL; + BackendDB *op_be, *bd = op->o_bd; + ber_slen_t diff; + + if( op->o_req_ndn.bv_len == 0 ) { + Debug( LDAP_DEBUG_ANY, "%s do_modrdn: root dse!\n", + op->o_log_prefix, 0, 0 ); + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "cannot rename the root DSE" ); + goto cleanup; + + } else if ( bvmatch( &op->o_req_ndn, &frontendDB->be_schemandn ) ) { + Debug( LDAP_DEBUG_ANY, "%s do_modrdn: subschema subentry: %s (%ld)\n", + op->o_log_prefix, frontendDB->be_schemandn.bv_val, (long)frontendDB->be_schemandn.bv_len ); + + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "cannot rename subschema subentry" ); + goto cleanup; + } + + if( op->orr_nnewSup ) { + dest_pndn = *op->orr_nnewSup; + } else { + dnParent( &op->o_req_ndn, &dest_pndn ); + } + build_new_dn( &dest_ndn, &dest_pndn, &op->orr_nnewrdn, op->o_tmpmemctx ); + + diff = (ber_slen_t) dest_ndn.bv_len - (ber_slen_t) op->o_req_ndn.bv_len; + if ( diff > 0 ? dnIsSuffix( &dest_ndn, &op->o_req_ndn ) + : diff < 0 && dnIsSuffix( &op->o_req_ndn, &dest_ndn ) ) + { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + diff > 0 ? "cannot place an entry below itself" + : "cannot place an entry above itself" ); + goto cleanup; + } + + /* + * We could be serving multiple database backends. Select the + * appropriate one, or send a referral to our "referral server" + * if we don't hold it. + */ + op->o_bd = select_backend( &op->o_req_ndn, 1 ); + if ( op->o_bd == NULL ) { + op->o_bd = bd; + rs->sr_ref = referral_rewrite( default_referral, + NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + if (!rs->sr_ref) rs->sr_ref = default_referral; + + if ( rs->sr_ref != NULL ) { + rs->sr_err = LDAP_REFERRAL; + send_ldap_result( op, rs ); + + if (rs->sr_ref != default_referral) ber_bvarray_free( rs->sr_ref ); + } else { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "no global superior knowledge" ); + } + goto cleanup; + } + + /* If we've got a glued backend, check the real backend */ + op_be = op->o_bd; + if ( SLAP_GLUE_INSTANCE( op->o_bd )) { + op->o_bd = select_backend( &op->o_req_ndn, 0 ); + } + + /* check restrictions */ + if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + /* check for referrals */ + if ( backend_check_referrals( op, rs ) != LDAP_SUCCESS ) { + goto cleanup; + } + + /* check that destination DN is in the same backend as source DN */ + if ( select_backend( &dest_ndn, 0 ) != op->o_bd ) { + send_ldap_error( op, rs, LDAP_AFFECTS_MULTIPLE_DSAS, + "cannot rename between DSAs" ); + goto cleanup; + } + + /* + * do the modrdn if 1 && (2 || 3) + * 1) there is a modrdn function implemented in this backend; + * 2) this backend is the provider for what it holds; + * 3) it's a replica and the dn supplied is the update_ndn. + */ + if ( op->o_bd->be_modrdn ) { + /* do the update here */ + int repl_user = be_isupdate( op ); + if ( !SLAP_SINGLE_SHADOW(op->o_bd) || repl_user ) + { + op->o_bd = op_be; + op->o_bd->be_modrdn( op, rs ); + + if ( op->o_bd->be_delete ) { + struct berval org_req_dn = BER_BVNULL; + struct berval org_req_ndn = BER_BVNULL; + struct berval org_dn = BER_BVNULL; + struct berval org_ndn = BER_BVNULL; + int org_managedsait; + + org_req_dn = op->o_req_dn; + org_req_ndn = op->o_req_ndn; + org_dn = op->o_dn; + org_ndn = op->o_ndn; + org_managedsait = get_manageDSAit( op ); + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + op->o_managedsait = SLAP_CONTROL_NONCRITICAL; + + while ( rs->sr_err == LDAP_SUCCESS && + op->o_delete_glue_parent ) { + op->o_delete_glue_parent = 0; + if ( !be_issuffix( op->o_bd, &op->o_req_ndn )) { + slap_callback cb = { NULL }; + cb.sc_response = slap_null_cb; + dnParent( &op->o_req_ndn, &pdn ); + op->o_req_dn = pdn; + op->o_req_ndn = pdn; + op->o_callback = &cb; + op->o_bd->be_delete( op, rs ); + } else { + break; + } + } + op->o_managedsait = org_managedsait; + op->o_dn = org_dn; + op->o_ndn = org_ndn; + op->o_req_dn = org_req_dn; + op->o_req_ndn = org_req_ndn; + op->o_delete_glue_parent = 0; + } + + } else { + BerVarray defref = op->o_bd->be_update_refs + ? op->o_bd->be_update_refs : default_referral; + + if ( defref != NULL ) { + rs->sr_ref = referral_rewrite( defref, + NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + if (!rs->sr_ref) rs->sr_ref = defref; + + rs->sr_err = LDAP_REFERRAL; + send_ldap_result( op, rs ); + + if (rs->sr_ref != defref) ber_bvarray_free( rs->sr_ref ); + } else { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "shadow context; no update referral" ); + } + } + } else { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "operation not supported within namingContext" ); + } + +cleanup:; + if ( dest_ndn.bv_val != NULL ) + ber_memfree_x( dest_ndn.bv_val, op->o_tmpmemctx ); + op->o_bd = bd; + return rs->sr_err; +} + +/* extracted from slap_modrdn2mods() */ +static int +mod_op_add_val( + Operation *op, + AttributeDescription * const desc, + struct berval * const val, + short const sm_op ) +{ + int rv = LDAP_SUCCESS; + Modifications *mod_tmp; + mod_tmp = ( Modifications * )ch_malloc( sizeof( Modifications ) ); + mod_tmp->sml_desc = desc; + BER_BVZERO( &mod_tmp->sml_type ); + mod_tmp->sml_numvals = 1; + mod_tmp->sml_values = ( BerVarray )ch_malloc( 2 * sizeof( struct berval ) ); + ber_dupbv( &mod_tmp->sml_values[0], val ); + mod_tmp->sml_values[1].bv_val = NULL; + if( desc->ad_type->sat_equality && desc->ad_type->sat_equality->smr_normalize) { + mod_tmp->sml_nvalues = ( BerVarray )ch_malloc( 2 * sizeof( struct berval ) ); + rv = desc->ad_type->sat_equality->smr_normalize( + SLAP_MR_EQUALITY|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX, + desc->ad_type->sat_syntax, + desc->ad_type->sat_equality, + &mod_tmp->sml_values[0], + &mod_tmp->sml_nvalues[0], NULL ); + if (rv != LDAP_SUCCESS) { + ch_free(mod_tmp->sml_nvalues); + ch_free(mod_tmp->sml_values[0].bv_val); + ch_free(mod_tmp->sml_values); + ch_free(mod_tmp); + goto done; + } + mod_tmp->sml_nvalues[1].bv_val = NULL; + } else { + mod_tmp->sml_nvalues = NULL; + } + mod_tmp->sml_op = sm_op; + mod_tmp->sml_flags = 0; + mod_tmp->sml_next = op->orr_modlist; + op->orr_modlist = mod_tmp; +done: + return rv; +} + +int +slap_modrdn2mods( + Operation *op, + SlapReply *rs ) +{ + int a_cnt, d_cnt; + LDAPRDN old_rdn = NULL; + LDAPRDN new_rdn = NULL; + + assert( !BER_BVISEMPTY( &op->oq_modrdn.rs_newrdn ) ); + + /* if requestDN is empty, silently reset deleteOldRDN */ + if ( BER_BVISEMPTY( &op->o_req_dn ) ) op->orr_deleteoldrdn = 0; + + if ( ldap_bv2rdn_x( &op->oq_modrdn.rs_newrdn, &new_rdn, + (char **)&rs->sr_text, LDAP_DN_FORMAT_LDAP, op->o_tmpmemctx ) ) { + Debug( LDAP_DEBUG_TRACE, + "%s slap_modrdn2mods: can't figure out " + "type(s)/value(s) of newrdn\n", + op->o_log_prefix, 0, 0 ); + rs->sr_err = LDAP_INVALID_DN_SYNTAX; + rs->sr_text = "unknown type(s)/value(s) used in RDN"; + goto done; + } + + if ( op->oq_modrdn.rs_deleteoldrdn ) { + if ( ldap_bv2rdn_x( &op->o_req_dn, &old_rdn, + (char **)&rs->sr_text, LDAP_DN_FORMAT_LDAP, op->o_tmpmemctx ) ) { + Debug( LDAP_DEBUG_TRACE, + "%s slap_modrdn2mods: can't figure out " + "type(s)/value(s) of oldrdn\n", + op->o_log_prefix, 0, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "cannot parse RDN from old DN"; + goto done; + } + } + rs->sr_text = NULL; + + /* Add new attribute values to the entry */ + for ( a_cnt = 0; new_rdn[a_cnt]; a_cnt++ ) { + AttributeDescription *desc = NULL; + + rs->sr_err = slap_bv2ad( &new_rdn[a_cnt]->la_attr, &desc, &rs->sr_text ); + + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "%s slap_modrdn2mods: %s: %s (new)\n", + op->o_log_prefix, + rs->sr_text, + new_rdn[ a_cnt ]->la_attr.bv_val ); + goto done; + } + + if ( !desc->ad_type->sat_equality ) { + Debug( LDAP_DEBUG_TRACE, + "%s slap_modrdn2mods: %s: %s (new)\n", + op->o_log_prefix, + rs->sr_text, + new_rdn[ a_cnt ]->la_attr.bv_val ); + rs->sr_text = "naming attribute has no equality matching rule"; + rs->sr_err = LDAP_NAMING_VIOLATION; + goto done; + } + + /* Apply modification */ + rs->sr_err = mod_op_add_val( op, desc, &new_rdn[a_cnt]->la_value, SLAP_MOD_SOFTADD ); + if (rs->sr_err != LDAP_SUCCESS) + goto done; + } + + /* Remove old rdn value if required */ + if ( op->orr_deleteoldrdn ) { + for ( d_cnt = 0; old_rdn[d_cnt]; d_cnt++ ) { + AttributeDescription *desc = NULL; + + rs->sr_err = slap_bv2ad( &old_rdn[d_cnt]->la_attr, &desc, &rs->sr_text ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "%s slap_modrdn2mods: %s: %s (old)\n", + op->o_log_prefix, + rs->sr_text, + old_rdn[d_cnt]->la_attr.bv_val ); + goto done; + } + + /* Apply modification */ + rs->sr_err = mod_op_add_val( op, desc, &old_rdn[d_cnt]->la_value, LDAP_MOD_DELETE ); + if (rs->sr_err != LDAP_SUCCESS) + goto done; + } + } + +done: + + /* LDAP v2 supporting correct attribute handling. */ + if ( rs->sr_err != LDAP_SUCCESS && op->orr_modlist != NULL ) { + slap_mods_free( op->orr_modlist, 1 ); + op->orr_modlist = NULL; + } + + if ( new_rdn != NULL ) { + ldap_rdnfree_x( new_rdn, op->o_tmpmemctx ); + } + if ( old_rdn != NULL ) { + ldap_rdnfree_x( old_rdn, op->o_tmpmemctx ); + } + + return rs->sr_err; +} + diff --git a/servers/slapd/mods.c b/servers/slapd/mods.c new file mode 100644 index 0000000..fbf821c --- /dev/null +++ b/servers/slapd/mods.c @@ -0,0 +1,487 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <ac/string.h> + +#include "slap.h" +#include "lutil.h" + +int +modify_add_values( + Entry *e, + Modification *mod, + int permissive, + const char **text, + char *textbuf, + size_t textlen ) +{ + int rc; + const char *op; + Attribute *a; + Modification pmod = *mod; + + switch ( mod->sm_op ) { + case LDAP_MOD_ADD: + op = "add"; + break; + case LDAP_MOD_REPLACE: + op = "replace"; + break; + default: + op = "?"; + assert( 0 ); + } + + /* FIXME: Catch old code that doesn't set sm_numvals. + */ + if ( !BER_BVISNULL( &mod->sm_values[mod->sm_numvals] )) { + unsigned i; + for ( i = 0; !BER_BVISNULL( &mod->sm_values[i] ); i++ ); + assert( mod->sm_numvals == i ); + } + + /* check if values to add exist in attribute */ + a = attr_find( e->e_attrs, mod->sm_desc ); + if ( a != NULL ) { + MatchingRule *mr; + struct berval *cvals; + int rc; + unsigned i, p, flags; + + mr = mod->sm_desc->ad_type->sat_equality; + if( mr == NULL || !mr->smr_match ) { + /* do not allow add of additional attribute + if no equality rule exists */ + *text = textbuf; + snprintf( textbuf, textlen, + "modify/%s: %s: no equality matching rule", + op, mod->sm_desc->ad_cname.bv_val ); + return LDAP_INAPPROPRIATE_MATCHING; + } + + if ( permissive ) { + i = mod->sm_numvals; + pmod.sm_values = (BerVarray)ch_malloc( + (i + 1) * sizeof( struct berval )); + if ( pmod.sm_nvalues != NULL ) { + pmod.sm_nvalues = (BerVarray)ch_malloc( + (i + 1) * sizeof( struct berval )); + } + } + + /* no normalization is done in this routine nor + * in the matching routines called by this routine. + * values are now normalized once on input to the + * server (whether from LDAP or from the underlying + * database). + */ + if ( a->a_desc == slap_schema.si_ad_objectClass ) { + /* Needed by ITS#5517 */ + flags = SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX; + + } else { + flags = SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX; + } + if ( mod->sm_nvalues ) { + flags |= SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH | + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH; + cvals = mod->sm_nvalues; + } else { + cvals = mod->sm_values; + } + for ( p = i = 0; i < mod->sm_numvals; i++ ) { + unsigned slot; + + rc = attr_valfind( a, flags, &cvals[i], &slot, NULL ); + if ( rc == LDAP_SUCCESS ) { + if ( !permissive ) { + /* value already exists */ + *text = textbuf; + snprintf( textbuf, textlen, + "modify/%s: %s: value #%u already exists", + op, mod->sm_desc->ad_cname.bv_val, i ); + return LDAP_TYPE_OR_VALUE_EXISTS; + } + } else if ( rc != LDAP_NO_SUCH_ATTRIBUTE ) { + return rc; + } + + if ( permissive && rc ) { + if ( pmod.sm_nvalues ) { + pmod.sm_nvalues[p] = mod->sm_nvalues[i]; + } + pmod.sm_values[p++] = mod->sm_values[i]; + } + } + + if ( permissive ) { + if ( p == 0 ) { + /* all new values match exist */ + ch_free( pmod.sm_values ); + if ( pmod.sm_nvalues ) ch_free( pmod.sm_nvalues ); + return LDAP_SUCCESS; + } + + BER_BVZERO( &pmod.sm_values[p] ); + if ( pmod.sm_nvalues ) { + BER_BVZERO( &pmod.sm_nvalues[p] ); + } + } + } + + /* no - add them */ + if ( mod->sm_desc->ad_type->sat_flags & SLAP_AT_ORDERED_VAL ) { + rc = ordered_value_add( e, mod->sm_desc, a, + pmod.sm_values, pmod.sm_nvalues ); + } else { + rc = attr_merge( e, mod->sm_desc, pmod.sm_values, pmod.sm_nvalues ); + } + + if ( a != NULL && permissive ) { + ch_free( pmod.sm_values ); + if ( pmod.sm_nvalues ) ch_free( pmod.sm_nvalues ); + } + + if ( rc != 0 ) { + /* this should return result of attr_merge */ + *text = textbuf; + snprintf( textbuf, textlen, + "modify/%s: %s: merge error (%d)", + op, mod->sm_desc->ad_cname.bv_val, rc ); + return LDAP_OTHER; + } + + return LDAP_SUCCESS; +} + +int +modify_delete_values( + Entry *e, + Modification *m, + int perm, + const char **text, + char *textbuf, size_t textlen ) +{ + return modify_delete_vindex( e, m, perm, text, textbuf, textlen, NULL ); +} + +int +modify_delete_vindex( + Entry *e, + Modification *mod, + int permissive, + const char **text, + char *textbuf, size_t textlen, + int *idx ) +{ + Attribute *a; + MatchingRule *mr = mod->sm_desc->ad_type->sat_equality; + struct berval *cvals; + int *id2 = NULL; + int rc = 0; + unsigned i, j, flags; + char dummy = '\0'; + + /* + * If permissive is set, then the non-existence of an + * attribute is not treated as an error. + */ + + /* delete the entire attribute */ + if ( mod->sm_values == NULL ) { + rc = attr_delete( &e->e_attrs, mod->sm_desc ); + + if( permissive ) { + rc = LDAP_SUCCESS; + } else if( rc != LDAP_SUCCESS ) { + *text = textbuf; + snprintf( textbuf, textlen, + "modify/delete: %s: no such attribute", + mod->sm_desc->ad_cname.bv_val ); + rc = LDAP_NO_SUCH_ATTRIBUTE; + } + return rc; + } + + /* FIXME: Catch old code that doesn't set sm_numvals. + */ + if ( !BER_BVISNULL( &mod->sm_values[mod->sm_numvals] )) { + for ( i = 0; !BER_BVISNULL( &mod->sm_values[i] ); i++ ); + assert( mod->sm_numvals == i ); + } + if ( !idx ) { + id2 = ch_malloc( mod->sm_numvals * sizeof( int )); + idx = id2; + } + + if( mr == NULL || !mr->smr_match ) { + /* disallow specific attributes from being deleted if + no equality rule */ + *text = textbuf; + snprintf( textbuf, textlen, + "modify/delete: %s: no equality matching rule", + mod->sm_desc->ad_cname.bv_val ); + rc = LDAP_INAPPROPRIATE_MATCHING; + goto return_result; + } + + /* delete specific values - find the attribute first */ + if ( (a = attr_find( e->e_attrs, mod->sm_desc )) == NULL ) { + if( permissive ) { + rc = LDAP_SUCCESS; + goto return_result; + } + *text = textbuf; + snprintf( textbuf, textlen, + "modify/delete: %s: no such attribute", + mod->sm_desc->ad_cname.bv_val ); + rc = LDAP_NO_SUCH_ATTRIBUTE; + goto return_result; + } + + if ( a->a_desc == slap_schema.si_ad_objectClass ) { + /* Needed by ITS#5517,ITS#5963 */ + flags = SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX; + + } else { + flags = SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX; + } + if ( mod->sm_nvalues ) { + flags |= SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH + | SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH; + cvals = mod->sm_nvalues; + } else { + cvals = mod->sm_values; + } + + /* Locate values to delete */ + for ( i = 0; !BER_BVISNULL( &mod->sm_values[i] ); i++ ) { + unsigned sort; + rc = attr_valfind( a, flags, &cvals[i], &sort, NULL ); + if ( rc == LDAP_SUCCESS ) { + idx[i] = sort; + } else if ( rc == LDAP_NO_SUCH_ATTRIBUTE ) { + if ( permissive ) { + idx[i] = -1; + continue; + } + *text = textbuf; + snprintf( textbuf, textlen, + "modify/delete: %s: no such value", + mod->sm_desc->ad_cname.bv_val ); + goto return_result; + } else { + *text = textbuf; + snprintf( textbuf, textlen, + "modify/delete: %s: matching rule failed", + mod->sm_desc->ad_cname.bv_val ); + goto return_result; + } + } + + /* Delete the values */ + for ( i = 0; i < mod->sm_numvals; i++ ) { + /* Skip permissive values that weren't found */ + if ( idx[i] < 0 ) + continue; + /* Skip duplicate delete specs */ + if ( a->a_vals[idx[i]].bv_val == &dummy ) + continue; + /* delete value and mark it as gone */ + free( a->a_vals[idx[i]].bv_val ); + a->a_vals[idx[i]].bv_val = &dummy; + if( a->a_nvals != a->a_vals ) { + free( a->a_nvals[idx[i]].bv_val ); + a->a_nvals[idx[i]].bv_val = &dummy; + } + a->a_numvals--; + } + + /* compact array skipping dummies */ + for ( i = 0, j = 0; !BER_BVISNULL( &a->a_vals[i] ); i++ ) { + /* skip dummies */ + if( a->a_vals[i].bv_val == &dummy ) { + assert( a->a_nvals[i].bv_val == &dummy ); + continue; + } + if ( j != i ) { + a->a_vals[ j ] = a->a_vals[ i ]; + if (a->a_nvals != a->a_vals) { + a->a_nvals[ j ] = a->a_nvals[ i ]; + } + } + j++; + } + + BER_BVZERO( &a->a_vals[j] ); + if (a->a_nvals != a->a_vals) { + BER_BVZERO( &a->a_nvals[j] ); + } + + /* if no values remain, delete the entire attribute */ + if ( !a->a_numvals ) { + if ( attr_delete( &e->e_attrs, mod->sm_desc ) ) { + /* Can never happen */ + *text = textbuf; + snprintf( textbuf, textlen, + "modify/delete: %s: no such attribute", + mod->sm_desc->ad_cname.bv_val ); + rc = LDAP_NO_SUCH_ATTRIBUTE; + } + } else if ( a->a_desc->ad_type->sat_flags & SLAP_AT_ORDERED_VAL ) { + /* For an ordered attribute, renumber the value indices */ + ordered_value_sort( a, 1 ); + } +return_result: + if ( id2 ) + ch_free( id2 ); + return rc; +} + +int +modify_replace_values( + Entry *e, + Modification *mod, + int permissive, + const char **text, + char *textbuf, size_t textlen ) +{ + (void) attr_delete( &e->e_attrs, mod->sm_desc ); + + if ( mod->sm_values ) { + return modify_add_values( e, mod, permissive, text, textbuf, textlen ); + } + + return LDAP_SUCCESS; +} + +int +modify_increment_values( + Entry *e, + Modification *mod, + int permissive, + const char **text, + char *textbuf, size_t textlen ) +{ + Attribute *a; + const char *syn_oid; + + a = attr_find( e->e_attrs, mod->sm_desc ); + if( a == NULL ) { + if ( permissive ) { + Modification modReplace = *mod; + + modReplace.sm_op = LDAP_MOD_REPLACE; + + return modify_add_values(e, &modReplace, permissive, text, textbuf, textlen); + } else { + *text = textbuf; + snprintf( textbuf, textlen, + "modify/increment: %s: no such attribute", + mod->sm_desc->ad_cname.bv_val ); + return LDAP_NO_SUCH_ATTRIBUTE; + } + } + + syn_oid = at_syntax( a->a_desc->ad_type ); + if ( syn_oid && !strcmp( syn_oid, SLAPD_INTEGER_SYNTAX )) { + int i; + char str[sizeof(long)*3 + 2]; /* overly long */ + long incr; + + if ( lutil_atol( &incr, mod->sm_values[0].bv_val ) != 0 ) { + *text = "modify/increment: invalid syntax of increment"; + return LDAP_INVALID_SYNTAX; + } + + /* treat zero and errors as a no-op */ + if( incr == 0 ) { + return LDAP_SUCCESS; + } + + for( i = 0; !BER_BVISNULL( &a->a_nvals[i] ); i++ ) { + char *tmp; + long value; + size_t strln; + if ( lutil_atol( &value, a->a_nvals[i].bv_val ) != 0 ) { + *text = "modify/increment: invalid syntax of original value"; + return LDAP_INVALID_SYNTAX; + } + strln = snprintf( str, sizeof(str), "%ld", value+incr ); + + tmp = SLAP_REALLOC( a->a_nvals[i].bv_val, strln+1 ); + if( tmp == NULL ) { + *text = "modify/increment: reallocation error"; + return LDAP_OTHER; + } + a->a_nvals[i].bv_val = tmp; + a->a_nvals[i].bv_len = strln; + + AC_MEMCPY( a->a_nvals[i].bv_val, str, strln+1 ); + } + + } else { + snprintf( textbuf, textlen, + "modify/increment: %s: increment not supported for value syntax %s", + mod->sm_desc->ad_cname.bv_val, + syn_oid ? syn_oid : "(NULL)" ); + return LDAP_CONSTRAINT_VIOLATION; + } + + return LDAP_SUCCESS; +} + +void +slap_mod_free( + Modification *mod, + int freeit ) +{ + if ( mod->sm_values != NULL ) ber_bvarray_free( mod->sm_values ); + mod->sm_values = NULL; + + if ( mod->sm_nvalues != NULL ) ber_bvarray_free( mod->sm_nvalues ); + mod->sm_nvalues = NULL; + + if( freeit ) free( mod ); +} + +void +slap_mods_free( + Modifications *ml, + int freevals ) +{ + Modifications *next; + + for ( ; ml != NULL; ml = next ) { + next = ml->sml_next; + + if ( freevals ) + slap_mod_free( &ml->sml_mod, 0 ); + free( ml ); + } +} + diff --git a/servers/slapd/module.c b/servers/slapd/module.c new file mode 100644 index 0000000..c775a28 --- /dev/null +++ b/servers/slapd/module.c @@ -0,0 +1,368 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" +#include <stdio.h> +#include "slap.h" + +#ifdef SLAPD_MODULES + +#include <ltdl.h> + +typedef int (*MODULE_INIT_FN)( + int argc, + char *argv[]); +typedef int (*MODULE_LOAD_FN)( + const void *module, + const char *filename); +typedef int (*MODULE_TERM_FN)(void); + + +struct module_regtable_t { + char *type; + MODULE_LOAD_FN proc; +} module_regtable[] = { + { "null", load_null_module }, +#ifdef SLAPD_EXTERNAL_EXTENSIONS + { "extension", load_extop_module }, +#endif + { NULL, NULL } +}; + +typedef struct module_loaded_t { + struct module_loaded_t *next; + lt_dlhandle lib; + char name[1]; +} module_loaded_t; + +module_loaded_t *module_list = NULL; + +static int module_int_unload (module_loaded_t *module); + +#ifdef HAVE_EBCDIC +static char ebuf[BUFSIZ]; +#endif + +int module_init (void) +{ + if (lt_dlinit()) { + const char *error = lt_dlerror(); +#ifdef HAVE_EBCDIC + strcpy( ebuf, error ); + __etoa( ebuf ); + error = ebuf; +#endif + Debug(LDAP_DEBUG_ANY, "lt_dlinit failed: %s\n", error, 0, 0); + + return -1; + } + + return module_path( LDAP_MODULEDIR ); +} + +int module_kill (void) +{ + /* unload all modules before shutdown */ + while (module_list != NULL) { + module_int_unload(module_list); + } + + if (lt_dlexit()) { + const char *error = lt_dlerror(); +#ifdef HAVE_EBCDIC + strcpy( ebuf, error ); + __etoa( ebuf ); + error = ebuf; +#endif + Debug(LDAP_DEBUG_ANY, "lt_dlexit failed: %s\n", error, 0, 0); + + return -1; + } + return 0; +} + +void * module_handle( const char *file_name ) +{ + module_loaded_t *module; + + for ( module = module_list; module; module= module->next ) { + if ( !strcmp( module->name, file_name )) { + return module; + } + } + return NULL; +} + +int module_unload( const char *file_name ) +{ + module_loaded_t *module; + + module = module_handle( file_name ); + if ( module ) { + module_int_unload( module ); + return 0; + } + return -1; /* not found */ +} + +int module_load(const char* file_name, int argc, char *argv[]) +{ + module_loaded_t *module; + const char *error; + int rc; + MODULE_INIT_FN initialize; +#ifdef HAVE_EBCDIC +#define file ebuf +#else +#define file file_name +#endif + + module = module_handle( file_name ); + if ( module ) { + Debug( LDAP_DEBUG_ANY, "module_load: (%s) already loaded\n", + file_name, 0, 0 ); + return -1; + } + + /* If loading a backend, see if we already have it */ + if ( !strncasecmp( file_name, "back_", 5 )) { + char *name = (char *)file_name + 5; + char *dot = strchr( name, '.'); + if (dot) *dot = '\0'; + rc = backend_info( name ) != NULL; + if (dot) *dot = '.'; + if ( rc ) { + Debug( LDAP_DEBUG_CONFIG, "module_load: (%s) already present (static)\n", + file_name, 0, 0 ); + return 0; + } + } else { + /* check for overlays too */ + char *dot = strchr( file_name, '.' ); + if ( dot ) *dot = '\0'; + rc = overlay_find( file_name ) != NULL; + if ( dot ) *dot = '.'; + if ( rc ) { + Debug( LDAP_DEBUG_CONFIG, "module_load: (%s) already present (static)\n", + file_name, 0, 0 ); + return 0; + } + } + + module = (module_loaded_t *)ch_calloc(1, sizeof(module_loaded_t) + + strlen(file_name)); + if (module == NULL) { + Debug(LDAP_DEBUG_ANY, "module_load failed: (%s) out of memory\n", file_name, + 0, 0); + + return -1; + } + strcpy( module->name, file_name ); + +#ifdef HAVE_EBCDIC + strcpy( file, file_name ); + __atoe( file ); +#endif + /* + * The result of lt_dlerror(), when called, must be cached prior + * to calling Debug. This is because Debug is a macro that expands + * into multiple function calls. + */ + if ((module->lib = lt_dlopenext(file)) == NULL) { + error = lt_dlerror(); +#ifdef HAVE_EBCDIC + strcpy( ebuf, error ); + __etoa( ebuf ); + error = ebuf; +#endif + Debug(LDAP_DEBUG_ANY, "lt_dlopenext failed: (%s) %s\n", file_name, + error, 0); + + ch_free(module); + return -1; + } + + Debug(LDAP_DEBUG_CONFIG, "loaded module %s\n", file_name, 0, 0); + + +#ifdef HAVE_EBCDIC +#pragma convlit(suspend) +#endif + if ((initialize = lt_dlsym(module->lib, "init_module")) == NULL) { +#ifdef HAVE_EBCDIC +#pragma convlit(resume) +#endif + Debug(LDAP_DEBUG_CONFIG, "module %s: no init_module() function found\n", + file_name, 0, 0); + + lt_dlclose(module->lib); + ch_free(module); + return -1; + } + + /* The imported init_module() routine passes back the type of + * module (i.e., which part of slapd it should be hooked into) + * or -1 for error. If it passes back 0, then you get the + * old behavior (i.e., the library is loaded and not hooked + * into anything). + * + * It might be better if the conf file could specify the type + * of module. That way, a single module could support multiple + * type of hooks. This could be done by using something like: + * + * moduleload extension /usr/local/openldap/whatever.so + * + * then we'd search through module_regtable for a matching + * module type, and hook in there. + */ + rc = initialize(argc, argv); + if (rc == -1) { + Debug(LDAP_DEBUG_CONFIG, "module %s: init_module() failed\n", + file_name, 0, 0); + + lt_dlclose(module->lib); + ch_free(module); + return rc; + } + + if (rc >= (int)(sizeof(module_regtable) / sizeof(struct module_regtable_t)) + || module_regtable[rc].proc == NULL) + { + Debug(LDAP_DEBUG_CONFIG, "module %s: unknown registration type (%d)\n", + file_name, rc, 0); + + module_int_unload(module); + return -1; + } + + rc = (module_regtable[rc].proc)(module, file_name); + if (rc != 0) { + Debug(LDAP_DEBUG_CONFIG, "module %s: %s module could not be registered\n", + file_name, module_regtable[rc].type, 0); + + module_int_unload(module); + return rc; + } + + module->next = module_list; + module_list = module; + + Debug(LDAP_DEBUG_CONFIG, "module %s: %s module registered\n", + file_name, module_regtable[rc].type, 0); + + return 0; +} + +int module_path(const char *path) +{ +#ifdef HAVE_EBCDIC + strcpy(ebuf, path); + __atoe(ebuf); + path = ebuf; +#endif + return lt_dlsetsearchpath( path ); +} + +void *module_resolve (const void *module, const char *name) +{ +#ifdef HAVE_EBCDIC + strcpy(ebuf, name); + __atoe(ebuf); + name = ebuf; +#endif + if (module == NULL || name == NULL) + return(NULL); + return(lt_dlsym(((module_loaded_t *)module)->lib, name)); +} + +static int module_int_unload (module_loaded_t *module) +{ + module_loaded_t *mod; + MODULE_TERM_FN terminate; + + if (module != NULL) { + /* remove module from tracking list */ + if (module_list == module) { + module_list = module->next; + } else { + for (mod = module_list; mod; mod = mod->next) { + if (mod->next == module) { + mod->next = module->next; + break; + } + } + } + + /* call module's terminate routine, if present */ +#ifdef HAVE_EBCDIC +#pragma convlit(suspend) +#endif + if ((terminate = lt_dlsym(module->lib, "term_module"))) { +#ifdef HAVE_EBCDIC +#pragma convlit(resume) +#endif + terminate(); + } + + /* close the library and free the memory */ + lt_dlclose(module->lib); + ch_free(module); + } + return 0; +} + +int load_null_module (const void *module, const char *file_name) +{ + return 0; +} + +#ifdef SLAPD_EXTERNAL_EXTENSIONS +int +load_extop_module ( + const void *module, + const char *file_name +) +{ + SLAP_EXTOP_MAIN_FN *ext_main; + SLAP_EXTOP_GETOID_FN *ext_getoid; + struct berval oid; + int rc; + + ext_main = (SLAP_EXTOP_MAIN_FN *)module_resolve(module, "ext_main"); + if (ext_main == NULL) { + return(-1); + } + + ext_getoid = module_resolve(module, "ext_getoid"); + if (ext_getoid == NULL) { + return(-1); + } + + rc = (ext_getoid)(0, &oid, 256); + if (rc != 0) { + return(rc); + } + if (oid.bv_val == NULL || oid.bv_len == 0) { + return(-1); + } + + /* FIXME: this is broken, and no longer needed, + * as a module can call load_extop() itself... */ + rc = load_extop( &oid, ext_main ); + return rc; +} +#endif /* SLAPD_EXTERNAL_EXTENSIONS */ +#endif /* SLAPD_MODULES */ + diff --git a/servers/slapd/mr.c b/servers/slapd/mr.c new file mode 100644 index 0000000..39348fa --- /dev/null +++ b/servers/slapd/mr.c @@ -0,0 +1,549 @@ +/* mr.c - routines to manage matching rule definitions */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" + +struct mindexrec { + struct berval mir_name; + MatchingRule *mir_mr; +}; + +static Avlnode *mr_index = NULL; +static LDAP_SLIST_HEAD(MRList, MatchingRule) mr_list + = LDAP_SLIST_HEAD_INITIALIZER(&mr_list); +static LDAP_SLIST_HEAD(MRUList, MatchingRuleUse) mru_list + = LDAP_SLIST_HEAD_INITIALIZER(&mru_list); + +static int +mr_index_cmp( + const void *v_mir1, + const void *v_mir2 +) +{ + const struct mindexrec *mir1 = v_mir1; + const struct mindexrec *mir2 = v_mir2; + int i = mir1->mir_name.bv_len - mir2->mir_name.bv_len; + if (i) return i; + return (strcasecmp( mir1->mir_name.bv_val, mir2->mir_name.bv_val )); +} + +static int +mr_index_name_cmp( + const void *v_name, + const void *v_mir +) +{ + const struct berval *name = v_name; + const struct mindexrec *mir = v_mir; + int i = name->bv_len - mir->mir_name.bv_len; + if (i) return i; + return (strncasecmp( name->bv_val, mir->mir_name.bv_val, name->bv_len )); +} + +MatchingRule * +mr_find( const char *mrname ) +{ + struct berval bv; + + bv.bv_val = (char *)mrname; + bv.bv_len = strlen( mrname ); + return mr_bvfind( &bv ); +} + +MatchingRule * +mr_bvfind( struct berval *mrname ) +{ + struct mindexrec *mir = NULL; + + if ( (mir = avl_find( mr_index, mrname, mr_index_name_cmp )) != NULL ) { + return( mir->mir_mr ); + } + return( NULL ); +} + +void +mr_destroy( void ) +{ + MatchingRule *m; + + avl_free(mr_index, ldap_memfree); + while( !LDAP_SLIST_EMPTY(&mr_list) ) { + m = LDAP_SLIST_FIRST(&mr_list); + LDAP_SLIST_REMOVE_HEAD(&mr_list, smr_next); + ch_free( m->smr_str.bv_val ); + ch_free( m->smr_compat_syntaxes ); + ldap_matchingrule_free((LDAPMatchingRule *)m); + } +} + +static int +mr_insert( + MatchingRule *smr, + const char **err +) +{ + struct mindexrec *mir; + char **names; + + LDAP_SLIST_NEXT( smr, smr_next ) = NULL; + LDAP_SLIST_INSERT_HEAD(&mr_list, smr, smr_next); + + if ( smr->smr_oid ) { + mir = (struct mindexrec *) + ch_calloc( 1, sizeof(struct mindexrec) ); + mir->mir_name.bv_val = smr->smr_oid; + mir->mir_name.bv_len = strlen( smr->smr_oid ); + mir->mir_mr = smr; + if ( avl_insert( &mr_index, (caddr_t) mir, + mr_index_cmp, avl_dup_error ) ) { + *err = smr->smr_oid; + ldap_memfree(mir); + return SLAP_SCHERR_MR_DUP; + } + /* FIX: temporal consistency check */ + mr_bvfind(&mir->mir_name); + } + if ( (names = smr->smr_names) ) { + while ( *names ) { + mir = (struct mindexrec *) + ch_calloc( 1, sizeof(struct mindexrec) ); + mir->mir_name.bv_val = *names; + mir->mir_name.bv_len = strlen( *names ); + mir->mir_mr = smr; + if ( avl_insert( &mr_index, (caddr_t) mir, + mr_index_cmp, avl_dup_error ) ) { + *err = *names; + ldap_memfree(mir); + return SLAP_SCHERR_MR_DUP; + } + /* FIX: temporal consistency check */ + mr_bvfind(&mir->mir_name); + names++; + } + } + return 0; +} + +int +mr_make_syntax_compat_with_mr( + Syntax *syn, + MatchingRule *mr ) +{ + int n = 0; + + assert( syn != NULL ); + assert( mr != NULL ); + + if ( mr->smr_compat_syntaxes ) { + /* count esisting */ + for ( n = 0; + mr->smr_compat_syntaxes[ n ]; + n++ ) + { + if ( mr->smr_compat_syntaxes[ n ] == syn ) { + /* already compatible; mmmmh... */ + return 1; + } + } + } + + mr->smr_compat_syntaxes = ch_realloc( + mr->smr_compat_syntaxes, + sizeof( Syntax * )*(n + 2) ); + mr->smr_compat_syntaxes[ n ] = syn; + mr->smr_compat_syntaxes[ n + 1 ] = NULL; + + return 0; +} + +int +mr_make_syntax_compat_with_mrs( + const char *syntax, + char *const *mrs ) +{ + int r, rc = 0; + Syntax *syn; + + assert( syntax != NULL ); + assert( mrs != NULL ); + + syn = syn_find( syntax ); + if ( syn == NULL ) { + return -1; + } + + for ( r = 0; mrs[ r ] != NULL; r++ ) { + MatchingRule *mr = mr_find( mrs[ r ] ); + if ( mr == NULL ) { + /* matchingRule not found -- ignore by now */ + continue; + } + + rc += mr_make_syntax_compat_with_mr( syn, mr ); + } + + return rc; +} + +int +mr_add( + LDAPMatchingRule *mr, + slap_mrule_defs_rec *def, + MatchingRule *amr, + const char **err +) +{ + MatchingRule *smr; + Syntax *syn; + Syntax **compat_syn = NULL; + int code; + + if( def->mrd_compat_syntaxes ) { + int i; + for( i=0; def->mrd_compat_syntaxes[i]; i++ ) { + /* just count em */ + } + + compat_syn = ch_malloc( sizeof(Syntax *) * (i+1) ); + + for( i=0; def->mrd_compat_syntaxes[i]; i++ ) { + compat_syn[i] = syn_find( def->mrd_compat_syntaxes[i] ); + if( compat_syn[i] == NULL ) { + ch_free( compat_syn ); + return SLAP_SCHERR_SYN_NOT_FOUND; + } + } + + compat_syn[i] = NULL; + } + + smr = (MatchingRule *) ch_calloc( 1, sizeof(MatchingRule) ); + AC_MEMCPY( &smr->smr_mrule, mr, sizeof(LDAPMatchingRule)); + + /* + * note: smr_bvoid uses the same memory of smr_mrule.mr_oid; + * smr_oidlen is #defined as smr_bvoid.bv_len + */ + smr->smr_bvoid.bv_val = smr->smr_mrule.mr_oid; + smr->smr_oidlen = strlen( mr->mr_oid ); + smr->smr_usage = def->mrd_usage; + smr->smr_compat_syntaxes = compat_syn; + smr->smr_normalize = def->mrd_normalize; + smr->smr_match = def->mrd_match; + smr->smr_indexer = def->mrd_indexer; + smr->smr_filter = def->mrd_filter; + smr->smr_associated = amr; + + if ( smr->smr_syntax_oid ) { + if ( (syn = syn_find(smr->smr_syntax_oid)) ) { + smr->smr_syntax = syn; + } else { + *err = smr->smr_syntax_oid; + ch_free( smr ); + return SLAP_SCHERR_SYN_NOT_FOUND; + } + } else { + *err = ""; + ch_free( smr ); + return SLAP_SCHERR_MR_INCOMPLETE; + } + code = mr_insert(smr,err); + return code; +} + +int +register_matching_rule( + slap_mrule_defs_rec *def ) +{ + LDAPMatchingRule *mr; + MatchingRule *amr = NULL; + int code; + const char *err; + + if( def->mrd_usage == SLAP_MR_NONE && def->mrd_compat_syntaxes == NULL ) { + Debug( LDAP_DEBUG_ANY, "register_matching_rule: not usable %s\n", + def->mrd_desc, 0, 0 ); + + return -1; + } + + if( def->mrd_associated != NULL ) { + amr = mr_find( def->mrd_associated ); + if( amr == NULL ) { + Debug( LDAP_DEBUG_ANY, "register_matching_rule: " + "could not locate associated matching rule %s for %s\n", + def->mrd_associated, def->mrd_desc, 0 ); + + return -1; + } + + if (( def->mrd_usage & SLAP_MR_EQUALITY ) && + (( def->mrd_usage & SLAP_MR_SUBTYPE_MASK ) == SLAP_MR_NONE )) + { + if (( def->mrd_usage & SLAP_MR_EQUALITY ) && + (( def->mrd_usage & SLAP_MR_SUBTYPE_MASK ) != SLAP_MR_NONE )) + { + Debug( LDAP_DEBUG_ANY, "register_matching_rule: " + "inappropriate (approx) association %s for %s\n", + def->mrd_associated, def->mrd_desc, 0 ); + return -1; + } + + } else if (!( amr->smr_usage & SLAP_MR_EQUALITY )) { + Debug( LDAP_DEBUG_ANY, "register_matching_rule: " + "inappropriate (equalilty) association %s for %s\n", + def->mrd_associated, def->mrd_desc, 0 ); + return -1; + } + } + + mr = ldap_str2matchingrule( def->mrd_desc, &code, &err, + LDAP_SCHEMA_ALLOW_ALL ); + if ( !mr ) { + Debug( LDAP_DEBUG_ANY, + "Error in register_matching_rule: %s before %s in %s\n", + ldap_scherr2str(code), err, def->mrd_desc ); + + return -1; + } + + + code = mr_add( mr, def, amr, &err ); + + ldap_memfree( mr ); + + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "Error in register_matching_rule: %s for %s in %s\n", + scherr2str(code), err, def->mrd_desc ); + + return -1; + } + + return 0; +} + +void +mru_destroy( void ) +{ + MatchingRuleUse *m; + + while( !LDAP_SLIST_EMPTY(&mru_list) ) { + m = LDAP_SLIST_FIRST(&mru_list); + LDAP_SLIST_REMOVE_HEAD(&mru_list, smru_next); + + if ( m->smru_str.bv_val ) { + ch_free( m->smru_str.bv_val ); + m->smru_str.bv_val = NULL; + } + /* memory borrowed from m->smru_mr */ + m->smru_oid = NULL; + m->smru_names = NULL; + m->smru_desc = NULL; + + /* free what's left (basically smru_mruleuse.mru_applies_oids) */ + ldap_matchingruleuse_free((LDAPMatchingRuleUse *)m); + } +} + +int +matching_rule_use_init( void ) +{ + MatchingRule *mr; + MatchingRuleUse **mru_ptr = &LDAP_SLIST_FIRST(&mru_list); + + Debug( LDAP_DEBUG_TRACE, "matching_rule_use_init\n", 0, 0, 0 ); + + LDAP_SLIST_FOREACH( mr, &mr_list, smr_next ) { + AttributeType *at; + MatchingRuleUse mru_storage = {{ 0 }}, + *mru = &mru_storage; + + char **applies_oids = NULL; + + mr->smr_mru = NULL; + + /* hide rules marked as HIDE */ + if ( mr->smr_usage & SLAP_MR_HIDE ) { + continue; + } + + /* hide rules not marked as designed for extensibility */ + /* MR_EXT means can be used any attribute type whose + * syntax is same as the assertion syntax. + * Another mechanism is needed where rule can be used + * with attribute of other syntaxes. + * Framework doesn't support this (yet). + */ + + if (!( ( mr->smr_usage & SLAP_MR_EXT ) + || mr->smr_compat_syntaxes ) ) + { + continue; + } + + /* + * Note: we're using the same values of the corresponding + * MatchingRule structure; maybe we'd copy them ... + */ + mru->smru_mr = mr; + mru->smru_obsolete = mr->smr_obsolete; + mru->smru_applies_oids = NULL; + LDAP_SLIST_NEXT(mru, smru_next) = NULL; + mru->smru_oid = mr->smr_oid; + mru->smru_names = mr->smr_names; + mru->smru_desc = mr->smr_desc; + + Debug( LDAP_DEBUG_TRACE, " %s (%s): ", + mru->smru_oid, + mru->smru_names ? mru->smru_names[ 0 ] : "", 0 ); + + at = NULL; + for ( at_start( &at ); at; at_next( &at ) ) { + if( at->sat_flags & SLAP_AT_HIDE ) continue; + + if( mr_usable_with_at( mr, at )) { + ldap_charray_add( &applies_oids, at->sat_cname.bv_val ); + } + } + + /* + * Note: the matchingRules that are not used + * by any attributeType are not listed as + * matchingRuleUse + */ + if ( applies_oids != NULL ) { + mru->smru_applies_oids = applies_oids; + { + char *str = ldap_matchingruleuse2str( &mru->smru_mruleuse ); + Debug( LDAP_DEBUG_TRACE, "matchingRuleUse: %s\n", str, 0, 0 ); + ldap_memfree( str ); + } + + mru = (MatchingRuleUse *)ber_memalloc( sizeof( MatchingRuleUse ) ); + /* call-forward from MatchingRule to MatchingRuleUse */ + mr->smr_mru = mru; + /* copy static data to newly allocated struct */ + *mru = mru_storage; + /* append the struct pointer to the end of the list */ + *mru_ptr = mru; + /* update the list head pointer */ + mru_ptr = &LDAP_SLIST_NEXT(mru,smru_next); + } + } + + return( 0 ); +} + +int +mr_usable_with_at( + MatchingRule *mr, + AttributeType *at ) +{ + if ( ( mr->smr_usage & SLAP_MR_EXT ) && ( + mr->smr_syntax == at->sat_syntax || + mr == at->sat_equality || + mr == at->sat_approx || + syn_is_sup( at->sat_syntax, mr->smr_syntax ) ) ) + { + return 1; + } + + if ( mr->smr_compat_syntaxes ) { + int i; + for( i=0; mr->smr_compat_syntaxes[i]; i++ ) { + if( at->sat_syntax == mr->smr_compat_syntaxes[i] ) { + return 1; + } + } + } + return 0; +} + +int mr_schema_info( Entry *e ) +{ + AttributeDescription *ad_matchingRules = slap_schema.si_ad_matchingRules; + MatchingRule *mr; + struct berval nval; + + LDAP_SLIST_FOREACH(mr, &mr_list, smr_next ) { + if ( mr->smr_usage & SLAP_MR_HIDE ) { + /* skip hidden rules */ + continue; + } + + if ( ! mr->smr_match ) { + /* skip rules without matching functions */ + continue; + } + + if ( mr->smr_str.bv_val == NULL ) { + if ( ldap_matchingrule2bv( &mr->smr_mrule, &mr->smr_str ) == NULL ) { + return -1; + } + } +#if 0 + Debug( LDAP_DEBUG_TRACE, "Merging mr [%lu] %s\n", + mr->smr_str.bv_len, mr->smr_str.bv_val, 0 ); +#endif + + nval.bv_val = mr->smr_oid; + nval.bv_len = strlen(mr->smr_oid); + if( attr_merge_one( e, ad_matchingRules, &mr->smr_str, &nval ) ) { + return -1; + } + } + return 0; +} + +int mru_schema_info( Entry *e ) +{ + AttributeDescription *ad_matchingRuleUse + = slap_schema.si_ad_matchingRuleUse; + MatchingRuleUse *mru; + struct berval nval; + + LDAP_SLIST_FOREACH( mru, &mru_list, smru_next ) { + assert( !( mru->smru_usage & SLAP_MR_HIDE ) ); + + if ( mru->smru_str.bv_val == NULL ) { + if ( ldap_matchingruleuse2bv( &mru->smru_mruleuse, &mru->smru_str ) + == NULL ) { + return -1; + } + } + +#if 0 + Debug( LDAP_DEBUG_TRACE, "Merging mru [%lu] %s\n", + mru->smru_str.bv_len, mru->smru_str.bv_val, 0 ); +#endif + + nval.bv_val = mru->smru_oid; + nval.bv_len = strlen(mru->smru_oid); + if( attr_merge_one( e, ad_matchingRuleUse, &mru->smru_str, &nval ) ) { + return -1; + } + } + return 0; +} diff --git a/servers/slapd/mra.c b/servers/slapd/mra.c new file mode 100644 index 0000000..d6ea475 --- /dev/null +++ b/servers/slapd/mra.c @@ -0,0 +1,231 @@ +/* mra.c - routines for dealing with extensible matching rule assertions */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" + +#ifdef LDAP_COMP_MATCH +#include "component.h" +#endif + +void +mra_free( + Operation *op, + MatchingRuleAssertion *mra, + int freeit ) +{ +#ifdef LDAP_COMP_MATCH + /* free component assertion */ + if ( mra->ma_rule->smr_usage & SLAP_MR_COMPONENT && mra->ma_cf ) { + component_free( mra->ma_cf ); + } +#endif + /* op->o_tmpfree( mra->ma_value.bv_val, op->o_tmpmemctx ); */ + ch_free( mra->ma_value.bv_val ); + if ( mra->ma_desc && mra->ma_desc->ad_flags & SLAP_DESC_TEMPORARY ) + op->o_tmpfree( mra->ma_desc, op->o_tmpmemctx ); + if ( freeit ) op->o_tmpfree( (char *) mra, op->o_tmpmemctx ); +} + +int +get_mra( + Operation *op, + BerElement *ber, + Filter *f, + const char **text ) +{ + int rc; + ber_tag_t tag, rtag; + ber_len_t length; + struct berval type = BER_BVNULL; + struct berval value = BER_BVNULL; + struct berval rule_text = BER_BVNULL; + MatchingRuleAssertion ma = { 0 }; +#ifdef LDAP_COMP_MATCH + AttributeAliasing* aa = NULL; +#endif + + rtag = ber_scanf( ber, "{t" /*"}"*/, &tag ); + + if( rtag == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, " get_mra ber_scanf\n", 0, 0, 0 ); + + *text = "Error parsing matching rule assertion"; + return SLAPD_DISCONNECT; + } + + if ( tag == LDAP_FILTER_EXT_OID ) { + rtag = ber_scanf( ber, "m", &rule_text ); + if ( rtag == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, " get_mra ber_scanf for mr\n", 0, 0, 0 ); + + *text = "Error parsing matching rule in matching rule assertion"; + return SLAPD_DISCONNECT; + } + + rtag = ber_scanf( ber, "t", &tag ); + if( rtag == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, " get_mra ber_scanf\n", 0, 0, 0 ); + + *text = "Error parsing matching rule assertion"; + return SLAPD_DISCONNECT; + } + } + + if ( tag == LDAP_FILTER_EXT_TYPE ) { + rtag = ber_scanf( ber, "m", &type ); + if ( rtag == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, " get_mra ber_scanf for ad\n", 0, 0, 0 ); + + *text = "Error parsing attribute description in matching rule assertion"; + return SLAPD_DISCONNECT; + } + + rtag = ber_scanf( ber, "t", &tag ); + if( rtag == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, " get_mra ber_scanf\n", 0, 0, 0 ); + + *text = "Error parsing matching rule assertion"; + return SLAPD_DISCONNECT; + } + } + + if ( tag != LDAP_FILTER_EXT_VALUE ) { + Debug( LDAP_DEBUG_ANY, " get_mra ber_scanf missing value\n", 0, 0, 0 ); + + *text = "Missing value in matching rule assertion"; + return SLAPD_DISCONNECT; + } + + rtag = ber_scanf( ber, "m", &value ); + + if( rtag == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, " get_mra ber_scanf\n", 0, 0, 0 ); + + *text = "Error decoding value in matching rule assertion"; + return SLAPD_DISCONNECT; + } + + tag = ber_peek_tag( ber, &length ); + + if ( tag == LDAP_FILTER_EXT_DNATTRS ) { + rtag = ber_scanf( ber, /*"{"*/ "b}", &ma.ma_dnattrs ); + } else { + rtag = ber_scanf( ber, /*"{"*/ "}" ); + } + + if( rtag == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, " get_mra ber_scanf\n", 0, 0, 0 ); + + *text = "Error decoding dnattrs matching rule assertion"; + return SLAPD_DISCONNECT; + } + + if( type.bv_val != NULL ) { + rc = slap_bv2ad( &type, &ma.ma_desc, text ); + if( rc != LDAP_SUCCESS ) { + f->f_choice |= SLAPD_FILTER_UNDEFINED; + rc = slap_bv2undef_ad( &type, &ma.ma_desc, text, + SLAP_AD_PROXIED|SLAP_AD_NOINSERT ); + + if( rc != LDAP_SUCCESS ) { + ma.ma_desc = slap_bv2tmp_ad( &type, op->o_tmpmemctx ); + rc = LDAP_SUCCESS; + } + } + } + + if( rule_text.bv_val != NULL ) { + ma.ma_rule = mr_bvfind( &rule_text ); + if( ma.ma_rule == NULL ) { + *text = "matching rule not recognized"; + return LDAP_INAPPROPRIATE_MATCHING; + } + } + + if ( ma.ma_rule == NULL ) { + /* + * Need either type or rule ... + */ + if ( ma.ma_desc == NULL ) { + *text = "no matching rule or type"; + return LDAP_INAPPROPRIATE_MATCHING; + } + + if ( ma.ma_desc->ad_type->sat_equality != NULL && + ma.ma_desc->ad_type->sat_equality->smr_usage & SLAP_MR_EXT ) + { + /* no matching rule was provided, use the attribute's + equality rule if it supports extensible matching. */ + ma.ma_rule = ma.ma_desc->ad_type->sat_equality; + + } else { + *text = "no appropriate rule to use for type"; + return LDAP_INAPPROPRIATE_MATCHING; + } + } + + if ( ma.ma_desc != NULL ) { + if( !mr_usable_with_at( ma.ma_rule, ma.ma_desc->ad_type ) ) { + *text = "matching rule use with this attribute not appropriate"; + return LDAP_INAPPROPRIATE_MATCHING; + } + + } + + /* + * Normalize per matching rule + */ + rc = asserted_value_validate_normalize( ma.ma_desc, + ma.ma_rule, + SLAP_MR_EXT|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX, + &value, &ma.ma_value, text, op->o_tmpmemctx ); + + if( rc != LDAP_SUCCESS ) return rc; + +#ifdef LDAP_COMP_MATCH + /* Check If this attribute is aliased */ + if ( is_aliased_attribute && ma.ma_desc && ( aa = is_aliased_attribute ( ma.ma_desc ) ) ) { + rc = get_aliased_filter ( op, &ma, aa, text ); + if ( rc != LDAP_SUCCESS ) return rc; + } + else if ( ma.ma_rule && ma.ma_rule->smr_usage & SLAP_MR_COMPONENT ) { + /* Matching Rule for Component Matching */ + rc = get_comp_filter( op, &ma.ma_value, &ma.ma_cf, text ); + if ( rc != LDAP_SUCCESS ) return rc; + } +#endif + + length = sizeof(ma); + /* Append rule_text to end of struct */ + if (rule_text.bv_val) length += rule_text.bv_len + 1; + f->f_mra = op->o_tmpalloc( length, op->o_tmpmemctx ); + *f->f_mra = ma; + if (rule_text.bv_val) { + f->f_mra->ma_rule_text.bv_len = rule_text.bv_len; + f->f_mra->ma_rule_text.bv_val = (char *)(f->f_mra+1); + AC_MEMCPY(f->f_mra->ma_rule_text.bv_val, rule_text.bv_val, + rule_text.bv_len+1); + } + + return LDAP_SUCCESS; +} diff --git a/servers/slapd/nt_svc.c b/servers/slapd/nt_svc.c new file mode 100644 index 0000000..a0ae09b --- /dev/null +++ b/servers/slapd/nt_svc.c @@ -0,0 +1,110 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" +#include <stdio.h> +#include <ac/string.h> +#include "slap.h" +#include "lutil.h" + +#ifdef HAVE_NT_SERVICE_MANAGER + +/* in main.c */ +void WINAPI ServiceMain( DWORD argc, LPTSTR *argv ); + +/* in ntservice.c */ +int main( int argc, LPTSTR *argv ) +{ + int length; + char filename[MAX_PATH], *fname_start; + + /* + * Because the service was registered as SERVICE_WIN32_OWN_PROCESS, + * the lpServiceName element of the SERVICE_TABLE_ENTRY will be + * ignored. + */ + + SERVICE_TABLE_ENTRY DispatchTable[] = { + { "", (LPSERVICE_MAIN_FUNCTION) ServiceMain }, + { NULL, NULL } + }; + + /* + * set the service's current directory to the installation directory + * for the service. this way we don't have to write absolute paths + * in the configuration files + */ + GetModuleFileName( NULL, filename, sizeof( filename ) ); + fname_start = strrchr( filename, *LDAP_DIRSEP ); + + if ( argc > 1 ) { + if ( _stricmp( "install", argv[1] ) == 0 ) + { + char *svcName = SERVICE_NAME; + char *displayName = "OpenLDAP Directory Service"; + BOOL auto_start = FALSE; + + if ( (argc > 2) && (argv[2] != NULL) ) + svcName = argv[2]; + + if ( argc > 3 && argv[3]) + displayName = argv[3]; + + if ( argc > 4 && stricmp(argv[4], "auto") == 0) + auto_start = TRUE; + + strcat(filename, " service"); + if ( !lutil_srv_install(svcName, displayName, filename, auto_start) ) + { + fputs( "service failed installation ...\n", stderr ); + return EXIT_FAILURE; + } + fputs( "service has been installed ...\n", stderr ); + return EXIT_SUCCESS; + } + + if ( _stricmp( "remove", argv[1] ) == 0 ) + { + char *svcName = SERVICE_NAME; + if ( (argc > 2) && (argv[2] != NULL) ) + svcName = argv[2]; + if ( !lutil_srv_remove(svcName, filename) ) + { + fputs( "failed to remove the service ...\n", stderr ); + return EXIT_FAILURE; + } + fputs( "service has been removed ...\n", stderr ); + return EXIT_SUCCESS; + } + if ( _stricmp( "service", argv[1] ) == 0 ) + { + is_NT_Service = 1; + *fname_start = '\0'; + SetCurrentDirectory( filename ); + } + } + + if (is_NT_Service) + { + StartServiceCtrlDispatcher(DispatchTable); + } else + { + ServiceMain( argc, argv ); + } + + return EXIT_SUCCESS; +} + +#endif diff --git a/servers/slapd/oc.c b/servers/slapd/oc.c new file mode 100644 index 0000000..dc6297e --- /dev/null +++ b/servers/slapd/oc.c @@ -0,0 +1,940 @@ +/* oc.c - object class routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" + +int is_object_subclass( + ObjectClass *sup, + ObjectClass *sub ) +{ + int i; + + if( sub == NULL || sup == NULL ) return 0; + +#if 0 + Debug( LDAP_DEBUG_TRACE, "is_object_subclass(%s,%s) %d\n", + sup->soc_oid, sub->soc_oid, sup == sub ); +#endif + + if ( sup == sub ) { + return 1; + } + + if ( sub->soc_sups == NULL ) { + return 0; + } + + for ( i = 0; sub->soc_sups[i] != NULL; i++ ) { + if ( is_object_subclass( sup, sub->soc_sups[i] ) ) { + return 1; + } + } + + return 0; +} + +int is_entry_objectclass( + Entry* e, + ObjectClass *oc, + unsigned flags ) +{ + /* + * set_flags should only be true if oc is one of operational + * object classes which we support objectClass flags for + * (e.g., referral, alias, ...). See <slap.h>. + */ + + Attribute *attr; + struct berval *bv; + + assert( !( e == NULL || oc == NULL ) ); + assert( ( flags & SLAP_OCF_MASK ) != SLAP_OCF_MASK ); + + if ( e == NULL || oc == NULL ) { + return 0; + } + + if ( flags == SLAP_OCF_SET_FLAGS && ( e->e_ocflags & SLAP_OC__END ) ) + { + /* flags are set, use them */ + return (e->e_ocflags & oc->soc_flags & SLAP_OC__MASK) != 0; + } + + /* + * find objectClass attribute + */ + attr = attr_find( e->e_attrs, slap_schema.si_ad_objectClass ); + if ( attr == NULL ) { + /* no objectClass attribute */ + Debug( LDAP_DEBUG_ANY, "is_entry_objectclass(\"%s\", \"%s\") " + "no objectClass attribute\n", + e->e_dn == NULL ? "" : e->e_dn, + oc->soc_oclass.oc_oid, 0 ); + + /* mark flags as set */ + e->e_ocflags |= SLAP_OC__END; + + return 0; + } + + for ( bv = attr->a_vals; bv->bv_val; bv++ ) { + ObjectClass *objectClass = oc_bvfind( bv ); + + if ( objectClass == NULL ) { + /* FIXME: is this acceptable? */ + continue; + } + + if ( !( flags & SLAP_OCF_SET_FLAGS ) ) { + if ( objectClass == oc ) { + return 1; + } + + if ( ( flags & SLAP_OCF_CHECK_SUP ) + && is_object_subclass( oc, objectClass ) ) + { + return 1; + } + } + + e->e_ocflags |= objectClass->soc_flags; + } + + /* mark flags as set */ + e->e_ocflags |= SLAP_OC__END; + + return ( e->e_ocflags & oc->soc_flags & SLAP_OC__MASK ) != 0; +} + + +struct oindexrec { + struct berval oir_name; + ObjectClass *oir_oc; +}; + +static Avlnode *oc_index = NULL; +static Avlnode *oc_cache = NULL; +static LDAP_STAILQ_HEAD(OCList, ObjectClass) oc_list + = LDAP_STAILQ_HEAD_INITIALIZER(oc_list); + +ObjectClass *oc_sys_tail; + +static int +oc_index_cmp( + const void *v_oir1, + const void *v_oir2 ) +{ + const struct oindexrec *oir1 = v_oir1, *oir2 = v_oir2; + int i = oir1->oir_name.bv_len - oir2->oir_name.bv_len; + if (i) return i; + return strcasecmp( oir1->oir_name.bv_val, oir2->oir_name.bv_val ); +} + +static int +oc_index_name_cmp( + const void *v_name, + const void *v_oir ) +{ + const struct berval *name = v_name; + const struct oindexrec *oir = v_oir; + int i = name->bv_len - oir->oir_name.bv_len; + if (i) return i; + return strncasecmp( name->bv_val, oir->oir_name.bv_val, name->bv_len ); +} + +ObjectClass * +oc_find( const char *ocname ) +{ + struct berval bv; + + bv.bv_val = (char *)ocname; + bv.bv_len = strlen( ocname ); + + return( oc_bvfind( &bv ) ); +} + +ObjectClass * +oc_bvfind( struct berval *ocname ) +{ + struct oindexrec *oir; + + if ( oc_cache ) { + oir = avl_find( oc_cache, ocname, oc_index_name_cmp ); + if ( oir ) return oir->oir_oc; + } + oir = avl_find( oc_index, ocname, oc_index_name_cmp ); + + if ( oir != NULL ) { + if ( at_oc_cache ) { + avl_insert( &oc_cache, (caddr_t) oir, + oc_index_cmp, avl_dup_error ); + } + return( oir->oir_oc ); + } + + return( NULL ); +} + +static LDAP_STAILQ_HEAD(OCUList, ObjectClass) oc_undef_list + = LDAP_STAILQ_HEAD_INITIALIZER(oc_undef_list); + +ObjectClass * +oc_bvfind_undef( struct berval *ocname ) +{ + ObjectClass *oc = oc_bvfind( ocname ); + + if ( oc ) { + return oc; + } + + LDAP_STAILQ_FOREACH( oc, &oc_undef_list, soc_next ) { + int d = oc->soc_cname.bv_len - ocname->bv_len; + + if ( d ) { + continue; + } + + if ( strcasecmp( oc->soc_cname.bv_val, ocname->bv_val ) == 0 ) { + break; + } + } + + if ( oc ) { + return oc; + } + + oc = ch_malloc( sizeof( ObjectClass ) + ocname->bv_len + 1 ); + memset( oc, 0, sizeof( ObjectClass ) ); + + oc->soc_cname.bv_len = ocname->bv_len; + oc->soc_cname.bv_val = (char *)&oc[ 1 ]; + AC_MEMCPY( oc->soc_cname.bv_val, ocname->bv_val, ocname->bv_len ); + oc->soc_cname.bv_val[ oc->soc_cname.bv_len ] = '\0'; + + /* canonical to upper case */ + ldap_pvt_str2upper( oc->soc_cname.bv_val ); + + LDAP_STAILQ_NEXT( oc, soc_next ) = NULL; + ldap_pvt_thread_mutex_lock( &oc_undef_mutex ); + LDAP_STAILQ_INSERT_HEAD( &oc_undef_list, oc, soc_next ); + ldap_pvt_thread_mutex_unlock( &oc_undef_mutex ); + + return oc; +} + +static int +oc_create_required( + ObjectClass *soc, + char **attrs, + int *op, + const char **err ) +{ + char **attrs1; + AttributeType *sat; + AttributeType **satp; + int i; + + if ( attrs ) { + attrs1 = attrs; + while ( *attrs1 ) { + sat = at_find(*attrs1); + if ( !sat ) { + *err = *attrs1; + return SLAP_SCHERR_ATTR_NOT_FOUND; + } + + if( is_at_operational( sat )) (*op)++; + + if ( at_find_in_list(sat, soc->soc_required) < 0) { + if ( at_append_to_list(sat, &soc->soc_required) ) { + *err = *attrs1; + return SLAP_SCHERR_OUTOFMEM; + } + } + attrs1++; + } + /* Now delete duplicates from the allowed list */ + for ( satp = soc->soc_required; *satp; satp++ ) { + i = at_find_in_list(*satp, soc->soc_allowed); + if ( i >= 0 ) { + at_delete_from_list(i, &soc->soc_allowed); + } + } + } + return 0; +} + +static int +oc_create_allowed( + ObjectClass *soc, + char **attrs, + int *op, + const char **err ) +{ + char **attrs1; + AttributeType *sat; + + if ( attrs ) { + attrs1 = attrs; + while ( *attrs1 ) { + sat = at_find(*attrs1); + if ( !sat ) { + *err = *attrs1; + return SLAP_SCHERR_ATTR_NOT_FOUND; + } + + if( is_at_operational( sat )) (*op)++; + + if ( at_find_in_list(sat, soc->soc_required) < 0 && + at_find_in_list(sat, soc->soc_allowed) < 0 ) { + if ( at_append_to_list(sat, &soc->soc_allowed) ) { + *err = *attrs1; + return SLAP_SCHERR_OUTOFMEM; + } + } + attrs1++; + } + } + return 0; +} + +static int +oc_add_sups( + ObjectClass *soc, + char **sups, + int *op, + const char **err ) +{ + int code; + ObjectClass *soc1; + int nsups; + char **sups1; + int add_sups = 0; + + if ( sups ) { + if ( !soc->soc_sups ) { + /* We are at the first recursive level */ + add_sups = 1; + nsups = 1; + sups1 = sups; + while ( *sups1 ) { + nsups++; + sups1++; + } + soc->soc_sups = (ObjectClass **)ch_calloc(nsups, + sizeof(ObjectClass *)); + } + + nsups = 0; + sups1 = sups; + while ( *sups1 ) { + soc1 = oc_find(*sups1); + if ( !soc1 ) { + *err = *sups1; + return SLAP_SCHERR_CLASS_NOT_FOUND; + } + + /* check object class usage + * abstract classes can only sup abstract classes + * structural classes can not sup auxiliary classes + * auxiliary classes can not sup structural classes + */ + if( soc->soc_kind != soc1->soc_kind + && soc1->soc_kind != LDAP_SCHEMA_ABSTRACT ) + { + *err = *sups1; + return SLAP_SCHERR_CLASS_BAD_SUP; + } + + if( soc1->soc_obsolete && !soc->soc_obsolete ) { + *err = *sups1; + return SLAP_SCHERR_CLASS_BAD_SUP; + } + + if( soc->soc_flags & SLAP_OC_OPERATIONAL ) (*op)++; + + if ( add_sups ) { + soc->soc_sups[nsups] = soc1; + } + + code = oc_add_sups( soc, soc1->soc_sup_oids, op, err ); + if ( code ) return code; + + code = oc_create_required( soc, soc1->soc_at_oids_must, op, err ); + if ( code ) return code; + + code = oc_create_allowed( soc, soc1->soc_at_oids_may, op, err ); + if ( code ) return code; + + nsups++; + sups1++; + } + } + + return 0; +} + +static void +oc_delete_names( ObjectClass *oc ) +{ + char **names = oc->soc_names; + + if (!names) return; + + while (*names) { + struct oindexrec tmpoir, *oir; + + ber_str2bv( *names, 0, 0, &tmpoir.oir_name ); + tmpoir.oir_oc = oc; + oir = (struct oindexrec *)avl_delete( &oc_index, + (caddr_t)&tmpoir, oc_index_cmp ); + assert( oir != NULL ); + ldap_memfree( oir ); + names++; + } +} + +/* Mark the ObjectClass as deleted, remove from list, and remove all its + * names from the AVL tree. Leave the OID in the tree. + */ +void +oc_delete( ObjectClass *oc ) +{ + oc->soc_flags |= SLAP_OC_DELETED; + + LDAP_STAILQ_REMOVE(&oc_list, oc, ObjectClass, soc_next); + + oc_delete_names( oc ); +} + +static void +oc_clean( ObjectClass *o ) +{ + if (o->soc_sups) { + ldap_memfree(o->soc_sups); + o->soc_sups = NULL; + } + if (o->soc_required) { + ldap_memfree(o->soc_required); + o->soc_required = NULL; + } + if (o->soc_allowed) { + ldap_memfree(o->soc_allowed); + o->soc_allowed = NULL; + } + if (o->soc_oidmacro) { + ldap_memfree(o->soc_oidmacro); + o->soc_oidmacro = NULL; + } +} + +static void +oc_destroy_one( void *v ) +{ + struct oindexrec *oir = v; + ObjectClass *o = oir->oir_oc; + + oc_clean( o ); + ldap_objectclass_free((LDAPObjectClass *)o); + ldap_memfree(oir); +} + +void +oc_destroy( void ) +{ + ObjectClass *o; + + while( !LDAP_STAILQ_EMPTY(&oc_list) ) { + o = LDAP_STAILQ_FIRST(&oc_list); + LDAP_STAILQ_REMOVE_HEAD(&oc_list, soc_next); + + oc_delete_names( o ); + } + + avl_free( oc_index, oc_destroy_one ); + + while( !LDAP_STAILQ_EMPTY(&oc_undef_list) ) { + o = LDAP_STAILQ_FIRST(&oc_undef_list); + LDAP_STAILQ_REMOVE_HEAD(&oc_undef_list, soc_next); + + ch_free( (ObjectClass *)o ); + } +} + +int +oc_start( ObjectClass **oc ) +{ + assert( oc != NULL ); + + *oc = LDAP_STAILQ_FIRST(&oc_list); + + return (*oc != NULL); +} + +int +oc_next( ObjectClass **oc ) +{ + assert( oc != NULL ); + +#if 0 /* pedantic check: breaks when deleting an oc, don't use it. */ + { + ObjectClass *tmp = NULL; + + LDAP_STAILQ_FOREACH(tmp,&oc_list,soc_next) { + if ( tmp == *oc ) { + break; + } + } + + assert( tmp != NULL ); + } +#endif + + if ( *oc == NULL ) { + return 0; + } + + *oc = LDAP_STAILQ_NEXT(*oc,soc_next); + + return (*oc != NULL); +} + +/* + * check whether the two ObjectClasses actually __are__ identical, + * or rather inconsistent + */ +static int +oc_check_dup( + ObjectClass *soc, + ObjectClass *new_soc ) +{ + if ( new_soc->soc_oid != NULL ) { + if ( soc->soc_oid == NULL ) { + return SLAP_SCHERR_CLASS_INCONSISTENT; + } + + if ( strcmp( soc->soc_oid, new_soc->soc_oid ) != 0 ) { + return SLAP_SCHERR_CLASS_INCONSISTENT; + } + + } else { + if ( soc->soc_oid != NULL ) { + return SLAP_SCHERR_CLASS_INCONSISTENT; + } + } + + if ( new_soc->soc_names ) { + int i; + + if ( soc->soc_names == NULL ) { + return SLAP_SCHERR_CLASS_INCONSISTENT; + } + + for ( i = 0; new_soc->soc_names[ i ]; i++ ) { + if ( soc->soc_names[ i ] == NULL ) { + return SLAP_SCHERR_CLASS_INCONSISTENT; + } + + if ( strcasecmp( soc->soc_names[ i ], + new_soc->soc_names[ i ] ) != 0 ) + { + return SLAP_SCHERR_CLASS_INCONSISTENT; + } + } + } else { + if ( soc->soc_names != NULL ) { + return SLAP_SCHERR_CLASS_INCONSISTENT; + } + } + + return SLAP_SCHERR_CLASS_DUP; +} + +static struct oindexrec *oir_old; + +static int +oc_dup_error( void *left, void *right ) +{ + oir_old = left; + return -1; +} + +static int +oc_insert( + ObjectClass **roc, + ObjectClass *prev, + const char **err ) +{ + struct oindexrec *oir; + char **names; + ObjectClass *soc = *roc; + + if ( soc->soc_oid ) { + oir = (struct oindexrec *) + ch_calloc( 1, sizeof(struct oindexrec) ); + ber_str2bv( soc->soc_oid, 0, 0, &oir->oir_name ); + oir->oir_oc = soc; + oir_old = NULL; + + if ( avl_insert( &oc_index, (caddr_t) oir, + oc_index_cmp, oc_dup_error ) ) + { + ObjectClass *old_soc; + int rc; + + *err = soc->soc_oid; + + assert( oir_old != NULL ); + old_soc = oir_old->oir_oc; + + /* replacing a deleted definition? */ + if ( old_soc->soc_flags & SLAP_OC_DELETED ) { + ObjectClass tmp; + + /* Keep old oid, free new oid; + * Keep new everything else, free old + */ + tmp = *old_soc; + *old_soc = *soc; + old_soc->soc_oid = tmp.soc_oid; + tmp.soc_oid = soc->soc_oid; + *soc = tmp; + + oc_clean( soc ); + oc_destroy_one( oir ); + + oir = oir_old; + soc = old_soc; + *roc = soc; + } else { + rc = oc_check_dup( old_soc, soc ); + + ldap_memfree( oir ); + return rc; + } + } + + /* FIX: temporal consistency check */ + assert( oc_bvfind( &oir->oir_name ) != NULL ); + } + + assert( soc != NULL ); + + if ( (names = soc->soc_names) ) { + while ( *names ) { + oir = (struct oindexrec *) + ch_calloc( 1, sizeof(struct oindexrec) ); + oir->oir_name.bv_val = *names; + oir->oir_name.bv_len = strlen( *names ); + oir->oir_oc = soc; + + if ( avl_insert( &oc_index, (caddr_t) oir, + oc_index_cmp, avl_dup_error ) ) + { + ObjectClass *old_soc; + int rc; + + *err = *names; + + old_soc = oc_bvfind( &oir->oir_name ); + assert( old_soc != NULL ); + rc = oc_check_dup( old_soc, soc ); + + ldap_memfree( oir ); + + while ( names > soc->soc_names ) { + struct oindexrec tmpoir; + + names--; + ber_str2bv( *names, 0, 0, &tmpoir.oir_name ); + tmpoir.oir_oc = soc; + oir = (struct oindexrec *)avl_delete( &oc_index, + (caddr_t)&tmpoir, oc_index_cmp ); + assert( oir != NULL ); + ldap_memfree( oir ); + } + + if ( soc->soc_oid ) { + struct oindexrec tmpoir; + + ber_str2bv( soc->soc_oid, 0, 0, &tmpoir.oir_name ); + tmpoir.oir_oc = soc; + oir = (struct oindexrec *)avl_delete( &oc_index, + (caddr_t)&tmpoir, oc_index_cmp ); + assert( oir != NULL ); + ldap_memfree( oir ); + } + + return rc; + } + + /* FIX: temporal consistency check */ + assert( oc_bvfind(&oir->oir_name) != NULL ); + + names++; + } + } + if ( soc->soc_flags & SLAP_OC_HARDCODE ) { + prev = oc_sys_tail; + oc_sys_tail = soc; + } + if ( prev ) { + LDAP_STAILQ_INSERT_AFTER( &oc_list, prev, soc, soc_next ); + } else { + LDAP_STAILQ_INSERT_TAIL( &oc_list, soc, soc_next ); + } + + return 0; +} + +int +oc_add( + LDAPObjectClass *oc, + int user, + ObjectClass **rsoc, + ObjectClass *prev, + const char **err ) +{ + ObjectClass *soc; + int code; + int op = 0; + char *oidm = NULL; + + if ( oc->oc_names != NULL ) { + int i; + + for( i=0; oc->oc_names[i]; i++ ) { + if( !slap_valid_descr( oc->oc_names[i] ) ) { + return SLAP_SCHERR_BAD_DESCR; + } + } + } + + if ( !OID_LEADCHAR( oc->oc_oid[0] )) { + /* Expand OID macros */ + char *oid = oidm_find( oc->oc_oid ); + if ( !oid ) { + *err = oc->oc_oid; + return SLAP_SCHERR_OIDM; + } + if ( oid != oc->oc_oid ) { + oidm = oc->oc_oid; + oc->oc_oid = oid; + } + } + + soc = (ObjectClass *) ch_calloc( 1, sizeof(ObjectClass) ); + AC_MEMCPY( &soc->soc_oclass, oc, sizeof(LDAPObjectClass) ); + + soc->soc_oidmacro = oidm; + if( oc->oc_names != NULL ) { + soc->soc_cname.bv_val = soc->soc_names[0]; + } else { + soc->soc_cname.bv_val = soc->soc_oid; + } + soc->soc_cname.bv_len = strlen( soc->soc_cname.bv_val ); + + if( soc->soc_sup_oids == NULL && + soc->soc_kind == LDAP_SCHEMA_STRUCTURAL ) + { + /* structural object classes implicitly inherit from 'top' */ + static char *top_oids[] = { SLAPD_TOP_OID, NULL }; + code = oc_add_sups( soc, top_oids, &op, err ); + } else { + code = oc_add_sups( soc, soc->soc_sup_oids, &op, err ); + } + + if ( code != 0 ) { + goto done; + } + + if ( user && op ) { + code = SLAP_SCHERR_CLASS_BAD_SUP; + goto done; + } + + code = oc_create_required( soc, soc->soc_at_oids_must, &op, err ); + if ( code != 0 ) { + goto done; + } + + code = oc_create_allowed( soc, soc->soc_at_oids_may, &op, err ); + if ( code != 0 ) { + goto done; + } + + if ( user && op ) { + code = SLAP_SCHERR_CLASS_BAD_USAGE; + goto done; + } + + if ( !user ) { + soc->soc_flags |= SLAP_OC_HARDCODE; + } + + code = oc_insert(&soc,prev,err); +done:; + if ( code != 0 ) { + if ( soc->soc_sups ) { + ch_free( soc->soc_sups ); + } + + if ( soc->soc_required ) { + ch_free( soc->soc_required ); + } + + if ( soc->soc_allowed ) { + ch_free( soc->soc_allowed ); + } + + if ( soc->soc_oidmacro ) { + ch_free( soc->soc_oidmacro ); + } + + ch_free( soc ); + + } else if ( rsoc ) { + *rsoc = soc; + } + return code; +} + +void +oc_unparse( BerVarray *res, ObjectClass *start, ObjectClass *end, int sys ) +{ + ObjectClass *oc; + int i, num; + struct berval bv, *bva = NULL, idx; + char ibuf[32]; + + if ( !start ) + start = LDAP_STAILQ_FIRST( &oc_list ); + + /* count the result size */ + i = 0; + for ( oc=start; oc; oc=LDAP_STAILQ_NEXT(oc, soc_next)) { + if ( sys && !(oc->soc_flags & SLAP_OC_HARDCODE)) break; + i++; + if ( oc == end ) break; + } + if (!i) return; + + num = i; + bva = ch_malloc( (num+1) * sizeof(struct berval) ); + BER_BVZERO( bva ); + idx.bv_val = ibuf; + if ( sys ) { + idx.bv_len = 0; + ibuf[0] = '\0'; + } + i = 0; + for ( oc=start; oc; oc=LDAP_STAILQ_NEXT(oc, soc_next)) { + LDAPObjectClass loc, *locp; + if ( sys && !(oc->soc_flags & SLAP_OC_HARDCODE)) break; + if ( oc->soc_oidmacro ) { + loc = oc->soc_oclass; + loc.oc_oid = oc->soc_oidmacro; + locp = &loc; + } else { + locp = &oc->soc_oclass; + } + if ( ldap_objectclass2bv( locp, &bv ) == NULL ) { + ber_bvarray_free( bva ); + } + if ( !sys ) { + idx.bv_len = sprintf(idx.bv_val, "{%d}", i); + } + bva[i].bv_len = idx.bv_len + bv.bv_len; + bva[i].bv_val = ch_malloc( bva[i].bv_len + 1 ); + strcpy( bva[i].bv_val, ibuf ); + strcpy( bva[i].bv_val + idx.bv_len, bv.bv_val ); + i++; + bva[i].bv_val = NULL; + ldap_memfree( bv.bv_val ); + if ( oc == end ) break; + } + *res = bva; +} + +int +oc_schema_info( Entry *e ) +{ + AttributeDescription *ad_objectClasses = slap_schema.si_ad_objectClasses; + ObjectClass *oc; + struct berval val; + struct berval nval; + + LDAP_STAILQ_FOREACH( oc, &oc_list, soc_next ) { + if( oc->soc_flags & SLAP_OC_HIDE ) continue; + + if ( ldap_objectclass2bv( &oc->soc_oclass, &val ) == NULL ) { + return -1; + } + + nval = oc->soc_cname; + +#if 0 + Debug( LDAP_DEBUG_TRACE, "Merging oc [%ld] %s (%s)\n", + (long) val.bv_len, val.bv_val, nval.bv_val ); +#endif + + if( attr_merge_one( e, ad_objectClasses, &val, &nval ) ) { + return -1; + } + ldap_memfree( val.bv_val ); + } + return 0; +} + +int +register_oc( const char *def, ObjectClass **soc, int dupok ) +{ + LDAPObjectClass *oc; + int code; + const char *err; + + oc = ldap_str2objectclass( def, &code, &err, LDAP_SCHEMA_ALLOW_ALL ); + if ( !oc ) { + Debug( LDAP_DEBUG_ANY, + "register_oc: objectclass \"%s\": %s, %s\n", + def, ldap_scherr2str(code), err ); + return code; + } + code = oc_add(oc,0,NULL,NULL,&err); + if ( code && ( code != SLAP_SCHERR_CLASS_DUP || !dupok )) { + Debug( LDAP_DEBUG_ANY, + "register_oc: objectclass \"%s\": %s, %s\n", + def, scherr2str(code), err ); + ldap_objectclass_free(oc); + return code; + } + if ( soc ) + *soc = oc_find(oc->oc_names[0]); + if ( code ) { + ldap_objectclass_free(oc); + } else { + ldap_memfree(oc); + } + return 0; +} diff --git a/servers/slapd/oidm.c b/servers/slapd/oidm.c new file mode 100644 index 0000000..6261c33 --- /dev/null +++ b/servers/slapd/oidm.c @@ -0,0 +1,217 @@ +/* oidm.c - object identifier macro routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "lutil.h" +#include "config.h" + +static LDAP_STAILQ_HEAD(OidMacroList, OidMacro) om_list + = LDAP_STAILQ_HEAD_INITIALIZER(om_list); + +OidMacro *om_sys_tail; + +/* Replace an OID Macro invocation with its full numeric OID. + * If the macro is used with "macroname:suffix" append ".suffix" + * to the expansion. + */ +char * +oidm_find(char *oid) +{ + OidMacro *om; + + /* OID macros must start alpha */ + if ( OID_LEADCHAR( *oid ) ) { + return oid; + } + + LDAP_STAILQ_FOREACH( om, &om_list, som_next ) { + BerVarray names = om->som_names; + + if( names == NULL ) { + continue; + } + + for( ; !BER_BVISNULL( names ) ; names++ ) { + int pos = dscompare(names->bv_val, oid, ':'); + + if( pos ) { + int suflen = strlen(oid + pos); + char *tmp = SLAP_MALLOC( om->som_oid.bv_len + + suflen + 1); + if( tmp == NULL ) { + Debug( LDAP_DEBUG_ANY, + "oidm_find: SLAP_MALLOC failed", 0, 0, 0 ); + return NULL; + } + strcpy(tmp, om->som_oid.bv_val); + if( suflen ) { + suflen = om->som_oid.bv_len; + tmp[suflen++] = '.'; + strcpy(tmp+suflen, oid+pos+1); + } + return tmp; + } + } + } + return NULL; +} + +void +oidm_destroy() +{ + OidMacro *om; + while( !LDAP_STAILQ_EMPTY( &om_list )) { + om = LDAP_STAILQ_FIRST( &om_list ); + LDAP_STAILQ_REMOVE_HEAD( &om_list, som_next ); + + ber_bvarray_free(om->som_names); + ber_bvarray_free(om->som_subs); + free(om->som_oid.bv_val); + free(om); + + } +} + +int +parse_oidm( + struct config_args_s *c, + int user, + OidMacro **rom) +{ + char *oid, *oidv; + OidMacro *om = NULL, *prev = NULL; + struct berval bv; + + oidv = oidm_find( c->argv[2] ); + if( !oidv ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s: OID %s not recognized", + c->argv[0], c->argv[2] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s %s\n", c->log, c->cr_msg, 0 ); + return 1; + } + + oid = oidm_find( c->argv[1] ); + if( oid != NULL ) { + int rc; + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s: \"%s\" previously defined \"%s\"", + c->argv[0], c->argv[1], oid ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s %s\n", c->log, c->cr_msg, 0 ); + /* Allow duplicate if the definition is identical */ + rc = strcmp( oid, oidv ) != 0; + SLAP_FREE( oid ); + if ( oidv != c->argv[2] ) + SLAP_FREE( oidv ); + return rc; + } + + om = (OidMacro *) SLAP_CALLOC( sizeof(OidMacro), 1 ); + if( om == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s: SLAP_CALLOC failed", c->argv[0] ); + Debug( LDAP_DEBUG_ANY, + "%s %s\n", c->log, c->cr_msg, 0 ); + if ( oidv != c->argv[2] ) + SLAP_FREE( oidv ); + return 1; + } + + om->som_names = NULL; + om->som_subs = NULL; + ber_str2bv( c->argv[1], 0, 1, &bv ); + ber_bvarray_add( &om->som_names, &bv ); + ber_str2bv( c->argv[2], 0, 1, &bv ); + ber_bvarray_add( &om->som_subs, &bv ); + om->som_oid.bv_val = oidv; + + if (om->som_oid.bv_val == c->argv[2]) { + om->som_oid.bv_val = ch_strdup( c->argv[2] ); + } + + om->som_oid.bv_len = strlen( om->som_oid.bv_val ); + if ( !user ) { + om->som_flags |= SLAP_OM_HARDCODE; + prev = om_sys_tail; + om_sys_tail = om; + } + + if ( prev ) { + LDAP_STAILQ_INSERT_AFTER( &om_list, prev, om, som_next ); + } else { + LDAP_STAILQ_INSERT_TAIL( &om_list, om, som_next ); + } + if ( rom ) *rom = om; + return 0; +} + +void oidm_unparse( BerVarray *res, OidMacro *start, OidMacro *end, int sys ) +{ + OidMacro *om; + int i, j, num; + struct berval *bva = NULL, idx; + char ibuf[32], *ptr; + + if ( !start ) + start = LDAP_STAILQ_FIRST( &om_list ); + + /* count the result size */ + i = 0; + for ( om=start; om; om=LDAP_STAILQ_NEXT(om, som_next)) { + if ( sys && !(om->som_flags & SLAP_OM_HARDCODE)) break; + for ( j=0; !BER_BVISNULL(&om->som_names[j]); j++ ); + i += j; + if ( om == end ) break; + } + num = i; + if (!i) return; + + bva = ch_malloc( (num+1) * sizeof(struct berval) ); + BER_BVZERO( bva+num ); + idx.bv_val = ibuf; + if ( sys ) { + idx.bv_len = 0; + ibuf[0] = '\0'; + } + for ( i=0,om=start; om; om=LDAP_STAILQ_NEXT(om, som_next)) { + if ( sys && !(om->som_flags & SLAP_OM_HARDCODE)) break; + for ( j=0; !BER_BVISNULL(&om->som_names[j]); i++,j++ ) { + if ( !sys ) { + idx.bv_len = sprintf(idx.bv_val, "{%d}", i ); + } + bva[i].bv_len = idx.bv_len + om->som_names[j].bv_len + + om->som_subs[j].bv_len + 1; + bva[i].bv_val = ch_malloc( bva[i].bv_len + 1 ); + ptr = lutil_strcopy( bva[i].bv_val, ibuf ); + ptr = lutil_strcopy( ptr, om->som_names[j].bv_val ); + *ptr++ = ' '; + strcpy( ptr, om->som_subs[j].bv_val ); + } + if ( i>=num ) break; + if ( om == end ) break; + } + *res = bva; +} diff --git a/servers/slapd/operation.c b/servers/slapd/operation.c new file mode 100644 index 0000000..f3333cf --- /dev/null +++ b/servers/slapd/operation.c @@ -0,0 +1,245 @@ +/* operation.c - routines to deal with pending ldap operations */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" + +#ifdef LDAP_SLAPI +#include "slapi/slapi.h" +#endif + +static ldap_pvt_thread_mutex_t slap_op_mutex; +static time_t last_time; +static int last_incr; + +void slap_op_init(void) +{ + ldap_pvt_thread_mutex_init( &slap_op_mutex ); +} + +void slap_op_destroy(void) +{ + ldap_pvt_thread_mutex_destroy( &slap_op_mutex ); +} + +static void +slap_op_q_destroy( void *key, void *data ) +{ + Operation *op, *op2; + for ( op = data; op; op = op2 ) { + op2 = LDAP_STAILQ_NEXT( op, o_next ); + ber_memfree_x( op, NULL ); + } +} + +void +slap_op_groups_free( Operation *op ) +{ + GroupAssertion *g, *n; + for ( g = op->o_groups; g; g = n ) { + n = g->ga_next; + slap_sl_free( g, op->o_tmpmemctx ); + } + op->o_groups = NULL; +} + +void +slap_op_free( Operation *op, void *ctx ) +{ + OperationBuffer *opbuf; + + assert( LDAP_STAILQ_NEXT(op, o_next) == NULL ); + + /* paranoia */ + op->o_abandon = 1; + + if ( op->o_ber != NULL ) { + ber_free( op->o_ber, 1 ); + } + if ( !BER_BVISNULL( &op->o_dn ) ) { + ch_free( op->o_dn.bv_val ); + } + if ( !BER_BVISNULL( &op->o_ndn ) ) { + ch_free( op->o_ndn.bv_val ); + } + if ( !BER_BVISNULL( &op->o_authmech ) ) { + ch_free( op->o_authmech.bv_val ); + } + if ( op->o_ctrls != NULL ) { + slap_free_ctrls( op, op->o_ctrls ); + } + +#ifdef LDAP_CONNECTIONLESS + if ( op->o_res_ber != NULL ) { + ber_free( op->o_res_ber, 1 ); + } +#endif + + if ( op->o_groups ) { + slap_op_groups_free( op ); + } + +#if defined( LDAP_SLAPI ) + if ( slapi_plugins_used ) { + slapi_int_free_object_extensions( SLAPI_X_EXT_OPERATION, op ); + } +#endif /* defined( LDAP_SLAPI ) */ + + if ( !BER_BVISNULL( &op->o_csn ) ) { + op->o_tmpfree( op->o_csn.bv_val, op->o_tmpmemctx ); + } + + if ( op->o_pagedresults_state != NULL ) { + op->o_tmpfree( op->o_pagedresults_state, op->o_tmpmemctx ); + } + + /* Selectively zero out the struct. Ignore fields that will + * get explicitly initialized later anyway. Keep o_abandon intact. + */ + opbuf = (OperationBuffer *) op; + op->o_bd = NULL; + BER_BVZERO( &op->o_req_dn ); + BER_BVZERO( &op->o_req_ndn ); + memset( op->o_hdr, 0, sizeof( *op->o_hdr )); + memset( &op->o_request, 0, sizeof( op->o_request )); + memset( &op->o_do_not_cache, 0, sizeof( Operation ) - offsetof( Operation, o_do_not_cache )); + memset( opbuf->ob_controls, 0, sizeof( opbuf->ob_controls )); + op->o_controls = opbuf->ob_controls; + + if ( ctx ) { + Operation *op2 = NULL; + ldap_pvt_thread_pool_setkey( ctx, (void *)slap_op_free, + op, slap_op_q_destroy, (void **)&op2, NULL ); + LDAP_STAILQ_NEXT( op, o_next ) = op2; + if ( op2 ) { + op->o_tincr = op2->o_tincr + 1; + /* No more than 10 ops on per-thread free list */ + if ( op->o_tincr > 10 ) { + ldap_pvt_thread_pool_setkey( ctx, (void *)slap_op_free, + op2, slap_op_q_destroy, NULL, NULL ); + ber_memfree_x( op, NULL ); + } + } else { + op->o_tincr = 1; + } + } else { + ber_memfree_x( op, NULL ); + } +} + +void +slap_op_time(time_t *t, int *nop) +{ + ldap_pvt_thread_mutex_lock( &slap_op_mutex ); + *t = slap_get_time(); + if ( *t == last_time ) { + *nop = ++last_incr; + } else { + last_time = *t; + last_incr = 0; + *nop = 0; + } + ldap_pvt_thread_mutex_unlock( &slap_op_mutex ); +} + +Operation * +slap_op_alloc( + BerElement *ber, + ber_int_t msgid, + ber_tag_t tag, + ber_int_t id, + void *ctx ) +{ + Operation *op = NULL; + + if ( ctx ) { + void *otmp = NULL; + ldap_pvt_thread_pool_getkey( ctx, (void *)slap_op_free, &otmp, NULL ); + if ( otmp ) { + op = otmp; + otmp = LDAP_STAILQ_NEXT( op, o_next ); + ldap_pvt_thread_pool_setkey( ctx, (void *)slap_op_free, + otmp, slap_op_q_destroy, NULL, NULL ); + op->o_abandon = 0; + op->o_cancel = 0; + } + } + if (!op) { + op = (Operation *) ch_calloc( 1, sizeof(OperationBuffer) ); + op->o_hdr = &((OperationBuffer *) op)->ob_hdr; + op->o_controls = ((OperationBuffer *) op)->ob_controls; + } + + op->o_ber = ber; + op->o_msgid = msgid; + op->o_tag = tag; + + slap_op_time( &op->o_time, &op->o_tincr ); + op->o_opid = id; + +#if defined( LDAP_SLAPI ) + if ( slapi_plugins_used ) { + slapi_int_create_object_extensions( SLAPI_X_EXT_OPERATION, op ); + } +#endif /* defined( LDAP_SLAPI ) */ + + return( op ); +} + +slap_op_t +slap_req2op( ber_tag_t tag ) +{ + switch ( tag ) { + case LDAP_REQ_BIND: + return SLAP_OP_BIND; + case LDAP_REQ_UNBIND: + return SLAP_OP_UNBIND; + case LDAP_REQ_ADD: + return SLAP_OP_ADD; + case LDAP_REQ_DELETE: + return SLAP_OP_DELETE; + case LDAP_REQ_MODRDN: + return SLAP_OP_MODRDN; + case LDAP_REQ_MODIFY: + return SLAP_OP_MODIFY; + case LDAP_REQ_COMPARE: + return SLAP_OP_COMPARE; + case LDAP_REQ_SEARCH: + return SLAP_OP_SEARCH; + case LDAP_REQ_ABANDON: + return SLAP_OP_ABANDON; + case LDAP_REQ_EXTENDED: + return SLAP_OP_EXTENDED; + } + + return SLAP_OP_LAST; +} diff --git a/servers/slapd/operational.c b/servers/slapd/operational.c new file mode 100644 index 0000000..567359f --- /dev/null +++ b/servers/slapd/operational.c @@ -0,0 +1,90 @@ +/* operational.c - routines to deal with on-the-fly operational attrs */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include "slap.h" + +/* + * helpers for on-the-fly operational attribute generation + */ + +Attribute * +slap_operational_subschemaSubentry( Backend *be ) +{ + Attribute *a; + + /* The backend wants to take care of it */ + if ( be && !SLAP_FRONTEND(be) && be->be_schemadn.bv_val ) return NULL; + + a = attr_alloc( slap_schema.si_ad_subschemaSubentry ); + + a->a_numvals = 1; + a->a_vals = ch_malloc( 2 * sizeof( struct berval ) ); + ber_dupbv( a->a_vals, &frontendDB->be_schemadn ); + a->a_vals[1].bv_len = 0; + a->a_vals[1].bv_val = NULL; + + a->a_nvals = ch_malloc( 2 * sizeof( struct berval ) ); + ber_dupbv( a->a_nvals, &frontendDB->be_schemandn ); + a->a_nvals[1].bv_len = 0; + a->a_nvals[1].bv_val = NULL; + + return a; +} + +Attribute * +slap_operational_entryDN( Entry *e ) +{ + Attribute *a; + + assert( e != NULL ); + assert( !BER_BVISNULL( &e->e_name ) ); + assert( !BER_BVISNULL( &e->e_nname ) ); + + a = attr_alloc( slap_schema.si_ad_entryDN ); + + a->a_numvals = 1; + a->a_vals = ch_malloc( 2 * sizeof( struct berval ) ); + ber_dupbv( &a->a_vals[ 0 ], &e->e_name ); + BER_BVZERO( &a->a_vals[ 1 ] ); + + a->a_nvals = ch_malloc( 2 * sizeof( struct berval ) ); + ber_dupbv( &a->a_nvals[ 0 ], &e->e_nname ); + BER_BVZERO( &a->a_nvals[ 1 ] ); + + return a; +} + +Attribute * +slap_operational_hasSubordinate( int hs ) +{ + Attribute *a; + struct berval val; + + val = hs ? slap_true_bv : slap_false_bv; + + a = attr_alloc( slap_schema.si_ad_hasSubordinates ); + a->a_numvals = 1; + a->a_vals = ch_malloc( 2 * sizeof( struct berval ) ); + + ber_dupbv( &a->a_vals[0], &val ); + a->a_vals[1].bv_val = NULL; + + a->a_nvals = a->a_vals; + + return a; +} + diff --git a/servers/slapd/overlays/Makefile.in b/servers/slapd/overlays/Makefile.in new file mode 100644 index 0000000..f85d936 --- /dev/null +++ b/servers/slapd/overlays/Makefile.in @@ -0,0 +1,156 @@ +# Makefile.in for overlays +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 2003-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +SRCS = overlays.c \ + accesslog.c \ + auditlog.c \ + constraint.c \ + dds.c \ + deref.c \ + dyngroup.c \ + dynlist.c \ + memberof.c \ + pcache.c \ + collect.c \ + ppolicy.c \ + refint.c \ + retcode.c \ + rwm.c rwmconf.c rwmdn.c rwmmap.c \ + seqmod.c \ + sssvlv.c \ + syncprov.c \ + translucent.c \ + unique.c \ + valsort.c +OBJS = statover.o \ + @SLAPD_STATIC_OVERLAYS@ \ + overlays.o + +# Add here the objs that are needed by overlays, but do not make it +# into SLAPD_STATIC_OVERLAYS... +OBJDEP=rwm.o rwmconf.o rwmdn.o rwmmap.o + +LTONLY_MOD = $(LTONLY_mod) +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +MOD_DEFS = -DSLAPD_IMPORT + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_R_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBRARY = ../liboverlays.a +PROGRAMS = @SLAPD_DYNAMIC_OVERLAYS@ + +XINCPATH = -I.. -I$(srcdir)/.. +XDEFS = $(MODULES_CPPFLAGS) + +static: $(LIBRARY) + +dynamic: $(PROGRAMS) + +accesslog.la : accesslog.lo + $(LTLINK_MOD) -module -o $@ accesslog.lo version.lo $(LINK_LIBS) + +auditlog.la : auditlog.lo + $(LTLINK_MOD) -module -o $@ auditlog.lo version.lo $(LINK_LIBS) + +constraint.la : constraint.lo + $(LTLINK_MOD) -module -o $@ constraint.lo version.lo $(LINK_LIBS) + +dds.la : dds.lo + $(LTLINK_MOD) -module -o $@ dds.lo version.lo $(LINK_LIBS) + +deref.la : deref.lo + $(LTLINK_MOD) -module -o $@ deref.lo version.lo $(LINK_LIBS) + +dyngroup.la : dyngroup.lo + $(LTLINK_MOD) -module -o $@ dyngroup.lo version.lo $(LINK_LIBS) + +dynlist.la : dynlist.lo + $(LTLINK_MOD) -module -o $@ dynlist.lo version.lo $(LINK_LIBS) + +memberof.la : memberof.lo + $(LTLINK_MOD) -module -o $@ memberof.lo version.lo $(LINK_LIBS) + +pcache.la : pcache.lo + $(LTLINK_MOD) -module -o $@ pcache.lo version.lo $(LINK_LIBS) + +collect.la : collect.lo + $(LTLINK_MOD) -module -o $@ collect.lo version.lo $(LINK_LIBS) + +ppolicy.la : ppolicy.lo + $(LTLINK_MOD) -module -o $@ ppolicy.lo version.lo $(LINK_LIBS) $(MODULES_LIBS) + +refint.la : refint.lo + $(LTLINK_MOD) -module -o $@ refint.lo version.lo $(LINK_LIBS) + +retcode.la : retcode.lo + $(LTLINK_MOD) -module -o $@ retcode.lo version.lo $(LINK_LIBS) + +rwm_x.o: rwm.o rwmconf.o rwmdn.o rwmmap.o + $(LD) -r -o $@ rwm.o rwmconf.o rwmdn.o rwmmap.o + +rwm.la : rwm.lo rwmconf.lo rwmdn.lo rwmmap.lo + $(LTLINK_MOD) -module -o $@ rwm.lo rwmconf.lo rwmdn.lo rwmmap.lo version.lo $(LINK_LIBS) + +seqmod.la : seqmod.lo + $(LTLINK_MOD) -module -o $@ seqmod.lo version.lo $(LINK_LIBS) + +sssvlv.la : sssvlv.lo + $(LTLINK_MOD) -module -o $@ sssvlv.lo version.lo $(LINK_LIBS) + +syncprov.la : syncprov.lo + $(LTLINK_MOD) -module -o $@ syncprov.lo version.lo $(LINK_LIBS) + +translucent.la : translucent.lo + $(LTLINK_MOD) -module -o $@ translucent.lo version.lo $(LINK_LIBS) + +unique.la : unique.lo + $(LTLINK_MOD) -module -o $@ unique.lo version.lo $(LINK_LIBS) + +valsort.la : valsort.lo + $(LTLINK_MOD) -module -o $@ valsort.lo version.lo $(LINK_LIBS) + +install-local: $(PROGRAMS) + @if test -n "$?" ; then \ + $(MKDIR) $(DESTDIR)$(moduledir); \ + $(LTINSTALL) $(INSTALLFLAGS) -m 755 $? $(DESTDIR)$(moduledir);\ + fi + +MKDEPFLAG = -l + +.SUFFIXES: .c .o .lo + +.c.lo: + $(LTCOMPILE_MOD) $< + +statover.o: statover.c $(srcdir)/../slap.h + +$(LIBRARY): $(OBJS) version.lo + $(AR) rs $@ $(OBJS) + +# Must fixup depends for non-libtool objects +depend-local: depend-common + @if test -n "$(OBJS)"; then \ + OBJ2=`echo $(OBJS) $(OBJDEP) | $(SED) -e 's/\.o//g'`; \ + SCR=''; for i in $$OBJ2; do SCR="$$SCR -e s/^$$i.lo:/$$i.o:/"; done; \ + mv Makefile Makefile.bak; $(SED) $$SCR Makefile.bak > Makefile && \ + $(RM) Makefile.bak; fi + +veryclean-local: + $(RM) statover.c + diff --git a/servers/slapd/overlays/README b/servers/slapd/overlays/README new file mode 100644 index 0000000..e426e4b --- /dev/null +++ b/servers/slapd/overlays/README @@ -0,0 +1,5 @@ +This directory contains a number of SLAPD overlays, some +project-maintained, some not. Some are generally usable, +others are purely experimental. Additional overlays can +be found in the contrib/slapd-modules directory. + diff --git a/servers/slapd/overlays/accesslog.c b/servers/slapd/overlays/accesslog.c new file mode 100644 index 0000000..88367cd --- /dev/null +++ b/servers/slapd/overlays/accesslog.c @@ -0,0 +1,2572 @@ +/* accesslog.c - log operations for audit/history purposes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2021 The OpenLDAP Foundation. + * Portions copyright 2004-2005 Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_ACCESSLOG + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/ctype.h> + +#include "slap.h" +#include "config.h" +#include "lutil.h" +#include "ldap_rq.h" + +#define LOG_OP_ADD 0x001 +#define LOG_OP_DELETE 0x002 +#define LOG_OP_MODIFY 0x004 +#define LOG_OP_MODRDN 0x008 +#define LOG_OP_COMPARE 0x010 +#define LOG_OP_SEARCH 0x020 +#define LOG_OP_BIND 0x040 +#define LOG_OP_UNBIND 0x080 +#define LOG_OP_ABANDON 0x100 +#define LOG_OP_EXTENDED 0x200 +#define LOG_OP_UNKNOWN 0x400 + +#define LOG_OP_WRITES (LOG_OP_ADD|LOG_OP_DELETE|LOG_OP_MODIFY|LOG_OP_MODRDN) +#define LOG_OP_READS (LOG_OP_COMPARE|LOG_OP_SEARCH) +#define LOG_OP_SESSION (LOG_OP_BIND|LOG_OP_UNBIND|LOG_OP_ABANDON) +#define LOG_OP_ALL (LOG_OP_READS|LOG_OP_WRITES|LOG_OP_SESSION| \ + LOG_OP_EXTENDED|LOG_OP_UNKNOWN) + +typedef struct log_attr { + struct log_attr *next; + AttributeDescription *attr; +} log_attr; + +typedef struct log_base { + struct log_base *lb_next; + slap_mask_t lb_ops; + struct berval lb_base; + struct berval lb_line; +} log_base; + +typedef struct log_info { + BackendDB *li_db; + struct berval li_db_suffix; + slap_mask_t li_ops; + int li_age; + int li_cycle; + struct re_s *li_task; + Filter *li_oldf; + Entry *li_old; + log_attr *li_oldattrs; + struct berval li_uuid; + int li_success; + log_base *li_bases; + ldap_pvt_thread_rmutex_t li_op_rmutex; + ldap_pvt_thread_mutex_t li_log_mutex; +} log_info; + +static ConfigDriver log_cf_gen; + +enum { + LOG_DB = 1, + LOG_OPS, + LOG_PURGE, + LOG_SUCCESS, + LOG_OLD, + LOG_OLDATTR, + LOG_BASE +}; + +static ConfigTable log_cfats[] = { + { "logdb", "suffix", 2, 2, 0, ARG_DN|ARG_MAGIC|LOG_DB, + log_cf_gen, "( OLcfgOvAt:4.1 NAME 'olcAccessLogDB' " + "DESC 'Suffix of database for log content' " + "SUP distinguishedName SINGLE-VALUE )", NULL, NULL }, + { "logops", "op|writes|reads|session|all", 2, 0, 0, + ARG_MAGIC|LOG_OPS, + log_cf_gen, "( OLcfgOvAt:4.2 NAME 'olcAccessLogOps' " + "DESC 'Operation types to log' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "logpurge", "age> <interval", 3, 3, 0, ARG_MAGIC|LOG_PURGE, + log_cf_gen, "( OLcfgOvAt:4.3 NAME 'olcAccessLogPurge' " + "DESC 'Log cleanup parameters' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "logsuccess", NULL, 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|LOG_SUCCESS, + log_cf_gen, "( OLcfgOvAt:4.4 NAME 'olcAccessLogSuccess' " + "DESC 'Log successful ops only' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "logold", "filter", 2, 2, 0, ARG_MAGIC|LOG_OLD, + log_cf_gen, "( OLcfgOvAt:4.5 NAME 'olcAccessLogOld' " + "DESC 'Log old values when modifying entries matching the filter' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "logoldattr", "attrs", 2, 0, 0, ARG_MAGIC|LOG_OLDATTR, + log_cf_gen, "( OLcfgOvAt:4.6 NAME 'olcAccessLogOldAttr' " + "DESC 'Log old values of these attributes even if unmodified' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "logbase", "op|writes|reads|session|all< <baseDN", 3, 3, 0, + ARG_MAGIC|LOG_BASE, + log_cf_gen, "( OLcfgOvAt:4.7 NAME 'olcAccessLogBase' " + "DESC 'Operation types to log under a specific branch' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { NULL } +}; + +static ConfigOCs log_cfocs[] = { + { "( OLcfgOvOc:4.1 " + "NAME 'olcAccessLogConfig' " + "DESC 'Access log configuration' " + "SUP olcOverlayConfig " + "MUST olcAccessLogDB " + "MAY ( olcAccessLogOps $ olcAccessLogPurge $ olcAccessLogSuccess $ " + "olcAccessLogOld $ olcAccessLogOldAttr $ olcAccessLogBase ) )", + Cft_Overlay, log_cfats }, + { NULL } +}; + +static slap_verbmasks logops[] = { + { BER_BVC("all"), LOG_OP_ALL }, + { BER_BVC("writes"), LOG_OP_WRITES }, + { BER_BVC("session"), LOG_OP_SESSION }, + { BER_BVC("reads"), LOG_OP_READS }, + { BER_BVC("add"), LOG_OP_ADD }, + { BER_BVC("delete"), LOG_OP_DELETE }, + { BER_BVC("modify"), LOG_OP_MODIFY }, + { BER_BVC("modrdn"), LOG_OP_MODRDN }, + { BER_BVC("compare"), LOG_OP_COMPARE }, + { BER_BVC("search"), LOG_OP_SEARCH }, + { BER_BVC("bind"), LOG_OP_BIND }, + { BER_BVC("unbind"), LOG_OP_UNBIND }, + { BER_BVC("abandon"), LOG_OP_ABANDON }, + { BER_BVC("extended"), LOG_OP_EXTENDED }, + { BER_BVC("unknown"), LOG_OP_UNKNOWN }, + { BER_BVNULL, 0 } +}; + +/* Start with "add" in logops */ +#define EN_OFFSET 4 + +enum { + LOG_EN_ADD = 0, + LOG_EN_DELETE, + LOG_EN_MODIFY, + LOG_EN_MODRDN, + LOG_EN_COMPARE, + LOG_EN_SEARCH, + LOG_EN_BIND, + LOG_EN_UNBIND, + LOG_EN_ABANDON, + LOG_EN_EXTENDED, + LOG_EN_UNKNOWN, + LOG_EN__COUNT +}; + +static ObjectClass *log_ocs[LOG_EN__COUNT], *log_container, + *log_oc_read, *log_oc_write; + +#define LOG_SCHEMA_ROOT "1.3.6.1.4.1.4203.666.11.5" + +#define LOG_SCHEMA_AT LOG_SCHEMA_ROOT ".1" +#define LOG_SCHEMA_OC LOG_SCHEMA_ROOT ".2" +#define LOG_SCHEMA_SYN LOG_SCHEMA_ROOT ".3" + +static AttributeDescription *ad_reqDN, *ad_reqStart, *ad_reqEnd, *ad_reqType, + *ad_reqSession, *ad_reqResult, *ad_reqAuthzID, *ad_reqControls, + *ad_reqRespControls, *ad_reqMethod, *ad_reqAssertion, *ad_reqNewRDN, + *ad_reqNewSuperior, *ad_reqDeleteOldRDN, *ad_reqMod, + *ad_reqScope, *ad_reqFilter, *ad_reqAttr, *ad_reqEntries, + *ad_reqSizeLimit, *ad_reqTimeLimit, *ad_reqAttrsOnly, *ad_reqData, + *ad_reqId, *ad_reqMessage, *ad_reqVersion, *ad_reqDerefAliases, + *ad_reqReferral, *ad_reqOld, *ad_auditContext, *ad_reqEntryUUID; + +static int +logSchemaControlValidate( + Syntax *syntax, + struct berval *val ); + +char *mrControl[] = { + "objectIdentifierFirstComponentMatch", + NULL +}; + +static struct { + char *oid; + slap_syntax_defs_rec syn; + char **mrs; +} lsyntaxes[] = { + { LOG_SCHEMA_SYN ".1" , + { "( " LOG_SCHEMA_SYN ".1 DESC 'Control' )", + SLAP_SYNTAX_HIDE, + NULL, + logSchemaControlValidate, + NULL }, + mrControl }, + { NULL } +}; + +static struct { + char *at; + AttributeDescription **ad; +} lattrs[] = { + { "( " LOG_SCHEMA_AT ".1 NAME 'reqDN' " + "DESC 'Target DN of request' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN " + "SINGLE-VALUE )", &ad_reqDN }, + { "( " LOG_SCHEMA_AT ".2 NAME 'reqStart' " + "DESC 'Start time of request' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "SINGLE-VALUE )", &ad_reqStart }, + { "( " LOG_SCHEMA_AT ".3 NAME 'reqEnd' " + "DESC 'End time of request' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "SINGLE-VALUE )", &ad_reqEnd }, + { "( " LOG_SCHEMA_AT ".4 NAME 'reqType' " + "DESC 'Type of request' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", &ad_reqType }, + { "( " LOG_SCHEMA_AT ".5 NAME 'reqSession' " + "DESC 'Session ID of request' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", &ad_reqSession }, + { "( " LOG_SCHEMA_AT ".6 NAME 'reqAuthzID' " + "DESC 'Authorization ID of requestor' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN " + "SINGLE-VALUE )", &ad_reqAuthzID }, + { "( " LOG_SCHEMA_AT ".7 NAME 'reqResult' " + "DESC 'Result code of request' " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", &ad_reqResult }, + { "( " LOG_SCHEMA_AT ".8 NAME 'reqMessage' " + "DESC 'Error text of request' " + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", &ad_reqMessage }, + { "( " LOG_SCHEMA_AT ".9 NAME 'reqReferral' " + "DESC 'Referrals returned for request' " + "SUP labeledURI )", &ad_reqReferral }, + { "( " LOG_SCHEMA_AT ".10 NAME 'reqControls' " + "DESC 'Request controls' " + "EQUALITY objectIdentifierFirstComponentMatch " + "SYNTAX " LOG_SCHEMA_SYN ".1 " + "X-ORDERED 'VALUES' )", &ad_reqControls }, + { "( " LOG_SCHEMA_AT ".11 NAME 'reqRespControls' " + "DESC 'Response controls of request' " + "EQUALITY objectIdentifierFirstComponentMatch " + "SYNTAX " LOG_SCHEMA_SYN ".1 " + "X-ORDERED 'VALUES' )", &ad_reqRespControls }, + { "( " LOG_SCHEMA_AT ".12 NAME 'reqId' " + "DESC 'ID of Request to Abandon' " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", &ad_reqId }, + { "( " LOG_SCHEMA_AT ".13 NAME 'reqVersion' " + "DESC 'Protocol version of Bind request' " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", &ad_reqVersion }, + { "( " LOG_SCHEMA_AT ".14 NAME 'reqMethod' " + "DESC 'Bind method of request' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", &ad_reqMethod }, + { "( " LOG_SCHEMA_AT ".15 NAME 'reqAssertion' " + "DESC 'Compare Assertion of request' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", &ad_reqAssertion }, + { "( " LOG_SCHEMA_AT ".16 NAME 'reqMod' " + "DESC 'Modifications of request' " + "EQUALITY octetStringMatch " + "SUBSTR octetStringSubstringsMatch " + "SYNTAX OMsOctetString )", &ad_reqMod }, + { "( " LOG_SCHEMA_AT ".17 NAME 'reqOld' " + "DESC 'Old values of entry before request completed' " + "EQUALITY octetStringMatch " + "SUBSTR octetStringSubstringsMatch " + "SYNTAX OMsOctetString )", &ad_reqOld }, + { "( " LOG_SCHEMA_AT ".18 NAME 'reqNewRDN' " + "DESC 'New RDN of request' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN " + "SINGLE-VALUE )", &ad_reqNewRDN }, + { "( " LOG_SCHEMA_AT ".19 NAME 'reqDeleteOldRDN' " + "DESC 'Delete old RDN' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", &ad_reqDeleteOldRDN }, + { "( " LOG_SCHEMA_AT ".20 NAME 'reqNewSuperior' " + "DESC 'New superior DN of request' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN " + "SINGLE-VALUE )", &ad_reqNewSuperior }, + { "( " LOG_SCHEMA_AT ".21 NAME 'reqScope' " + "DESC 'Scope of request' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", &ad_reqScope }, + { "( " LOG_SCHEMA_AT ".22 NAME 'reqDerefAliases' " + "DESC 'Disposition of Aliases in request' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", &ad_reqDerefAliases }, + { "( " LOG_SCHEMA_AT ".23 NAME 'reqAttrsOnly' " + "DESC 'Attributes and values of request' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", &ad_reqAttrsOnly }, + { "( " LOG_SCHEMA_AT ".24 NAME 'reqFilter' " + "DESC 'Filter of request' " + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", &ad_reqFilter }, + { "( " LOG_SCHEMA_AT ".25 NAME 'reqAttr' " + "DESC 'Attributes of request' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", &ad_reqAttr }, + { "( " LOG_SCHEMA_AT ".26 NAME 'reqSizeLimit' " + "DESC 'Size limit of request' " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", &ad_reqSizeLimit }, + { "( " LOG_SCHEMA_AT ".27 NAME 'reqTimeLimit' " + "DESC 'Time limit of request' " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", &ad_reqTimeLimit }, + { "( " LOG_SCHEMA_AT ".28 NAME 'reqEntries' " + "DESC 'Number of entries returned' " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", &ad_reqEntries }, + { "( " LOG_SCHEMA_AT ".29 NAME 'reqData' " + "DESC 'Data of extended request' " + "EQUALITY octetStringMatch " + "SUBSTR octetStringSubstringsMatch " + "SYNTAX OMsOctetString " + "SINGLE-VALUE )", &ad_reqData }, + + /* + * from <draft-chu-ldap-logschema-01.txt>: + * + + ( LOG_SCHEMA_AT .30 NAME 'auditContext' + DESC 'DN of auditContainer' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation ) + + * - removed EQUALITY matchingRule + * - changed directoryOperation in dSAOperation + */ + { "( " LOG_SCHEMA_AT ".30 NAME 'auditContext' " + "DESC 'DN of auditContainer' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 " + "SINGLE-VALUE " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", &ad_auditContext }, + + /* + * ITS#6656 + */ + { "( " LOG_SCHEMA_AT ".31 NAME 'reqEntryUUID' " + "DESC 'UUID of entry' " + "EQUALITY UUIDMatch " + "ORDERING UUIDOrderingMatch " + "SYNTAX 1.3.6.1.1.16.1 " + "SINGLE-VALUE )", &ad_reqEntryUUID }, + { NULL, NULL } +}; + +static struct { + char *ot; + ObjectClass **oc; +} locs[] = { + { "( " LOG_SCHEMA_OC ".0 NAME 'auditContainer' " + "DESC 'AuditLog container' " + "SUP top STRUCTURAL " + "MAY ( cn $ reqStart $ reqEnd ) )", &log_container }, + { "( " LOG_SCHEMA_OC ".1 NAME 'auditObject' " + "DESC 'OpenLDAP request auditing' " + "SUP top STRUCTURAL " + "MUST ( reqStart $ reqType $ reqSession ) " + "MAY ( reqDN $ reqAuthzID $ reqControls $ reqRespControls $ reqEnd $ " + "reqResult $ reqMessage $ reqReferral $ reqEntryUUID ) )", + &log_ocs[LOG_EN_UNBIND] }, + { "( " LOG_SCHEMA_OC ".2 NAME 'auditReadObject' " + "DESC 'OpenLDAP read request record' " + "SUP auditObject STRUCTURAL )", &log_oc_read }, + { "( " LOG_SCHEMA_OC ".3 NAME 'auditWriteObject' " + "DESC 'OpenLDAP write request record' " + "SUP auditObject STRUCTURAL )", &log_oc_write }, + { "( " LOG_SCHEMA_OC ".4 NAME 'auditAbandon' " + "DESC 'Abandon operation' " + "SUP auditObject STRUCTURAL " + "MUST reqId )", &log_ocs[LOG_EN_ABANDON] }, + { "( " LOG_SCHEMA_OC ".5 NAME 'auditAdd' " + "DESC 'Add operation' " + "SUP auditWriteObject STRUCTURAL " + "MUST reqMod )", &log_ocs[LOG_EN_ADD] }, + { "( " LOG_SCHEMA_OC ".6 NAME 'auditBind' " + "DESC 'Bind operation' " + "SUP auditObject STRUCTURAL " + "MUST ( reqVersion $ reqMethod ) )", &log_ocs[LOG_EN_BIND] }, + { "( " LOG_SCHEMA_OC ".7 NAME 'auditCompare' " + "DESC 'Compare operation' " + "SUP auditReadObject STRUCTURAL " + "MUST reqAssertion )", &log_ocs[LOG_EN_COMPARE] }, + { "( " LOG_SCHEMA_OC ".8 NAME 'auditDelete' " + "DESC 'Delete operation' " + "SUP auditWriteObject STRUCTURAL " + "MAY reqOld )", &log_ocs[LOG_EN_DELETE] }, + { "( " LOG_SCHEMA_OC ".9 NAME 'auditModify' " + "DESC 'Modify operation' " + "SUP auditWriteObject STRUCTURAL " + "MAY reqOld MUST reqMod )", &log_ocs[LOG_EN_MODIFY] }, + { "( " LOG_SCHEMA_OC ".10 NAME 'auditModRDN' " + "DESC 'ModRDN operation' " + "SUP auditWriteObject STRUCTURAL " + "MUST ( reqNewRDN $ reqDeleteOldRDN ) " + "MAY ( reqNewSuperior $ reqMod $ reqOld ) )", &log_ocs[LOG_EN_MODRDN] }, + { "( " LOG_SCHEMA_OC ".11 NAME 'auditSearch' " + "DESC 'Search operation' " + "SUP auditReadObject STRUCTURAL " + "MUST ( reqScope $ reqDerefAliases $ reqAttrsonly ) " + "MAY ( reqFilter $ reqAttr $ reqEntries $ reqSizeLimit $ " + "reqTimeLimit ) )", &log_ocs[LOG_EN_SEARCH] }, + { "( " LOG_SCHEMA_OC ".12 NAME 'auditExtended' " + "DESC 'Extended operation' " + "SUP auditObject STRUCTURAL " + "MAY reqData )", &log_ocs[LOG_EN_EXTENDED] }, + { NULL, NULL } +}; + +#define RDNEQ "reqStart=" + +/* Our time intervals are of the form [ddd+]hh:mm[:ss] + * If a field is present, it must be two digits. (Except for + * days, which can be arbitrary width.) + */ +static int +log_age_parse(char *agestr) +{ + int t1, t2; + int gotdays = 0; + char *endptr; + + t1 = strtol( agestr, &endptr, 10 ); + /* Is there a days delimiter? */ + if ( *endptr == '+' ) { + /* 32 bit time only covers about 68 years */ + if ( t1 < 0 || t1 > 25000 ) + return -1; + t1 *= 24; + gotdays = 1; + agestr = endptr + 1; + } else { + if ( agestr[2] != ':' ) { + /* No valid delimiter found, fail */ + return -1; + } + t1 *= 60; + agestr += 3; + } + + t2 = atoi( agestr ); + t1 += t2; + + if ( agestr[2] ) { + /* if there's a delimiter, it can only be a colon */ + if ( agestr[2] != ':' ) + return -1; + } else { + /* If we're at the end of the string, and we started with days, + * fail because we expected to find minutes too. + */ + return gotdays ? -1 : t1 * 60; + } + + agestr += 3; + t2 = atoi( agestr ); + + /* last field can only be seconds */ + if ( agestr[2] && ( agestr[2] != ':' || !gotdays )) + return -1; + t1 *= 60; + t1 += t2; + + if ( agestr[2] ) { + agestr += 3; + if ( agestr[2] ) + return -1; + t1 *= 60; + t1 += atoi( agestr ); + } else if ( gotdays ) { + /* only got days+hh:mm */ + t1 *= 60; + } + return t1; +} + +static void +log_age_unparse( int age, struct berval *agebv, size_t size ) +{ + int dd, hh, mm, ss, len; + char *ptr; + + assert( size > 0 ); + + ss = age % 60; + age /= 60; + mm = age % 60; + age /= 60; + hh = age % 24; + age /= 24; + dd = age; + + ptr = agebv->bv_val; + + if ( dd ) { + len = snprintf( ptr, size, "%d+", dd ); + assert( len >= 0 && (unsigned) len < size ); + size -= len; + ptr += len; + } + len = snprintf( ptr, size, "%02d:%02d", hh, mm ); + assert( len >= 0 && (unsigned) len < size ); + size -= len; + ptr += len; + if ( ss ) { + len = snprintf( ptr, size, ":%02d", ss ); + assert( len >= 0 && (unsigned) len < size ); + size -= len; + ptr += len; + } + + agebv->bv_len = ptr - agebv->bv_val; +} + +static slap_callback nullsc; + +#define PURGE_INCREMENT 100 + +typedef struct purge_data { + int slots; + int used; + BerVarray dn; + BerVarray ndn; + struct berval csn; /* an arbitrary old CSN */ +} purge_data; + +static int +log_old_lookup( Operation *op, SlapReply *rs ) +{ + purge_data *pd = op->o_callback->sc_private; + Attribute *a; + + if ( rs->sr_type != REP_SEARCH) return 0; + + if ( slapd_shutdown ) return 0; + + /* Remember max CSN: should always be the last entry + * seen, since log entries are ordered chronologically... + */ + a = attr_find( rs->sr_entry->e_attrs, + slap_schema.si_ad_entryCSN ); + if ( a ) { + ber_len_t len = a->a_nvals[0].bv_len; + /* Paranoid len check, normalized CSNs are always the same length */ + if ( len > LDAP_PVT_CSNSTR_BUFSIZE ) + len = LDAP_PVT_CSNSTR_BUFSIZE; + if ( memcmp( a->a_nvals[0].bv_val, pd->csn.bv_val, len ) > 0 ) { + AC_MEMCPY( pd->csn.bv_val, a->a_nvals[0].bv_val, len ); + pd->csn.bv_len = len; + } + } + if ( pd->used >= pd->slots ) { + pd->slots += PURGE_INCREMENT; + pd->dn = ch_realloc( pd->dn, pd->slots * sizeof( struct berval )); + pd->ndn = ch_realloc( pd->ndn, pd->slots * sizeof( struct berval )); + } + ber_dupbv( &pd->dn[pd->used], &rs->sr_entry->e_name ); + ber_dupbv( &pd->ndn[pd->used], &rs->sr_entry->e_nname ); + pd->used++; + return 0; +} + +/* Periodically search for old entries in the log database and delete them */ +static void * +accesslog_purge( void *ctx, void *arg ) +{ + struct re_s *rtask = arg; + struct log_info *li = rtask->arg; + + Connection conn = {0}; + OperationBuffer opbuf; + Operation *op; + SlapReply rs = {REP_RESULT}; + slap_callback cb = { NULL, log_old_lookup, NULL, NULL, NULL }; + Filter f; + AttributeAssertion ava = ATTRIBUTEASSERTION_INIT; + purge_data pd = {0}; + char timebuf[LDAP_LUTIL_GENTIME_BUFSIZE]; + char csnbuf[LDAP_PVT_CSNSTR_BUFSIZE]; + time_t old = slap_get_time(); + + connection_fake_init( &conn, &opbuf, ctx ); + op = &opbuf.ob_op; + + f.f_choice = LDAP_FILTER_LE; + f.f_ava = &ava; + f.f_next = NULL; + + ava.aa_desc = ad_reqStart; + ava.aa_value.bv_val = timebuf; + ava.aa_value.bv_len = sizeof(timebuf); + + old -= li->li_age; + slap_timestamp( &old, &ava.aa_value ); + + op->o_tag = LDAP_REQ_SEARCH; + op->o_bd = li->li_db; + op->o_dn = li->li_db->be_rootdn; + op->o_ndn = li->li_db->be_rootndn; + op->o_req_dn = li->li_db->be_suffix[0]; + op->o_req_ndn = li->li_db->be_nsuffix[0]; + op->o_callback = &cb; + op->ors_scope = LDAP_SCOPE_ONELEVEL; + op->ors_deref = LDAP_DEREF_NEVER; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_slimit = SLAP_NO_LIMIT; + op->ors_filter = &f; + filter2bv_x( op, &f, &op->ors_filterstr ); + op->ors_attrs = slap_anlist_no_attrs; + op->ors_attrsonly = 1; + + pd.csn.bv_len = sizeof( csnbuf ); + pd.csn.bv_val = csnbuf; + csnbuf[0] = '\0'; + cb.sc_private = &pd; + + op->o_bd->be_search( op, &rs ); + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + + if ( pd.used ) { + int i; + + /* delete the expired entries */ + op->o_tag = LDAP_REQ_DELETE; + op->o_callback = &nullsc; + op->o_csn = pd.csn; + op->o_dont_replicate = 1; + op->o_csn = slap_empty_bv; + + for (i=0; i<pd.used; i++) { + op->o_req_dn = pd.dn[i]; + op->o_req_ndn = pd.ndn[i]; + if ( !slapd_shutdown ) { + rs_reinit( &rs, REP_RESULT ); + op->o_bd->be_delete( op, &rs ); + } + ch_free( pd.ndn[i].bv_val ); + ch_free( pd.dn[i].bv_val ); + ldap_pvt_thread_pool_pausecheck( &connection_pool ); + } + ch_free( pd.ndn ); + ch_free( pd.dn ); + + { + Modifications mod; + struct berval bv[2]; + rs_reinit( &rs, REP_RESULT ); + /* update context's entryCSN to reflect oldest CSN */ + mod.sml_numvals = 1; + mod.sml_values = bv; + bv[0] = pd.csn; + BER_BVZERO(&bv[1]); + mod.sml_nvalues = NULL; + mod.sml_desc = slap_schema.si_ad_entryCSN; + mod.sml_op = LDAP_MOD_REPLACE; + mod.sml_flags = SLAP_MOD_INTERNAL; + mod.sml_next = NULL; + + op->o_tag = LDAP_REQ_MODIFY; + op->orm_modlist = &mod; + op->orm_no_opattrs = 1; + op->o_req_dn = li->li_db->be_suffix[0]; + op->o_req_ndn = li->li_db->be_nsuffix[0]; + op->o_no_schema_check = 1; + op->o_managedsait = SLAP_CONTROL_NONCRITICAL; + op->o_bd->be_modify( op, &rs ); + if ( mod.sml_next ) { + slap_mods_free( mod.sml_next, 1 ); + } + } + } + + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + ldap_pvt_runqueue_stoptask( &slapd_rq, rtask ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + return NULL; +} + +static int +log_cf_gen(ConfigArgs *c) +{ + slap_overinst *on = (slap_overinst *)c->bi; + struct log_info *li = on->on_bi.bi_private; + int rc = 0; + slap_mask_t tmask = 0; + char agebuf[2*STRLENOF("ddddd+hh:mm:ss ")]; + struct berval agebv, cyclebv; + + switch( c->op ) { + case SLAP_CONFIG_EMIT: + switch( c->type ) { + case LOG_DB: + if ( !BER_BVISEMPTY( &li->li_db_suffix )) { + value_add_one( &c->rvalue_vals, &li->li_db_suffix ); + value_add_one( &c->rvalue_nvals, &li->li_db_suffix ); + } else if ( li->li_db ) { + value_add_one( &c->rvalue_vals, li->li_db->be_suffix ); + value_add_one( &c->rvalue_nvals, li->li_db->be_nsuffix ); + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "accesslog: \"logdb <suffix>\" must be specified" ); + Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", + c->log, c->cr_msg, c->value_dn.bv_val ); + rc = 1; + break; + } + break; + case LOG_OPS: + rc = mask_to_verbs( logops, li->li_ops, &c->rvalue_vals ); + break; + case LOG_PURGE: + if ( !li->li_age ) { + rc = 1; + break; + } + agebv.bv_val = agebuf; + log_age_unparse( li->li_age, &agebv, sizeof( agebuf ) ); + agebv.bv_val[agebv.bv_len] = ' '; + agebv.bv_len++; + cyclebv.bv_val = agebv.bv_val + agebv.bv_len; + log_age_unparse( li->li_cycle, &cyclebv, sizeof( agebuf ) - agebv.bv_len ); + agebv.bv_len += cyclebv.bv_len; + value_add_one( &c->rvalue_vals, &agebv ); + break; + case LOG_SUCCESS: + if ( li->li_success ) + c->value_int = li->li_success; + else + rc = 1; + break; + case LOG_OLD: + if ( li->li_oldf ) { + filter2bv( li->li_oldf, &agebv ); + ber_bvarray_add( &c->rvalue_vals, &agebv ); + } + else + rc = 1; + break; + case LOG_OLDATTR: + if ( li->li_oldattrs ) { + log_attr *la; + + for ( la = li->li_oldattrs; la; la=la->next ) + value_add_one( &c->rvalue_vals, &la->attr->ad_cname ); + } + else + rc = 1; + break; + case LOG_BASE: + if ( li->li_bases ) { + log_base *lb; + + for ( lb = li->li_bases; lb; lb=lb->lb_next ) + value_add_one( &c->rvalue_vals, &lb->lb_line ); + } + else + rc = 1; + break; + } + break; + case LDAP_MOD_DELETE: + switch( c->type ) { + case LOG_DB: + /* noop. this should always be a valid backend. */ + break; + case LOG_OPS: + if ( c->valx < 0 ) { + li->li_ops = 0; + } else { + rc = verbs_to_mask( 1, &c->line, logops, &tmask ); + if ( rc == 0 ) + li->li_ops &= ~tmask; + } + break; + case LOG_PURGE: + if ( li->li_task ) { + struct re_s *re = li->li_task; + li->li_task = NULL; + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re )) + ldap_pvt_runqueue_stoptask( &slapd_rq, re ); + ldap_pvt_runqueue_remove( &slapd_rq, re ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + li->li_age = 0; + li->li_cycle = 0; + break; + case LOG_SUCCESS: + li->li_success = 0; + break; + case LOG_OLD: + if ( li->li_oldf ) { + filter_free( li->li_oldf ); + li->li_oldf = NULL; + } + break; + case LOG_OLDATTR: + if ( c->valx < 0 ) { + log_attr *la, *ln; + + for ( la = li->li_oldattrs; la; la = ln ) { + ln = la->next; + ch_free( la ); + } + } else { + log_attr *la = NULL, **lp; + int i; + + for ( lp = &li->li_oldattrs, i=0; i < c->valx; i++ ) { + la = *lp; + lp = &la->next; + } + *lp = la->next; + ch_free( la ); + } + break; + case LOG_BASE: + if ( c->valx < 0 ) { + log_base *lb, *ln; + + for ( lb = li->li_bases; lb; lb = ln ) { + ln = lb->lb_next; + ch_free( lb ); + } + } else { + log_base *lb = NULL, **lp; + int i; + + for ( lp = &li->li_bases, i=0; i < c->valx; i++ ) { + lb = *lp; + lp = &lb->lb_next; + } + *lp = lb->lb_next; + ch_free( lb ); + } + break; + } + break; + default: + switch( c->type ) { + case LOG_DB: + if ( CONFIG_ONLINE_ADD( c )) { + li->li_db = select_backend( &c->value_ndn, 0 ); + if ( !li->li_db ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> no matching backend found for suffix", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", + c->log, c->cr_msg, c->value_dn.bv_val ); + rc = 1; + } + ch_free( c->value_ndn.bv_val ); + } else { + li->li_db_suffix = c->value_ndn; + } + ch_free( c->value_dn.bv_val ); + break; + case LOG_OPS: + rc = verbs_to_mask( c->argc, c->argv, logops, &tmask ); + if ( rc == 0 ) + li->li_ops |= tmask; + break; + case LOG_PURGE: + li->li_age = log_age_parse( c->argv[1] ); + if ( li->li_age < 1 ) { + rc = 1; + } else { + li->li_cycle = log_age_parse( c->argv[2] ); + if ( li->li_cycle < 1 ) { + rc = 1; + } else if ( slapMode & SLAP_SERVER_MODE ) { + struct re_s *re = li->li_task; + if ( re ) + re->interval.tv_sec = li->li_cycle; + else { + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + li->li_task = ldap_pvt_runqueue_insert( &slapd_rq, + li->li_cycle, accesslog_purge, li, + "accesslog_purge", li->li_db ? + li->li_db->be_suffix[0].bv_val : + c->be->be_suffix[0].bv_val ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + } + } + break; + case LOG_SUCCESS: + li->li_success = c->value_int; + break; + case LOG_OLD: + li->li_oldf = str2filter( c->argv[1] ); + if ( !li->li_oldf ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "bad filter!" ); + rc = 1; + } + break; + case LOG_OLDATTR: { + int i; + AttributeDescription *ad; + const char *text; + + for ( i=1; i< c->argc; i++ ) { + ad = NULL; + if ( slap_str2ad( c->argv[i], &ad, &text ) == LDAP_SUCCESS ) { + log_attr *la = ch_malloc( sizeof( log_attr )); + la->attr = ad; + la->next = li->li_oldattrs; + li->li_oldattrs = la; + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s <%s>: %s", + c->argv[0], c->argv[i], text ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg, 0 ); + rc = ARG_BAD_CONF; + break; + } + } + } + break; + case LOG_BASE: { + slap_mask_t m = 0; + rc = verbstring_to_mask( logops, c->argv[1], '|', &m ); + if ( rc == 0 ) { + struct berval dn, ndn; + ber_str2bv( c->argv[2], 0, 0, &dn ); + rc = dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL ); + if ( rc == 0 ) { + log_base *lb; + struct berval mbv; + char *ptr; + mask_to_verbstring( logops, m, '|', &mbv ); + lb = ch_malloc( sizeof( log_base ) + mbv.bv_len + ndn.bv_len + 3 + 1 ); + lb->lb_line.bv_val = (char *)(lb + 1); + lb->lb_line.bv_len = mbv.bv_len + ndn.bv_len + 3; + ptr = lutil_strcopy( lb->lb_line.bv_val, mbv.bv_val ); + *ptr++ = ' '; + *ptr++ = '"'; + lb->lb_base.bv_val = ptr; + lb->lb_base.bv_len = ndn.bv_len; + ptr = lutil_strcopy( ptr, ndn.bv_val ); + *ptr++ = '"'; + lb->lb_ops = m; + lb->lb_next = li->li_bases; + li->li_bases = lb; + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid DN: %s", + c->argv[0], c->argv[2] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg, 0 ); + rc = ARG_BAD_CONF; + } + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid ops: %s", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg, 0 ); + rc = ARG_BAD_CONF; + } + } + break; + } + break; + } + return rc; +} + +static int +logSchemaControlValidate( + Syntax *syntax, + struct berval *valp ) +{ + struct berval val, bv; + ber_len_t i; + int rc = LDAP_SUCCESS; + + assert( valp != NULL ); + + val = *valp; + + /* check minimal size */ + if ( val.bv_len < STRLENOF( "{*}" ) ) { + return LDAP_INVALID_SYNTAX; + } + + val.bv_len--; + + /* check SEQUENCE boundaries */ + if ( val.bv_val[ 0 ] != '{' /*}*/ || + val.bv_val[ val.bv_len ] != /*{*/ '}' ) + { + return LDAP_INVALID_SYNTAX; + } + + /* extract and check OID */ + for ( i = 1; i < val.bv_len; i++ ) { + if ( !ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + bv.bv_val = &val.bv_val[ i ]; + + for ( i++; i < val.bv_len; i++ ) { + if ( ASCII_SPACE( val.bv_val[ i ] ) ) + { + break; + } + } + + bv.bv_len = &val.bv_val[ i ] - bv.bv_val; + + rc = numericoidValidate( NULL, &bv ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( i == val.bv_len ) { + return LDAP_SUCCESS; + } + + if ( val.bv_val[ i ] != ' ' ) { + return LDAP_INVALID_SYNTAX; + } + + for ( i++; i < val.bv_len; i++ ) { + if ( !ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + if ( i == val.bv_len ) { + return LDAP_SUCCESS; + } + + /* extract and check criticality */ + if ( strncasecmp( &val.bv_val[ i ], "criticality ", STRLENOF( "criticality " ) ) == 0 ) + { + i += STRLENOF( "criticality " ); + for ( ; i < val.bv_len; i++ ) { + if ( !ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + if ( i == val.bv_len ) { + return LDAP_INVALID_SYNTAX; + } + + bv.bv_val = &val.bv_val[ i ]; + + for ( ; i < val.bv_len; i++ ) { + if ( ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + bv.bv_len = &val.bv_val[ i ] - bv.bv_val; + + if ( !bvmatch( &bv, &slap_true_bv ) && !bvmatch( &bv, &slap_false_bv ) ) + { + return LDAP_INVALID_SYNTAX; + } + + if ( i == val.bv_len ) { + return LDAP_SUCCESS; + } + + if ( val.bv_val[ i ] != ' ' ) { + return LDAP_INVALID_SYNTAX; + } + + for ( i++; i < val.bv_len; i++ ) { + if ( !ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + if ( i == val.bv_len ) { + return LDAP_SUCCESS; + } + } + + /* extract and check controlValue */ + if ( strncasecmp( &val.bv_val[ i ], "controlValue ", STRLENOF( "controlValue " ) ) == 0 ) + { + ber_len_t valueStart, valueLen; + + i += STRLENOF( "controlValue " ); + for ( ; i < val.bv_len; i++ ) { + if ( !ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + if ( i == val.bv_len ) { + return LDAP_INVALID_SYNTAX; + } + + if ( val.bv_val[ i ] != '"' ) { + return LDAP_INVALID_SYNTAX; + } + + i++; + valueStart = i; + + for ( ; i < val.bv_len; i++ ) { + if ( val.bv_val[ i ] == '"' ) { + break; + } + + if ( !ASCII_HEX( val.bv_val[ i ] ) ) { + return LDAP_INVALID_SYNTAX; + } + } + + if ( val.bv_val[ i ] != '"' ) { + return LDAP_INVALID_SYNTAX; + } + + valueLen = i - valueStart; + if ( (valueLen/2)*2 != valueLen ) { + return LDAP_INVALID_SYNTAX; + } + + for ( i++; i < val.bv_len; i++ ) { + if ( !ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + if ( i == val.bv_len ) { + return LDAP_SUCCESS; + } + } + + return LDAP_INVALID_SYNTAX; +} + +static int +accesslog_ctrls( + LDAPControl **ctrls, + BerVarray *valsp, + BerVarray *nvalsp, + void *memctx ) +{ + long i, rc = 0; + + assert( valsp != NULL ); + assert( ctrls != NULL ); + + *valsp = NULL; + *nvalsp = NULL; + + for ( i = 0; ctrls[ i ] != NULL; i++ ) { + struct berval idx, + oid, + noid, + bv; + char *ptr, + buf[ 32 ]; + + if ( ctrls[ i ]->ldctl_oid == NULL ) { + return LDAP_PROTOCOL_ERROR; + } + + idx.bv_len = snprintf( buf, sizeof( buf ), "{%ld}", i ); + idx.bv_val = buf; + + ber_str2bv( ctrls[ i ]->ldctl_oid, 0, 0, &oid ); + noid.bv_len = idx.bv_len + oid.bv_len; + ptr = noid.bv_val = ber_memalloc_x( noid.bv_len + 1, memctx ); + ptr = lutil_strcopy( ptr, idx.bv_val ); + ptr = lutil_strcopy( ptr, oid.bv_val ); + + bv.bv_len = idx.bv_len + STRLENOF( "{}" ) + oid.bv_len; + + if ( ctrls[ i ]->ldctl_iscritical ) { + bv.bv_len += STRLENOF( " criticality TRUE" ); + } + + if ( !BER_BVISNULL( &ctrls[ i ]->ldctl_value ) ) { + bv.bv_len += STRLENOF( " controlValue \"\"" ) + + 2 * ctrls[ i ]->ldctl_value.bv_len; + } + + ptr = bv.bv_val = ber_memalloc_x( bv.bv_len + 1, memctx ); + if ( ptr == NULL ) { + ber_bvarray_free( *valsp ); + *valsp = NULL; + ber_bvarray_free( *nvalsp ); + *nvalsp = NULL; + return LDAP_OTHER; + } + + ptr = lutil_strcopy( ptr, idx.bv_val ); + + *ptr++ = '{' /*}*/ ; + ptr = lutil_strcopy( ptr, oid.bv_val ); + + if ( ctrls[ i ]->ldctl_iscritical ) { + ptr = lutil_strcopy( ptr, " criticality TRUE" ); + } + + if ( !BER_BVISNULL( &ctrls[ i ]->ldctl_value ) ) { + ber_len_t j; + + ptr = lutil_strcopy( ptr, " controlValue \"" ); + for ( j = 0; j < ctrls[ i ]->ldctl_value.bv_len; j++ ) { + *ptr++ = SLAP_ESCAPE_HI(ctrls[ i ]->ldctl_value.bv_val[ j ]); + *ptr++ = SLAP_ESCAPE_LO(ctrls[ i ]->ldctl_value.bv_val[ j ]); + } + + *ptr++ = '"'; + } + + *ptr++ = '}'; + *ptr = '\0'; + + ber_bvarray_add_x( valsp, &bv, memctx ); + ber_bvarray_add_x( nvalsp, &noid, memctx ); + } + + return rc; + +} + +static Entry *accesslog_entry( Operation *op, SlapReply *rs, + log_info *li, int logop, Operation *op2 ) { + + char rdnbuf[STRLENOF(RDNEQ)+LDAP_LUTIL_GENTIME_BUFSIZE+8]; + char nrdnbuf[STRLENOF(RDNEQ)+LDAP_LUTIL_GENTIME_BUFSIZE+8]; + + struct berval rdn, nrdn, timestamp, ntimestamp, bv; + slap_verbmasks *lo = logops+logop+EN_OFFSET; + + Entry *e = entry_alloc(); + + strcpy( rdnbuf, RDNEQ ); + rdn.bv_val = rdnbuf; + strcpy( nrdnbuf, RDNEQ ); + nrdn.bv_val = nrdnbuf; + + timestamp.bv_val = rdnbuf+STRLENOF(RDNEQ); + timestamp.bv_len = sizeof(rdnbuf) - STRLENOF(RDNEQ); + slap_timestamp( &op->o_time, ×tamp ); + snprintf( timestamp.bv_val + timestamp.bv_len-1, sizeof(".123456Z"), ".%06dZ", op->o_tincr ); + timestamp.bv_len += STRLENOF(".123456"); + + rdn.bv_len = STRLENOF(RDNEQ)+timestamp.bv_len; + ad_reqStart->ad_type->sat_equality->smr_normalize( + SLAP_MR_VALUE_OF_ASSERTION_SYNTAX, ad_reqStart->ad_type->sat_syntax, + ad_reqStart->ad_type->sat_equality, ×tamp, &ntimestamp, + op->o_tmpmemctx ); + + strcpy( nrdn.bv_val + STRLENOF(RDNEQ), ntimestamp.bv_val ); + nrdn.bv_len = STRLENOF(RDNEQ)+ntimestamp.bv_len; + build_new_dn( &e->e_name, li->li_db->be_suffix, &rdn, NULL ); + build_new_dn( &e->e_nname, li->li_db->be_nsuffix, &nrdn, NULL ); + + attr_merge_one( e, slap_schema.si_ad_objectClass, + &log_ocs[logop]->soc_cname, NULL ); + attr_merge_one( e, slap_schema.si_ad_structuralObjectClass, + &log_ocs[logop]->soc_cname, NULL ); + attr_merge_one( e, ad_reqStart, ×tamp, &ntimestamp ); + op->o_tmpfree( ntimestamp.bv_val, op->o_tmpmemctx ); + + slap_op_time( &op2->o_time, &op2->o_tincr ); + + timestamp.bv_len = sizeof(rdnbuf) - STRLENOF(RDNEQ); + slap_timestamp( &op2->o_time, ×tamp ); + snprintf( timestamp.bv_val + timestamp.bv_len-1, sizeof(".123456Z"), ".%06dZ", op2->o_tincr ); + timestamp.bv_len += STRLENOF(".123456"); + + attr_merge_normalize_one( e, ad_reqEnd, ×tamp, op->o_tmpmemctx ); + + /* Exops have OID appended */ + if ( logop == LOG_EN_EXTENDED ) { + bv.bv_len = lo->word.bv_len + op->ore_reqoid.bv_len + 2; + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + AC_MEMCPY( bv.bv_val, lo->word.bv_val, lo->word.bv_len ); + bv.bv_val[lo->word.bv_len] = '{'; + AC_MEMCPY( bv.bv_val+lo->word.bv_len+1, op->ore_reqoid.bv_val, + op->ore_reqoid.bv_len ); + bv.bv_val[bv.bv_len-1] = '}'; + bv.bv_val[bv.bv_len] = '\0'; + attr_merge_one( e, ad_reqType, &bv, NULL ); + } else { + attr_merge_one( e, ad_reqType, &lo->word, NULL ); + } + + rdn.bv_len = snprintf( rdn.bv_val, sizeof( rdnbuf ), "%lu", op->o_connid ); + if ( rdn.bv_len < sizeof( rdnbuf ) ) { + attr_merge_one( e, ad_reqSession, &rdn, NULL ); + } /* else? */ + + if ( BER_BVISNULL( &op->o_dn ) ) { + attr_merge_one( e, ad_reqAuthzID, (struct berval *)&slap_empty_bv, + (struct berval *)&slap_empty_bv ); + } else { + attr_merge_one( e, ad_reqAuthzID, &op->o_dn, &op->o_ndn ); + } + + /* FIXME: need to add reqControls and reqRespControls */ + if ( op->o_ctrls ) { + BerVarray vals = NULL, + nvals = NULL; + + if ( accesslog_ctrls( op->o_ctrls, &vals, &nvals, + op->o_tmpmemctx ) == LDAP_SUCCESS && vals ) + { + attr_merge( e, ad_reqControls, vals, nvals ); + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + ber_bvarray_free_x( nvals, op->o_tmpmemctx ); + } + } + + if ( rs->sr_ctrls ) { + BerVarray vals = NULL, + nvals = NULL; + + if ( accesslog_ctrls( rs->sr_ctrls, &vals, &nvals, + op->o_tmpmemctx ) == LDAP_SUCCESS && vals ) + { + attr_merge( e, ad_reqRespControls, vals, nvals ); + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + ber_bvarray_free_x( nvals, op->o_tmpmemctx ); + } + + } + + return e; +} + +static struct berval scopes[] = { + BER_BVC("base"), + BER_BVC("one"), + BER_BVC("sub"), + BER_BVC("subord") +}; + +static struct berval derefs[] = { + BER_BVC("never"), + BER_BVC("searching"), + BER_BVC("finding"), + BER_BVC("always") +}; + +static struct berval simple = BER_BVC("SIMPLE"); + +static void accesslog_val2val(AttributeDescription *ad, struct berval *val, + char c_op, struct berval *dst) { + char *ptr; + + dst->bv_len = ad->ad_cname.bv_len + val->bv_len + 2; + if ( c_op ) dst->bv_len++; + + dst->bv_val = ch_malloc( dst->bv_len+1 ); + + ptr = lutil_strcopy( dst->bv_val, ad->ad_cname.bv_val ); + *ptr++ = ':'; + if ( c_op ) + *ptr++ = c_op; + *ptr++ = ' '; + AC_MEMCPY( ptr, val->bv_val, val->bv_len ); + dst->bv_val[dst->bv_len] = '\0'; +} + +static int +accesslog_op2logop( Operation *op ) +{ + switch ( op->o_tag ) { + case LDAP_REQ_ADD: return LOG_EN_ADD; + case LDAP_REQ_DELETE: return LOG_EN_DELETE; + case LDAP_REQ_MODIFY: return LOG_EN_MODIFY; + case LDAP_REQ_MODRDN: return LOG_EN_MODRDN; + case LDAP_REQ_COMPARE: return LOG_EN_COMPARE; + case LDAP_REQ_SEARCH: return LOG_EN_SEARCH; + case LDAP_REQ_BIND: return LOG_EN_BIND; + case LDAP_REQ_EXTENDED: return LOG_EN_EXTENDED; + default: /* unknown operation type */ + break; + } /* Unbind and Abandon never reach here */ + return LOG_EN_UNKNOWN; +} + +static int accesslog_response(Operation *op, SlapReply *rs) { + slap_overinst *on = (slap_overinst *)op->o_callback->sc_private; + log_info *li = on->on_bi.bi_private; + Attribute *a, *last_attr; + Modifications *m; + struct berval *b, uuid = BER_BVNULL; + int i; + int logop; + slap_verbmasks *lo; + Entry *e = NULL, *old = NULL, *e_uuid = NULL; + char timebuf[LDAP_LUTIL_GENTIME_BUFSIZE+8]; + struct berval bv; + char *ptr; + BerVarray vals; + Operation op2 = {0}; + SlapReply rs2 = {REP_RESULT}; + + { + slap_callback *sc = op->o_callback; + op->o_callback = sc->sc_next; + op->o_tmpfree(sc, op->o_tmpmemctx ); + } + + if ( rs->sr_type != REP_RESULT && rs->sr_type != REP_EXTENDED ) + return SLAP_CB_CONTINUE; + + logop = accesslog_op2logop( op ); + lo = logops+logop+EN_OFFSET; + if ( !( li->li_ops & lo->mask )) { + log_base *lb; + + i = 0; + for ( lb = li->li_bases; lb; lb=lb->lb_next ) + if (( lb->lb_ops & lo->mask ) && dnIsSuffix( &op->o_req_ndn, &lb->lb_base )) { + i = 1; + break; + } + if ( !i ) + return SLAP_CB_CONTINUE; + } + + /* mutex and so were only set for write operations; + * if we got here, the operation must be logged */ + if ( lo->mask & LOG_OP_WRITES ) { + slap_callback *cb; + + /* These internal ops are not logged */ + if ( op->o_dont_replicate ) + return SLAP_CB_CONTINUE; + + ldap_pvt_thread_mutex_lock( &li->li_log_mutex ); + old = li->li_old; + uuid = li->li_uuid; + li->li_old = NULL; + BER_BVZERO( &li->li_uuid ); +#ifdef RMUTEX_DEBUG + Debug( LDAP_DEBUG_SYNC, + "accesslog_response: unlocking rmutex for tid %x\n", + op->o_tid, 0, 0 ); +#endif + ldap_pvt_thread_rmutex_unlock( &li->li_op_rmutex, op->o_tid ); + } + + /* ignore these internal reads */ + if (( lo->mask & LOG_OP_READS ) && op->o_do_not_cache ) { + return SLAP_CB_CONTINUE; + } + + if ( li->li_success && rs->sr_err != LDAP_SUCCESS ) + goto done; + + e = accesslog_entry( op, rs, li, logop, &op2 ); + + if ( !BER_BVISNULL( &op->o_req_ndn )) + attr_merge_one( e, ad_reqDN, &op->o_req_dn, &op->o_req_ndn ); + + if ( rs->sr_text ) { + ber_str2bv( rs->sr_text, 0, 0, &bv ); + attr_merge_normalize_one( e, ad_reqMessage, &bv, op->o_tmpmemctx ); + } + bv.bv_len = snprintf( timebuf, sizeof( timebuf ), "%d", rs->sr_err ); + if ( bv.bv_len < sizeof( timebuf ) ) { + bv.bv_val = timebuf; + attr_merge_one( e, ad_reqResult, &bv, NULL ); + } + + last_attr = attr_find( e->e_attrs, ad_reqResult ); + + e_uuid = old; + switch( logop ) { + case LOG_EN_ADD: + case LOG_EN_DELETE: { + char c_op; + Entry *e2; + + if ( logop == LOG_EN_ADD ) { + e2 = op->ora_e; + e_uuid = op->ora_e; + c_op = '+'; + + } else { + if ( !old ) + break; + e2 = old; + c_op = 0; + } + /* count all the vals */ + i = 0; + for ( a=e2->e_attrs; a; a=a->a_next ) { + i += a->a_numvals; + } + vals = ch_malloc( (i+1) * sizeof( struct berval )); + i = 0; + for ( a=e2->e_attrs; a; a=a->a_next ) { + if ( a->a_vals ) { + for (b=a->a_vals; !BER_BVISNULL( b ); b++,i++) { + accesslog_val2val( a->a_desc, b, c_op, &vals[i] ); + } + } + } + vals[i].bv_val = NULL; + vals[i].bv_len = 0; + a = attr_alloc( logop == LOG_EN_ADD ? ad_reqMod : ad_reqOld ); + a->a_numvals = i; + a->a_vals = vals; + a->a_nvals = vals; + last_attr->a_next = a; + break; + } + + case LOG_EN_MODRDN: + case LOG_EN_MODIFY: + /* count all the mods + attributes (ITS#6545) */ + i = 0; + for ( m = op->orm_modlist; m; m = m->sml_next ) { + if ( m->sml_values ) { + i += m->sml_numvals; + } else if ( m->sml_op == LDAP_MOD_DELETE || + m->sml_op == SLAP_MOD_SOFTDEL || + m->sml_op == LDAP_MOD_REPLACE ) + { + i++; + } + if ( m->sml_next && m->sml_desc == m->sml_next->sml_desc ) { + i++; + } + } + vals = ch_malloc( (i+1) * sizeof( struct berval )); + i = 0; + + /* init flags on old entry */ + if ( old ) { + for ( a = old->e_attrs; a; a = a->a_next ) { + log_attr *la; + a->a_flags = 0; + + /* look for attrs that are always logged */ + for ( la = li->li_oldattrs; la; la = la->next ) { + if ( a->a_desc == la->attr ) { + a->a_flags = 1; + } + } + } + } + + for ( m = op->orm_modlist; m; m = m->sml_next ) { + /* Mark this attribute as modified */ + if ( old ) { + a = attr_find( old->e_attrs, m->sml_desc ); + if ( a ) { + a->a_flags = 1; + } + } + + /* don't log the RDN mods; they're explicitly logged later */ + if ( logop == LOG_EN_MODRDN && + ( m->sml_op == SLAP_MOD_SOFTADD || + m->sml_op == LDAP_MOD_DELETE ) ) + { + continue; + } + + if ( m->sml_values ) { + for ( b = m->sml_values; !BER_BVISNULL( b ); b++, i++ ) { + char c_op; + + switch ( m->sml_op ) { + case LDAP_MOD_ADD: /* FALLTHRU */ + case SLAP_MOD_SOFTADD: c_op = '+'; break; + case LDAP_MOD_DELETE: /* FALLTHRU */ + case SLAP_MOD_SOFTDEL: c_op = '-'; break; + case LDAP_MOD_REPLACE: c_op = '='; break; + case LDAP_MOD_INCREMENT: c_op = '#'; break; + + /* unknown op. there shouldn't be any of these. we + * don't know what to do with it, but we shouldn't just + * ignore it. + */ + default: c_op = '?'; break; + } + accesslog_val2val( m->sml_desc, b, c_op, &vals[i] ); + } + } else if ( m->sml_op == LDAP_MOD_DELETE || + m->sml_op == SLAP_MOD_SOFTDEL || + m->sml_op == LDAP_MOD_REPLACE ) + { + vals[i].bv_len = m->sml_desc->ad_cname.bv_len + 2; + vals[i].bv_val = ch_malloc( vals[i].bv_len + 1 ); + ptr = lutil_strcopy( vals[i].bv_val, + m->sml_desc->ad_cname.bv_val ); + *ptr++ = ':'; + if ( m->sml_op == LDAP_MOD_DELETE || m->sml_op == SLAP_MOD_SOFTDEL ) { + *ptr++ = '-'; + } else { + *ptr++ = '='; + } + *ptr = '\0'; + i++; + } + /* ITS#6545: when the same attribute is edited multiple times, + * record the transition */ + if ( m->sml_next && m->sml_desc == m->sml_next->sml_desc && + m->sml_op == m->sml_next->sml_op ) { + ber_str2bv( ":", STRLENOF(":"), 1, &vals[i] ); + i++; + } + } + + if ( i > 0 ) { + BER_BVZERO( &vals[i] ); + a = attr_alloc( ad_reqMod ); + a->a_numvals = i; + a->a_vals = vals; + a->a_nvals = vals; + last_attr->a_next = a; + last_attr = a; + + } else { + ch_free( vals ); + } + + if ( old ) { + /* count all the vals */ + i = 0; + for ( a = old->e_attrs; a != NULL; a = a->a_next ) { + if ( a->a_vals && a->a_flags ) { + i += a->a_numvals; + } + } + if ( i ) { + vals = ch_malloc( (i + 1) * sizeof( struct berval ) ); + i = 0; + for ( a=old->e_attrs; a; a=a->a_next ) { + if ( a->a_vals && a->a_flags ) { + for (b=a->a_vals; !BER_BVISNULL( b ); b++,i++) { + accesslog_val2val( a->a_desc, b, 0, &vals[i] ); + } + } + } + vals[i].bv_val = NULL; + vals[i].bv_len = 0; + a = attr_alloc( ad_reqOld ); + a->a_numvals = i; + a->a_vals = vals; + a->a_nvals = vals; + last_attr->a_next = a; + } + } + if ( logop == LOG_EN_MODIFY ) { + break; + } + + /* Now log the actual modRDN info */ + attr_merge_one( e, ad_reqNewRDN, &op->orr_newrdn, &op->orr_nnewrdn ); + attr_merge_one( e, ad_reqDeleteOldRDN, op->orr_deleteoldrdn ? + (struct berval *)&slap_true_bv : (struct berval *)&slap_false_bv, + NULL ); + if ( op->orr_newSup ) { + attr_merge_one( e, ad_reqNewSuperior, op->orr_newSup, op->orr_nnewSup ); + } + break; + + case LOG_EN_COMPARE: + bv.bv_len = op->orc_ava->aa_desc->ad_cname.bv_len + 1 + + op->orc_ava->aa_value.bv_len; + bv.bv_val = op->o_tmpalloc( bv.bv_len+1, op->o_tmpmemctx ); + ptr = lutil_strcopy( bv.bv_val, op->orc_ava->aa_desc->ad_cname.bv_val ); + *ptr++ = '='; + AC_MEMCPY( ptr, op->orc_ava->aa_value.bv_val, op->orc_ava->aa_value.bv_len ); + bv.bv_val[bv.bv_len] = '\0'; + attr_merge_one( e, ad_reqAssertion, &bv, NULL ); + op->o_tmpfree( bv.bv_val, op->o_tmpmemctx ); + break; + + case LOG_EN_SEARCH: + attr_merge_one( e, ad_reqScope, &scopes[op->ors_scope], NULL ); + attr_merge_one( e, ad_reqDerefAliases, &derefs[op->ors_deref], NULL ); + attr_merge_one( e, ad_reqAttrsOnly, op->ors_attrsonly ? + (struct berval *)&slap_true_bv : (struct berval *)&slap_false_bv, + NULL ); + if ( !BER_BVISEMPTY( &op->ors_filterstr )) + attr_merge_normalize_one( e, ad_reqFilter, &op->ors_filterstr, op->o_tmpmemctx ); + if ( op->ors_attrs ) { + int j; + /* count them */ + for (i=0; !BER_BVISNULL(&op->ors_attrs[i].an_name );i++) + ; + vals = op->o_tmpalloc( (i+1) * sizeof(struct berval), + op->o_tmpmemctx ); + for (i=0, j=0; !BER_BVISNULL(&op->ors_attrs[i].an_name );i++) { + if (!BER_BVISEMPTY(&op->ors_attrs[i].an_name)) { + vals[j] = op->ors_attrs[i].an_name; + j++; + } + } + BER_BVZERO(&vals[j]); + attr_merge_normalize( e, ad_reqAttr, vals, op->o_tmpmemctx ); + op->o_tmpfree( vals, op->o_tmpmemctx ); + } + bv.bv_val = timebuf; + bv.bv_len = snprintf( bv.bv_val, sizeof( timebuf ), "%d", rs->sr_nentries ); + if ( bv.bv_len < sizeof( timebuf ) ) { + attr_merge_one( e, ad_reqEntries, &bv, NULL ); + } /* else? */ + + bv.bv_len = snprintf( bv.bv_val, sizeof( timebuf ), "%d", op->ors_tlimit ); + if ( bv.bv_len < sizeof( timebuf ) ) { + attr_merge_one( e, ad_reqTimeLimit, &bv, NULL ); + } /* else? */ + + bv.bv_len = snprintf( bv.bv_val, sizeof( timebuf ), "%d", op->ors_slimit ); + if ( bv.bv_len < sizeof( timebuf ) ) { + attr_merge_one( e, ad_reqSizeLimit, &bv, NULL ); + } /* else? */ + break; + + case LOG_EN_BIND: + bv.bv_val = timebuf; + bv.bv_len = snprintf( bv.bv_val, sizeof( timebuf ), "%d", op->o_protocol ); + if ( bv.bv_len < sizeof( timebuf ) ) { + attr_merge_one( e, ad_reqVersion, &bv, NULL ); + } /* else? */ + if ( op->orb_method == LDAP_AUTH_SIMPLE ) { + attr_merge_normalize_one( e, ad_reqMethod, &simple, op->o_tmpmemctx ); + } else { + bv.bv_len = STRLENOF("SASL()") + op->orb_mech.bv_len; + bv.bv_val = op->o_tmpalloc( bv.bv_len + 1, op->o_tmpmemctx ); + ptr = lutil_strcopy( bv.bv_val, "SASL(" ); + ptr = lutil_strcopy( ptr, op->orb_mech.bv_val ); + *ptr++ = ')'; + *ptr = '\0'; + attr_merge_normalize_one( e, ad_reqMethod, &bv, op->o_tmpmemctx ); + op->o_tmpfree( bv.bv_val, op->o_tmpmemctx ); + } + + break; + + case LOG_EN_EXTENDED: + if ( op->ore_reqdata ) { + attr_merge_one( e, ad_reqData, op->ore_reqdata, NULL ); + } + break; + + case LOG_EN_UNKNOWN: + /* we don't know its parameters, don't add any */ + break; + } + + if ( e_uuid || !BER_BVISNULL( &uuid ) ) { + struct berval *pbv = NULL; + + if ( !BER_BVISNULL( &uuid ) ) { + pbv = &uuid; + + } else { + a = attr_find( e_uuid->e_attrs, slap_schema.si_ad_entryUUID ); + if ( a ) { + pbv = &a->a_vals[0]; + } + } + + if ( pbv ) { + attr_merge_normalize_one( e, ad_reqEntryUUID, pbv, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &uuid ) ) { + ber_memfree( uuid.bv_val ); + BER_BVZERO( &uuid ); + } + } + + op2.o_hdr = op->o_hdr; + op2.o_tag = LDAP_REQ_ADD; + op2.o_bd = li->li_db; + op2.o_dn = li->li_db->be_rootdn; + op2.o_ndn = li->li_db->be_rootndn; + op2.o_req_dn = e->e_name; + op2.o_req_ndn = e->e_nname; + op2.ora_e = e; + op2.o_callback = &nullsc; + op2.o_csn = op->o_csn; + /* contextCSN updates may still reach here */ + op2.o_dont_replicate = op->o_dont_replicate; + + if (( lo->mask & LOG_OP_WRITES ) && !BER_BVISEMPTY( &op->o_csn )) { + struct berval maxcsn; + char cbuf[LDAP_PVT_CSNSTR_BUFSIZE]; + int foundit; + cbuf[0] = '\0'; + maxcsn.bv_val = cbuf; + maxcsn.bv_len = sizeof(cbuf); + /* If there was a commit CSN on the main DB, + * we must propagate it to the log DB for its + * own syncprov. Otherwise, don't generate one. + */ + slap_get_commit_csn( op, &maxcsn, &foundit ); + if ( !BER_BVISEMPTY( &maxcsn ) ) { + slap_queue_csn( &op2, &op->o_csn ); + } else { + attr_merge_normalize_one( e, slap_schema.si_ad_entryCSN, + &op->o_csn, op->o_tmpmemctx ); + } + } + + op2.o_bd->be_add( &op2, &rs2 ); + if ( rs2.sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_SYNC, + "accesslog_response: got result 0x%x adding log entry %s\n", + rs2.sr_err, op2.o_req_dn.bv_val, 0 ); + } + if ( e == op2.ora_e ) entry_free( e ); + e = NULL; + +done: + if ( lo->mask & LOG_OP_WRITES ) + ldap_pvt_thread_mutex_unlock( &li->li_log_mutex ); + if ( old ) entry_free( old ); + return SLAP_CB_CONTINUE; +} + +static int +accesslog_op_misc( Operation *op, SlapReply *rs ) +{ + slap_callback *sc; + + sc = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx ); + sc->sc_response = accesslog_response; + sc->sc_private = op->o_bd->bd_info; + + if ( op->o_callback ) { + sc->sc_next = op->o_callback->sc_next; + op->o_callback->sc_next = sc; + } else { + op->o_callback = sc; + } + return SLAP_CB_CONTINUE; +} + +static int +accesslog_op_mod( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + log_info *li = on->on_bi.bi_private; + slap_verbmasks *lo; + int logop; + int doit = 0; + + /* These internal ops are not logged */ + if ( op->o_dont_replicate ) + return SLAP_CB_CONTINUE; + + logop = accesslog_op2logop( op ); + lo = logops+logop+EN_OFFSET; + + if ( li->li_ops & lo->mask ) { + doit = 1; + } else { + log_base *lb; + for ( lb = li->li_bases; lb; lb = lb->lb_next ) + if (( lb->lb_ops & lo->mask ) && dnIsSuffix( &op->o_req_ndn, &lb->lb_base )) { + doit = 1; + break; + } + } + + if ( doit ) { + slap_callback *cb = op->o_tmpcalloc( 1, sizeof( slap_callback ), op->o_tmpmemctx ); + cb->sc_cleanup = accesslog_response; + cb->sc_response = accesslog_response; + cb->sc_private = on; + cb->sc_next = op->o_callback; + op->o_callback = cb; + +#ifdef RMUTEX_DEBUG + Debug( LDAP_DEBUG_SYNC, + "accesslog_op_mod: locking rmutex for tid %x\n", + op->o_tid, 0, 0 ); +#endif + ldap_pvt_thread_rmutex_lock( &li->li_op_rmutex, op->o_tid ); +#ifdef RMUTEX_DEBUG + Debug( LDAP_DEBUG_STATS, + "accesslog_op_mod: locked rmutex for tid %x\n", + op->o_tid, 0, 0 ); +#endif + if ( li->li_oldf && ( op->o_tag == LDAP_REQ_DELETE || + op->o_tag == LDAP_REQ_MODIFY || + ( op->o_tag == LDAP_REQ_MODRDN && li->li_oldattrs ))) + { + int rc; + Entry *e; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e ); + if ( e ) { + if ( test_filter( op, e, li->li_oldf ) == LDAP_COMPARE_TRUE ) + li->li_old = entry_dup( e ); + be_entry_release_rw( op, e, 0 ); + } + op->o_bd->bd_info = (BackendInfo *)on; + + } else { + int rc; + Entry *e; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e ); + if ( e ) { + Attribute *a = attr_find( e->e_attrs, slap_schema.si_ad_entryUUID ); + if ( a ) { + ber_dupbv( &li->li_uuid, &a->a_vals[0] ); + } + be_entry_release_rw( op, e, 0 ); + } + op->o_bd->bd_info = (BackendInfo *)on; + } + } + return SLAP_CB_CONTINUE; +} + +/* unbinds are broadcast to all backends; we only log it if this + * backend was used for the original bind. + */ +static int +accesslog_unbind( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + if ( op->o_conn->c_authz_backend == on->on_info->oi_origdb ) { + log_info *li = on->on_bi.bi_private; + Operation op2 = {0}; + void *cids[SLAP_MAX_CIDS]; + SlapReply rs2 = {REP_RESULT}; + Entry *e; + + if ( !( li->li_ops & LOG_OP_UNBIND )) { + log_base *lb; + int i = 0; + + for ( lb = li->li_bases; lb; lb=lb->lb_next ) + if (( lb->lb_ops & LOG_OP_UNBIND ) && dnIsSuffix( &op->o_ndn, &lb->lb_base )) { + i = 1; + break; + } + if ( !i ) + return SLAP_CB_CONTINUE; + } + + e = accesslog_entry( op, rs, li, LOG_EN_UNBIND, &op2 ); + op2.o_hdr = op->o_hdr; + op2.o_tag = LDAP_REQ_ADD; + op2.o_bd = li->li_db; + op2.o_dn = li->li_db->be_rootdn; + op2.o_ndn = li->li_db->be_rootndn; + op2.o_req_dn = e->e_name; + op2.o_req_ndn = e->e_nname; + op2.ora_e = e; + op2.o_callback = &nullsc; + op2.o_controls = cids; + memset(cids, 0, sizeof( cids )); + + op2.o_bd->be_add( &op2, &rs2 ); + if ( e == op2.ora_e ) + entry_free( e ); + } + return SLAP_CB_CONTINUE; +} + +static int +accesslog_abandon( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + log_info *li = on->on_bi.bi_private; + Operation op2 = {0}; + void *cids[SLAP_MAX_CIDS]; + SlapReply rs2 = {REP_RESULT}; + Entry *e; + char buf[64]; + struct berval bv; + + if ( !op->o_time ) + return SLAP_CB_CONTINUE; + + if ( !( li->li_ops & LOG_OP_ABANDON )) { + log_base *lb; + int i = 0; + + for ( lb = li->li_bases; lb; lb=lb->lb_next ) + if (( lb->lb_ops & LOG_OP_ABANDON ) && dnIsSuffix( &op->o_ndn, &lb->lb_base )) { + i = 1; + break; + } + if ( !i ) + return SLAP_CB_CONTINUE; + } + + e = accesslog_entry( op, rs, li, LOG_EN_ABANDON, &op2 ); + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%d", op->orn_msgid ); + if ( bv.bv_len < sizeof( buf ) ) { + attr_merge_one( e, ad_reqId, &bv, NULL ); + } /* else? */ + + op2.o_hdr = op->o_hdr; + op2.o_tag = LDAP_REQ_ADD; + op2.o_bd = li->li_db; + op2.o_dn = li->li_db->be_rootdn; + op2.o_ndn = li->li_db->be_rootndn; + op2.o_req_dn = e->e_name; + op2.o_req_ndn = e->e_nname; + op2.ora_e = e; + op2.o_callback = &nullsc; + op2.o_controls = cids; + memset(cids, 0, sizeof( cids )); + + op2.o_bd->be_add( &op2, &rs2 ); + if ( e == op2.ora_e ) + entry_free( e ); + + return SLAP_CB_CONTINUE; +} + +static int +accesslog_operational( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + log_info *li = on->on_bi.bi_private; + + if ( op->o_sync != SLAP_CONTROL_NONE ) + return SLAP_CB_CONTINUE; + + if ( rs->sr_entry != NULL + && dn_match( &op->o_bd->be_nsuffix[0], &rs->sr_entry->e_nname ) ) + { + Attribute **ap; + + for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next ) + /* just count */ ; + + if ( SLAP_OPATTRS( rs->sr_attr_flags ) || + ad_inlist( ad_auditContext, rs->sr_attrs ) ) + { + *ap = attr_alloc( ad_auditContext ); + attr_valadd( *ap, + &li->li_db->be_suffix[0], + &li->li_db->be_nsuffix[0], 1 ); + } + } + + return SLAP_CB_CONTINUE; +} + +static slap_overinst accesslog; + +static int +accesslog_db_init( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + log_info *li = ch_calloc(1, sizeof(log_info)); + + on->on_bi.bi_private = li; + ldap_pvt_thread_rmutex_init( &li->li_op_rmutex ); + ldap_pvt_thread_mutex_init( &li->li_log_mutex ); + return 0; +} + +static int +accesslog_db_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + log_info *li = on->on_bi.bi_private; + log_attr *la; + + if ( li->li_oldf ) + filter_free( li->li_oldf ); + for ( la=li->li_oldattrs; la; la=li->li_oldattrs ) { + li->li_oldattrs = la->next; + ch_free( la ); + } + ldap_pvt_thread_mutex_destroy( &li->li_log_mutex ); + ldap_pvt_thread_rmutex_destroy( &li->li_op_rmutex ); + free( li ); + return LDAP_SUCCESS; +} + +/* Create the logdb's root entry if it's missing */ +static void * +accesslog_db_root( + void *ctx, + void *arg ) +{ + struct re_s *rtask = arg; + slap_overinst *on = rtask->arg; + log_info *li = on->on_bi.bi_private; + + Connection conn = {0}; + OperationBuffer opbuf; + Operation *op; + + Entry *e; + int rc; + + connection_fake_init( &conn, &opbuf, ctx ); + op = &opbuf.ob_op; + op->o_bd = li->li_db; + op->o_dn = li->li_db->be_rootdn; + op->o_ndn = li->li_db->be_rootndn; + rc = be_entry_get_rw( op, li->li_db->be_nsuffix, NULL, NULL, 0, &e ); + + if ( e ) { + be_entry_release_rw( op, e, 0 ); + + } else { + SlapReply rs = {REP_RESULT}; + struct berval rdn, nrdn, attr; + char *ptr; + AttributeDescription *ad = NULL; + const char *text = NULL; + Entry *e_ctx; + BackendDB db; + + e = entry_alloc(); + ber_dupbv( &e->e_name, li->li_db->be_suffix ); + ber_dupbv( &e->e_nname, li->li_db->be_nsuffix ); + + attr_merge_one( e, slap_schema.si_ad_objectClass, + &log_container->soc_cname, NULL ); + + dnRdn( &e->e_name, &rdn ); + dnRdn( &e->e_nname, &nrdn ); + ptr = ber_bvchr( &rdn, '=' ); + + assert( ptr != NULL ); + + attr.bv_val = rdn.bv_val; + attr.bv_len = ptr - rdn.bv_val; + + slap_bv2ad( &attr, &ad, &text ); + + rdn.bv_val = ptr+1; + rdn.bv_len -= attr.bv_len + 1; + ptr = ber_bvchr( &nrdn, '=' ); + nrdn.bv_len -= ptr - nrdn.bv_val + 1; + nrdn.bv_val = ptr+1; + attr_merge_one( e, ad, &rdn, &nrdn ); + + /* Get contextCSN from main DB */ + op->o_bd = on->on_info->oi_origdb; + rc = be_entry_get_rw( op, op->o_bd->be_nsuffix, NULL, + slap_schema.si_ad_contextCSN, 0, &e_ctx ); + + if ( e_ctx ) { + Attribute *a; + + a = attr_find( e_ctx->e_attrs, slap_schema.si_ad_contextCSN ); + if ( a ) { + /* FIXME: contextCSN could have multiple values! + * should select the one with the server's SID */ + attr_merge_one( e, slap_schema.si_ad_entryCSN, + &a->a_vals[0], &a->a_nvals[0] ); + attr_merge( e, a->a_desc, a->a_vals, a->a_nvals ); + } + be_entry_release_rw( op, e_ctx, 0 ); + } + db = *li->li_db; + op->o_bd = &db; + + op->o_tag = LDAP_REQ_ADD; + op->ora_e = e; + op->o_req_dn = e->e_name; + op->o_req_ndn = e->e_nname; + op->o_callback = &nullsc; + SLAP_DBFLAGS( op->o_bd ) |= SLAP_DBFLAG_NOLASTMOD; + rc = op->o_bd->be_add( op, &rs ); + if ( e == op->ora_e ) + entry_free( e ); + } + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + ldap_pvt_runqueue_stoptask( &slapd_rq, rtask ); + ldap_pvt_runqueue_remove( &slapd_rq, rtask ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + return NULL; +} + +static int +accesslog_db_open( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + log_info *li = on->on_bi.bi_private; + + + if ( !BER_BVISEMPTY( &li->li_db_suffix )) { + li->li_db = select_backend( &li->li_db_suffix, 0 ); + ch_free( li->li_db_suffix.bv_val ); + BER_BVZERO( &li->li_db_suffix ); + } + if ( li->li_db == NULL ) { + Debug( LDAP_DEBUG_ANY, + "accesslog: \"logdb <suffix>\" missing or invalid.\n", + 0, 0, 0 ); + return 1; + } + + if ( slapMode & SLAP_TOOL_MODE ) + return 0; + + if ( BER_BVISEMPTY( &li->li_db->be_rootndn )) { + ber_dupbv( &li->li_db->be_rootdn, li->li_db->be_suffix ); + ber_dupbv( &li->li_db->be_rootndn, li->li_db->be_nsuffix ); + } + + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + ldap_pvt_runqueue_insert( &slapd_rq, 3600, accesslog_db_root, on, + "accesslog_db_root", li->li_db->be_suffix[0].bv_val ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + return 0; +} + +enum { start = 0 }; + +static int +check_rdntime_syntax (struct berval *val, + int *parts, + struct berval *fraction) +{ + /* + * GeneralizedTime YYYYmmddHH[MM[SS]][(./,)d...](Z|(+/-)HH[MM]) + * GeneralizedTime supports leap seconds, UTCTime does not. + */ + static const int ceiling[9] = { 100, 100, 12, 31, 24, 60, 60, 24, 60 }; + static const int mdays[2][12] = { + /* non-leap years */ + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + /* leap years */ + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } + }; + char *p, *e; + int part, c, c1, c2, tzoffset, leapyear = 0; + + p = val->bv_val; + e = p + val->bv_len; + + for (part = start; part < 7 && p < e; part++) { + c1 = *p; + if (!ASCII_DIGIT(c1)) { + break; + } + p++; + if (p == e) { + return LDAP_INVALID_SYNTAX; + } + c = *p++; + if (!ASCII_DIGIT(c)) { + return LDAP_INVALID_SYNTAX; + } + c += c1 * 10 - '0' * 11; + if ((part | 1) == 3) { + --c; + if (c < 0) { + return LDAP_INVALID_SYNTAX; + } + } + if (c >= ceiling[part]) { + if (! (c == 60 && part == 6 && start == 0)) + return LDAP_INVALID_SYNTAX; + } + parts[part] = c; + } + if (part < 5 + start) { + return LDAP_INVALID_SYNTAX; + } + for (; part < 9; part++) { + parts[part] = 0; + } + + /* leapyear check for the Gregorian calendar (year>1581) */ + if (parts[parts[1] == 0 ? 0 : 1] % 4 == 0) { + leapyear = 1; + } + + if (parts[3] >= mdays[leapyear][parts[2]]) { + return LDAP_INVALID_SYNTAX; + } + + if (start == 0) { + fraction->bv_val = p; + fraction->bv_len = 0; + if (p < e && (*p == '.' || *p == ',')) { + char *end_num; + while (++p < e && ASCII_DIGIT(*p)) { + /* EMPTY */; + } + if (p - fraction->bv_val == 1) { + return LDAP_INVALID_SYNTAX; + } + +#if 0 /* don't truncate trailing zeros */ + for (end_num = p; end_num[-1] == '0'; --end_num) { + /* EMPTY */; + } + c = end_num - fraction->bv_val; +#else + c = p - fraction->bv_val; +#endif + if (c != 1) fraction->bv_len = c; + } + } + + if (p == e) { + /* no time zone */ + return start == 0 ? LDAP_INVALID_SYNTAX : LDAP_SUCCESS; + } + + tzoffset = *p++; + switch (tzoffset) { + case 'Z': + /* UTC */ + break; + default: + return LDAP_INVALID_SYNTAX; + } + + return p != e ? LDAP_INVALID_SYNTAX : LDAP_SUCCESS; +} + +static int +rdnTimestampValidate( + Syntax *syntax, + struct berval *in ) +{ + int parts[9]; + struct berval fraction; + return check_rdntime_syntax(in, parts, &fraction); +} + +static int +rdnTimestampNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + int parts[9], rc; + unsigned int len; + struct berval fraction; + + rc = check_rdntime_syntax(val, parts, &fraction); + if (rc != LDAP_SUCCESS) { + return rc; + } + + len = STRLENOF("YYYYmmddHHMMSSZ") + fraction.bv_len; + normalized->bv_val = slap_sl_malloc( len + 1, ctx ); + if ( BER_BVISNULL( normalized ) ) { + return LBER_ERROR_MEMORY; + } + + sprintf( normalized->bv_val, "%02d%02d%02d%02d%02d%02d%02d", + parts[0], parts[1], parts[2] + 1, parts[3] + 1, + parts[4], parts[5], parts[6] ); + if ( !BER_BVISEMPTY( &fraction ) ) { + memcpy( normalized->bv_val + STRLENOF("YYYYmmddHHMMSSZ")-1, + fraction.bv_val, fraction.bv_len ); + normalized->bv_val[STRLENOF("YYYYmmddHHMMSSZ")-1] = '.'; + } + strcpy( normalized->bv_val + len-1, "Z" ); + normalized->bv_len = len; + + return LDAP_SUCCESS; +} + + +int accesslog_initialize() +{ + int i, rc; + Syntax *rdnTimestampSyntax; + MatchingRule *rdnTimestampMatch; + + accesslog.on_bi.bi_type = "accesslog"; + accesslog.on_bi.bi_db_init = accesslog_db_init; + accesslog.on_bi.bi_db_destroy = accesslog_db_destroy; + accesslog.on_bi.bi_db_open = accesslog_db_open; + + accesslog.on_bi.bi_op_add = accesslog_op_mod; + accesslog.on_bi.bi_op_bind = accesslog_op_misc; + accesslog.on_bi.bi_op_compare = accesslog_op_misc; + accesslog.on_bi.bi_op_delete = accesslog_op_mod; + accesslog.on_bi.bi_op_modify = accesslog_op_mod; + accesslog.on_bi.bi_op_modrdn = accesslog_op_mod; + accesslog.on_bi.bi_op_search = accesslog_op_misc; + accesslog.on_bi.bi_extended = accesslog_op_misc; + accesslog.on_bi.bi_op_unbind = accesslog_unbind; + accesslog.on_bi.bi_op_abandon = accesslog_abandon; + accesslog.on_bi.bi_operational = accesslog_operational; + + accesslog.on_bi.bi_cf_ocs = log_cfocs; + + nullsc.sc_response = slap_null_cb; + + rc = config_register_schema( log_cfats, log_cfocs ); + if ( rc ) return rc; + + /* log schema integration */ + for ( i=0; lsyntaxes[i].oid; i++ ) { + int code; + + code = register_syntax( &lsyntaxes[ i ].syn ); + if ( code != 0 ) { + Debug( LDAP_DEBUG_ANY, + "accesslog_init: register_syntax failed\n", + 0, 0, 0 ); + return code; + } + + if ( lsyntaxes[i].mrs != NULL ) { + code = mr_make_syntax_compat_with_mrs( + lsyntaxes[i].oid, lsyntaxes[i].mrs ); + if ( code < 0 ) { + Debug( LDAP_DEBUG_ANY, + "accesslog_init: " + "mr_make_syntax_compat_with_mrs " + "failed\n", + 0, 0, 0 ); + return code; + } + } + } + + for ( i=0; lattrs[i].at; i++ ) { + int code; + + code = register_at( lattrs[i].at, lattrs[i].ad, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "accesslog_init: register_at failed\n", + 0, 0, 0 ); + return -1; + } +#ifndef LDAP_DEVEL + (*lattrs[i].ad)->ad_type->sat_flags |= SLAP_AT_HIDE; +#endif + } + + /* Inject custom normalizer for reqStart/reqEnd */ + rdnTimestampMatch = ch_malloc( sizeof( MatchingRule )); + rdnTimestampSyntax = ch_malloc( sizeof( Syntax )); + *rdnTimestampMatch = *ad_reqStart->ad_type->sat_equality; + rdnTimestampMatch->smr_normalize = rdnTimestampNormalize; + *rdnTimestampSyntax = *ad_reqStart->ad_type->sat_syntax; + rdnTimestampSyntax->ssyn_validate = rdnTimestampValidate; + ad_reqStart->ad_type->sat_equality = rdnTimestampMatch; + ad_reqStart->ad_type->sat_syntax = rdnTimestampSyntax; + + rdnTimestampMatch = ch_malloc( sizeof( MatchingRule )); + rdnTimestampSyntax = ch_malloc( sizeof( Syntax )); + *rdnTimestampMatch = *ad_reqStart->ad_type->sat_equality; + rdnTimestampMatch->smr_normalize = rdnTimestampNormalize; + *rdnTimestampSyntax = *ad_reqStart->ad_type->sat_syntax; + rdnTimestampSyntax->ssyn_validate = rdnTimestampValidate; + ad_reqEnd->ad_type->sat_equality = rdnTimestampMatch; + ad_reqEnd->ad_type->sat_syntax = rdnTimestampSyntax; + + for ( i=0; locs[i].ot; i++ ) { + int code; + + code = register_oc( locs[i].ot, locs[i].oc, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "accesslog_init: register_oc failed\n", + 0, 0, 0 ); + return -1; + } +#ifndef LDAP_DEVEL + (*locs[i].oc)->soc_flags |= SLAP_OC_HIDE; +#endif + } + + return overlay_register(&accesslog); +} + +#if SLAPD_OVER_ACCESSLOG == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return accesslog_initialize(); +} +#endif + +#endif /* SLAPD_OVER_ACCESSLOG */ diff --git a/servers/slapd/overlays/auditlog.c b/servers/slapd/overlays/auditlog.c new file mode 100644 index 0000000..b98aea1 --- /dev/null +++ b/servers/slapd/overlays/auditlog.c @@ -0,0 +1,240 @@ +/* auditlog.c - log modifications for audit/history purposes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2021 The OpenLDAP Foundation. + * Portions copyright 2004-2005 Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Symas Corp. for inclusion in + * OpenLDAP Software. This work was sponsored by Hewlett-Packard. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_AUDITLOG + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/ctype.h> + +#include "slap.h" +#include "config.h" +#include "ldif.h" + +typedef struct auditlog_data { + ldap_pvt_thread_mutex_t ad_mutex; + char *ad_logfile; +} auditlog_data; + +static ConfigTable auditlogcfg[] = { + { "auditlog", "filename", 2, 2, 0, + ARG_STRING|ARG_OFFSET, + (void *)offsetof(auditlog_data, ad_logfile), + "( OLcfgOvAt:15.1 NAME 'olcAuditlogFile' " + "DESC 'Filename for auditlogging' " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs auditlogocs[] = { + { "( OLcfgOvOc:15.1 " + "NAME 'olcAuditlogConfig' " + "DESC 'Auditlog configuration' " + "SUP olcOverlayConfig " + "MAY ( olcAuditlogFile ) )", + Cft_Overlay, auditlogcfg }, + { NULL, 0, NULL } +}; + +static int fprint_ldif(FILE *f, char *name, char *val, ber_len_t len) { + char *s; + if((s = ldif_put(LDIF_PUT_VALUE, name, val, len)) == NULL) + return(-1); + fputs(s, f); + ber_memfree(s); + return(0); +} + +static int auditlog_response(Operation *op, SlapReply *rs) { + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + auditlog_data *ad = on->on_bi.bi_private; + FILE *f; + Attribute *a; + Modifications *m; + struct berval *b, *who = NULL, peername; + char *what, *whatm, *suffix; + time_t stamp; + int i; + + if ( rs->sr_err != LDAP_SUCCESS ) return SLAP_CB_CONTINUE; + + if ( !ad->ad_logfile ) return SLAP_CB_CONTINUE; + +/* +** add or modify: use modifiersName if present +** +*/ + switch(op->o_tag) { + case LDAP_REQ_MODRDN: what = "modrdn"; break; + case LDAP_REQ_DELETE: what = "delete"; break; + case LDAP_REQ_ADD: + what = "add"; + for(a = op->ora_e->e_attrs; a; a = a->a_next) + if( a->a_desc == slap_schema.si_ad_modifiersName ) { + who = &a->a_vals[0]; + break; + } + break; + case LDAP_REQ_MODIFY: + what = "modify"; + for(m = op->orm_modlist; m; m = m->sml_next) + if( m->sml_desc == slap_schema.si_ad_modifiersName && + ( m->sml_op == LDAP_MOD_ADD || + m->sml_op == LDAP_MOD_REPLACE )) { + who = &m->sml_values[0]; + break; + } + break; + default: + return SLAP_CB_CONTINUE; + } + + suffix = op->o_bd->be_suffix[0].bv_len ? op->o_bd->be_suffix[0].bv_val : + "global"; + +/* +** note: this means requestor's dn when modifiersName is null +*/ + if ( !who ) + who = &op->o_dn; + + peername = op->o_conn->c_peer_name; + ldap_pvt_thread_mutex_lock(&ad->ad_mutex); + if((f = fopen(ad->ad_logfile, "a")) == NULL) { + ldap_pvt_thread_mutex_unlock(&ad->ad_mutex); + return SLAP_CB_CONTINUE; + } + + stamp = slap_get_time(); + fprintf(f, "# %s %ld %s%s%s %s conn=%ld\n", + what, (long)stamp, suffix, who ? " " : "", who ? who->bv_val : "", + peername.bv_val ? peername.bv_val: "", op->o_conn->c_connid); + + if ( !BER_BVISEMPTY( &op->o_conn->c_dn ) && + (!who || !dn_match( who, &op->o_conn->c_dn ))) + fprintf(f, "# realdn: %s\n", op->o_conn->c_dn.bv_val ); + + fprintf(f, "dn: %s\nchangetype: %s\n", + op->o_req_dn.bv_val, what); + + switch(op->o_tag) { + case LDAP_REQ_ADD: + for(a = op->ora_e->e_attrs; a; a = a->a_next) + if((b = a->a_vals) != NULL) + for(i = 0; b[i].bv_val; i++) + fprint_ldif(f, a->a_desc->ad_cname.bv_val, b[i].bv_val, b[i].bv_len); + break; + + case LDAP_REQ_MODIFY: + for(m = op->orm_modlist; m; m = m->sml_next) { + switch(m->sml_op & LDAP_MOD_OP) { + case LDAP_MOD_ADD: whatm = "add"; break; + case LDAP_MOD_REPLACE: whatm = "replace"; break; + case LDAP_MOD_DELETE: whatm = "delete"; break; + case LDAP_MOD_INCREMENT: whatm = "increment"; break; + default: + fprintf(f, "# MOD_TYPE_UNKNOWN:%02x\n", m->sml_op & LDAP_MOD_OP); + continue; + } + fprintf(f, "%s: %s\n", whatm, m->sml_desc->ad_cname.bv_val); + if((b = m->sml_values) != NULL) + for(i = 0; b[i].bv_val; i++) + fprint_ldif(f, m->sml_desc->ad_cname.bv_val, b[i].bv_val, b[i].bv_len); + fprintf(f, "-\n"); + } + break; + + case LDAP_REQ_MODRDN: + fprintf(f, "newrdn: %s\ndeleteoldrdn: %s\n", + op->orr_newrdn.bv_val, op->orr_deleteoldrdn ? "1" : "0"); + if(op->orr_newSup) fprintf(f, "newsuperior: %s\n", op->orr_newSup->bv_val); + break; + + case LDAP_REQ_DELETE: + /* nothing else needed */ + break; + } + + fprintf(f, "# end %s %ld\n\n", what, (long)stamp); + + fclose(f); + ldap_pvt_thread_mutex_unlock(&ad->ad_mutex); + return SLAP_CB_CONTINUE; +} + +static slap_overinst auditlog; + +static int +auditlog_db_init( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + auditlog_data *ad = ch_calloc(1, sizeof(auditlog_data)); + + on->on_bi.bi_private = ad; + ldap_pvt_thread_mutex_init( &ad->ad_mutex ); + return 0; +} + +static int +auditlog_db_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + auditlog_data *ad = on->on_bi.bi_private; + + ldap_pvt_thread_mutex_destroy( &ad->ad_mutex ); + free( ad->ad_logfile ); + free( ad ); + return 0; +} + +int auditlog_initialize() { + int rc; + + auditlog.on_bi.bi_type = "auditlog"; + auditlog.on_bi.bi_db_init = auditlog_db_init; + auditlog.on_bi.bi_db_destroy = auditlog_db_destroy; + auditlog.on_response = auditlog_response; + + auditlog.on_bi.bi_cf_ocs = auditlogocs; + rc = config_register_schema( auditlogcfg, auditlogocs ); + if ( rc ) return rc; + + return overlay_register(&auditlog); +} + +#if SLAPD_OVER_AUDITLOG == SLAPD_MOD_DYNAMIC && defined(PIC) +int +init_module( int argc, char *argv[] ) +{ + return auditlog_initialize(); +} +#endif + +#endif /* SLAPD_OVER_AUDITLOG */ diff --git a/servers/slapd/overlays/collect.c b/servers/slapd/overlays/collect.c new file mode 100644 index 0000000..08d87af --- /dev/null +++ b/servers/slapd/overlays/collect.c @@ -0,0 +1,439 @@ +/* collect.c - Demonstration of overlay code */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * Portions Copyright 2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_COLLECT + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "config.h" + +#include "lutil.h" + +/* This is a cheap hack to implement a collective attribute. + * + * This demonstration overlay looks for a specified attribute in an + * ancestor of a given entry and adds that attribute to the given + * entry when it is returned in a search response. It takes no effect + * for any other operations. If the ancestor does not exist, there + * is no effect. If no attribute was configured, there is no effect. + */ + +typedef struct collect_info { + struct collect_info *ci_next; + struct berval ci_dn; + int ci_ad_num; + AttributeDescription *ci_ad[1]; +} collect_info; + +static int collect_cf( ConfigArgs *c ); + +static ConfigTable collectcfg[] = { + { "collectinfo", "dn> <attribute", 3, 3, 0, + ARG_MAGIC, collect_cf, + "( OLcfgOvAt:19.1 NAME 'olcCollectInfo' " + "DESC 'DN of entry and attribute to distribute' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs collectocs[] = { + { "( OLcfgOvOc:19.1 " + "NAME 'olcCollectConfig' " + "DESC 'Collective Attribute configuration' " + "SUP olcOverlayConfig " + "MAY olcCollectInfo )", + Cft_Overlay, collectcfg }, + { NULL, 0, NULL } +}; + +/* + * inserts a collect_info into on->on_bi.bi_private taking into account + * order. this means longer dn's (i.e. more specific dn's) will be found + * first when searching, allowing some limited overlap of dn's + */ +static void +insert_ordered( slap_overinst *on, collect_info *ci ) { + collect_info *find = on->on_bi.bi_private; + collect_info *prev = NULL; + int found = 0; + + while (!found) { + if (find == NULL) { + if (prev == NULL) { + /* base case - empty list */ + on->on_bi.bi_private = ci; + ci->ci_next = NULL; + } else { + /* final case - end of list */ + prev->ci_next = ci; + ci->ci_next = NULL; + } + found = 1; + } else if (find->ci_dn.bv_len < ci->ci_dn.bv_len) { + /* insert into list here */ + if (prev == NULL) { + /* entry is head of list */ + ci->ci_next = on->on_bi.bi_private; + on->on_bi.bi_private = ci; + } else { + /* entry is not head of list */ + prev->ci_next = ci; + ci->ci_next = find; + } + found = 1; + } else { + /* keep looking */ + prev = find; + find = find->ci_next; + } + } +} + +static int +collect_cf( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + int rc = 1, idx; + + switch( c->op ) { + case SLAP_CONFIG_EMIT: + { + collect_info *ci; + for ( ci = on->on_bi.bi_private; ci; ci = ci->ci_next ) { + struct berval bv; + char *ptr; + int len; + + /* calculate the length & malloc memory */ + bv.bv_len = ci->ci_dn.bv_len + STRLENOF("\"\" "); + for (idx=0; idx<ci->ci_ad_num; idx++) { + bv.bv_len += ci->ci_ad[idx]->ad_cname.bv_len; + if (idx<(ci->ci_ad_num-1)) { + bv.bv_len++; + } + } + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + + /* copy the value and update len */ + len = snprintf( bv.bv_val, bv.bv_len + 1, "\"%s\" ", + ci->ci_dn.bv_val); + ptr = bv.bv_val + len; + for (idx=0; idx<ci->ci_ad_num; idx++) { + ptr = lutil_strncopy( ptr, + ci->ci_ad[idx]->ad_cname.bv_val, + ci->ci_ad[idx]->ad_cname.bv_len); + if (idx<(ci->ci_ad_num-1)) { + *ptr++ = ','; + } + } + *ptr = '\0'; + bv.bv_len = ptr - bv.bv_val; + + ber_bvarray_add( &c->rvalue_vals, &bv ); + rc = 0; + } + } + break; + case LDAP_MOD_DELETE: + if ( c->valx == -1 ) { + /* Delete entire attribute */ + collect_info *ci; + while (( ci = on->on_bi.bi_private )) { + on->on_bi.bi_private = ci->ci_next; + ch_free( ci->ci_dn.bv_val ); + ch_free( ci ); + } + } else { + /* Delete just one value */ + collect_info **cip, *ci; + int i; + cip = (collect_info **)&on->on_bi.bi_private; + ci = *cip; + for ( i=0; i < c->valx; i++ ) { + cip = &ci->ci_next; + ci = *cip; + } + *cip = ci->ci_next; + ch_free( ci->ci_dn.bv_val ); + ch_free( ci ); + } + rc = 0; + break; + case SLAP_CONFIG_ADD: + case LDAP_MOD_ADD: + { + collect_info *ci; + struct berval bv, dn; + const char *text; + int idx, count=0; + char *arg; + + /* count delimiters in attribute argument */ + arg = strtok(c->argv[2], ","); + while (arg!=NULL) { + count++; + arg = strtok(NULL, ","); + } + + /* validate and normalize dn */ + ber_str2bv( c->argv[1], 0, 0, &bv ); + if ( dnNormalize( 0, NULL, NULL, &bv, &dn, NULL ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid DN: \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + + /* check for duplicate DNs */ + for ( ci = (collect_info *)on->on_bi.bi_private; ci; + ci = ci->ci_next ) { + /* If new DN is longest, there are no possible matches */ + if ( dn.bv_len > ci->ci_dn.bv_len ) { + ci = NULL; + break; + } + if ( bvmatch( &dn, &ci->ci_dn )) { + break; + } + } + if ( ci ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s DN already configured: \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + + /* allocate config info with room for attribute array */ + ci = ch_malloc( sizeof( collect_info ) + + sizeof( AttributeDescription * ) * count ); + + /* load attribute description for attribute list */ + arg = c->argv[2]; + for( idx=0; idx<count; idx++) { + ci->ci_ad[idx] = NULL; + + if ( slap_str2ad( arg, &ci->ci_ad[idx], &text ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s attribute description unknown: \"%s\"", + c->argv[0], arg); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg, 0 ); + ch_free( ci ); + return ARG_BAD_CONF; + } + while(*arg!='\0') { + arg++; /* skip to end of argument */ + } + if (idx<count-1) { + arg++; /* skip inner delimiters */ + } + } + + /* The on->on_bi.bi_private pointer can be used for + * anything this instance of the overlay needs. + */ + ci->ci_ad[count] = NULL; + ci->ci_ad_num = count; + ci->ci_dn = dn; + + /* creates list of ci's ordered by dn length */ + insert_ordered ( on, ci ); + + /* New ci wasn't simply appended to end, adjust its + * position in the config entry's a_vals + */ + if ( c->ca_entry && ci->ci_next ) { + Attribute *a = attr_find( c->ca_entry->e_attrs, + collectcfg[0].ad ); + if ( a ) { + struct berval bv, nbv; + collect_info *c2 = (collect_info *)on->on_bi.bi_private; + int i, j; + for ( i=0; c2 != ci; i++, c2 = c2->ci_next ); + bv = a->a_vals[a->a_numvals-1]; + nbv = a->a_nvals[a->a_numvals-1]; + for ( j=a->a_numvals-1; j>i; j-- ) { + a->a_vals[j] = a->a_vals[j-1]; + a->a_nvals[j] = a->a_nvals[j-1]; + } + a->a_vals[j] = bv; + a->a_nvals[j] = nbv; + } + } + + rc = 0; + } + } + return rc; +} + +static int +collect_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + collect_info *ci; + + while (( ci = on->on_bi.bi_private )) { + on->on_bi.bi_private = ci->ci_next; + ch_free( ci->ci_dn.bv_val ); + ch_free( ci ); + } + return 0; +} + +static int +collect_modify( Operation *op, SlapReply *rs) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + collect_info *ci = on->on_bi.bi_private; + Modifications *ml; + char errMsg[100]; + int idx; + + for ( ml = op->orm_modlist; ml != NULL; ml = ml->sml_next) { + for (; ci; ci=ci->ci_next ) { + /* Is this entry an ancestor of this collectinfo ? */ + if (!dnIsSuffix(&op->o_req_ndn, &ci->ci_dn)) { + /* this collectinfo does not match */ + continue; + } + + /* Is this entry the same as the template DN ? */ + if ( dn_match(&op->o_req_ndn, &ci->ci_dn)) { + /* all changes in this ci are allowed */ + continue; + } + + /* check for collect attributes - disallow modify if present */ + for(idx=0; idx<ci->ci_ad_num; idx++) { + if (ml->sml_desc == ci->ci_ad[idx]) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + snprintf( errMsg, sizeof( errMsg ), + "cannot change virtual attribute '%s'", + ci->ci_ad[idx]->ad_cname.bv_val); + rs->sr_text = errMsg; + send_ldap_result( op, rs ); + return rs->sr_err; + } + } + } + + } + + return SLAP_CB_CONTINUE; +} + +static int +collect_response( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + collect_info *ci = on->on_bi.bi_private; + + /* If we've been configured and the current response is + * a search entry + */ + if ( ci && rs->sr_type == REP_SEARCH ) { + int rc; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + + for (; ci; ci=ci->ci_next ) { + int idx=0; + + /* Is this entry an ancestor of this collectinfo ? */ + if (!dnIsSuffix(&rs->sr_entry->e_nname, &ci->ci_dn)) { + /* collectinfo does not match */ + continue; + } + + /* Is this entry the same as the template DN ? */ + if ( dn_match(&rs->sr_entry->e_nname, &ci->ci_dn)) { + /* dont apply change to parent */ + continue; + } + + /* The current entry may live in a cache, so + * don't modify it directly. Make a copy and + * work with that instead. + */ + rs_entry2modifiable( op, rs, on ); + + /* Loop for each attribute in this collectinfo */ + for(idx=0; idx<ci->ci_ad_num; idx++) { + BerVarray vals = NULL; + + /* Extract the values of the desired attribute from + * the ancestor entry */ + rc = backend_attribute( op, NULL, &ci->ci_dn, + ci->ci_ad[idx], &vals, ACL_READ ); + + /* If there are any values, merge them into the + * current search result + */ + if ( vals ) { + attr_merge_normalize( rs->sr_entry, ci->ci_ad[idx], + vals, op->o_tmpmemctx ); + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + } + } + } + + /* Default is to just fall through to the normal processing */ + return SLAP_CB_CONTINUE; +} + +static slap_overinst collect; + +int collect_initialize() { + int code; + + collect.on_bi.bi_type = "collect"; + collect.on_bi.bi_db_destroy = collect_destroy; + collect.on_bi.bi_op_modify = collect_modify; + collect.on_response = collect_response; + + collect.on_bi.bi_cf_ocs = collectocs; + code = config_register_schema( collectcfg, collectocs ); + if ( code ) return code; + + return overlay_register( &collect ); +} + +#if SLAPD_OVER_COLLECT == SLAPD_MOD_DYNAMIC +int init_module(int argc, char *argv[]) { + return collect_initialize(); +} +#endif + +#endif /* SLAPD_OVER_COLLECT */ diff --git a/servers/slapd/overlays/constraint.c b/servers/slapd/overlays/constraint.c new file mode 100644 index 0000000..ccc942f --- /dev/null +++ b/servers/slapd/overlays/constraint.c @@ -0,0 +1,1231 @@ +/* $OpenLDAP$ */ +/* constraint.c - Overlay to constrain attributes to certain values */ +/* + * Copyright 2003-2004 Hewlett-Packard Company + * Copyright 2007 Emmanuel Dreyfus + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* + * Authors: Neil Dunbar <neil.dunbar@hp.com> + * Emmannuel Dreyfus <manu@netbsd.org> + */ +#include "portable.h" + +#ifdef SLAPD_OVER_CONSTRAINT + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> +#include <ac/regex.h> + +#include "lutil.h" +#include "slap.h" +#include "config.h" + +/* + * This overlay limits the values which can be placed into an + * attribute, over and above the limits placed by the schema. + * + * It traps only LDAP adds and modify commands (and only seeks to + * control the add and modify value mods of a modify) + */ + +#define REGEX_STR "regex" +#define URI_STR "uri" +#define SET_STR "set" +#define SIZE_STR "size" +#define COUNT_STR "count" + +/* + * Linked list of attribute constraints which we should enforce. + * This is probably a sub optimal structure - some form of sorted + * array would be better if the number of attributes contrained is + * likely to be much bigger than 4 or 5. We stick with a list for + * the moment. + */ + +typedef struct constraint { + struct constraint *ap_next; + AttributeDescription **ap; + + LDAPURLDesc *restrict_lud; + struct berval restrict_ndn; + Filter *restrict_filter; + struct berval restrict_val; + + int type; + regex_t *re; + LDAPURLDesc *lud; + int set; + size_t size; + size_t count; + AttributeDescription **attrs; + struct berval val; /* constraint value */ + struct berval dn; + struct berval filter; +} constraint; + +enum { + CONSTRAINT_ATTRIBUTE = 1, + CONSTRAINT_COUNT, + CONSTRAINT_SIZE, + CONSTRAINT_REGEX, + CONSTRAINT_SET, + CONSTRAINT_URI, +}; + +static ConfigDriver constraint_cf_gen; + +static ConfigTable constraintcfg[] = { + { "constraint_attribute", "attribute[list]> (regex|uri|set|size|count) <value> [<restrict URI>]", + 4, 0, 0, ARG_MAGIC | CONSTRAINT_ATTRIBUTE, constraint_cf_gen, + "( OLcfgOvAt:13.1 NAME 'olcConstraintAttribute' " + "DESC 'constraint for list of attributes' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs constraintocs[] = { + { "( OLcfgOvOc:13.1 " + "NAME 'olcConstraintConfig' " + "DESC 'Constraint overlay configuration' " + "SUP olcOverlayConfig " + "MAY ( olcConstraintAttribute ) )", + Cft_Overlay, constraintcfg }, + { NULL, 0, NULL } +}; + +static void +constraint_free( constraint *cp, int freeme ) +{ + if (cp->restrict_lud) + ldap_free_urldesc(cp->restrict_lud); + if (!BER_BVISNULL(&cp->restrict_ndn)) + ch_free(cp->restrict_ndn.bv_val); + if (cp->restrict_filter != NULL && cp->restrict_filter != slap_filter_objectClass_pres) + filter_free(cp->restrict_filter); + if (!BER_BVISNULL(&cp->restrict_val)) + ch_free(cp->restrict_val.bv_val); + if (cp->re) { + regfree(cp->re); + ch_free(cp->re); + } + if (!BER_BVISNULL(&cp->val)) + ch_free(cp->val.bv_val); + if (cp->lud) + ldap_free_urldesc(cp->lud); + if (cp->attrs) + ch_free(cp->attrs); + if (cp->ap) + ch_free(cp->ap); + if (freeme) + ch_free(cp); +} + +static int +constraint_cf_gen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)(c->bi); + constraint *cn = on->on_bi.bi_private, *cp; + struct berval bv; + int i, rc = 0; + constraint ap = { NULL }; + const char *text = NULL; + + switch ( c->op ) { + case SLAP_CONFIG_EMIT: + switch (c->type) { + case CONSTRAINT_ATTRIBUTE: + for (cp=cn; cp; cp=cp->ap_next) { + char *s; + char *tstr = NULL; + int quotes = 0, numeric = 0; + int j; + size_t val; + char val_buf[SLAP_TEXT_BUFLEN] = { '\0' }; + + bv.bv_len = STRLENOF(" "); + for (j = 0; cp->ap[j]; j++) { + bv.bv_len += cp->ap[j]->ad_cname.bv_len; + } + + /* room for commas */ + bv.bv_len += j - 1; + + switch (cp->type) { + case CONSTRAINT_COUNT: + tstr = COUNT_STR; + val = cp->count; + numeric = 1; + break; + case CONSTRAINT_SIZE: + tstr = SIZE_STR; + val = cp->size; + numeric = 1; + break; + case CONSTRAINT_REGEX: + tstr = REGEX_STR; + quotes = 1; + break; + case CONSTRAINT_SET: + tstr = SET_STR; + quotes = 1; + break; + case CONSTRAINT_URI: + tstr = URI_STR; + quotes = 1; + break; + default: + abort(); + } + + bv.bv_len += strlen(tstr); + bv.bv_len += cp->val.bv_len + 2*quotes; + + if (cp->restrict_lud != NULL) { + bv.bv_len += cp->restrict_val.bv_len + STRLENOF(" restrict=\"\""); + } + + if (numeric) { + int len = snprintf(val_buf, sizeof(val_buf), "%zu", val); + if (len <= 0) { + /* error */ + return -1; + } + bv.bv_len += len; + } + + s = bv.bv_val = ch_malloc(bv.bv_len + 1); + + s = lutil_strncopy( s, cp->ap[0]->ad_cname.bv_val, cp->ap[0]->ad_cname.bv_len ); + for (j = 1; cp->ap[j]; j++) { + *s++ = ','; + s = lutil_strncopy( s, cp->ap[j]->ad_cname.bv_val, cp->ap[j]->ad_cname.bv_len ); + } + *s++ = ' '; + s = lutil_strcopy( s, tstr ); + *s++ = ' '; + if (numeric) { + s = lutil_strcopy( s, val_buf ); + } else { + if ( quotes ) *s++ = '"'; + s = lutil_strncopy( s, cp->val.bv_val, cp->val.bv_len ); + if ( quotes ) *s++ = '"'; + } + if (cp->restrict_lud != NULL) { + s = lutil_strcopy( s, " restrict=\"" ); + s = lutil_strncopy( s, cp->restrict_val.bv_val, cp->restrict_val.bv_len ); + *s++ = '"'; + } + *s = '\0'; + + rc = value_add_one( &c->rvalue_vals, &bv ); + if (rc == LDAP_SUCCESS) + rc = value_add_one( &c->rvalue_nvals, &bv ); + ch_free(bv.bv_val); + if (rc) return rc; + } + break; + default: + abort(); + break; + } + break; + case LDAP_MOD_DELETE: + switch (c->type) { + case CONSTRAINT_ATTRIBUTE: + if (!cn) break; /* nothing to do */ + + if (c->valx < 0) { + /* zap all constraints */ + while (cn) { + cp = cn->ap_next; + constraint_free( cn, 1 ); + cn = cp; + } + + on->on_bi.bi_private = NULL; + } else { + constraint **cpp; + + /* zap constraint numbered 'valx' */ + for(i=0, cp = cn, cpp = &cn; + (cp) && (i<c->valx); + i++, cpp = &cp->ap_next, cp = *cpp); + + if (cp) { + /* zap cp, and join cpp to cp->ap_next */ + *cpp = cp->ap_next; + constraint_free( cp, 1 ); + } + on->on_bi.bi_private = cn; + } + break; + + default: + abort(); + break; + } + break; + case SLAP_CONFIG_ADD: + case LDAP_MOD_ADD: + switch (c->type) { + case CONSTRAINT_ATTRIBUTE: { + int j; + char **attrs = ldap_str2charray( c->argv[1], "," ); + + for ( j = 0; attrs[j]; j++) + /* just count */ ; + ap.ap = ch_calloc( sizeof(AttributeDescription*), j + 1 ); + for ( j = 0; attrs[j]; j++) { + if ( slap_str2ad( attrs[j], &ap.ap[j], &text ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s <%s>: %s\n", c->argv[0], attrs[j], text ); + rc = ARG_BAD_CONF; + goto done; + } + } + + if ( strcasecmp( c->argv[2], REGEX_STR ) == 0) { + int err; + + ap.type = CONSTRAINT_REGEX; + ap.re = ch_malloc( sizeof(regex_t) ); + if ((err = regcomp( ap.re, + c->argv[3], REG_EXTENDED )) != 0) { + char errmsg[1024]; + + regerror( err, ap.re, errmsg, sizeof(errmsg) ); + ch_free(ap.re); + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: Illegal regular expression \"%s\": Error %s", + c->argv[0], c->argv[1], c->argv[3], errmsg); + ap.re = NULL; + rc = ARG_BAD_CONF; + goto done; + } + ber_str2bv( c->argv[3], 0, 1, &ap.val ); + } else if ( strcasecmp( c->argv[2], SIZE_STR ) == 0 ) { + size_t size; + char *endptr; + + ap.type = CONSTRAINT_SIZE; + ap.size = strtoull(c->argv[3], &endptr, 10); + if ( *endptr ) + rc = ARG_BAD_CONF; + } else if ( strcasecmp( c->argv[2], COUNT_STR ) == 0 ) { + size_t count; + char *endptr; + + ap.type = CONSTRAINT_COUNT; + ap.count = strtoull(c->argv[3], &endptr, 10); + if ( *endptr ) + rc = ARG_BAD_CONF; + } else if ( strcasecmp( c->argv[2], URI_STR ) == 0 ) { + int err; + + ap.type = CONSTRAINT_URI; + err = ldap_url_parse(c->argv[3], &ap.lud); + if ( err != LDAP_URL_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: Invalid URI \"%s\"", + c->argv[0], c->argv[1], c->argv[3]); + rc = ARG_BAD_CONF; + goto done; + } + + if (ap.lud->lud_host != NULL) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: unsupported hostname in URI \"%s\"", + c->argv[0], c->argv[1], c->argv[3]); + ldap_free_urldesc(ap.lud); + rc = ARG_BAD_CONF; + goto done; + } + + for ( i=0; ap.lud->lud_attrs[i]; i++); + /* FIXME: This is worthless without at least one attr */ + if ( i ) { + ap.attrs = ch_malloc( (i+1)*sizeof(AttributeDescription *)); + for ( i=0; ap.lud->lud_attrs[i]; i++) { + ap.attrs[i] = NULL; + if ( slap_str2ad( ap.lud->lud_attrs[i], &ap.attrs[i], &text ) ) { + ch_free( ap.attrs ); + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s <%s>: %s\n", c->argv[0], ap.lud->lud_attrs[i], text ); + rc = ARG_BAD_CONF; + goto done; + } + } + ap.attrs[i] = NULL; + } + + if (ap.lud->lud_dn == NULL) { + ap.lud->lud_dn = ch_strdup(""); + } else { + struct berval dn, ndn; + + ber_str2bv( ap.lud->lud_dn, 0, 0, &dn ); + if (dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL ) ) { + /* cleanup */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: URI %s DN normalization failed", + c->argv[0], c->argv[1], c->argv[3] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg, 0 ); + rc = ARG_BAD_CONF; + goto done; + } + ldap_memfree( ap.lud->lud_dn ); + ap.lud->lud_dn = ndn.bv_val; + } + + if (ap.lud->lud_filter == NULL) { + ap.lud->lud_filter = ch_strdup("objectClass=*"); + } else if ( ap.lud->lud_filter[0] == '(' ) { + ber_len_t len = strlen( ap.lud->lud_filter ); + if ( ap.lud->lud_filter[len - 1] != ')' ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: invalid URI filter: %s", + c->argv[0], c->argv[1], ap.lud->lud_filter ); + rc = ARG_BAD_CONF; + goto done; + } + AC_MEMCPY( &ap.lud->lud_filter[0], &ap.lud->lud_filter[1], len - 2 ); + ap.lud->lud_filter[len - 2] = '\0'; + } + + ber_str2bv( c->argv[3], 0, 1, &ap.val ); + + } else if ( strcasecmp( c->argv[2], SET_STR ) == 0 ) { + ap.set = 1; + ber_str2bv( c->argv[3], 0, 1, &ap.val ); + ap.type = CONSTRAINT_SET; + + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: Unknown constraint type: %s", + c->argv[0], c->argv[1], c->argv[2] ); + rc = ARG_BAD_CONF; + goto done; + } + + if ( c->argc > 4 ) { + int argidx; + + for ( argidx = 4; argidx < c->argc; argidx++ ) { + if ( strncasecmp( c->argv[argidx], "restrict=", STRLENOF("restrict=") ) == 0 ) { + int err; + char *arg = c->argv[argidx] + STRLENOF("restrict="); + + err = ldap_url_parse(arg, &ap.restrict_lud); + if ( err != LDAP_URL_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: Invalid restrict URI \"%s\"", + c->argv[0], c->argv[1], arg); + rc = ARG_BAD_CONF; + goto done; + } + + if (ap.restrict_lud->lud_host != NULL) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: unsupported hostname in restrict URI \"%s\"", + c->argv[0], c->argv[1], arg); + rc = ARG_BAD_CONF; + goto done; + } + + if ( ap.restrict_lud->lud_attrs != NULL ) { + if ( ap.restrict_lud->lud_attrs[0] != NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: attrs not allowed in restrict URI %s\n", + c->argv[0], c->argv[1], arg); + rc = ARG_BAD_CONF; + goto done; + } + ldap_memvfree((void *)ap.restrict_lud->lud_attrs); + ap.restrict_lud->lud_attrs = NULL; + } + + if (ap.restrict_lud->lud_dn != NULL) { + if (ap.restrict_lud->lud_dn[0] == '\0') { + ldap_memfree(ap.restrict_lud->lud_dn); + ap.restrict_lud->lud_dn = NULL; + + } else { + struct berval dn, ndn; + int j; + + ber_str2bv(ap.restrict_lud->lud_dn, 0, 0, &dn); + if (dnNormalize(0, NULL, NULL, &dn, &ndn, NULL)) { + /* cleanup */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: restrict URI %s DN normalization failed", + c->argv[0], c->argv[1], arg ); + rc = ARG_BAD_CONF; + goto done; + } + + assert(c->be != NULL); + if (c->be->be_nsuffix == NULL) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: restrict URI requires suffix", + c->argv[0], c->argv[1] ); + rc = ARG_BAD_CONF; + goto done; + } + + for ( j = 0; !BER_BVISNULL(&c->be->be_nsuffix[j]); j++) { + if (dnIsSuffix(&ndn, &c->be->be_nsuffix[j])) break; + } + + if (BER_BVISNULL(&c->be->be_nsuffix[j])) { + /* error */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: restrict URI DN %s not within database naming context(s)", + c->argv[0], c->argv[1], dn.bv_val ); + rc = ARG_BAD_CONF; + goto done; + } + + ap.restrict_ndn = ndn; + } + } + + if (ap.restrict_lud->lud_filter != NULL) { + ap.restrict_filter = str2filter(ap.restrict_lud->lud_filter); + if (ap.restrict_filter == NULL) { + /* error */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: restrict URI filter %s invalid", + c->argv[0], c->argv[1], ap.restrict_lud->lud_filter ); + rc = ARG_BAD_CONF; + goto done; + } + } + + ber_str2bv(c->argv[argidx] + STRLENOF("restrict="), 0, 1, &ap.restrict_val); + + } else { + /* cleanup */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: unrecognized arg #%d (%s)", + c->argv[0], c->argv[1], argidx, c->argv[argidx] ); + rc = ARG_BAD_CONF; + goto done; + } + } + } + +done:; + if ( rc == LDAP_SUCCESS ) { + constraint *a2 = ch_calloc( sizeof(constraint), 1 ); + a2->ap_next = on->on_bi.bi_private; + a2->ap = ap.ap; + a2->type = ap.type; + a2->re = ap.re; + a2->val = ap.val; + a2->lud = ap.lud; + a2->set = ap.set; + a2->size = ap.size; + a2->count = ap.count; + if ( a2->lud ) { + ber_str2bv(a2->lud->lud_dn, 0, 0, &a2->dn); + ber_str2bv(a2->lud->lud_filter, 0, 0, &a2->filter); + } + a2->attrs = ap.attrs; + a2->restrict_lud = ap.restrict_lud; + a2->restrict_ndn = ap.restrict_ndn; + a2->restrict_filter = ap.restrict_filter; + a2->restrict_val = ap.restrict_val; + on->on_bi.bi_private = a2; + + } else { + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg, 0 ); + constraint_free( &ap, 0 ); + } + + ldap_memvfree((void**)attrs); + } break; + default: + abort(); + break; + } + break; + default: + abort(); + } + + return rc; +} + +static int +constraint_uri_cb( Operation *op, SlapReply *rs ) +{ + if(rs->sr_type == REP_SEARCH) { + int *foundp = op->o_callback->sc_private; + + *foundp = 1; + + Debug(LDAP_DEBUG_TRACE, "==> constraint_uri_cb <%s>\n", + rs->sr_entry ? rs->sr_entry->e_name.bv_val : "UNKNOWN_DN", 0, 0); + } + return 0; +} + +static int +constraint_violation( constraint *c, struct berval *bv, Operation *op ) +{ + if ((!c) || (!bv)) return LDAP_SUCCESS; + + switch (c->type) { + case CONSTRAINT_SIZE: + if (bv->bv_len > c->size) + return LDAP_CONSTRAINT_VIOLATION; /* size violation */ + break; + case CONSTRAINT_REGEX: + if (regexec(c->re, bv->bv_val, 0, NULL, 0) == REG_NOMATCH) + return LDAP_CONSTRAINT_VIOLATION; /* regular expression violation */ + break; + case CONSTRAINT_URI: { + Operation nop = *op; + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + slap_callback cb = { 0 }; + int i; + int found = 0; + int rc; + size_t len; + struct berval filterstr; + char *ptr; + + cb.sc_response = constraint_uri_cb; + cb.sc_private = &found; + + nop.o_protocol = LDAP_VERSION3; + nop.o_tag = LDAP_REQ_SEARCH; + nop.o_time = slap_get_time(); + if (c->lud->lud_dn) { + struct berval dn; + + ber_str2bv(c->lud->lud_dn, 0, 0, &dn); + nop.o_req_dn = dn; + nop.o_req_ndn = dn; + nop.o_bd = select_backend(&nop.o_req_ndn, 1 ); + if (!nop.o_bd) { + return LDAP_NO_SUCH_OBJECT; /* unexpected error */ + } + if (!nop.o_bd->be_search) { + return LDAP_OTHER; /* unexpected error */ + } + } else { + nop.o_req_dn = nop.o_bd->be_nsuffix[0]; + nop.o_req_ndn = nop.o_bd->be_nsuffix[0]; + nop.o_bd = on->on_info->oi_origdb; + } + nop.o_do_not_cache = 1; + nop.o_callback = &cb; + + nop.ors_scope = c->lud->lud_scope; + nop.ors_deref = LDAP_DEREF_NEVER; + nop.ors_slimit = SLAP_NO_LIMIT; + nop.ors_tlimit = SLAP_NO_LIMIT; + nop.ors_limit = NULL; + + nop.ors_attrsonly = 0; + nop.ors_attrs = slap_anlist_no_attrs; + + len = STRLENOF("(&(") + + c->filter.bv_len + + STRLENOF(")(|"); + + for (i = 0; c->attrs[i]; i++) { + len += STRLENOF("(") + + c->attrs[i]->ad_cname.bv_len + + STRLENOF("=") + + bv->bv_len + + STRLENOF(")"); + } + + len += STRLENOF("))"); + filterstr.bv_len = len; + filterstr.bv_val = op->o_tmpalloc(len + 1, op->o_tmpmemctx); + + ptr = filterstr.bv_val + + snprintf(filterstr.bv_val, len, "(&(%s)(|", c->lud->lud_filter); + for (i = 0; c->attrs[i]; i++) { + *ptr++ = '('; + ptr = lutil_strcopy( ptr, c->attrs[i]->ad_cname.bv_val ); + *ptr++ = '='; + ptr = lutil_strcopy( ptr, bv->bv_val ); + *ptr++ = ')'; + } + *ptr++ = ')'; + *ptr++ = ')'; + *ptr++ = '\0'; + + nop.ors_filterstr = filterstr; + nop.ors_filter = str2filter_x(&nop, filterstr.bv_val); + if ( nop.ors_filter == NULL ) { + Debug( LDAP_DEBUG_ANY, + "%s constraint_violation uri filter=\"%s\" invalid\n", + op->o_log_prefix, filterstr.bv_val, 0 ); + rc = LDAP_OTHER; + + } else { + SlapReply nrs = { REP_RESULT }; + + Debug(LDAP_DEBUG_TRACE, + "==> constraint_violation uri filter = %s\n", + filterstr.bv_val, 0, 0); + + rc = nop.o_bd->be_search( &nop, &nrs ); + + Debug(LDAP_DEBUG_TRACE, + "==> constraint_violation uri rc = %d, found = %d\n", + rc, found, 0); + } + op->o_tmpfree(filterstr.bv_val, op->o_tmpmemctx); + + if ((rc != LDAP_SUCCESS) && (rc != LDAP_NO_SUCH_OBJECT)) { + return rc; /* unexpected error */ + } + + if (!found) + return LDAP_CONSTRAINT_VIOLATION; /* constraint violation */ + break; + } + } + + return LDAP_SUCCESS; +} + +static char * +print_message( struct berval *errtext, AttributeDescription *a ) +{ + char *ret; + int sz; + + sz = errtext->bv_len + sizeof(" on ") + a->ad_cname.bv_len; + ret = ch_malloc(sz); + snprintf( ret, sz, "%s on %s", errtext->bv_val, a->ad_cname.bv_val ); + return ret; +} + +static unsigned +constraint_count_attr(Entry *e, AttributeDescription *ad) +{ + struct Attribute *a; + + if ((a = attr_find(e->e_attrs, ad)) != NULL) + return a->a_numvals; + return 0; +} + +static int +constraint_check_restrict( Operation *op, constraint *c, Entry *e ) +{ + assert( c->restrict_lud != NULL ); + + if ( c->restrict_lud->lud_dn != NULL ) { + int diff = e->e_nname.bv_len - c->restrict_ndn.bv_len; + + if ( diff < 0 ) { + return 0; + } + + if ( c->restrict_lud->lud_scope == LDAP_SCOPE_BASE ) { + return bvmatch( &e->e_nname, &c->restrict_ndn ); + } + + if ( !dnIsSuffix( &e->e_nname, &c->restrict_ndn ) ) { + return 0; + } + + if ( c->restrict_lud->lud_scope != LDAP_SCOPE_SUBTREE ) { + struct berval pdn; + + if ( diff == 0 ) { + return 0; + } + + dnParent( &e->e_nname, &pdn ); + + if ( c->restrict_lud->lud_scope == LDAP_SCOPE_ONELEVEL + && pdn.bv_len != c->restrict_ndn.bv_len ) + { + return 0; + } + } + } + + if ( c->restrict_filter != NULL ) { + int rc; + struct berval save_dn = op->o_dn, save_ndn = op->o_ndn; + + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + rc = test_filter( op, e, c->restrict_filter ); + op->o_dn = save_dn; + op->o_ndn = save_ndn; + + if ( rc != LDAP_COMPARE_TRUE ) { + return 0; + } + } + + return 1; +} + +static int +constraint_add( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + Attribute *a; + constraint *c = on->on_bi.bi_private, *cp; + BerVarray b = NULL; + int i; + struct berval rsv = BER_BVC("add breaks constraint"); + int rc = 0; + char *msg = NULL; + + if (get_relax(op) || SLAPD_SYNC_IS_SYNCCONN( op->o_connid )) { + return SLAP_CB_CONTINUE; + } + + if ((a = op->ora_e->e_attrs) == NULL) { + op->o_bd->bd_info = (BackendInfo *)(on->on_info); + send_ldap_error(op, rs, LDAP_INVALID_SYNTAX, + "constraint_add: no attrs"); + return(rs->sr_err); + } + + for(; a; a = a->a_next ) { + /* we don't constrain operational attributes */ + if (is_at_operational(a->a_desc->ad_type)) continue; + + for(cp = c; cp; cp = cp->ap_next) { + int j; + for (j = 0; cp->ap[j]; j++) { + if (cp->ap[j] == a->a_desc) break; + } + if (cp->ap[j] == NULL) continue; + if ((b = a->a_vals) == NULL) continue; + + if (cp->restrict_lud != NULL && constraint_check_restrict(op, cp, op->ora_e) == 0) { + continue; + } + + Debug(LDAP_DEBUG_TRACE, + "==> constraint_add, " + "a->a_numvals = %u, cp->count = %lu\n", + a->a_numvals, (unsigned long) cp->count, 0); + + switch (cp->type) { + case CONSTRAINT_COUNT: + if (a->a_numvals > cp->count) + rc = LDAP_CONSTRAINT_VIOLATION; + break; + case CONSTRAINT_SET: + if (acl_match_set(&cp->val, op, op->ora_e, NULL) == 0) + rc = LDAP_CONSTRAINT_VIOLATION; + break; + default: + for ( i = 0; b[i].bv_val; i++ ) { + rc = constraint_violation( cp, &b[i], op ); + if ( rc ) { + goto add_violation; + } + } + } + if ( rc ) + goto add_violation; + + } + } + + /* Default is to just fall through to the normal processing */ + return SLAP_CB_CONTINUE; + +add_violation: + op->o_bd->bd_info = (BackendInfo *)(on->on_info); + if (rc == LDAP_CONSTRAINT_VIOLATION ) { + msg = print_message( &rsv, a->a_desc ); + } + send_ldap_error(op, rs, rc, msg ); + ch_free(msg); + return (rs->sr_err); +} + + +static int +constraint_check_count_violation( Modifications *m, Entry *target_entry, constraint *cp ) +{ + BerVarray b = NULL; + unsigned ce = 0; + unsigned ca; + int j; + + for ( j = 0; cp->ap[j]; j++ ) { + /* Get this attribute count */ + if ( target_entry ) + ce = constraint_count_attr( target_entry, cp->ap[j] ); + + for( ; m; m = m->sml_next ) { + if ( cp->ap[j] == m->sml_desc ) { + ca = m->sml_numvals; + switch ( m->sml_op ) { + case LDAP_MOD_DELETE: + case SLAP_MOD_SOFTDEL: + if ( !ca || ca > ce ) { + ce = 0; + } else { + /* No need to check for values' validity. Invalid values + * cause the whole transaction to die anyway. */ + ce -= ca; + } + break; + + case LDAP_MOD_ADD: + case SLAP_MOD_SOFTADD: + ce += ca; + break; + + case LDAP_MOD_REPLACE: + ce = ca; + break; + +#if 0 + /* TODO */ + case handle SLAP_MOD_ADD_IF_NOT_PRESENT: +#endif + + default: + /* impossible! assert? */ + return 1; + } + + Debug(LDAP_DEBUG_TRACE, + "==> constraint_check_count_violation ce = %u, " + "ca = %u, cp->count = %lu\n", + ce, ca, (unsigned long) cp->count); + } + } + } + + return ( ce > cp->count ); +} + +static int +constraint_update( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + Backend *be = op->o_bd; + constraint *c = on->on_bi.bi_private, *cp; + Entry *target_entry = NULL, *target_entry_copy = NULL; + Modifications *modlist, *m; + BerVarray b = NULL; + int i; + struct berval rsv = BER_BVC("modify breaks constraint"); + int rc; + char *msg = NULL; + int is_v; + + if (get_relax(op) || SLAPD_SYNC_IS_SYNCCONN( op->o_connid )) { + return SLAP_CB_CONTINUE; + } + + switch ( op->o_tag ) { + case LDAP_REQ_MODIFY: + modlist = op->orm_modlist; + break; + + case LDAP_REQ_MODRDN: + modlist = op->orr_modlist; + break; + + default: + /* impossible! assert? */ + return LDAP_OTHER; + } + + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "constraint_update()\n", 0,0,0); + if ((m = modlist) == NULL) { + op->o_bd->bd_info = (BackendInfo *)(on->on_info); + send_ldap_error(op, rs, LDAP_INVALID_SYNTAX, + "constraint_update() got null modlist"); + return(rs->sr_err); + } + + op->o_bd = on->on_info->oi_origdb; + rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &target_entry ); + op->o_bd = be; + + /* let the backend send the error */ + if ( target_entry == NULL ) + return SLAP_CB_CONTINUE; + + /* Do we need to count attributes? */ + for(cp = c; cp; cp = cp->ap_next) { + if (cp->type == CONSTRAINT_COUNT) { + if (cp->restrict_lud && constraint_check_restrict(op, cp, target_entry) == 0) { + continue; + } + + is_v = constraint_check_count_violation(m, target_entry, cp); + + Debug(LDAP_DEBUG_TRACE, + "==> constraint_update is_v: %d\n", is_v, 0, 0); + + if (is_v) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto mod_violation; + } + } + } + + rc = LDAP_CONSTRAINT_VIOLATION; + for(;m; m = m->sml_next) { + unsigned ce = 0; + + if (is_at_operational( m->sml_desc->ad_type )) continue; + + if ((( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_ADD) && + (( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_REPLACE) && + (( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_DELETE)) + continue; + /* we only care about ADD and REPLACE modifications */ + /* and DELETE are used to track attribute count */ + if ((( b = m->sml_values ) == NULL ) || (b[0].bv_val == NULL)) + continue; + + for(cp = c; cp; cp = cp->ap_next) { + int j; + for (j = 0; cp->ap[j]; j++) { + if (cp->ap[j] == m->sml_desc) { + break; + } + } + if (cp->ap[j] == NULL) continue; + + if (cp->restrict_lud != NULL && constraint_check_restrict(op, cp, target_entry) == 0) { + continue; + } + + /* DELETE are to be ignored beyond this point */ + if (( m->sml_op & LDAP_MOD_OP ) == LDAP_MOD_DELETE) + continue; + + for ( i = 0; b[i].bv_val; i++ ) { + rc = constraint_violation( cp, &b[i], op ); + if ( rc ) { + goto mod_violation; + } + } + + if (cp->type == CONSTRAINT_SET && target_entry) { + if (target_entry_copy == NULL) { + Modifications *ml; + + target_entry_copy = entry_dup(target_entry); + + /* if rename, set the new entry's name + * (in normalized form only) */ + if ( op->o_tag == LDAP_REQ_MODRDN ) { + struct berval pdn, ndn = BER_BVNULL; + + if ( op->orr_nnewSup ) { + pdn = *op->orr_nnewSup; + + } else { + dnParent( &target_entry_copy->e_nname, &pdn ); + } + + build_new_dn( &ndn, &pdn, &op->orr_nnewrdn, NULL ); + + ber_memfree( target_entry_copy->e_nname.bv_val ); + target_entry_copy->e_nname = ndn; + ber_bvreplace( &target_entry_copy->e_name, &ndn ); + } + + /* apply modifications, in an attempt + * to estimate what the entry would + * look like in case all modifications + * pass */ + for ( ml = modlist; ml; ml = ml->sml_next ) { + Modification *mod = &ml->sml_mod; + const char *text; + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof(textbuf); + int err; + + switch ( mod->sm_op ) { + case LDAP_MOD_ADD: + err = modify_add_values( target_entry_copy, + mod, get_permissiveModify(op), + &text, textbuf, textlen ); + break; + + case LDAP_MOD_DELETE: + err = modify_delete_values( target_entry_copy, + mod, get_permissiveModify(op), + &text, textbuf, textlen ); + break; + + case LDAP_MOD_REPLACE: + err = modify_replace_values( target_entry_copy, + mod, get_permissiveModify(op), + &text, textbuf, textlen ); + break; + + case LDAP_MOD_INCREMENT: + err = modify_increment_values( target_entry_copy, + mod, get_permissiveModify(op), + &text, textbuf, textlen ); + break; + + case SLAP_MOD_SOFTADD: + mod->sm_op = LDAP_MOD_ADD; + err = modify_add_values( target_entry_copy, + mod, get_permissiveModify(op), + &text, textbuf, textlen ); + mod->sm_op = SLAP_MOD_SOFTADD; + if ( err == LDAP_TYPE_OR_VALUE_EXISTS ) { + err = LDAP_SUCCESS; + } + break; + + case SLAP_MOD_SOFTDEL: + mod->sm_op = LDAP_MOD_ADD; + err = modify_delete_values( target_entry_copy, + mod, get_permissiveModify(op), + &text, textbuf, textlen ); + mod->sm_op = SLAP_MOD_SOFTDEL; + if ( err == LDAP_NO_SUCH_ATTRIBUTE ) { + err = LDAP_SUCCESS; + } + break; + + case SLAP_MOD_ADD_IF_NOT_PRESENT: + if ( attr_find( target_entry_copy->e_attrs, mod->sm_desc ) ) { + err = LDAP_SUCCESS; + break; + } + mod->sm_op = LDAP_MOD_ADD; + err = modify_add_values( target_entry_copy, + mod, get_permissiveModify(op), + &text, textbuf, textlen ); + mod->sm_op = SLAP_MOD_ADD_IF_NOT_PRESENT; + break; + + default: + err = LDAP_OTHER; + break; + } + + if ( err != LDAP_SUCCESS ) { + rc = err; + goto mod_violation; + } + } + } + + if ( acl_match_set(&cp->val, op, target_entry_copy, NULL) == 0) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto mod_violation; + } + } + } + } + + if (target_entry) { + op->o_bd = on->on_info->oi_origdb; + be_entry_release_r(op, target_entry); + op->o_bd = be; + } + + if (target_entry_copy) { + entry_free(target_entry_copy); + } + + return SLAP_CB_CONTINUE; + +mod_violation: + /* violation */ + if (target_entry) { + op->o_bd = on->on_info->oi_origdb; + be_entry_release_r(op, target_entry); + op->o_bd = be; + } + + if (target_entry_copy) { + entry_free(target_entry_copy); + } + + op->o_bd->bd_info = (BackendInfo *)(on->on_info); + if ( rc == LDAP_CONSTRAINT_VIOLATION ) { + msg = print_message( &rsv, m->sml_desc ); + } + send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION, msg ); + ch_free(msg); + return (rs->sr_err); +} + +static int +constraint_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + constraint *ap, *a2; + + for ( ap = on->on_bi.bi_private; ap; ap = a2 ) { + a2 = ap->ap_next; + constraint_free( ap, 1 ); + } + + return 0; +} + +static slap_overinst constraint_ovl; + +#if SLAPD_OVER_CONSTRAINT == SLAPD_MOD_DYNAMIC +static +#endif +int +constraint_initialize( void ) { + int rc; + + constraint_ovl.on_bi.bi_type = "constraint"; + constraint_ovl.on_bi.bi_db_destroy = constraint_destroy; + constraint_ovl.on_bi.bi_op_add = constraint_add; + constraint_ovl.on_bi.bi_op_modify = constraint_update; + constraint_ovl.on_bi.bi_op_modrdn = constraint_update; + + constraint_ovl.on_bi.bi_private = NULL; + + constraint_ovl.on_bi.bi_cf_ocs = constraintocs; + rc = config_register_schema( constraintcfg, constraintocs ); + if (rc) return rc; + + return overlay_register( &constraint_ovl ); +} + +#if SLAPD_OVER_CONSTRAINT == SLAPD_MOD_DYNAMIC +int init_module(int argc, char *argv[]) { + return constraint_initialize(); +} +#endif + +#endif /* defined(SLAPD_OVER_CONSTRAINT) */ + diff --git a/servers/slapd/overlays/dds.c b/servers/slapd/overlays/dds.c new file mode 100644 index 0000000..01a41a1 --- /dev/null +++ b/servers/slapd/overlays/dds.c @@ -0,0 +1,2046 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2021 The OpenLDAP Foundation. + * Portions Copyright 2005-2006 SysNet s.n.c. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software, sponsored by SysNet s.n.c. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_DDS + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/time.h> + +#include "slap.h" +#include "lutil.h" +#include "ldap_rq.h" + +#include "config.h" + +#define DDS_RF2589_MAX_TTL (31557600) /* 1 year + 6 hours */ +#define DDS_RF2589_DEFAULT_TTL (86400) /* 1 day */ +#define DDS_DEFAULT_INTERVAL (3600) /* 1 hour */ + +typedef struct dds_info_t { + unsigned di_flags; +#define DDS_FOFF (0x1U) /* is this really needed? */ +#define DDS_SET(di, f) ( (di)->di_flags & (f) ) + +#define DDS_OFF(di) DDS_SET( (di), DDS_FOFF ) + + time_t di_max_ttl; + time_t di_min_ttl; + time_t di_default_ttl; +#define DDS_DEFAULT_TTL(di) \ + ( (di)->di_default_ttl ? (di)->di_default_ttl : (di)->di_max_ttl ) + + time_t di_tolerance; + + /* expire check interval and task */ + time_t di_interval; +#define DDS_INTERVAL(di) \ + ( (di)->di_interval ? (di)->di_interval : DDS_DEFAULT_INTERVAL ) + struct re_s *di_expire_task; + + /* allows to limit the maximum number of dynamic objects */ + ldap_pvt_thread_mutex_t di_mutex; + int di_num_dynamicObjects; + int di_max_dynamicObjects; + + /* used to advertize the dynamicSubtrees in the root DSE, + * and to select the database in the expiration task */ + BerVarray di_suffix; + BerVarray di_nsuffix; +} dds_info_t; + +static struct berval slap_EXOP_REFRESH = BER_BVC( LDAP_EXOP_REFRESH ); +static AttributeDescription *ad_entryExpireTimestamp; + +/* list of expired DNs */ +typedef struct dds_expire_t { + struct berval de_ndn; + struct dds_expire_t *de_next; +} dds_expire_t; + +typedef struct dds_cb_t { + dds_expire_t *dc_ndnlist; +} dds_cb_t; + +static int +dds_expire_cb( Operation *op, SlapReply *rs ) +{ + dds_cb_t *dc = (dds_cb_t *)op->o_callback->sc_private; + dds_expire_t *de; + int rc; + + switch ( rs->sr_type ) { + case REP_SEARCH: + /* alloc list and buffer for berval all in one */ + de = op->o_tmpalloc( sizeof( dds_expire_t ) + rs->sr_entry->e_nname.bv_len + 1, + op->o_tmpmemctx ); + + de->de_next = dc->dc_ndnlist; + dc->dc_ndnlist = de; + + de->de_ndn.bv_len = rs->sr_entry->e_nname.bv_len; + de->de_ndn.bv_val = (char *)&de[ 1 ]; + AC_MEMCPY( de->de_ndn.bv_val, rs->sr_entry->e_nname.bv_val, + rs->sr_entry->e_nname.bv_len + 1 ); + rc = 0; + break; + + case REP_SEARCHREF: + case REP_RESULT: + rc = rs->sr_err; + break; + + default: + assert( 0 ); + } + + return rc; +} + +static int +dds_expire( void *ctx, dds_info_t *di ) +{ + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation *op; + slap_callback sc = { 0 }; + dds_cb_t dc = { 0 }; + dds_expire_t *de = NULL, **dep; + SlapReply rs = { REP_RESULT }; + + time_t expire; + char tsbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + struct berval ts; + + int ndeletes, ntotdeletes; + + int rc; + char *extra = ""; + + connection_fake_init2( &conn, &opbuf, ctx, 0 ); + op = &opbuf.ob_op; + + op->o_tag = LDAP_REQ_SEARCH; + memset( &op->oq_search, 0, sizeof( op->oq_search ) ); + + op->o_bd = select_backend( &di->di_nsuffix[ 0 ], 0 ); + + op->o_req_dn = op->o_bd->be_suffix[ 0 ]; + op->o_req_ndn = op->o_bd->be_nsuffix[ 0 ]; + + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + + op->ors_scope = LDAP_SCOPE_SUBTREE; + op->ors_tlimit = DDS_INTERVAL( di )/2 + 1; + op->ors_slimit = SLAP_NO_LIMIT; + op->ors_attrs = slap_anlist_no_attrs; + + expire = slap_get_time() - di->di_tolerance; + ts.bv_val = tsbuf; + ts.bv_len = sizeof( tsbuf ); + slap_timestamp( &expire, &ts ); + + op->ors_filterstr.bv_len = STRLENOF( "(&(objectClass=" ")(" "<=" "))" ) + + slap_schema.si_oc_dynamicObject->soc_cname.bv_len + + ad_entryExpireTimestamp->ad_cname.bv_len + + ts.bv_len; + op->ors_filterstr.bv_val = op->o_tmpalloc( op->ors_filterstr.bv_len + 1, op->o_tmpmemctx ); + snprintf( op->ors_filterstr.bv_val, op->ors_filterstr.bv_len + 1, + "(&(objectClass=%s)(%s<=%s))", + slap_schema.si_oc_dynamicObject->soc_cname.bv_val, + ad_entryExpireTimestamp->ad_cname.bv_val, ts.bv_val ); + + op->ors_filter = str2filter_x( op, op->ors_filterstr.bv_val ); + if ( op->ors_filter == NULL ) { + rs.sr_err = LDAP_OTHER; + goto done_search; + } + + op->o_callback = ≻ + sc.sc_response = dds_expire_cb; + sc.sc_private = &dc; + + (void)op->o_bd->bd_info->bi_op_search( op, &rs ); + +done_search:; + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + filter_free_x( op, op->ors_filter, 1 ); + + rc = rs.sr_err; + switch ( rs.sr_err ) { + case LDAP_SUCCESS: + break; + + case LDAP_NO_SUCH_OBJECT: + /* (ITS#5267) database not created yet? */ + rs.sr_err = LDAP_SUCCESS; + extra = " (ignored)"; + /* fallthru */ + + default: + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "DDS expired objects lookup failed err=%d%s\n", + rc, extra ); + goto done; + } + + op->o_tag = LDAP_REQ_DELETE; + op->o_callback = ≻ + sc.sc_response = slap_null_cb; + sc.sc_private = NULL; + + for ( ntotdeletes = 0, ndeletes = 1; dc.dc_ndnlist != NULL && ndeletes > 0; ) { + ndeletes = 0; + + for ( dep = &dc.dc_ndnlist; *dep != NULL; ) { + de = *dep; + + op->o_req_dn = de->de_ndn; + op->o_req_ndn = de->de_ndn; + (void)op->o_bd->bd_info->bi_op_delete( op, &rs ); + switch ( rs.sr_err ) { + case LDAP_SUCCESS: + Log1( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO, + "DDS dn=\"%s\" expired.\n", + de->de_ndn.bv_val ); + ndeletes++; + break; + + case LDAP_NOT_ALLOWED_ON_NONLEAF: + Log1( LDAP_DEBUG_ANY, LDAP_LEVEL_NOTICE, + "DDS dn=\"%s\" is non-leaf; " + "deferring.\n", + de->de_ndn.bv_val ); + dep = &de->de_next; + de = NULL; + break; + + default: + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_NOTICE, + "DDS dn=\"%s\" err=%d; " + "deferring.\n", + de->de_ndn.bv_val, rs.sr_err ); + break; + } + + if ( de != NULL ) { + *dep = de->de_next; + op->o_tmpfree( de, op->o_tmpmemctx ); + } + } + + ntotdeletes += ndeletes; + } + + rs.sr_err = LDAP_SUCCESS; + + Log1( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO, + "DDS expired=%d\n", ntotdeletes ); + +done:; + return rs.sr_err; +} + +static void * +dds_expire_fn( void *ctx, void *arg ) +{ + struct re_s *rtask = arg; + dds_info_t *di = rtask->arg; + + assert( di->di_expire_task == rtask ); + + (void)dds_expire( ctx, di ); + + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, rtask )) { + ldap_pvt_runqueue_stoptask( &slapd_rq, rtask ); + } + ldap_pvt_runqueue_resched( &slapd_rq, rtask, 0 ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + return NULL; +} + +/* frees the callback */ +static int +dds_freeit_cb( Operation *op, SlapReply *rs ) +{ + op->o_tmpfree( op->o_callback, op->o_tmpmemctx ); + op->o_callback = NULL; + + return SLAP_CB_CONTINUE; +} + +/* updates counter - installed on add/delete only if required */ +static int +dds_counter_cb( Operation *op, SlapReply *rs ) +{ + assert( rs->sr_type == REP_RESULT ); + + if ( rs->sr_err == LDAP_SUCCESS ) { + dds_info_t *di = op->o_callback->sc_private; + + ldap_pvt_thread_mutex_lock( &di->di_mutex ); + switch ( op->o_tag ) { + case LDAP_REQ_DELETE: + assert( di->di_num_dynamicObjects > 0 ); + di->di_num_dynamicObjects--; + break; + + case LDAP_REQ_ADD: + assert( di->di_num_dynamicObjects < di->di_max_dynamicObjects ); + di->di_num_dynamicObjects++; + break; + + default: + assert( 0 ); + } + ldap_pvt_thread_mutex_unlock( &di->di_mutex ); + } + + return dds_freeit_cb( op, rs ); +} + +static int +dds_op_add( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + dds_info_t *di = on->on_bi.bi_private; + int is_dynamicObject; + + if ( DDS_OFF( di ) ) { + return SLAP_CB_CONTINUE; + } + + is_dynamicObject = is_entry_dynamicObject( op->ora_e ); + + /* FIXME: do not allow this right now, pending clarification */ + if ( is_dynamicObject ) { + rs->sr_err = LDAP_SUCCESS; + + if ( is_entry_referral( op->ora_e ) ) { + rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; + rs->sr_text = "a referral cannot be a dynamicObject"; + + } else if ( is_entry_alias( op->ora_e ) ) { + rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; + rs->sr_text = "an alias cannot be a dynamicObject"; + } + + if ( rs->sr_err != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_result( op, rs ); + return rs->sr_err; + } + } + + /* we don't allow dynamicObjects to have static subordinates */ + if ( !dn_match( &op->o_req_ndn, &op->o_bd->be_nsuffix[ 0 ] ) ) { + struct berval p_ndn; + Entry *e = NULL; + int rc; + BackendInfo *bi = op->o_bd->bd_info; + + dnParent( &op->o_req_ndn, &p_ndn ); + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, &p_ndn, + slap_schema.si_oc_dynamicObject, NULL, 0, &e ); + if ( rc == LDAP_SUCCESS && e != NULL ) { + if ( !is_dynamicObject ) { + /* return referral only if "disclose" + * is granted on the object */ + if ( ! access_allowed( op, e, + slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) ) + { + rc = rs->sr_err = LDAP_NO_SUCH_OBJECT; + send_ldap_result( op, rs ); + + } else { + rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + send_ldap_error( op, rs, rc, "no static subordinate entries allowed for dynamicObject" ); + } + } + + be_entry_release_r( op, e ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + } + op->o_bd->bd_info = bi; + } + + /* handle dynamic object operational attr(s) */ + if ( is_dynamicObject ) { + time_t ttl, expire; + char ttlbuf[STRLENOF("31557600") + 1]; + char tsbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + struct berval bv; + + if ( !be_isroot_dn( op->o_bd, &op->o_req_ndn ) ) { + ldap_pvt_thread_mutex_lock( &di->di_mutex ); + rs->sr_err = ( di->di_max_dynamicObjects && + di->di_num_dynamicObjects >= di->di_max_dynamicObjects ); + ldap_pvt_thread_mutex_unlock( &di->di_mutex ); + if ( rs->sr_err ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "too many dynamicObjects in context" ); + return rs->sr_err; + } + } + + ttl = DDS_DEFAULT_TTL( di ); + + /* assert because should be checked at configure */ + assert( ttl <= DDS_RF2589_MAX_TTL ); + + bv.bv_val = ttlbuf; + bv.bv_len = snprintf( ttlbuf, sizeof( ttlbuf ), "%ld", ttl ); + assert( bv.bv_len < sizeof( ttlbuf ) ); + + /* FIXME: apparently, values in op->ora_e are malloc'ed + * on the thread's slab; works fine by chance, + * only because the attribute doesn't exist yet. */ + assert( attr_find( op->ora_e->e_attrs, slap_schema.si_ad_entryTtl ) == NULL ); + attr_merge_one( op->ora_e, slap_schema.si_ad_entryTtl, &bv, &bv ); + + expire = slap_get_time() + ttl; + bv.bv_val = tsbuf; + bv.bv_len = sizeof( tsbuf ); + slap_timestamp( &expire, &bv ); + assert( attr_find( op->ora_e->e_attrs, ad_entryExpireTimestamp ) == NULL ); + attr_merge_one( op->ora_e, ad_entryExpireTimestamp, &bv, &bv ); + + /* if required, install counter callback */ + if ( di->di_max_dynamicObjects > 0) { + slap_callback *sc; + + sc = op->o_tmpalloc( sizeof( slap_callback ), op->o_tmpmemctx ); + sc->sc_cleanup = dds_freeit_cb; + sc->sc_response = dds_counter_cb; + sc->sc_private = di; + sc->sc_next = op->o_callback; + sc->sc_writewait = 0; + + op->o_callback = sc; + } + } + + return SLAP_CB_CONTINUE; +} + +static int +dds_op_delete( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + dds_info_t *di = on->on_bi.bi_private; + + /* if required, install counter callback */ + if ( !DDS_OFF( di ) && di->di_max_dynamicObjects > 0 ) { + Entry *e = NULL; + BackendInfo *bi = op->o_bd->bd_info; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn, + slap_schema.si_oc_dynamicObject, NULL, 0, &e ); + + /* FIXME: couldn't the entry be added before deletion? */ + if ( rs->sr_err == LDAP_SUCCESS && e != NULL ) { + slap_callback *sc; + + be_entry_release_r( op, e ); + e = NULL; + + sc = op->o_tmpalloc( sizeof( slap_callback ), op->o_tmpmemctx ); + sc->sc_cleanup = dds_freeit_cb; + sc->sc_response = dds_counter_cb; + sc->sc_private = di; + sc->sc_writewait = 0; + sc->sc_next = op->o_callback; + + op->o_callback = sc; + } + op->o_bd->bd_info = bi; + } + + return SLAP_CB_CONTINUE; +} + +static int +dds_op_modify( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + dds_info_t *di = (dds_info_t *)on->on_bi.bi_private; + Modifications *mod; + Entry *e = NULL; + BackendInfo *bi = op->o_bd->bd_info; + int was_dynamicObject = 0, + is_dynamicObject = 0; + struct berval bv_entryTtl = BER_BVNULL; + time_t entryTtl = 0; + char textbuf[ SLAP_TEXT_BUFLEN ]; + + if ( DDS_OFF( di ) ) { + return SLAP_CB_CONTINUE; + } + + /* bv_entryTtl stores the string representation of the entryTtl + * across modifies for consistency checks of the final value; + * the bv_val points to a static buffer; the bv_len is zero when + * the attribute is deleted. + * entryTtl stores the integer representation of the entryTtl; + * its value is -1 when the attribute is deleted; it is 0 only + * if no modifications of the entryTtl occurred, as an entryTtl + * of 0 is invalid. */ + bv_entryTtl.bv_val = textbuf; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn, + slap_schema.si_oc_dynamicObject, slap_schema.si_ad_entryTtl, 0, &e ); + if ( rs->sr_err == LDAP_SUCCESS && e != NULL ) { + Attribute *a = attr_find( e->e_attrs, slap_schema.si_ad_entryTtl ); + + /* the value of the entryTtl is saved for later checks */ + if ( a != NULL ) { + unsigned long ttl; + int rc; + + bv_entryTtl.bv_len = a->a_nvals[ 0 ].bv_len; + AC_MEMCPY( bv_entryTtl.bv_val, a->a_nvals[ 0 ].bv_val, bv_entryTtl.bv_len ); + bv_entryTtl.bv_val[ bv_entryTtl.bv_len ] = '\0'; + rc = lutil_atoul( &ttl, bv_entryTtl.bv_val ); + assert( rc == 0 ); + entryTtl = (time_t)ttl; + } + + be_entry_release_r( op, e ); + e = NULL; + was_dynamicObject = is_dynamicObject = 1; + } + op->o_bd->bd_info = bi; + + rs->sr_err = LDAP_SUCCESS; + for ( mod = op->orm_modlist; mod; mod = mod->sml_next ) { + if ( mod->sml_desc == slap_schema.si_ad_objectClass ) { + int i; + ObjectClass *oc; + + switch ( mod->sml_op ) { + case LDAP_MOD_DELETE: + if ( mod->sml_values == NULL ) { + is_dynamicObject = 0; + break; + } + + for ( i = 0; !BER_BVISNULL( &mod->sml_values[ i ] ); i++ ) { + oc = oc_bvfind( &mod->sml_values[ i ] ); + if ( oc == slap_schema.si_oc_dynamicObject ) { + is_dynamicObject = 0; + break; + } + } + + break; + + case LDAP_MOD_REPLACE: + if ( mod->sml_values == NULL ) { + is_dynamicObject = 0; + break; + } + /* fallthru */ + + case LDAP_MOD_ADD: + for ( i = 0; !BER_BVISNULL( &mod->sml_values[ i ] ); i++ ) { + oc = oc_bvfind( &mod->sml_values[ i ] ); + if ( oc == slap_schema.si_oc_dynamicObject ) { + is_dynamicObject = 1; + break; + } + } + break; + } + + } else if ( mod->sml_desc == slap_schema.si_ad_entryTtl ) { + unsigned long uttl; + time_t ttl; + int rc; + + switch ( mod->sml_op ) { + case LDAP_MOD_DELETE: + case SLAP_MOD_SOFTDEL: /* FIXME? */ + if ( mod->sml_values != NULL ) { + if ( BER_BVISEMPTY( &bv_entryTtl ) + || !bvmatch( &bv_entryTtl, &mod->sml_values[ 0 ] ) ) + { + rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn, + slap_schema.si_ad_entry, NULL, ACL_DISCLOSE ); + if ( rs->sr_err == LDAP_INSUFFICIENT_ACCESS ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + + } else { + rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE; + } + goto done; + } + } + bv_entryTtl.bv_len = 0; + entryTtl = -1; + break; + + case LDAP_MOD_REPLACE: + bv_entryTtl.bv_len = 0; + entryTtl = -1; + /* fallthru */ + + case LDAP_MOD_ADD: + case SLAP_MOD_SOFTADD: /* FIXME? */ + case SLAP_MOD_ADD_IF_NOT_PRESENT: /* FIXME? */ + assert( mod->sml_values != NULL ); + assert( BER_BVISNULL( &mod->sml_values[ 1 ] ) ); + + if ( !BER_BVISEMPTY( &bv_entryTtl ) ) { + rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn, + slap_schema.si_ad_entry, NULL, ACL_DISCLOSE ); + if ( rs->sr_err == LDAP_INSUFFICIENT_ACCESS ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + + } else { + rs->sr_text = "attribute 'entryTtl' cannot have multiple values"; + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + } + goto done; + } + + rc = lutil_atoul( &uttl, mod->sml_values[ 0 ].bv_val ); + ttl = (time_t)uttl; + assert( rc == 0 ); + if ( ttl > DDS_RF2589_MAX_TTL ) { + rs->sr_err = LDAP_PROTOCOL_ERROR; + rs->sr_text = "invalid time-to-live for dynamicObject"; + goto done; + } + + if ( ttl <= 0 || ttl > di->di_max_ttl ) { + /* FIXME: I don't understand if this has to be an error, + * or an indication that the requested Ttl has been + * shortened to di->di_max_ttl >= 1 day */ + rs->sr_err = LDAP_SIZELIMIT_EXCEEDED; + rs->sr_text = "time-to-live for dynamicObject exceeds administrative limit"; + goto done; + } + + entryTtl = ttl; + bv_entryTtl.bv_len = mod->sml_values[ 0 ].bv_len; + AC_MEMCPY( bv_entryTtl.bv_val, mod->sml_values[ 0 ].bv_val, bv_entryTtl.bv_len ); + bv_entryTtl.bv_val[ bv_entryTtl.bv_len ] = '\0'; + break; + + case LDAP_MOD_INCREMENT: + if ( BER_BVISEMPTY( &bv_entryTtl ) ) { + rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn, + slap_schema.si_ad_entry, NULL, ACL_DISCLOSE ); + if ( rs->sr_err == LDAP_INSUFFICIENT_ACCESS ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + + } else { + rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE; + rs->sr_text = "modify/increment: entryTtl: no such attribute"; + } + goto done; + } + + entryTtl++; + if ( entryTtl > DDS_RF2589_MAX_TTL ) { + rs->sr_err = LDAP_PROTOCOL_ERROR; + rs->sr_text = "invalid time-to-live for dynamicObject"; + + } else if ( entryTtl <= 0 || entryTtl > di->di_max_ttl ) { + /* FIXME: I don't understand if this has to be an error, + * or an indication that the requested Ttl has been + * shortened to di->di_max_ttl >= 1 day */ + rs->sr_err = LDAP_SIZELIMIT_EXCEEDED; + rs->sr_text = "time-to-live for dynamicObject exceeds administrative limit"; + } + + if ( rs->sr_err != LDAP_SUCCESS ) { + rc = backend_attribute( op, NULL, &op->o_req_ndn, + slap_schema.si_ad_entry, NULL, ACL_DISCLOSE ); + if ( rc == LDAP_INSUFFICIENT_ACCESS ) { + rs->sr_text = NULL; + rs->sr_err = LDAP_NO_SUCH_OBJECT; + + } + goto done; + } + + bv_entryTtl.bv_len = snprintf( textbuf, sizeof( textbuf ), "%ld", entryTtl ); + break; + + default: + assert( 0 ); + break; + } + + } else if ( mod->sml_desc == ad_entryExpireTimestamp ) { + /* should have been trapped earlier */ + assert( mod->sml_flags & SLAP_MOD_INTERNAL ); + } + } + +done:; + if ( rs->sr_err == LDAP_SUCCESS ) { + int rc; + + /* FIXME: this could be allowed when the Relax control is used... + * in that case: + * + * TODO + * + * static => dynamic: + * entryTtl must be provided; add + * entryExpireTimestamp accordingly + * + * dynamic => static: + * entryTtl must be removed; remove + * entryTimestamp accordingly + * + * ... but we need to make sure that there are no subordinate + * issues... + */ + rc = is_dynamicObject - was_dynamicObject; + if ( rc ) { +#if 0 /* fix subordinate issues first */ + if ( get_relax( op ) ) { + switch ( rc ) { + case -1: + /* need to delete entryTtl to have a consistent entry */ + if ( entryTtl != -1 ) { + rs->sr_text = "objectClass modification from dynamicObject to static entry requires entryTtl deletion"; + rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; + } + break; + + case 1: + /* need to add entryTtl to have a consistent entry */ + if ( entryTtl <= 0 ) { + rs->sr_text = "objectClass modification from static entry to dynamicObject requires entryTtl addition"; + rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; + } + break; + } + + } else +#endif + { + switch ( rc ) { + case -1: + rs->sr_text = "objectClass modification cannot turn dynamicObject into static entry"; + break; + + case 1: + rs->sr_text = "objectClass modification cannot turn static entry into dynamicObject"; + break; + } + rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; + } + + if ( rc != LDAP_SUCCESS ) { + rc = backend_attribute( op, NULL, &op->o_req_ndn, + slap_schema.si_ad_entry, NULL, ACL_DISCLOSE ); + if ( rc == LDAP_INSUFFICIENT_ACCESS ) { + rs->sr_text = NULL; + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } + } + } + } + + if ( rs->sr_err == LDAP_SUCCESS && entryTtl != 0 ) { + Modifications *tmpmod = NULL, **modp; + + for ( modp = &op->orm_modlist; *modp; modp = &(*modp)->sml_next ) + ; + + tmpmod = ch_calloc( 1, sizeof( Modifications ) ); + tmpmod->sml_flags = SLAP_MOD_INTERNAL; + tmpmod->sml_type = ad_entryExpireTimestamp->ad_cname; + tmpmod->sml_desc = ad_entryExpireTimestamp; + + *modp = tmpmod; + + if ( entryTtl == -1 ) { + /* delete entryExpireTimestamp */ + tmpmod->sml_op = LDAP_MOD_DELETE; + + } else { + time_t expire; + char tsbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + struct berval bv; + + /* keep entryExpireTimestamp consistent + * with entryTtl */ + expire = slap_get_time() + entryTtl; + bv.bv_val = tsbuf; + bv.bv_len = sizeof( tsbuf ); + slap_timestamp( &expire, &bv ); + + tmpmod->sml_op = LDAP_MOD_REPLACE; + value_add_one( &tmpmod->sml_values, &bv ); + value_add_one( &tmpmod->sml_nvalues, &bv ); + tmpmod->sml_numvals = 1; + } + } + + if ( rs->sr_err ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_result( op, rs ); + return rs->sr_err; + } + + return SLAP_CB_CONTINUE; +} + +static int +dds_op_rename( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + dds_info_t *di = on->on_bi.bi_private; + + if ( DDS_OFF( di ) ) { + return SLAP_CB_CONTINUE; + } + + /* we don't allow dynamicObjects to have static subordinates */ + if ( op->orr_nnewSup != NULL ) { + Entry *e = NULL; + BackendInfo *bi = op->o_bd->bd_info; + int is_dynamicObject = 0, + rc; + + rs->sr_err = LDAP_SUCCESS; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, &op->o_req_ndn, + slap_schema.si_oc_dynamicObject, NULL, 0, &e ); + if ( rc == LDAP_SUCCESS && e != NULL ) { + be_entry_release_r( op, e ); + e = NULL; + is_dynamicObject = 1; + } + + rc = be_entry_get_rw( op, op->orr_nnewSup, + slap_schema.si_oc_dynamicObject, NULL, 0, &e ); + if ( rc == LDAP_SUCCESS && e != NULL ) { + if ( !is_dynamicObject ) { + /* return referral only if "disclose" + * is granted on the object */ + if ( ! access_allowed( op, e, + slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + send_ldap_result( op, rs ); + + } else { + send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION, + "static entry cannot have dynamicObject as newSuperior" ); + } + } + be_entry_release_r( op, e ); + } + op->o_bd->bd_info = bi; + if ( rs->sr_err != LDAP_SUCCESS ) { + return rs->sr_err; + } + } + + return SLAP_CB_CONTINUE; +} + +/* entryTtl update for client */ +static int +dds_response( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + dds_info_t *di = on->on_bi.bi_private; + int rc; + + if ( !DDS_OFF( di ) + && rs->sr_type == REP_SEARCH + && attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryTtl ) ) + { + BerVarray vals = NULL; + struct lutil_tm tm; + struct lutil_timet tt; + char ttlbuf[STRLENOF("31557600") + 1]; + struct berval ttlvalue; + time_t ttl; + int len; + + /* User already has access to entryTtl, skip ACL checks on + * entryExpireTimestamp */ + rc = backend_attribute( op, NULL, &rs->sr_entry->e_nname, + ad_entryExpireTimestamp, &vals, ACL_NONE ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + assert( vals[0].bv_val[vals[0].bv_len] == '\0' ); + if ( lutil_parsetime( vals[0].bv_val, &tm ) ) { + goto done; + } + + lutil_tm2time( &tm, &tt ); + ttl = tt.tt_sec - op->o_time; + ttl = (ttl < 0) ? 0 : ttl; + assert( ttl <= DDS_RF2589_MAX_TTL ); + + len = snprintf( ttlbuf, sizeof(ttlbuf), "%ld", ttl ); + if ( len < 0 ) + { + goto done; + } + ttlvalue.bv_val = ttlbuf; + ttlvalue.bv_len = len; + + rs_entry2modifiable( op, rs, on ); + + if ( attr_delete( &rs->sr_entry->e_attrs, + slap_schema.si_ad_entryTtl ) ) + { + goto done; + } + if ( attr_merge_normalize_one( rs->sr_entry, + slap_schema.si_ad_entryTtl, + &ttlvalue, op->o_tmpmemctx ) ) + { + goto done; + } + +done:; + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + return SLAP_CB_CONTINUE; +} + +static int +slap_parse_refresh( + struct berval *in, + struct berval *ndn, + time_t *ttl, + const char **text, + void *ctx ) +{ + int rc = LDAP_SUCCESS; + ber_tag_t tag; + ber_len_t len = -1; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + struct berval reqdata = BER_BVNULL; + int tmp; + + *text = NULL; + + if ( ndn ) { + BER_BVZERO( ndn ); + } + + if ( in == NULL || in->bv_len == 0 ) { + *text = "empty request data field in refresh exop"; + return LDAP_PROTOCOL_ERROR; + } + + ber_dupbv_x( &reqdata, in, ctx ); + + /* ber_init2 uses reqdata directly, doesn't allocate new buffers */ + ber_init2( ber, &reqdata, 0 ); + + tag = ber_scanf( ber, "{" /*}*/ ); + + if ( tag == LBER_ERROR ) { + Log0( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR, + "slap_parse_refresh: decoding error.\n" ); + goto decoding_error; + } + + tag = ber_peek_tag( ber, &len ); + if ( tag != LDAP_TAG_EXOP_REFRESH_REQ_DN ) { + Log0( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR, + "slap_parse_refresh: decoding error.\n" ); + goto decoding_error; + } + + if ( ndn ) { + struct berval dn; + + tag = ber_scanf( ber, "m", &dn ); + if ( tag == LBER_ERROR ) { + Log0( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR, + "slap_parse_refresh: DN parse failed.\n" ); + goto decoding_error; + } + + rc = dnNormalize( 0, NULL, NULL, &dn, ndn, ctx ); + if ( rc != LDAP_SUCCESS ) { + *text = "invalid DN in refresh exop request data"; + goto done; + } + + } else { + tag = ber_scanf( ber, "x" /* "m" */ ); + if ( tag == LBER_DEFAULT ) { + goto decoding_error; + } + } + + tag = ber_peek_tag( ber, &len ); + + if ( tag != LDAP_TAG_EXOP_REFRESH_REQ_TTL ) { + Log0( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR, + "slap_parse_refresh: decoding error.\n" ); + goto decoding_error; + } + + tag = ber_scanf( ber, "i", &tmp ); + if ( tag == LBER_ERROR ) { + Log0( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR, + "slap_parse_refresh: TTL parse failed.\n" ); + goto decoding_error; + } + + if ( ttl ) { + *ttl = tmp; + } + + tag = ber_peek_tag( ber, &len ); + + if ( tag != LBER_DEFAULT || len != 0 ) { +decoding_error:; + Log1( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR, + "slap_parse_refresh: decoding error, len=%ld\n", + (long)len ); + rc = LDAP_PROTOCOL_ERROR; + *text = "data decoding error"; + +done:; + if ( ndn && !BER_BVISNULL( ndn ) ) { + slap_sl_free( ndn->bv_val, ctx ); + BER_BVZERO( ndn ); + } + } + + if ( !BER_BVISNULL( &reqdata ) ) { + ber_memfree_x( reqdata.bv_val, ctx ); + } + + return rc; +} + +static int +dds_op_extended( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + dds_info_t *di = on->on_bi.bi_private; + + if ( DDS_OFF( di ) ) { + return SLAP_CB_CONTINUE; + } + + if ( bvmatch( &op->ore_reqoid, &slap_EXOP_REFRESH ) ) { + Entry *e = NULL; + time_t ttl; + BackendDB db = *op->o_bd; + SlapReply rs2 = { REP_RESULT }; + Operation op2 = *op; + slap_callback sc = { 0 }; + Modifications ttlmod = { { 0 } }; + struct berval ttlvalues[ 2 ]; + char ttlbuf[STRLENOF("31557600") + 1]; + + rs->sr_err = slap_parse_refresh( op->ore_reqdata, NULL, &ttl, + &rs->sr_text, NULL ); + assert( rs->sr_err == LDAP_SUCCESS ); + + if ( ttl <= 0 || ttl > DDS_RF2589_MAX_TTL ) { + rs->sr_err = LDAP_PROTOCOL_ERROR; + rs->sr_text = "invalid time-to-live for dynamicObject"; + return rs->sr_err; + } + + if ( ttl > di->di_max_ttl ) { + /* FIXME: I don't understand if this has to be an error, + * or an indication that the requested Ttl has been + * shortened to di->di_max_ttl >= 1 day */ + rs->sr_err = LDAP_SIZELIMIT_EXCEEDED; + rs->sr_text = "time-to-live for dynamicObject exceeds limit"; + return rs->sr_err; + } + + if ( di->di_min_ttl && ttl < di->di_min_ttl ) { + ttl = di->di_min_ttl; + } + + /* This does not apply to multi-provider case */ + if ( !( !SLAP_SINGLE_SHADOW( op->o_bd ) || be_isupdate( op ) ) ) { + /* we SHOULD return a referral in this case */ + BerVarray defref = op->o_bd->be_update_refs + ? op->o_bd->be_update_refs : default_referral; + + if ( defref != NULL ) { + rs->sr_ref = referral_rewrite( op->o_bd->be_update_refs, + NULL, NULL, LDAP_SCOPE_DEFAULT ); + if ( rs->sr_ref ) { + rs->sr_flags |= REP_REF_MUSTBEFREED; + } else { + rs->sr_ref = defref; + } + rs->sr_err = LDAP_REFERRAL; + + } else { + rs->sr_text = "shadow context; no update referral"; + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + } + + return rs->sr_err; + } + + assert( !BER_BVISNULL( &op->o_req_ndn ) ); + + + + /* check if exists but not dynamicObject */ + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn, + slap_schema.si_oc_dynamicObject, NULL, 0, &e ); + if ( rs->sr_err != LDAP_SUCCESS ) { + rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn, + NULL, NULL, 0, &e ); + if ( rs->sr_err == LDAP_SUCCESS && e != NULL ) { + /* return referral only if "disclose" + * is granted on the object */ + if ( ! access_allowed( op, e, + slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + + } else { + rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; + rs->sr_text = "refresh operation only applies to dynamic objects"; + } + be_entry_release_r( op, e ); + + } else { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } + return rs->sr_err; + + } else if ( e != NULL ) { + be_entry_release_r( op, e ); + } + + /* we require manage privileges on the entryTtl, + * and fake a Relax control */ + op2.o_tag = LDAP_REQ_MODIFY; + op2.o_bd = &db; + db.bd_info = (BackendInfo *)on->on_info; + op2.o_callback = ≻ + sc.sc_response = slap_null_cb; + op2.o_relax = SLAP_CONTROL_CRITICAL; + op2.orm_modlist = &ttlmod; + + ttlmod.sml_op = LDAP_MOD_REPLACE; + ttlmod.sml_flags = SLAP_MOD_MANAGING; + ttlmod.sml_desc = slap_schema.si_ad_entryTtl; + ttlmod.sml_values = ttlvalues; + ttlmod.sml_numvals = 1; + ttlvalues[ 0 ].bv_val = ttlbuf; + ttlvalues[ 0 ].bv_len = snprintf( ttlbuf, sizeof( ttlbuf ), "%ld", ttl ); + BER_BVZERO( &ttlvalues[ 1 ] ); + + /* the entryExpireTimestamp is added by modify */ + rs->sr_err = op2.o_bd->be_modify( &op2, &rs2 ); + + if ( ttlmod.sml_next != NULL ) { + slap_mods_free( ttlmod.sml_next, 1 ); + } + + if ( rs->sr_err == LDAP_SUCCESS ) { + int rc; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + + ber_init_w_nullc( ber, LBER_USE_DER ); + + rc = ber_printf( ber, "{tiN}", LDAP_TAG_EXOP_REFRESH_RES_TTL, (int)ttl ); + + if ( rc < 0 ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + + } else { + (void)ber_flatten( ber, &rs->sr_rspdata ); + rs->sr_rspoid = ch_strdup( slap_EXOP_REFRESH.bv_val ); + + Log3( LDAP_DEBUG_TRACE, LDAP_LEVEL_INFO, + "%s REFRESH dn=\"%s\" TTL=%ld\n", + op->o_log_prefix, op->o_req_ndn.bv_val, ttl ); + } + + ber_free_buf( ber ); + } + + return rs->sr_err; + } + + return SLAP_CB_CONTINUE; +} + +enum { + DDS_STATE = 1, + DDS_MAXTTL, + DDS_MINTTL, + DDS_DEFAULTTTL, + DDS_INTERVAL, + DDS_TOLERANCE, + DDS_MAXDYNAMICOBJS, + + DDS_LAST +}; + +static ConfigDriver dds_cfgen; +#if 0 +static ConfigLDAPadd dds_ldadd; +static ConfigCfAdd dds_cfadd; +#endif + +static ConfigTable dds_cfg[] = { + { "dds-state", "on|off", + 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|DDS_STATE, dds_cfgen, + "( OLcfgOvAt:9.1 NAME 'olcDDSstate' " + "DESC 'RFC2589 Dynamic directory services state' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", NULL, NULL }, + { "dds-max-ttl", "ttl", + 2, 2, 0, ARG_MAGIC|DDS_MAXTTL, dds_cfgen, + "( OLcfgOvAt:9.2 NAME 'olcDDSmaxTtl' " + "DESC 'RFC2589 Dynamic directory services max TTL' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", NULL, NULL }, + { "dds-min-ttl", "ttl", + 2, 2, 0, ARG_MAGIC|DDS_MINTTL, dds_cfgen, + "( OLcfgOvAt:9.3 NAME 'olcDDSminTtl' " + "DESC 'RFC2589 Dynamic directory services min TTL' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", NULL, NULL }, + { "dds-default-ttl", "ttl", + 2, 2, 0, ARG_MAGIC|DDS_DEFAULTTTL, dds_cfgen, + "( OLcfgOvAt:9.4 NAME 'olcDDSdefaultTtl' " + "DESC 'RFC2589 Dynamic directory services default TTL' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", NULL, NULL }, + { "dds-interval", "interval", + 2, 2, 0, ARG_MAGIC|DDS_INTERVAL, dds_cfgen, + "( OLcfgOvAt:9.5 NAME 'olcDDSinterval' " + "DESC 'RFC2589 Dynamic directory services expiration " + "task run interval' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", NULL, NULL }, + { "dds-tolerance", "ttl", + 2, 2, 0, ARG_MAGIC|DDS_TOLERANCE, dds_cfgen, + "( OLcfgOvAt:9.6 NAME 'olcDDStolerance' " + "DESC 'RFC2589 Dynamic directory services additional " + "TTL in expiration scheduling' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", NULL, NULL }, + { "dds-max-dynamicObjects", "num", + 2, 2, 0, ARG_MAGIC|ARG_INT|DDS_MAXDYNAMICOBJS, dds_cfgen, + "( OLcfgOvAt:9.7 NAME 'olcDDSmaxDynamicObjects' " + "DESC 'RFC2589 Dynamic directory services max number of dynamic objects' " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs dds_ocs[] = { + { "( OLcfgOvOc:9.1 " + "NAME 'olcDDSConfig' " + "DESC 'RFC2589 Dynamic directory services configuration' " + "SUP olcOverlayConfig " + "MAY ( " + "olcDDSstate " + "$ olcDDSmaxTtl " + "$ olcDDSminTtl " + "$ olcDDSdefaultTtl " + "$ olcDDSinterval " + "$ olcDDStolerance " + "$ olcDDSmaxDynamicObjects " + " ) " + ")", Cft_Overlay, dds_cfg, NULL, NULL /* dds_cfadd */ }, + { NULL, 0, NULL } +}; + +#if 0 +static int +dds_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca ) +{ + return LDAP_SUCCESS; +} + +static int +dds_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca ) +{ + return 0; +} +#endif + +static int +dds_cfgen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + dds_info_t *di = on->on_bi.bi_private; + int rc = 0; + unsigned long t; + + + if ( c->op == SLAP_CONFIG_EMIT ) { + char buf[ SLAP_TEXT_BUFLEN ]; + struct berval bv; + + switch( c->type ) { + case DDS_STATE: + c->value_int = !DDS_OFF( di ); + break; + + case DDS_MAXTTL: + lutil_unparse_time( buf, sizeof( buf ), di->di_max_ttl ); + ber_str2bv( buf, 0, 0, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + break; + + case DDS_MINTTL: + if ( di->di_min_ttl ) { + lutil_unparse_time( buf, sizeof( buf ), di->di_min_ttl ); + ber_str2bv( buf, 0, 0, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + + } else { + rc = 1; + } + break; + + case DDS_DEFAULTTTL: + if ( di->di_default_ttl ) { + lutil_unparse_time( buf, sizeof( buf ), di->di_default_ttl ); + ber_str2bv( buf, 0, 0, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + + } else { + rc = 1; + } + break; + + case DDS_INTERVAL: + if ( di->di_interval ) { + lutil_unparse_time( buf, sizeof( buf ), di->di_interval ); + ber_str2bv( buf, 0, 0, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + + } else { + rc = 1; + } + break; + + case DDS_TOLERANCE: + if ( di->di_tolerance ) { + lutil_unparse_time( buf, sizeof( buf ), di->di_tolerance ); + ber_str2bv( buf, 0, 0, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + + } else { + rc = 1; + } + break; + + case DDS_MAXDYNAMICOBJS: + if ( di->di_max_dynamicObjects > 0 ) { + c->value_int = di->di_max_dynamicObjects; + + } else { + rc = 1; + } + break; + + default: + rc = 1; + break; + } + + return rc; + + } else if ( c->op == LDAP_MOD_DELETE ) { + switch( c->type ) { + case DDS_STATE: + di->di_flags &= ~DDS_FOFF; + break; + + case DDS_MAXTTL: + di->di_min_ttl = DDS_RF2589_DEFAULT_TTL; + break; + + case DDS_MINTTL: + di->di_min_ttl = 0; + break; + + case DDS_DEFAULTTTL: + di->di_default_ttl = 0; + break; + + case DDS_INTERVAL: + di->di_interval = 0; + break; + + case DDS_TOLERANCE: + di->di_tolerance = 0; + break; + + case DDS_MAXDYNAMICOBJS: + di->di_max_dynamicObjects = 0; + break; + + default: + rc = 1; + break; + } + + return rc; + } + + switch ( c->type ) { + case DDS_STATE: + if ( c->value_int ) { + di->di_flags &= ~DDS_FOFF; + + } else { + di->di_flags |= DDS_FOFF; + } + break; + + case DDS_MAXTTL: + if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "DDS unable to parse dds-max-ttl \"%s\"", + c->argv[ 1 ] ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + if ( t < DDS_RF2589_DEFAULT_TTL || t > DDS_RF2589_MAX_TTL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "DDS invalid dds-max-ttl=%lu; must be between %d and %d", + t, DDS_RF2589_DEFAULT_TTL, DDS_RF2589_MAX_TTL ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + di->di_max_ttl = (time_t)t; + break; + + case DDS_MINTTL: + if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "DDS unable to parse dds-min-ttl \"%s\"", + c->argv[ 1 ] ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + if ( t > DDS_RF2589_MAX_TTL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "DDS invalid dds-min-ttl=%lu", + t ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + if ( t == 0 ) { + di->di_min_ttl = DDS_RF2589_DEFAULT_TTL; + + } else { + di->di_min_ttl = (time_t)t; + } + break; + + case DDS_DEFAULTTTL: + if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "DDS unable to parse dds-default-ttl \"%s\"", + c->argv[ 1 ] ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + if ( t > DDS_RF2589_MAX_TTL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "DDS invalid dds-default-ttl=%lu", + t ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + if ( t == 0 ) { + di->di_default_ttl = DDS_RF2589_DEFAULT_TTL; + + } else { + di->di_default_ttl = (time_t)t; + } + break; + + case DDS_INTERVAL: + if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "DDS unable to parse dds-interval \"%s\"", + c->argv[ 1 ] ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + if ( t <= 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "DDS invalid dds-interval=%lu", + t ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + if ( t < 60 ) { + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_NOTICE, + "%s: dds-interval=%lu may be too small.\n", + c->log, t ); + } + + di->di_interval = (time_t)t; + if ( di->di_expire_task ) { + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, di->di_expire_task ) ) { + ldap_pvt_runqueue_stoptask( &slapd_rq, di->di_expire_task ); + } + di->di_expire_task->interval.tv_sec = DDS_INTERVAL( di ); + ldap_pvt_runqueue_resched( &slapd_rq, di->di_expire_task, 0 ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + break; + + case DDS_TOLERANCE: + if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "DDS unable to parse dds-tolerance \"%s\"", + c->argv[ 1 ] ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + if ( t > DDS_RF2589_MAX_TTL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "DDS invalid dds-tolerance=%lu", + t ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + di->di_tolerance = (time_t)t; + break; + + case DDS_MAXDYNAMICOBJS: + if ( c->value_int < 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "DDS invalid dds-max-dynamicObjects=%d", c->value_int ); + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + di->di_max_dynamicObjects = c->value_int; + break; + + default: + rc = 1; + break; + } + + return rc; +} + +static int +dds_db_init( + BackendDB *be, + ConfigReply *cr) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + dds_info_t *di; + BackendInfo *bi = on->on_info->oi_orig; + + if ( SLAP_ISGLOBALOVERLAY( be ) ) { + Log0( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "DDS cannot be used as global overlay.\n" ); + return 1; + } + + /* check support for required functions */ + /* FIXME: some could be provided by other overlays in between */ + if ( bi->bi_op_add == NULL /* object creation */ + || bi->bi_op_delete == NULL /* object deletion */ + || bi->bi_op_modify == NULL /* object refresh */ + || bi->bi_op_search == NULL /* object expiration */ + || bi->bi_entry_get_rw == NULL ) /* object type/existence checking */ + { + Log1( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "DDS backend \"%s\" does not provide " + "required functionality.\n", + bi->bi_type ); + return 1; + } + + di = (dds_info_t *)ch_calloc( 1, sizeof( dds_info_t ) ); + on->on_bi.bi_private = di; + + di->di_max_ttl = DDS_RF2589_DEFAULT_TTL; + di->di_max_ttl = DDS_RF2589_DEFAULT_TTL; + + ldap_pvt_thread_mutex_init( &di->di_mutex ); + + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_DYNAMIC; + + return 0; +} + +/* adds dynamicSubtrees to root DSE */ +static int +dds_entry_info( void *arg, Entry *e ) +{ + dds_info_t *di = (dds_info_t *)arg; + + attr_merge( e, slap_schema.si_ad_dynamicSubtrees, + di->di_suffix, di->di_nsuffix ); + + return 0; +} + +/* callback that counts the returned entries, since the search + * does not get to the point in slap_send_search_entries where + * the actual count occurs */ +static int +dds_count_cb( Operation *op, SlapReply *rs ) +{ + int *nump = (int *)op->o_callback->sc_private; + + switch ( rs->sr_type ) { + case REP_SEARCH: + (*nump)++; + break; + + case REP_SEARCHREF: + case REP_RESULT: + break; + + default: + assert( 0 ); + } + + return 0; +} + +/* count dynamic objects existing in the database at startup */ +static int +dds_count( void *ctx, BackendDB *be ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + dds_info_t *di = (dds_info_t *)on->on_bi.bi_private; + + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation *op; + slap_callback sc = { 0 }; + SlapReply rs = { REP_RESULT }; + + int rc; + char *extra = ""; + + connection_fake_init2( &conn, &opbuf, ctx, 0 ); + op = &opbuf.ob_op; + + op->o_tag = LDAP_REQ_SEARCH; + memset( &op->oq_search, 0, sizeof( op->oq_search ) ); + + op->o_bd = be; + + op->o_req_dn = op->o_bd->be_suffix[ 0 ]; + op->o_req_ndn = op->o_bd->be_nsuffix[ 0 ]; + + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + + op->ors_scope = LDAP_SCOPE_SUBTREE; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_slimit = SLAP_NO_LIMIT; + op->ors_attrs = slap_anlist_no_attrs; + + op->ors_filterstr.bv_len = STRLENOF( "(objectClass=" ")" ) + + slap_schema.si_oc_dynamicObject->soc_cname.bv_len; + op->ors_filterstr.bv_val = op->o_tmpalloc( op->ors_filterstr.bv_len + 1, op->o_tmpmemctx ); + snprintf( op->ors_filterstr.bv_val, op->ors_filterstr.bv_len + 1, + "(objectClass=%s)", + slap_schema.si_oc_dynamicObject->soc_cname.bv_val ); + + op->ors_filter = str2filter_x( op, op->ors_filterstr.bv_val ); + if ( op->ors_filter == NULL ) { + rs.sr_err = LDAP_OTHER; + goto done_search; + } + + op->o_callback = ≻ + sc.sc_response = dds_count_cb; + sc.sc_private = &di->di_num_dynamicObjects; + di->di_num_dynamicObjects = 0; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + (void)op->o_bd->bd_info->bi_op_search( op, &rs ); + op->o_bd->bd_info = (BackendInfo *)on; + +done_search:; + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + filter_free_x( op, op->ors_filter, 1 ); + + rc = rs.sr_err; + switch ( rs.sr_err ) { + case LDAP_SUCCESS: + Log1( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO, + "DDS non-expired=%d\n", + di->di_num_dynamicObjects ); + break; + + case LDAP_NO_SUCH_OBJECT: + /* (ITS#5267) database not created yet? */ + rs.sr_err = LDAP_SUCCESS; + extra = " (ignored)"; + /* fallthru */ + + default: + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "DDS non-expired objects lookup failed err=%d%s\n", + rc, extra ); + break; + } + + return rs.sr_err; +} + +static int +dds_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + dds_info_t *di = on->on_bi.bi_private; + int rc = 0; + void *thrctx = ldap_pvt_thread_pool_context(); + + if ( slapMode & SLAP_TOOL_MODE ) + return 0; + + if ( DDS_OFF( di ) ) { + goto done; + } + + if ( SLAP_SINGLE_SHADOW( be ) ) { + Log1( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "DDS incompatible with shadow database \"%s\".\n", + be->be_suffix[ 0 ].bv_val ); + return 1; + } + + if ( di->di_max_ttl == 0 ) { + di->di_max_ttl = DDS_RF2589_DEFAULT_TTL; + } + + if ( di->di_min_ttl == 0 ) { + di->di_max_ttl = DDS_RF2589_DEFAULT_TTL; + } + + di->di_suffix = be->be_suffix; + di->di_nsuffix = be->be_nsuffix; + + /* count the dynamic objects first */ + rc = dds_count( thrctx, be ); + if ( rc != LDAP_SUCCESS ) { + rc = 1; + goto done; + } + + /* start expire task */ + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + di->di_expire_task = ldap_pvt_runqueue_insert( &slapd_rq, + DDS_INTERVAL( di ), + dds_expire_fn, di, "dds_expire_fn", + be->be_suffix[ 0 ].bv_val ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + /* register dinamicSubtrees root DSE info support */ + rc = entry_info_register( dds_entry_info, (void *)di ); + +done:; + + return rc; +} + +static int +dds_db_close( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + dds_info_t *di = on->on_bi.bi_private; + + /* stop expire task */ + if ( di && di->di_expire_task ) { + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, di->di_expire_task ) ) { + ldap_pvt_runqueue_stoptask( &slapd_rq, di->di_expire_task ); + } + ldap_pvt_runqueue_remove( &slapd_rq, di->di_expire_task ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + di->di_expire_task = NULL; + } + + (void)entry_info_unregister( dds_entry_info, (void *)di ); + + return 0; +} + +static int +dds_db_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + dds_info_t *di = on->on_bi.bi_private; + + if ( di != NULL ) { + ldap_pvt_thread_mutex_destroy( &di->di_mutex ); + + free( di ); + } + + return 0; +} + +static int +slap_exop_refresh( + Operation *op, + SlapReply *rs ) +{ + BackendDB *bd = op->o_bd; + + rs->sr_err = slap_parse_refresh( op->ore_reqdata, &op->o_req_ndn, NULL, + &rs->sr_text, op->o_tmpmemctx ); + if ( rs->sr_err != LDAP_SUCCESS ) { + return rs->sr_err; + } + + Log2( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO, + "%s REFRESH dn=\"%s\"\n", + op->o_log_prefix, op->o_req_ndn.bv_val ); + op->o_req_dn = op->o_req_ndn; + + op->o_bd = select_backend( &op->o_req_ndn, 0 ); + if ( op->o_bd == NULL ) { + send_ldap_error( op, rs, LDAP_NO_SUCH_OBJECT, + "no global superior knowledge" ); + goto done; + } + + if ( !SLAP_DYNAMIC( op->o_bd ) ) { + send_ldap_error( op, rs, LDAP_UNAVAILABLE_CRITICAL_EXTENSION, + "backend does not support dynamic directory services" ); + goto done; + } + + rs->sr_err = backend_check_restrictions( op, rs, + (struct berval *)&slap_EXOP_REFRESH ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto done; + } + + if ( op->o_bd->be_extended == NULL ) { + send_ldap_error( op, rs, LDAP_UNAVAILABLE_CRITICAL_EXTENSION, + "backend does not support extended operations" ); + goto done; + } + + op->o_bd->be_extended( op, rs ); + +done:; + if ( !BER_BVISNULL( &op->o_req_ndn ) ) { + op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &op->o_req_ndn ); + BER_BVZERO( &op->o_req_dn ); + } + op->o_bd = bd; + + return rs->sr_err; +} + +static slap_overinst dds; + +static int do_not_load_exop; +static int do_not_replace_exop; +static int do_not_load_schema; + +#if SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC +static +#endif /* SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC */ +int +dds_initialize() +{ + int rc = 0; + int i, code; + + /* Make sure we don't exceed the bits reserved for userland */ + config_check_userland( DDS_LAST ); + + if ( !do_not_load_schema ) { + static struct { + char *desc; + slap_mask_t flags; + AttributeDescription **ad; + } s_at[] = { + { "( 1.3.6.1.4.1.4203.666.1.57 " + "NAME ( 'entryExpireTimestamp' ) " + "DESC 'RFC2589 OpenLDAP extension: expire time of a dynamic object, " + "computed as now + entryTtl' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "SINGLE-VALUE " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + SLAP_AT_HIDE, + &ad_entryExpireTimestamp }, + { NULL } + }; + + for ( i = 0; s_at[ i ].desc != NULL; i++ ) { + code = register_at( s_at[ i ].desc, s_at[ i ].ad, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "dds_initialize: register_at failed\n", 0, 0, 0 ); + return code; + } + (*s_at[ i ].ad)->ad_type->sat_flags |= SLAP_AT_HIDE; + } + } + + if ( !do_not_load_exop ) { + rc = load_extop2( (struct berval *)&slap_EXOP_REFRESH, + SLAP_EXOP_WRITES|SLAP_EXOP_HIDE, slap_exop_refresh, + !do_not_replace_exop ); + if ( rc != LDAP_SUCCESS ) { + Log1( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "DDS unable to register refresh exop: %d.\n", + rc ); + return rc; + } + } + + dds.on_bi.bi_type = "dds"; + + dds.on_bi.bi_db_init = dds_db_init; + dds.on_bi.bi_db_open = dds_db_open; + dds.on_bi.bi_db_close = dds_db_close; + dds.on_bi.bi_db_destroy = dds_db_destroy; + + dds.on_bi.bi_op_add = dds_op_add; + dds.on_bi.bi_op_delete = dds_op_delete; + dds.on_bi.bi_op_modify = dds_op_modify; + dds.on_bi.bi_op_modrdn = dds_op_rename; + dds.on_bi.bi_extended = dds_op_extended; + dds.on_response = dds_response; + + dds.on_bi.bi_cf_ocs = dds_ocs; + + rc = config_register_schema( dds_cfg, dds_ocs ); + if ( rc ) { + return rc; + } + + return overlay_register( &dds ); +} + +#if SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + int i; + + for ( i = 0; i < argc; i++ ) { + char *arg = argv[ i ]; + int no = 0; + + if ( strncasecmp( arg, "no-", STRLENOF( "no-" ) ) == 0 ) { + arg += STRLENOF( "no-" ); + no = 1; + } + + if ( strcasecmp( arg, "exop" ) == 0 ) { + do_not_load_exop = no; + + } else if ( strcasecmp( arg, "replace" ) == 0 ) { + do_not_replace_exop = no; + + } else if ( strcasecmp( arg, "schema" ) == 0 ) { + do_not_load_schema = no; + + } else { + Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "DDS unknown module arg[#%d]=\"%s\".\n", + i, argv[ i ] ); + return 1; + } + } + + return dds_initialize(); +} +#endif /* SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC */ + +#endif /* defined(SLAPD_OVER_DDS) */ diff --git a/servers/slapd/overlays/deref.c b/servers/slapd/overlays/deref.c new file mode 100644 index 0000000..f6358ba --- /dev/null +++ b/servers/slapd/overlays/deref.c @@ -0,0 +1,585 @@ +/* deref.c - dereference overlay */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * Portions Copyright 2008 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati + * for inclusion in OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_DEREF + +#include <stdio.h> + +#include "ac/string.h" +#include "ac/socket.h" + +#include "slap.h" +#include "config.h" + +#include "lutil.h" + +/* + * 1. Specification + * + * 1.1. Request + * + * controlValue ::= SEQUENCE OF derefSpec DerefSpec + * + * DerefSpec ::= SEQUENCE { + * derefAttr attributeDescription, ; DN-valued + * attributes AttributeList } + * + * AttributeList ::= SEQUENCE OF attr AttributeDescription + * + * derefAttr MUST be unique within controlValue + * + * + * 1.2. Response + * + * controlValue ::= SEQUENCE OF DerefRes + * + * From RFC 4511: + * PartialAttribute ::= SEQUENCE { + * type AttributeDescription, + * vals SET OF value AttributeValue } + * + * PartialAttributeList ::= SEQUENCE OF + * partialAttribute PartialAttribute + * + * DerefRes ::= SEQUENCE { + * derefAttr AttributeDescription, + * derefVal LDAPDN, + * attrVals [0] PartialAttributeList OPTIONAL } + * + * If vals is empty, partialAttribute is omitted. + * If all vals in attrVals are empty, attrVals is omitted. + * + * 2. Examples + * + * 2.1. Example + * + * 2.1.1. Request + * + * { { member, { GUID, SID } }, { memberOf, { GUID, SID } } } + * + * 2.1.2. Response + * + * { { memberOf, "cn=abartlet,cn=users,dc=abartlet,dc=net", + * { { GUID, [ "0bc11d00-e431-40a0-8767-344a320142fa" ] }, + * { SID, [ "S-1-2-3-2345" ] } } }, + * { memberOf, "cn=ando,cn=users,dc=sys-net,dc=it", + * { { GUID, [ "0bc11d00-e431-40a0-8767-344a320142fb" ] }, + * { SID, [ "S-1-2-3-2346" ] } } } } + * + * 2.2. Example + * + * 2.2.1. Request + * + * { { member, { cn, uid, drink } } } + * + * 2.2.2. Response + * + * { { member, "cn=ando,cn=users,dc=sys-net,dc=it", + * { { cn, [ "ando", "Pierangelo Masarati" ] }, + * { uid, [ "ando" ] } } }, + * { member, "dc=sys-net,dc=it" } } + * + * + * 3. Security considerations + * + * The control result must not disclose information the client's + * identity could not have accessed directly by performing the related + * search operations. The presence of a derefVal in the control + * response does not imply neither the existence of nor any access + * privilege to the corresponding entry. It is merely a consequence + * of the read access the client's identity has on the corresponding + * attribute's value. + */ + +#define o_deref o_ctrlflag[deref_cid] +#define o_ctrlderef o_controls[deref_cid] + +typedef struct DerefSpec { + AttributeDescription *ds_derefAttr; + AttributeDescription **ds_attributes; + int ds_nattrs; + struct DerefSpec *ds_next; +} DerefSpec; + +typedef struct DerefVal { + struct berval dv_derefSpecVal; + BerVarray *dv_attrVals; +} DerefVal; + +typedef struct DerefRes { + DerefSpec dr_spec; + DerefVal *dr_vals; + struct DerefRes *dr_next; +} DerefRes; + +typedef struct deref_cb_t { + slap_overinst *dc_on; + DerefSpec *dc_ds; +} deref_cb_t; + +static int deref_cid; +static slap_overinst deref; +static int ov_count; + +static int +deref_parseCtrl ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + ber_tag_t tag; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_len_t len; + char *last; + DerefSpec *dshead = NULL, **dsp = &dshead; + BerVarray attributes = NULL; + + if ( op->o_deref != SLAP_CONTROL_NONE ) { + rs->sr_text = "Dereference control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = "Dereference control value is absent"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) { + rs->sr_text = "Dereference control value is empty"; + return LDAP_PROTOCOL_ERROR; + } + + ber_init2( ber, &ctrl->ldctl_value, 0 ); + + for ( tag = ber_first_element( ber, &len, &last ); + tag != LBER_DEFAULT; + tag = ber_next_element( ber, &len, last ) ) + { + struct berval derefAttr; + DerefSpec *ds, *dstmp; + const char *text; + int rc; + ber_len_t cnt = sizeof(struct berval); + ber_len_t off = 0; + + if ( ber_scanf( ber, "{m{M}}", &derefAttr, &attributes, &cnt, off ) == LBER_ERROR + || !cnt ) + { + rs->sr_text = "Dereference control: derefSpec decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + + ds = (DerefSpec *)op->o_tmpcalloc( 1, + sizeof(DerefSpec) + sizeof(AttributeDescription *)*(cnt + 1), + op->o_tmpmemctx ); + ds->ds_attributes = (AttributeDescription **)&ds[ 1 ]; + ds->ds_nattrs = cnt; + + rc = slap_bv2ad( &derefAttr, &ds->ds_derefAttr, &text ); + if ( rc != LDAP_SUCCESS ) { + rs->sr_text = "Dereference control: derefAttr decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + + for ( dstmp = dshead; dstmp && dstmp != ds; dstmp = dstmp->ds_next ) { + if ( dstmp->ds_derefAttr == ds->ds_derefAttr ) { + rs->sr_text = "Dereference control: derefAttr must be unique within control"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + } + + if ( !( ds->ds_derefAttr->ad_type->sat_syntax->ssyn_flags & SLAP_SYNTAX_DN )) { + if ( ctrl->ldctl_iscritical ) { + rs->sr_text = "Dereference control: derefAttr syntax not distinguishedName"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + + rs->sr_err = LDAP_SUCCESS; + goto justcleanup; + } + + for ( cnt = 0; !BER_BVISNULL( &attributes[ cnt ] ); cnt++ ) { + rc = slap_bv2ad( &attributes[ cnt ], &ds->ds_attributes[ cnt ], &text ); + if ( rc != LDAP_SUCCESS ) { + rs->sr_text = "Dereference control: attribute decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + } + + ber_memfree_x( attributes, op->o_tmpmemctx ); + attributes = NULL; + + *dsp = ds; + dsp = &ds->ds_next; + } + + op->o_ctrlderef = (void *)dshead; + + op->o_deref = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + rs->sr_err = LDAP_SUCCESS; + +done:; + if ( rs->sr_err != LDAP_SUCCESS ) { +justcleanup:; + for ( ; dshead; ) { + DerefSpec *dsnext = dshead->ds_next; + op->o_tmpfree( dshead, op->o_tmpmemctx ); + dshead = dsnext; + } + } + + if ( attributes != NULL ) { + ber_memfree_x( attributes, op->o_tmpmemctx ); + } + + return rs->sr_err; +} + +static int +deref_cleanup( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_RESULT || rs->sr_err == SLAPD_ABANDON ) { + op->o_tmpfree( op->o_callback, op->o_tmpmemctx ); + op->o_callback = NULL; + + op->o_tmpfree( op->o_ctrlderef, op->o_tmpmemctx ); + op->o_ctrlderef = NULL; + } + + return SLAP_CB_CONTINUE; +} + +static int +deref_response( Operation *op, SlapReply *rs ) +{ + int rc = SLAP_CB_CONTINUE; + + if ( rs->sr_type == REP_SEARCH ) { + BerElementBuffer berbuf; + BerElement *ber = (BerElement *) &berbuf; + deref_cb_t *dc = (deref_cb_t *)op->o_callback->sc_private; + DerefSpec *ds; + DerefRes *dr, *drhead = NULL, **drp = &drhead; + struct berval bv = BER_BVNULL; + int nDerefRes = 0, nDerefVals = 0, nAttrs = 0, nVals = 0; + struct berval ctrlval; + LDAPControl *ctrl, *ctrlsp[2]; + AccessControlState acl_state = ACL_STATE_INIT; + static char dummy = '\0'; + Entry *ebase; + int i; + + rc = overlay_entry_get_ov( op, &rs->sr_entry->e_nname, NULL, NULL, 0, &ebase, dc->dc_on ); + if ( rc != LDAP_SUCCESS || ebase == NULL ) { + return SLAP_CB_CONTINUE; + } + + for ( ds = dc->dc_ds; ds; ds = ds->ds_next ) { + Attribute *a = attr_find( ebase->e_attrs, ds->ds_derefAttr ); + + if ( a != NULL ) { + DerefVal *dv; + BerVarray *bva; + + if ( !access_allowed( op, rs->sr_entry, a->a_desc, + NULL, ACL_READ, &acl_state ) ) + { + continue; + } + + dr = op->o_tmpcalloc( 1, + sizeof( DerefRes ) + ( sizeof( DerefVal ) + sizeof( BerVarray * ) * ds->ds_nattrs ) * ( a->a_numvals + 1 ), + op->o_tmpmemctx ); + dr->dr_spec = *ds; + dv = dr->dr_vals = (DerefVal *)&dr[ 1 ]; + bva = (BerVarray *)&dv[ a->a_numvals + 1 ]; + + bv.bv_len += ds->ds_derefAttr->ad_cname.bv_len; + nAttrs++; + nDerefRes++; + + for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) { + Entry *e = NULL; + + dv[ i ].dv_attrVals = bva; + bva += ds->ds_nattrs; + + + if ( !access_allowed( op, rs->sr_entry, a->a_desc, + &a->a_nvals[ i ], ACL_READ, &acl_state ) ) + { + dv[ i ].dv_derefSpecVal.bv_val = &dummy; + continue; + } + + ber_dupbv_x( &dv[ i ].dv_derefSpecVal, &a->a_vals[ i ], op->o_tmpmemctx ); + bv.bv_len += dv[ i ].dv_derefSpecVal.bv_len; + nVals++; + nDerefVals++; + + rc = overlay_entry_get_ov( op, &a->a_nvals[ i ], NULL, NULL, 0, &e, dc->dc_on ); + if ( rc == LDAP_SUCCESS && e != NULL ) { + int j; + + if ( access_allowed( op, e, slap_schema.si_ad_entry, + NULL, ACL_READ, NULL ) ) + { + for ( j = 0; j < ds->ds_nattrs; j++ ) { + Attribute *aa; + + if ( !access_allowed( op, e, ds->ds_attributes[ j ], NULL, + ACL_READ, &acl_state ) ) + { + continue; + } + + aa = attr_find( e->e_attrs, ds->ds_attributes[ j ] ); + if ( aa != NULL ) { + unsigned k, h, last = aa->a_numvals; + + ber_bvarray_dup_x( &dv[ i ].dv_attrVals[ j ], + aa->a_vals, op->o_tmpmemctx ); + + bv.bv_len += ds->ds_attributes[ j ]->ad_cname.bv_len; + + for ( k = 0, h = 0; k < aa->a_numvals; k++ ) { + if ( !access_allowed( op, e, + aa->a_desc, + &aa->a_nvals[ k ], + ACL_READ, &acl_state ) ) + { + op->o_tmpfree( dv[ i ].dv_attrVals[ j ][ h ].bv_val, + op->o_tmpmemctx ); + dv[ i ].dv_attrVals[ j ][ h ] = dv[ i ].dv_attrVals[ j ][ --last ]; + BER_BVZERO( &dv[ i ].dv_attrVals[ j ][ last ] ); + continue; + } + bv.bv_len += dv[ i ].dv_attrVals[ j ][ h ].bv_len; + nVals++; + h++; + } + nAttrs++; + } + } + } + + overlay_entry_release_ov( op, e, 0, dc->dc_on ); + } + } + + *drp = dr; + drp = &dr->dr_next; + } + } + overlay_entry_release_ov( op, ebase, 0, dc->dc_on ); + + if ( drhead == NULL ) { + return SLAP_CB_CONTINUE; + } + + /* cook the control value */ + bv.bv_len += nVals * sizeof(struct berval) + + nAttrs * sizeof(struct berval) + + nDerefVals * sizeof(DerefVal) + + nDerefRes * sizeof(DerefRes); + bv.bv_val = op->o_tmpalloc( bv.bv_len, op->o_tmpmemctx ); + + ber_init2( ber, &bv, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + rc = ber_printf( ber, "{" /*}*/ ); + for ( dr = drhead; dr != NULL; dr = dr->dr_next ) { + for ( i = 0; !BER_BVISNULL( &dr->dr_vals[ i ].dv_derefSpecVal ); i++ ) { + int j, first = 1; + + if ( dr->dr_vals[ i ].dv_derefSpecVal.bv_val == &dummy ) { + continue; + } + + rc = ber_printf( ber, "{OO" /*}*/, + &dr->dr_spec.ds_derefAttr->ad_cname, + &dr->dr_vals[ i ].dv_derefSpecVal ); + op->o_tmpfree( dr->dr_vals[ i ].dv_derefSpecVal.bv_val, op->o_tmpmemctx ); + for ( j = 0; j < dr->dr_spec.ds_nattrs; j++ ) { + if ( dr->dr_vals[ i ].dv_attrVals[ j ] != NULL ) { + if ( first ) { + rc = ber_printf( ber, "t{" /*}*/, + (LBER_CONSTRUCTED|LBER_CLASS_CONTEXT) ); + first = 0; + } + rc = ber_printf( ber, "{O[W]}", + &dr->dr_spec.ds_attributes[ j ]->ad_cname, + dr->dr_vals[ i ].dv_attrVals[ j ] ); + op->o_tmpfree( dr->dr_vals[ i ].dv_attrVals[ j ], + op->o_tmpmemctx ); + } + } + if ( !first ) { + rc = ber_printf( ber, /*{{*/ "}N}" ); + } else { + rc = ber_printf( ber, /*{*/ "}" ); + } + } + } + rc = ber_printf( ber, /*{*/ "}" ); + if ( ber_flatten2( ber, &ctrlval, 0 ) == -1 ) { + if ( op->o_deref == SLAP_CONTROL_CRITICAL ) { + rc = LDAP_CONSTRAINT_VIOLATION; + + } else { + rc = SLAP_CB_CONTINUE; + } + goto cleanup; + } + + ctrl = op->o_tmpcalloc( 1, + sizeof( LDAPControl ) + ctrlval.bv_len + 1, + op->o_tmpmemctx ); + ctrl->ldctl_value.bv_val = (char *)&ctrl[ 1 ]; + ctrl->ldctl_oid = LDAP_CONTROL_X_DEREF; + ctrl->ldctl_iscritical = 0; + ctrl->ldctl_value.bv_len = ctrlval.bv_len; + AC_MEMCPY( ctrl->ldctl_value.bv_val, ctrlval.bv_val, ctrlval.bv_len ); + ctrl->ldctl_value.bv_val[ ctrl->ldctl_value.bv_len ] = '\0'; + + ber_free_buf( ber ); + + ctrlsp[0] = ctrl; + ctrlsp[1] = NULL; + slap_add_ctrls( op, rs, ctrlsp ); + + rc = SLAP_CB_CONTINUE; + +cleanup:; + /* release all */ + for ( ; drhead != NULL; ) { + DerefRes *drnext = drhead->dr_next; + op->o_tmpfree( drhead, op->o_tmpmemctx ); + drhead = drnext; + } + + } else if ( rs->sr_type == REP_RESULT ) { + rc = deref_cleanup( op, rs ); + } + + return rc; +} + +static int +deref_op_search( Operation *op, SlapReply *rs ) +{ + if ( op->o_deref ) { + slap_callback *sc; + deref_cb_t *dc; + + sc = op->o_tmpcalloc( 1, sizeof( slap_callback ) + sizeof( deref_cb_t ), op->o_tmpmemctx ); + + dc = (deref_cb_t *)&sc[ 1 ]; + dc->dc_on = (slap_overinst *)op->o_bd->bd_info; + dc->dc_ds = (DerefSpec *)op->o_ctrlderef; + + sc->sc_response = deref_response; + sc->sc_cleanup = deref_cleanup; + sc->sc_private = (void *)dc; + + sc->sc_next = op->o_callback->sc_next; + op->o_callback->sc_next = sc; + } + + return SLAP_CB_CONTINUE; +} + +static int +deref_db_init( BackendDB *be, ConfigReply *cr) +{ + if ( ov_count == 0 ) { + int rc; + + rc = register_supported_control2( LDAP_CONTROL_X_DEREF, + SLAP_CTRL_SEARCH, + NULL, + deref_parseCtrl, + 1, /* replace */ + &deref_cid ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "deref_init: Failed to register control (%d)\n", + rc, 0, 0 ); + return rc; + } + } + ov_count++; + return LDAP_SUCCESS; +} + +static int +deref_db_open( BackendDB *be, ConfigReply *cr) +{ + return overlay_register_control( be, LDAP_CONTROL_X_DEREF ); +} + +#ifdef SLAP_CONFIG_DELETE +static int +deref_db_destroy( BackendDB *be, ConfigReply *cr) +{ + ov_count--; + overlay_unregister_control( be, LDAP_CONTROL_X_DEREF ); + if ( ov_count == 0 ) { + unregister_supported_control( LDAP_CONTROL_X_DEREF ); + } + return 0; +} +#endif /* SLAP_CONFIG_DELETE */ + +int +deref_initialize(void) +{ + deref.on_bi.bi_type = "deref"; + deref.on_bi.bi_db_init = deref_db_init; + deref.on_bi.bi_db_open = deref_db_open; +#ifdef SLAP_CONFIG_DELETE + deref.on_bi.bi_db_destroy = deref_db_destroy; +#endif /* SLAP_CONFIG_DELETE */ + deref.on_bi.bi_op_search = deref_op_search; + + return overlay_register( &deref ); +} + +#if SLAPD_OVER_DEREF == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return deref_initialize(); +} +#endif /* SLAPD_OVER_DEREF == SLAPD_MOD_DYNAMIC */ + +#endif /* SLAPD_OVER_DEREF */ diff --git a/servers/slapd/overlays/dyngroup.c b/servers/slapd/overlays/dyngroup.c new file mode 100644 index 0000000..561b6cd --- /dev/null +++ b/servers/slapd/overlays/dyngroup.c @@ -0,0 +1,228 @@ +/* dyngroup.c - Demonstration of overlay code */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * Copyright 2003 by Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_DYNGROUP + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "lutil.h" +#include "slap.h" +#include "config.h" + +/* This overlay extends the Compare operation to detect members of a + * dynamic group. It has no effect on any other operations. It must + * be configured with a pair of attributes to trigger on, e.g. + * attrpair member memberURL + * will cause compares on "member" to trigger a compare on "memberURL". + */ + +typedef struct adpair { + struct adpair *ap_next; + AttributeDescription *ap_mem; + AttributeDescription *ap_uri; +} adpair; + +static int dgroup_cf( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + int rc = 1; + + switch( c->op ) { + case SLAP_CONFIG_EMIT: + { + adpair *ap; + for ( ap = on->on_bi.bi_private; ap; ap = ap->ap_next ) { + struct berval bv; + char *ptr; + bv.bv_len = ap->ap_mem->ad_cname.bv_len + 1 + + ap->ap_uri->ad_cname.bv_len; + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + ptr = lutil_strcopy( bv.bv_val, ap->ap_mem->ad_cname.bv_val ); + *ptr++ = ' '; + strcpy( ptr, ap->ap_uri->ad_cname.bv_val ); + ber_bvarray_add( &c->rvalue_vals, &bv ); + rc = 0; + } + } + break; + case LDAP_MOD_DELETE: + if ( c->valx == -1 ) { + adpair *ap; + while (( ap = on->on_bi.bi_private )) { + on->on_bi.bi_private = ap->ap_next; + ch_free( ap ); + } + } else { + adpair **app, *ap; + int i; + app = (adpair **)&on->on_bi.bi_private; + for (i=0; i<=c->valx; i++, app = &ap->ap_next) { + ap = *app; + } + *app = ap->ap_next; + ch_free( ap ); + } + rc = 0; + break; + case SLAP_CONFIG_ADD: + case LDAP_MOD_ADD: + { + adpair ap = { NULL, NULL, NULL }, *a2; + const char *text; + if ( slap_str2ad( c->argv[1], &ap.ap_mem, &text ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s attribute description unknown: \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + if ( slap_str2ad( c->argv[2], &ap.ap_uri, &text ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s attribute description unknown: \"%s\"", + c->argv[0], c->argv[2] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + /* The on->on_bi.bi_private pointer can be used for + * anything this instance of the overlay needs. + */ + a2 = ch_malloc( sizeof(adpair) ); + a2->ap_next = on->on_bi.bi_private; + a2->ap_mem = ap.ap_mem; + a2->ap_uri = ap.ap_uri; + on->on_bi.bi_private = a2; + rc = 0; + } + } + return rc; +} + +static ConfigTable dgroupcfg[] = { + { "attrpair", "member-attribute> <URL-attribute", 3, 3, 0, + ARG_MAGIC, dgroup_cf, + "( OLcfgOvAt:17.1 NAME 'olcDGAttrPair' " + "DESC 'Member and MemberURL attribute pair' " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs dgroupocs[] = { + { "( OLcfgOvOc:17.1 " + "NAME 'olcDGConfig' " + "DESC 'Dynamic Group configuration' " + "SUP olcOverlayConfig " + "MAY olcDGAttrPair )", + Cft_Overlay, dgroupcfg }, + { NULL, 0, NULL } +}; + +static int +dyngroup_response( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + adpair *ap = on->on_bi.bi_private; + + /* If we've been configured and the current response is + * what we're looking for... + */ + if ( ap && op->o_tag == LDAP_REQ_COMPARE && + rs->sr_err == LDAP_NO_SUCH_ATTRIBUTE ) { + + for (;ap;ap=ap->ap_next) { + if ( op->oq_compare.rs_ava->aa_desc == ap->ap_mem ) { + /* This compare is for one of the attributes we're + * interested in. We'll use slapd's existing dyngroup + * evaluator to get the answer we want. + */ + int cache = op->o_do_not_cache; + + op->o_do_not_cache = 1; + rs->sr_err = backend_group( op, NULL, &op->o_req_ndn, + &op->oq_compare.rs_ava->aa_value, NULL, ap->ap_uri ); + op->o_do_not_cache = cache; + switch ( rs->sr_err ) { + case LDAP_SUCCESS: + rs->sr_err = LDAP_COMPARE_TRUE; + break; + + case LDAP_NO_SUCH_OBJECT: + rs->sr_err = LDAP_COMPARE_FALSE; + break; + } + break; + } + } + } + /* Default is to just fall through to the normal processing */ + return SLAP_CB_CONTINUE; +} + +static int +dyngroup_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + adpair *ap, *a2; + + for ( ap = on->on_bi.bi_private; ap; ap = a2 ) { + a2 = ap->ap_next; + ch_free( ap ); + } + return 0; +} + +static slap_overinst dyngroup; + +/* This overlay is set up for dynamic loading via moduleload. For static + * configuration, you'll need to arrange for the slap_overinst to be + * initialized and registered by some other function inside slapd. + */ + +int dyngroup_initialize() { + int code; + + dyngroup.on_bi.bi_type = "dyngroup"; + dyngroup.on_bi.bi_db_destroy = dyngroup_destroy; + dyngroup.on_response = dyngroup_response; + + dyngroup.on_bi.bi_cf_ocs = dgroupocs; + code = config_register_schema( dgroupcfg, dgroupocs ); + if ( code ) return code; + + return overlay_register( &dyngroup ); +} + +#if SLAPD_OVER_DYNGROUP == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return dyngroup_initialize(); +} +#endif + +#endif /* defined(SLAPD_OVER_DYNGROUP) */ diff --git a/servers/slapd/overlays/dynlist.c b/servers/slapd/overlays/dynlist.c new file mode 100644 index 0000000..7efc1a8 --- /dev/null +++ b/servers/slapd/overlays/dynlist.c @@ -0,0 +1,1574 @@ +/* dynlist.c - dynamic list overlay */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * Portions Copyright 2004-2005 Pierangelo Masarati. + * Portions Copyright 2008 Emmanuel Dreyfus. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati + * for SysNet s.n.c., for inclusion in OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_DYNLIST + +#if SLAPD_OVER_DYNGROUP != SLAPD_MOD_STATIC +#define TAKEOVER_DYNGROUP +#endif + +#include <stdio.h> + +#include <ac/string.h> + +#include "slap.h" +#include "config.h" +#include "lutil.h" + +static AttributeDescription *ad_dgIdentity, *ad_dgAuthz; + +typedef struct dynlist_map_t { + AttributeDescription *dlm_member_ad; + AttributeDescription *dlm_mapped_ad; + struct dynlist_map_t *dlm_next; +} dynlist_map_t; + +typedef struct dynlist_info_t { + ObjectClass *dli_oc; + AttributeDescription *dli_ad; + struct dynlist_map_t *dli_dlm; + struct berval dli_uri; + LDAPURLDesc *dli_lud; + struct berval dli_uri_nbase; + Filter *dli_uri_filter; + struct berval dli_default_filter; + struct dynlist_info_t *dli_next; +} dynlist_info_t; + +#define DYNLIST_USAGE \ + "\"dynlist-attrset <oc> [uri] <URL-ad> [[<mapped-ad>:]<member-ad> ...]\": " + +static dynlist_info_t * +dynlist_is_dynlist_next( Operation *op, SlapReply *rs, dynlist_info_t *old_dli ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + dynlist_info_t *dli; + + Attribute *a; + + if ( old_dli == NULL ) { + dli = (dynlist_info_t *)on->on_bi.bi_private; + + } else { + dli = old_dli->dli_next; + } + + a = attrs_find( rs->sr_entry->e_attrs, slap_schema.si_ad_objectClass ); + if ( a == NULL ) { + /* FIXME: objectClass must be present; for non-storage + * backends, like back-ldap, it needs to be added + * to the requested attributes */ + return NULL; + } + + for ( ; dli; dli = dli->dli_next ) { + if ( dli->dli_lud != NULL ) { + /* check base and scope */ + if ( !BER_BVISNULL( &dli->dli_uri_nbase ) + && !dnIsSuffixScope( &rs->sr_entry->e_nname, + &dli->dli_uri_nbase, + dli->dli_lud->lud_scope ) ) + { + continue; + } + + /* check filter */ + if ( dli->dli_uri_filter && test_filter( op, rs->sr_entry, dli->dli_uri_filter ) != LDAP_COMPARE_TRUE ) { + continue; + } + } + + if ( attr_valfind( a, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + &dli->dli_oc->soc_cname, NULL, + op->o_tmpmemctx ) == 0 ) + { + return dli; + } + } + + return NULL; +} + +static int +dynlist_make_filter( Operation *op, Entry *e, const char *url, struct berval *oldf, struct berval *newf ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + dynlist_info_t *dli = (dynlist_info_t *)on->on_bi.bi_private; + + char *ptr; + int needBrackets = 0; + + assert( oldf != NULL ); + assert( newf != NULL ); + assert( !BER_BVISNULL( oldf ) ); + assert( !BER_BVISEMPTY( oldf ) ); + + if ( oldf->bv_val[0] != '(' ) { + Debug( LDAP_DEBUG_ANY, "%s: dynlist, DN=\"%s\": missing brackets in URI=\"%s\" filter\n", + op->o_log_prefix, e->e_name.bv_val, url ); + needBrackets = 2; + } + + newf->bv_len = STRLENOF( "(&(!(objectClass=" "))" ")" ) + + dli->dli_oc->soc_cname.bv_len + oldf->bv_len + needBrackets; + newf->bv_val = op->o_tmpalloc( newf->bv_len + 1, op->o_tmpmemctx ); + if ( newf->bv_val == NULL ) { + return -1; + } + ptr = lutil_strcopy( newf->bv_val, "(&(!(objectClass=" ); + ptr = lutil_strcopy( ptr, dli->dli_oc->soc_cname.bv_val ); + ptr = lutil_strcopy( ptr, "))" ); + if ( needBrackets ) *ptr++ = '('; + ptr = lutil_strcopy( ptr, oldf->bv_val ); + if ( needBrackets ) *ptr++ = ')'; + ptr = lutil_strcopy( ptr, ")" ); + newf->bv_len = ptr - newf->bv_val; + + return 0; +} + +/* dynlist_sc_update() callback info set by dynlist_prepare_entry() */ +typedef struct dynlist_sc_t { + dynlist_info_t *dlc_dli; + Entry *dlc_e; +} dynlist_sc_t; + +static int +dynlist_sc_update( Operation *op, SlapReply *rs ) +{ + Entry *e; + Attribute *a; + int opattrs, + userattrs; + AccessControlState acl_state = ACL_STATE_INIT; + + dynlist_sc_t *dlc; + dynlist_map_t *dlm; + + if ( rs->sr_type != REP_SEARCH ) { + return 0; + } + + dlc = (dynlist_sc_t *)op->o_callback->sc_private; + e = dlc->dlc_e; + + assert( e != NULL ); + assert( rs->sr_entry != NULL ); + + /* test access to entry */ + if ( !access_allowed( op, rs->sr_entry, slap_schema.si_ad_entry, + NULL, ACL_READ, NULL ) ) + { + goto done; + } + + /* if there is only one member_ad, and it's not mapped, + * consider it as old-style member listing */ + dlm = dlc->dlc_dli->dli_dlm; + if ( dlm && dlm->dlm_mapped_ad == NULL && dlm->dlm_next == NULL ) { + /* if access allowed, try to add values, emulating permissive + * control to silently ignore duplicates */ + if ( access_allowed( op, rs->sr_entry, slap_schema.si_ad_entry, + NULL, ACL_READ, NULL ) ) + { + Modification mod; + const char *text = NULL; + char textbuf[1024]; + struct berval vals[ 2 ], nvals[ 2 ]; + + vals[ 0 ] = rs->sr_entry->e_name; + BER_BVZERO( &vals[ 1 ] ); + nvals[ 0 ] = rs->sr_entry->e_nname; + BER_BVZERO( &nvals[ 1 ] ); + + mod.sm_op = LDAP_MOD_ADD; + mod.sm_desc = dlm->dlm_member_ad; + mod.sm_type = dlm->dlm_member_ad->ad_cname; + mod.sm_values = vals; + mod.sm_nvalues = nvals; + mod.sm_numvals = 1; + + (void)modify_add_values( e, &mod, /* permissive */ 1, + &text, textbuf, sizeof( textbuf ) ); + } + + goto done; + } + + opattrs = SLAP_OPATTRS( rs->sr_attr_flags ); + userattrs = SLAP_USERATTRS( rs->sr_attr_flags ); + + for ( a = rs->sr_entry->e_attrs; a != NULL; a = a->a_next ) { + BerVarray vals, nvals = NULL; + int i, j, + is_oc = a->a_desc == slap_schema.si_ad_objectClass; + + /* if attribute is not requested, skip it */ + if ( rs->sr_attrs == NULL ) { + if ( is_at_operational( a->a_desc->ad_type ) ) { + continue; + } + + } else { + if ( is_at_operational( a->a_desc->ad_type ) ) { + if ( !opattrs && !ad_inlist( a->a_desc, rs->sr_attrs ) ) + { + continue; + } + + } else { + if ( !userattrs && !ad_inlist( a->a_desc, rs->sr_attrs ) ) + { + continue; + } + } + } + + /* test access to attribute */ + if ( op->ors_attrsonly ) { + if ( !access_allowed( op, rs->sr_entry, a->a_desc, NULL, + ACL_READ, &acl_state ) ) + { + continue; + } + } + + /* single-value check: keep first only */ + if ( is_at_single_value( a->a_desc->ad_type ) ) { + if ( attr_find( e->e_attrs, a->a_desc ) != NULL ) { + continue; + } + } + + /* test access to attribute */ + i = a->a_numvals; + + vals = op->o_tmpalloc( ( i + 1 ) * sizeof( struct berval ), op->o_tmpmemctx ); + if ( a->a_nvals != a->a_vals ) { + nvals = op->o_tmpalloc( ( i + 1 ) * sizeof( struct berval ), op->o_tmpmemctx ); + } + + for ( i = 0, j = 0; !BER_BVISNULL( &a->a_vals[i] ); i++ ) { + if ( is_oc ) { + ObjectClass *soc = oc_bvfind( &a->a_vals[i] ); + + if ( soc->soc_kind == LDAP_SCHEMA_STRUCTURAL ) { + continue; + } + } + + if ( access_allowed( op, rs->sr_entry, a->a_desc, + &a->a_nvals[i], ACL_READ, &acl_state ) ) + { + vals[j] = a->a_vals[i]; + if ( nvals ) { + nvals[j] = a->a_nvals[i]; + } + j++; + } + } + + /* if access allowed, try to add values, emulating permissive + * control to silently ignore duplicates */ + if ( j != 0 ) { + Modification mod; + const char *text = NULL; + char textbuf[1024]; + dynlist_map_t *dlm; + AttributeDescription *ad; + + BER_BVZERO( &vals[j] ); + if ( nvals ) { + BER_BVZERO( &nvals[j] ); + } + + ad = a->a_desc; + for ( dlm = dlc->dlc_dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_member_ad == a->a_desc ) { + if ( dlm->dlm_mapped_ad ) { + ad = dlm->dlm_mapped_ad; + } + break; + } + } + + mod.sm_op = LDAP_MOD_ADD; + mod.sm_desc = ad; + mod.sm_type = ad->ad_cname; + mod.sm_values = vals; + mod.sm_nvalues = nvals; + mod.sm_numvals = j; + + (void)modify_add_values( e, &mod, /* permissive */ 1, + &text, textbuf, sizeof( textbuf ) ); + } + + op->o_tmpfree( vals, op->o_tmpmemctx ); + if ( nvals ) { + op->o_tmpfree( nvals, op->o_tmpmemctx ); + } + } + +done:; + if ( rs->sr_flags & REP_ENTRY_MUSTBEFREED ) { + entry_free( rs->sr_entry ); + rs->sr_entry = NULL; + rs->sr_flags &= ~REP_ENTRY_MASK; + } + + return 0; +} + +static int +dynlist_prepare_entry( Operation *op, SlapReply *rs, dynlist_info_t *dli ) +{ + Attribute *a, *id = NULL; + slap_callback cb = { 0 }; + Operation o = *op; + struct berval *url; + Entry *e; + int opattrs, + userattrs; + dynlist_sc_t dlc = { 0 }; + dynlist_map_t *dlm; + + a = attrs_find( rs->sr_entry->e_attrs, dli->dli_ad ); + if ( a == NULL ) { + /* FIXME: error? */ + return SLAP_CB_CONTINUE; + } + + opattrs = SLAP_OPATTRS( rs->sr_attr_flags ); + userattrs = SLAP_USERATTRS( rs->sr_attr_flags ); + + /* Don't generate member list if it wasn't requested */ + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad; + if ( userattrs || ad_inlist( ad, rs->sr_attrs ) ) + break; + } + if ( dli->dli_dlm && !dlm ) + return SLAP_CB_CONTINUE; + + if ( ad_dgIdentity && ( id = attrs_find( rs->sr_entry->e_attrs, ad_dgIdentity ))) { + Attribute *authz = NULL; + + /* if not rootdn and dgAuthz is present, + * check if user can be authorized as dgIdentity */ + if ( ad_dgAuthz && !BER_BVISEMPTY( &id->a_nvals[0] ) && !be_isroot( op ) + && ( authz = attrs_find( rs->sr_entry->e_attrs, ad_dgAuthz ) ) ) + { + if ( slap_sasl_matches( op, authz->a_nvals, + &o.o_ndn, &o.o_ndn ) != LDAP_SUCCESS ) + { + return SLAP_CB_CONTINUE; + } + } + + o.o_dn = id->a_vals[0]; + o.o_ndn = id->a_nvals[0]; + o.o_groups = NULL; + } + + e = rs->sr_entry; + /* ensure e is modifiable, but do not replace + * sr_entry yet since we have pointers into it */ + if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE ) ) { + e = entry_dup( rs->sr_entry ); + } + + dlc.dlc_e = e; + dlc.dlc_dli = dli; + cb.sc_private = &dlc; + cb.sc_response = dynlist_sc_update; + + o.o_callback = &cb; + o.ors_deref = LDAP_DEREF_NEVER; + o.ors_limit = NULL; + o.ors_tlimit = SLAP_NO_LIMIT; + o.ors_slimit = SLAP_NO_LIMIT; + + for ( url = a->a_nvals; !BER_BVISNULL( url ); url++ ) { + LDAPURLDesc *lud = NULL; + int i, j; + struct berval dn; + int rc; + + BER_BVZERO( &o.o_req_dn ); + BER_BVZERO( &o.o_req_ndn ); + o.ors_filter = NULL; + o.ors_attrs = NULL; + BER_BVZERO( &o.ors_filterstr ); + + if ( ldap_url_parse( url->bv_val, &lud ) != LDAP_URL_SUCCESS ) { + /* FIXME: error? */ + continue; + } + + if ( lud->lud_host != NULL ) { + /* FIXME: host not allowed; reject as illegal? */ + Debug( LDAP_DEBUG_ANY, "dynlist_prepare_entry(\"%s\"): " + "illegal URI \"%s\"\n", + e->e_name.bv_val, url->bv_val, 0 ); + goto cleanup; + } + + if ( lud->lud_dn == NULL ) { + /* note that an empty base is not honored in terms + * of defaultSearchBase, because select_backend() + * is not aware of the defaultSearchBase option; + * this can be useful in case of a database serving + * the empty suffix */ + BER_BVSTR( &dn, "" ); + + } else { + ber_str2bv( lud->lud_dn, 0, 0, &dn ); + } + rc = dnPrettyNormal( NULL, &dn, &o.o_req_dn, &o.o_req_ndn, op->o_tmpmemctx ); + if ( rc != LDAP_SUCCESS ) { + /* FIXME: error? */ + goto cleanup; + } + o.ors_scope = lud->lud_scope; + + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_mapped_ad != NULL ) { + break; + } + } + + if ( dli->dli_dlm && !dlm ) { + /* if ( lud->lud_attrs != NULL ), + * the URL should be ignored */ + o.ors_attrs = slap_anlist_no_attrs; + + } else if ( lud->lud_attrs == NULL ) { + o.ors_attrs = rs->sr_attrs; + + } else { + for ( i = 0; lud->lud_attrs[i]; i++) + /* just count */ ; + + o.ors_attrs = op->o_tmpcalloc( i + 1, sizeof( AttributeName ), op->o_tmpmemctx ); + for ( i = 0, j = 0; lud->lud_attrs[i]; i++) { + const char *text = NULL; + + ber_str2bv( lud->lud_attrs[i], 0, 0, &o.ors_attrs[j].an_name ); + o.ors_attrs[j].an_desc = NULL; + (void)slap_bv2ad( &o.ors_attrs[j].an_name, &o.ors_attrs[j].an_desc, &text ); + /* FIXME: ignore errors... */ + + if ( rs->sr_attrs == NULL ) { + if ( o.ors_attrs[j].an_desc != NULL && + is_at_operational( o.ors_attrs[j].an_desc->ad_type ) ) + { + continue; + } + + } else { + if ( o.ors_attrs[j].an_desc != NULL && + is_at_operational( o.ors_attrs[j].an_desc->ad_type ) ) + { + if ( !opattrs ) { + continue; + } + + if ( !ad_inlist( o.ors_attrs[j].an_desc, rs->sr_attrs ) ) { + /* lookup if mapped -- linear search, + * not very efficient unless list + * is very short */ + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_member_ad == o.ors_attrs[j].an_desc ) { + break; + } + } + + if ( dlm == NULL ) { + continue; + } + } + + } else { + if ( !userattrs && + o.ors_attrs[j].an_desc != NULL && + !ad_inlist( o.ors_attrs[j].an_desc, rs->sr_attrs ) ) + { + /* lookup if mapped -- linear search, + * not very efficient unless list + * is very short */ + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_member_ad == o.ors_attrs[j].an_desc ) { + break; + } + } + + if ( dlm == NULL ) { + continue; + } + } + } + } + + j++; + } + + if ( j == 0 ) { + goto cleanup; + } + + BER_BVZERO( &o.ors_attrs[j].an_name ); + } + + if ( lud->lud_filter == NULL ) { + ber_dupbv_x( &o.ors_filterstr, + &dli->dli_default_filter, op->o_tmpmemctx ); + + } else { + struct berval flt; + ber_str2bv( lud->lud_filter, 0, 0, &flt ); + if ( dynlist_make_filter( op, rs->sr_entry, url->bv_val, &flt, &o.ors_filterstr ) ) { + /* error */ + goto cleanup; + } + } + o.ors_filter = str2filter_x( op, o.ors_filterstr.bv_val ); + if ( o.ors_filter == NULL ) { + goto cleanup; + } + + o.o_bd = select_backend( &o.o_req_ndn, 1 ); + if ( o.o_bd && o.o_bd->be_search ) { + SlapReply r = { REP_SEARCH }; + r.sr_attr_flags = slap_attr_flags( o.ors_attrs ); + (void)o.o_bd->be_search( &o, &r ); + } + +cleanup:; + if ( id ) { + slap_op_groups_free( &o ); + } + if ( o.ors_filter ) { + filter_free_x( &o, o.ors_filter, 1 ); + } + if ( o.ors_attrs && o.ors_attrs != rs->sr_attrs + && o.ors_attrs != slap_anlist_no_attrs ) + { + op->o_tmpfree( o.ors_attrs, op->o_tmpmemctx ); + } + if ( !BER_BVISNULL( &o.o_req_dn ) ) { + op->o_tmpfree( o.o_req_dn.bv_val, op->o_tmpmemctx ); + } + if ( !BER_BVISNULL( &o.o_req_ndn ) ) { + op->o_tmpfree( o.o_req_ndn.bv_val, op->o_tmpmemctx ); + } + assert( BER_BVISNULL( &o.ors_filterstr ) + || o.ors_filterstr.bv_val != lud->lud_filter ); + op->o_tmpfree( o.ors_filterstr.bv_val, op->o_tmpmemctx ); + ldap_free_urldesc( lud ); + } + + if ( e != rs->sr_entry ) { + rs_replace_entry( op, rs, (slap_overinst *)op->o_bd->bd_info, e ); + rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED; + } + + return SLAP_CB_CONTINUE; +} + +/* dynlist_sc_compare_entry() callback set by dynlist_compare() */ +typedef struct dynlist_cc_t { + slap_callback dc_cb; +# define dc_ava dc_cb.sc_private /* attr:val to compare with */ + int *dc_res; +} dynlist_cc_t; + +static int +dynlist_sc_compare_entry( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH && rs->sr_entry != NULL ) { + dynlist_cc_t *dc = (dynlist_cc_t *)op->o_callback; + AttributeAssertion *ava = dc->dc_ava; + Attribute *a = attrs_find( rs->sr_entry->e_attrs, ava->aa_desc ); + + if ( a != NULL ) { + while ( LDAP_SUCCESS != attr_valfind( a, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + &ava->aa_value, NULL, op->o_tmpmemctx ) + && (a = attrs_find( a->a_next, ava->aa_desc )) != NULL ) + ; + *dc->dc_res = a ? LDAP_COMPARE_TRUE : LDAP_COMPARE_FALSE; + } + } + + return 0; +} + +static int +dynlist_compare( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + dynlist_info_t *dli = (dynlist_info_t *)on->on_bi.bi_private; + Operation o = *op; + Entry *e = NULL; + dynlist_map_t *dlm; + BackendDB *be; + + for ( ; dli != NULL; dli = dli->dli_next ) { + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) + if ( op->oq_compare.rs_ava->aa_desc == dlm->dlm_member_ad ) + break; + + if ( dlm ) { + /* This compare is for one of the attributes we're + * interested in. We'll use slapd's existing dyngroup + * evaluator to get the answer we want. + */ + BerVarray id = NULL, authz = NULL; + + o.o_do_not_cache = 1; + + if ( ad_dgIdentity && backend_attribute( &o, NULL, &o.o_req_ndn, + ad_dgIdentity, &id, ACL_READ ) == LDAP_SUCCESS ) + { + /* if not rootdn and dgAuthz is present, + * check if user can be authorized as dgIdentity */ + if ( ad_dgAuthz && !BER_BVISEMPTY( id ) && !be_isroot( op ) + && backend_attribute( &o, NULL, &o.o_req_ndn, + ad_dgAuthz, &authz, ACL_READ ) == LDAP_SUCCESS ) + { + + rs->sr_err = slap_sasl_matches( op, authz, + &o.o_ndn, &o.o_ndn ); + ber_bvarray_free_x( authz, op->o_tmpmemctx ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto done; + } + } + + o.o_dn = *id; + o.o_ndn = *id; + o.o_groups = NULL; /* authz changed, invalidate cached groups */ + } + + rs->sr_err = backend_group( &o, NULL, &o.o_req_ndn, + &o.oq_compare.rs_ava->aa_value, dli->dli_oc, dli->dli_ad ); + switch ( rs->sr_err ) { + case LDAP_SUCCESS: + rs->sr_err = LDAP_COMPARE_TRUE; + break; + + case LDAP_NO_SUCH_OBJECT: + /* NOTE: backend_group() returns noSuchObject + * if op_ndn does not exist; however, since + * dynamic list expansion means that the + * member attribute is virtually present, the + * non-existence of the asserted value implies + * the assertion is FALSE rather than + * UNDEFINED */ + rs->sr_err = LDAP_COMPARE_FALSE; + break; + } + +done:; + if ( id ) ber_bvarray_free_x( id, o.o_tmpmemctx ); + + return SLAP_CB_CONTINUE; + } + } + + be = select_backend( &o.o_req_ndn, 1 ); + if ( !be || !be->be_search ) { + return SLAP_CB_CONTINUE; + } + + if ( overlay_entry_get_ov( &o, &o.o_req_ndn, NULL, NULL, 0, &e, on ) != + LDAP_SUCCESS || e == NULL ) + { + return SLAP_CB_CONTINUE; + } + + /* check for dynlist objectClass; done if not found */ + dli = (dynlist_info_t *)on->on_bi.bi_private; + while ( dli != NULL && !is_entry_objectclass_or_sub( e, dli->dli_oc ) ) { + dli = dli->dli_next; + } + if ( dli == NULL ) { + goto release; + } + + if ( ad_dgIdentity ) { + Attribute *id = attrs_find( e->e_attrs, ad_dgIdentity ); + if ( id ) { + Attribute *authz; + + /* if not rootdn and dgAuthz is present, + * check if user can be authorized as dgIdentity */ + if ( ad_dgAuthz && !BER_BVISEMPTY( &id->a_nvals[0] ) && !be_isroot( op ) + && ( authz = attrs_find( e->e_attrs, ad_dgAuthz ) ) ) + { + if ( slap_sasl_matches( op, authz->a_nvals, + &o.o_ndn, &o.o_ndn ) != LDAP_SUCCESS ) + { + goto release; + } + } + + o.o_dn = id->a_vals[0]; + o.o_ndn = id->a_nvals[0]; + o.o_groups = NULL; + } + } + + /* generate dynamic list with dynlist_response() and compare */ + { + SlapReply r = { REP_SEARCH }; + dynlist_cc_t dc = { { 0, dynlist_sc_compare_entry, 0, 0 }, 0 }; + AttributeName an[2]; + + dc.dc_ava = op->orc_ava; + dc.dc_res = &rs->sr_err; + o.o_callback = (slap_callback *) &dc; + + o.o_tag = LDAP_REQ_SEARCH; + o.ors_limit = NULL; + o.ors_tlimit = SLAP_NO_LIMIT; + o.ors_slimit = SLAP_NO_LIMIT; + + o.ors_filterstr = *slap_filterstr_objectClass_pres; + o.ors_filter = (Filter *) slap_filter_objectClass_pres; + + o.ors_scope = LDAP_SCOPE_BASE; + o.ors_deref = LDAP_DEREF_NEVER; + an[0].an_name = op->orc_ava->aa_desc->ad_cname; + an[0].an_desc = op->orc_ava->aa_desc; + BER_BVZERO( &an[1].an_name ); + o.ors_attrs = an; + o.ors_attrsonly = 0; + + o.o_acl_priv = ACL_COMPARE; + + o.o_bd = be; + (void)be->be_search( &o, &r ); + + if ( o.o_dn.bv_val != op->o_dn.bv_val ) { + slap_op_groups_free( &o ); + } + } + +release:; + if ( e != NULL ) { + overlay_entry_release_ov( &o, e, 0, on ); + } + + return SLAP_CB_CONTINUE; +} + +static int +dynlist_response( Operation *op, SlapReply *rs ) +{ + switch ( op->o_tag ) { + case LDAP_REQ_SEARCH: + if ( rs->sr_type == REP_SEARCH && !get_manageDSAit( op ) ) + { + int rc = SLAP_CB_CONTINUE; + dynlist_info_t *dli = NULL; + + while ( (dli = dynlist_is_dynlist_next( op, rs, dli )) != NULL ) { + rc = dynlist_prepare_entry( op, rs, dli ); + } + + return rc; + } + break; + + case LDAP_REQ_COMPARE: + switch ( rs->sr_err ) { + /* NOTE: we waste a few cycles running the dynamic list + * also when the result is FALSE, which occurs if the + * dynamic entry itself contains the AVA attribute */ + /* FIXME: this approach is less than optimal; a dedicated + * compare op should be implemented, that fetches the + * entry, checks if it has the appropriate objectClass + * and, in case, runs a compare thru all the URIs, + * stopping at the first positive occurrence; see ITS#3756 */ + case LDAP_COMPARE_FALSE: + case LDAP_NO_SUCH_ATTRIBUTE: + return dynlist_compare( op, rs ); + } + break; + } + + return SLAP_CB_CONTINUE; +} + +static int +dynlist_build_def_filter( dynlist_info_t *dli ) +{ + char *ptr; + + dli->dli_default_filter.bv_len = STRLENOF( "(!(objectClass=" "))" ) + + dli->dli_oc->soc_cname.bv_len; + dli->dli_default_filter.bv_val = ch_malloc( dli->dli_default_filter.bv_len + 1 ); + if ( dli->dli_default_filter.bv_val == NULL ) { + Debug( LDAP_DEBUG_ANY, "dynlist_db_open: malloc failed.\n", + 0, 0, 0 ); + return -1; + } + + ptr = lutil_strcopy( dli->dli_default_filter.bv_val, "(!(objectClass=" ); + ptr = lutil_strcopy( ptr, dli->dli_oc->soc_cname.bv_val ); + ptr = lutil_strcopy( ptr, "))" ); + + assert( ptr == &dli->dli_default_filter.bv_val[dli->dli_default_filter.bv_len] ); + + return 0; +} + +enum { + DL_ATTRSET = 1, + DL_ATTRPAIR, + DL_ATTRPAIR_COMPAT, + DL_LAST +}; + +static ConfigDriver dl_cfgen; + +/* XXXmanu 255 is the maximum arguments we allow. Can we go beyond? */ +static ConfigTable dlcfg[] = { + { "dynlist-attrset", "group-oc> [uri] <URL-ad> <[mapped:]member-ad> [...]", + 3, 0, 0, ARG_MAGIC|DL_ATTRSET, dl_cfgen, + "( OLcfgOvAt:8.1 NAME 'olcDlAttrSet' " + "DESC 'Dynamic list: <group objectClass>, <URL attributeDescription>, <member attributeDescription>' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "X-ORDERED 'VALUES' )", + NULL, NULL }, + { "dynlist-attrpair", "member-ad> <URL-ad", + 3, 3, 0, ARG_MAGIC|DL_ATTRPAIR, dl_cfgen, + NULL, NULL, NULL }, +#ifdef TAKEOVER_DYNGROUP + { "attrpair", "member-ad> <URL-ad", + 3, 3, 0, ARG_MAGIC|DL_ATTRPAIR_COMPAT, dl_cfgen, + NULL, NULL, NULL }, +#endif + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs dlocs[] = { + { "( OLcfgOvOc:8.1 " + "NAME 'olcDynamicList' " + "DESC 'Dynamic list configuration' " + "SUP olcOverlayConfig " + "MAY olcDLattrSet )", + Cft_Overlay, dlcfg, NULL, NULL }, + { NULL, 0, NULL } +}; + +static int +dl_cfgen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + dynlist_info_t *dli = (dynlist_info_t *)on->on_bi.bi_private; + + int rc = 0, i; + + if ( c->op == SLAP_CONFIG_EMIT ) { + switch( c->type ) { + case DL_ATTRSET: + for ( i = 0; dli; i++, dli = dli->dli_next ) { + struct berval bv; + char *ptr = c->cr_msg; + dynlist_map_t *dlm; + + assert( dli->dli_oc != NULL ); + assert( dli->dli_ad != NULL ); + + /* FIXME: check buffer overflow! */ + ptr += snprintf( c->cr_msg, sizeof( c->cr_msg ), + SLAP_X_ORDERED_FMT "%s", i, + dli->dli_oc->soc_cname.bv_val ); + + if ( !BER_BVISNULL( &dli->dli_uri ) ) { + *ptr++ = ' '; + *ptr++ = '"'; + ptr = lutil_strncopy( ptr, dli->dli_uri.bv_val, + dli->dli_uri.bv_len ); + *ptr++ = '"'; + } + + *ptr++ = ' '; + ptr = lutil_strncopy( ptr, dli->dli_ad->ad_cname.bv_val, + dli->dli_ad->ad_cname.bv_len ); + + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + ptr[ 0 ] = ' '; + ptr++; + if ( dlm->dlm_mapped_ad ) { + ptr = lutil_strcopy( ptr, dlm->dlm_mapped_ad->ad_cname.bv_val ); + ptr[ 0 ] = ':'; + ptr++; + } + + ptr = lutil_strcopy( ptr, dlm->dlm_member_ad->ad_cname.bv_val ); + } + + bv.bv_val = c->cr_msg; + bv.bv_len = ptr - bv.bv_val; + value_add_one( &c->rvalue_vals, &bv ); + } + break; + + case DL_ATTRPAIR_COMPAT: + case DL_ATTRPAIR: + rc = 1; + break; + + default: + rc = 1; + break; + } + + return rc; + + } else if ( c->op == LDAP_MOD_DELETE ) { + switch( c->type ) { + case DL_ATTRSET: + if ( c->valx < 0 ) { + dynlist_info_t *dli_next; + + for ( dli_next = dli; dli_next; dli = dli_next ) { + dynlist_map_t *dlm = dli->dli_dlm; + dynlist_map_t *dlm_next; + + dli_next = dli->dli_next; + + if ( !BER_BVISNULL( &dli->dli_uri ) ) { + ch_free( dli->dli_uri.bv_val ); + } + + if ( dli->dli_lud != NULL ) { + ldap_free_urldesc( dli->dli_lud ); + } + + if ( !BER_BVISNULL( &dli->dli_uri_nbase ) ) { + ber_memfree( dli->dli_uri_nbase.bv_val ); + } + + if ( dli->dli_uri_filter != NULL ) { + filter_free( dli->dli_uri_filter ); + } + + ch_free( dli->dli_default_filter.bv_val ); + + while ( dlm != NULL ) { + dlm_next = dlm->dlm_next; + ch_free( dlm ); + dlm = dlm_next; + } + ch_free( dli ); + } + + on->on_bi.bi_private = NULL; + + } else { + dynlist_info_t **dlip; + dynlist_map_t *dlm; + dynlist_map_t *dlm_next; + + for ( i = 0, dlip = (dynlist_info_t **)&on->on_bi.bi_private; + i < c->valx; i++ ) + { + if ( *dlip == NULL ) { + return 1; + } + dlip = &(*dlip)->dli_next; + } + + dli = *dlip; + *dlip = dli->dli_next; + + if ( !BER_BVISNULL( &dli->dli_uri ) ) { + ch_free( dli->dli_uri.bv_val ); + } + + if ( dli->dli_lud != NULL ) { + ldap_free_urldesc( dli->dli_lud ); + } + + if ( !BER_BVISNULL( &dli->dli_uri_nbase ) ) { + ber_memfree( dli->dli_uri_nbase.bv_val ); + } + + if ( dli->dli_uri_filter != NULL ) { + filter_free( dli->dli_uri_filter ); + } + + ch_free( dli->dli_default_filter.bv_val ); + + dlm = dli->dli_dlm; + while ( dlm != NULL ) { + dlm_next = dlm->dlm_next; + ch_free( dlm ); + dlm = dlm_next; + } + ch_free( dli ); + + dli = (dynlist_info_t *)on->on_bi.bi_private; + } + break; + + case DL_ATTRPAIR_COMPAT: + case DL_ATTRPAIR: + rc = 1; + break; + + default: + rc = 1; + break; + } + + return rc; + } + + switch( c->type ) { + case DL_ATTRSET: { + dynlist_info_t **dlip, + *dli_next = NULL; + ObjectClass *oc = NULL; + AttributeDescription *ad = NULL; + int attridx = 2; + LDAPURLDesc *lud = NULL; + struct berval nbase = BER_BVNULL; + Filter *filter = NULL; + struct berval uri = BER_BVNULL; + dynlist_map_t *dlm = NULL, *dlml = NULL; + const char *text; + + oc = oc_find( c->argv[ 1 ] ); + if ( oc == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE + "unable to find ObjectClass \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + return 1; + } + + if ( strncasecmp( c->argv[ attridx ], "ldap://", STRLENOF("ldap://") ) == 0 ) { + if ( ldap_url_parse( c->argv[ attridx ], &lud ) != LDAP_URL_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE + "unable to parse URI \"%s\"", + c->argv[ attridx ] ); + rc = 1; + goto done_uri; + } + + if ( lud->lud_host != NULL ) { + if ( lud->lud_host[0] == '\0' ) { + ch_free( lud->lud_host ); + lud->lud_host = NULL; + + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE + "host not allowed in URI \"%s\"", + c->argv[ attridx ] ); + rc = 1; + goto done_uri; + } + } + + if ( lud->lud_attrs != NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE + "attrs not allowed in URI \"%s\"", + c->argv[ attridx ] ); + rc = 1; + goto done_uri; + } + + if ( lud->lud_exts != NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE + "extensions not allowed in URI \"%s\"", + c->argv[ attridx ] ); + rc = 1; + goto done_uri; + } + + if ( lud->lud_dn != NULL && lud->lud_dn[ 0 ] != '\0' ) { + struct berval dn; + ber_str2bv( lud->lud_dn, 0, 0, &dn ); + rc = dnNormalize( 0, NULL, NULL, &dn, &nbase, NULL ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE + "DN normalization failed in URI \"%s\"", + c->argv[ attridx ] ); + goto done_uri; + } + } + + if ( lud->lud_filter != NULL && lud->lud_filter[ 0 ] != '\0' ) { + filter = str2filter( lud->lud_filter ); + if ( filter == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE + "filter parsing failed in URI \"%s\"", + c->argv[ attridx ] ); + rc = 1; + goto done_uri; + } + } + + ber_str2bv( c->argv[ attridx ], 0, 1, &uri ); + +done_uri:; + if ( rc ) { + if ( lud ) { + ldap_free_urldesc( lud ); + } + + if ( !BER_BVISNULL( &nbase ) ) { + ber_memfree( nbase.bv_val ); + } + + if ( filter != NULL ) { + filter_free( filter ); + } + + while ( dlm != NULL ) { + dlml = dlm; + dlm = dlm->dlm_next; + ch_free( dlml ); + } + + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + + return rc; + } + + attridx++; + } + + rc = slap_str2ad( c->argv[ attridx ], &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE + "unable to find AttributeDescription \"%s\"", + c->argv[ attridx ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + rc = 1; + goto done_uri; + } + + if ( !is_at_subtype( ad->ad_type, slap_schema.si_ad_labeledURI->ad_type ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE + "AttributeDescription \"%s\" " + "must be a subtype of \"labeledURI\"", + c->argv[ attridx ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + rc = 1; + goto done_uri; + } + + attridx++; + + for ( i = attridx; i < c->argc; i++ ) { + char *arg; + char *cp; + AttributeDescription *member_ad = NULL; + AttributeDescription *mapped_ad = NULL; + dynlist_map_t *dlmp; + + + /* + * If no mapped attribute is given, dn is used + * for backward compatibility. + */ + arg = c->argv[i]; + if ( ( cp = strchr( arg, ':' ) ) != NULL ) { + struct berval bv; + ber_str2bv( arg, cp - arg, 0, &bv ); + rc = slap_bv2ad( &bv, &mapped_ad, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + DYNLIST_USAGE + "unable to find mapped AttributeDescription #%d \"%s\"\n", + i - 3, c->argv[ i ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + rc = 1; + goto done_uri; + } + arg = cp + 1; + } + + rc = slap_str2ad( arg, &member_ad, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + DYNLIST_USAGE + "unable to find AttributeDescription #%d \"%s\"\n", + i - 3, c->argv[ i ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + rc = 1; + goto done_uri; + } + + dlmp = (dynlist_map_t *)ch_calloc( 1, sizeof( dynlist_map_t ) ); + if ( dlm == NULL ) { + dlm = dlmp; + } + dlmp->dlm_member_ad = member_ad; + dlmp->dlm_mapped_ad = mapped_ad; + dlmp->dlm_next = NULL; + + if ( dlml != NULL ) + dlml->dlm_next = dlmp; + dlml = dlmp; + } + + if ( c->valx > 0 ) { + int i; + + for ( i = 0, dlip = (dynlist_info_t **)&on->on_bi.bi_private; + i < c->valx; i++ ) + { + if ( *dlip == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + DYNLIST_USAGE + "invalid index {%d}\n", + c->valx ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + rc = 1; + goto done_uri; + } + dlip = &(*dlip)->dli_next; + } + dli_next = *dlip; + + } else { + for ( dlip = (dynlist_info_t **)&on->on_bi.bi_private; + *dlip; dlip = &(*dlip)->dli_next ) + /* goto last */; + } + + *dlip = (dynlist_info_t *)ch_calloc( 1, sizeof( dynlist_info_t ) ); + + (*dlip)->dli_oc = oc; + (*dlip)->dli_ad = ad; + (*dlip)->dli_dlm = dlm; + (*dlip)->dli_next = dli_next; + + (*dlip)->dli_lud = lud; + (*dlip)->dli_uri_nbase = nbase; + (*dlip)->dli_uri_filter = filter; + (*dlip)->dli_uri = uri; + + rc = dynlist_build_def_filter( *dlip ); + + } break; + + case DL_ATTRPAIR_COMPAT: + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "warning: \"attrpair\" only supported for limited " + "backward compatibility with overlay \"dyngroup\"" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + /* fallthru */ + + case DL_ATTRPAIR: { + dynlist_info_t **dlip; + ObjectClass *oc = NULL; + AttributeDescription *ad = NULL, + *member_ad = NULL; + const char *text; + + oc = oc_find( "groupOfURLs" ); + if ( oc == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"dynlist-attrpair <member-ad> <URL-ad>\": " + "unable to find default ObjectClass \"groupOfURLs\"" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + return 1; + } + + rc = slap_str2ad( c->argv[ 1 ], &member_ad, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"dynlist-attrpair <member-ad> <URL-ad>\": " + "unable to find AttributeDescription \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + return 1; + } + + rc = slap_str2ad( c->argv[ 2 ], &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"dynlist-attrpair <member-ad> <URL-ad>\": " + "unable to find AttributeDescription \"%s\"\n", + c->argv[ 2 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + return 1; + } + + if ( !is_at_subtype( ad->ad_type, slap_schema.si_ad_labeledURI->ad_type ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + DYNLIST_USAGE + "AttributeDescription \"%s\" " + "must be a subtype of \"labeledURI\"", + c->argv[ 2 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + return 1; + } + + for ( dlip = (dynlist_info_t **)&on->on_bi.bi_private; + *dlip; dlip = &(*dlip)->dli_next ) + { + /* + * The same URL attribute / member attribute pair + * cannot be repeated, but we enforce this only + * when the member attribute is unique. Performing + * the check for multiple values would require + * sorting and comparing the lists, which is left + * as a future improvement + */ + if ( (*dlip)->dli_ad == ad && + (*dlip)->dli_dlm->dlm_next == NULL && + member_ad == (*dlip)->dli_dlm->dlm_member_ad ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"dynlist-attrpair <member-ad> <URL-ad>\": " + "URL attributeDescription \"%s\" already mapped.\n", + ad->ad_cname.bv_val ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); +#if 0 + /* make it a warning... */ + return 1; +#endif + } + } + + *dlip = (dynlist_info_t *)ch_calloc( 1, sizeof( dynlist_info_t ) ); + + (*dlip)->dli_oc = oc; + (*dlip)->dli_ad = ad; + (*dlip)->dli_dlm = (dynlist_map_t *)ch_calloc( 1, sizeof( dynlist_map_t ) ); + (*dlip)->dli_dlm->dlm_member_ad = member_ad; + (*dlip)->dli_dlm->dlm_mapped_ad = NULL; + + rc = dynlist_build_def_filter( *dlip ); + + } break; + + default: + rc = 1; + break; + } + + return rc; +} + +static int +dynlist_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + dynlist_info_t *dli = (dynlist_info_t *)on->on_bi.bi_private; + ObjectClass *oc = NULL; + AttributeDescription *ad = NULL; + const char *text; + int rc; + + if ( dli == NULL ) { + dli = ch_calloc( 1, sizeof( dynlist_info_t ) ); + on->on_bi.bi_private = (void *)dli; + } + + for ( ; dli; dli = dli->dli_next ) { + if ( dli->dli_oc == NULL ) { + if ( oc == NULL ) { + oc = oc_find( "groupOfURLs" ); + if ( oc == NULL ) { + snprintf( cr->msg, sizeof( cr->msg), + "unable to fetch objectClass \"groupOfURLs\"" ); + Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s.\n", cr->msg, 0, 0 ); + return 1; + } + } + + dli->dli_oc = oc; + } + + if ( dli->dli_ad == NULL ) { + if ( ad == NULL ) { + rc = slap_str2ad( "memberURL", &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( cr->msg, sizeof( cr->msg), + "unable to fetch attributeDescription \"memberURL\": %d (%s)", + rc, text ); + Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s.\n", cr->msg, 0, 0 ); + return 1; + } + } + + dli->dli_ad = ad; + } + + if ( BER_BVISNULL( &dli->dli_default_filter ) ) { + rc = dynlist_build_def_filter( dli ); + if ( rc != 0 ) { + return rc; + } + } + } + + if ( ad_dgIdentity == NULL ) { + rc = slap_str2ad( "dgIdentity", &ad_dgIdentity, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( cr->msg, sizeof( cr->msg), + "unable to fetch attributeDescription \"dgIdentity\": %d (%s)", + rc, text ); + Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s\n", cr->msg, 0, 0 ); + /* Just a warning */ + } + } + + if ( ad_dgAuthz == NULL ) { + rc = slap_str2ad( "dgAuthz", &ad_dgAuthz, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( cr->msg, sizeof( cr->msg), + "unable to fetch attributeDescription \"dgAuthz\": %d (%s)", + rc, text ); + Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s\n", cr->msg, 0, 0 ); + /* Just a warning */ + } + } + + return 0; +} + +static int +dynlist_db_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + + if ( on->on_bi.bi_private ) { + dynlist_info_t *dli = (dynlist_info_t *)on->on_bi.bi_private, + *dli_next; + + for ( dli_next = dli; dli_next; dli = dli_next ) { + dynlist_map_t *dlm; + dynlist_map_t *dlm_next; + + dli_next = dli->dli_next; + + if ( !BER_BVISNULL( &dli->dli_uri ) ) { + ch_free( dli->dli_uri.bv_val ); + } + + if ( dli->dli_lud != NULL ) { + ldap_free_urldesc( dli->dli_lud ); + } + + if ( !BER_BVISNULL( &dli->dli_uri_nbase ) ) { + ber_memfree( dli->dli_uri_nbase.bv_val ); + } + + if ( dli->dli_uri_filter != NULL ) { + filter_free( dli->dli_uri_filter ); + } + + ch_free( dli->dli_default_filter.bv_val ); + + dlm = dli->dli_dlm; + while ( dlm != NULL ) { + dlm_next = dlm->dlm_next; + ch_free( dlm ); + dlm = dlm_next; + } + ch_free( dli ); + } + } + + return 0; +} + +static slap_overinst dynlist = { { NULL } }; +#ifdef TAKEOVER_DYNGROUP +static char *obsolete_names[] = { + "dyngroup", + NULL +}; +#endif + +#if SLAPD_OVER_DYNLIST == SLAPD_MOD_DYNAMIC +static +#endif /* SLAPD_OVER_DYNLIST == SLAPD_MOD_DYNAMIC */ +int +dynlist_initialize(void) +{ + int rc = 0; + + dynlist.on_bi.bi_type = "dynlist"; + +#ifdef TAKEOVER_DYNGROUP + /* makes dynlist incompatible with dyngroup */ + dynlist.on_bi.bi_obsolete_names = obsolete_names; +#endif + + dynlist.on_bi.bi_db_config = config_generic_wrapper; + dynlist.on_bi.bi_db_open = dynlist_db_open; + dynlist.on_bi.bi_db_destroy = dynlist_db_destroy; + + dynlist.on_response = dynlist_response; + + dynlist.on_bi.bi_cf_ocs = dlocs; + + rc = config_register_schema( dlcfg, dlocs ); + if ( rc ) { + return rc; + } + + return overlay_register( &dynlist ); +} + +#if SLAPD_OVER_DYNLIST == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return dynlist_initialize(); +} +#endif + +#endif /* SLAPD_OVER_DYNLIST */ diff --git a/servers/slapd/overlays/memberof.c b/servers/slapd/overlays/memberof.c new file mode 100644 index 0000000..60c103f --- /dev/null +++ b/servers/slapd/overlays/memberof.c @@ -0,0 +1,2206 @@ +/* memberof.c - back-reference for group membership */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2007 Pierangelo Masarati <ando@sys-net.it> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software, sponsored by SysNet s.r.l. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_MEMBEROF + +#include <stdio.h> + +#include "ac/string.h" +#include "ac/socket.h" + +#include "slap.h" +#include "config.h" +#include "lutil.h" + +/* + * Glossary: + * + * GROUP a group object (an entry with GROUP_OC + * objectClass) + * MEMBER a member object (an entry whose DN is + * listed as MEMBER_AT value of a GROUP) + * GROUP_OC the objectClass of the group object + * (default: groupOfNames) + * MEMBER_AT the membership attribute, DN-valued; + * note: nameAndOptionalUID is tolerated + * as soon as the optionalUID is absent + * (default: member) + * MEMBER_OF reverse membership attribute + * (default: memberOf) + * + * - add: + * - if the entry that is being added is a GROUP, + * the MEMBER_AT defined as values of the add operation + * get the MEMBER_OF value directly from the request. + * + * if configured to do so, the MEMBER objects do not exist, + * and no relax control is issued, either: + * - fail + * - drop non-existing members + * (by default: don't muck with values) + * + * - if (configured to do so,) the referenced GROUP exists, + * the relax control is set and the user has + * "manage" privileges, allow to add MEMBER_OF values to + * generic entries. + * + * - modify: + * - if the entry being modified is a GROUP_OC and the + * MEMBER_AT attribute is modified, the MEMBER_OF value + * of the (existing) MEMBER_AT entries that are affected + * is modified according to the request: + * - if a MEMBER is removed from the group, + * delete the corresponding MEMBER_OF + * - if a MEMBER is added to a group, + * add the corresponding MEMBER_OF + * + * We need to determine, from the database, if it is + * a GROUP_OC, and we need to check, from the + * modification list, if the MEMBER_AT attribute is being + * affected, and what MEMBER_AT values are affected. + * + * if configured to do so, the entries corresponding to + * the MEMBER_AT values do not exist, and no relax control + * is issued, either: + * - fail + * - drop non-existing members + * (by default: don't muck with values) + * + * - if configured to do so, the referenced GROUP exists, + * (the relax control is set) and the user has + * "manage" privileges, allow to add MEMBER_OF values to + * generic entries; the change is NOT automatically reflected + * in the MEMBER attribute of the GROUP referenced + * by the value of MEMBER_OF; a separate modification, + * with or without relax control, needs to be performed. + * + * - modrdn: + * - if the entry being renamed is a GROUP, the MEMBER_OF + * value of the (existing) MEMBER objects is modified + * accordingly based on the newDN of the GROUP. + * + * We need to determine, from the database, if it is + * a GROUP; the list of MEMBER objects is obtained from + * the database. + * + * Non-existing MEMBER objects are ignored, since the + * MEMBER_AT is not being addressed by the operation. + * + * - if the entry being renamed has the MEMBER_OF attribute, + * the corresponding MEMBER value must be modified in the + * respective group entries. + * + * + * - delete: + * - if the entry being deleted is a GROUP, the (existing) + * MEMBER objects are modified accordingly; a copy of the + * values of the MEMBER_AT is saved and, if the delete + * succeeds, the MEMBER_OF value of the (existing) MEMBER + * objects is deleted. + * + * We need to determine, from the database, if it is + * a GROUP. + * + * Non-existing MEMBER objects are ignored, since the entry + * is being deleted. + * + * - if the entry being deleted has the MEMBER_OF attribute, + * the corresponding value of the MEMBER_AT must be deleted + * from the respective GROUP entries. + */ + +#define SLAPD_MEMBEROF_ATTR "memberOf" + +static AttributeDescription *ad_member; +static AttributeDescription *ad_memberOf; + +static ObjectClass *oc_group; + +static slap_overinst memberof; + +typedef struct memberof_t { + struct berval mo_dn; + struct berval mo_ndn; + + ObjectClass *mo_oc_group; + AttributeDescription *mo_ad_member; + AttributeDescription *mo_ad_memberof; + + struct berval mo_groupFilterstr; + AttributeAssertion mo_groupAVA; + Filter mo_groupFilter; + + struct berval mo_memberFilterstr; + Filter mo_memberFilter; + + unsigned mo_flags; +#define MEMBEROF_NONE 0x00U +#define MEMBEROF_FDANGLING_DROP 0x01U +#define MEMBEROF_FDANGLING_ERROR 0x02U +#define MEMBEROF_FDANGLING_MASK (MEMBEROF_FDANGLING_DROP|MEMBEROF_FDANGLING_ERROR) +#define MEMBEROF_FREFINT 0x04U +#define MEMBEROF_FREVERSE 0x08U + + ber_int_t mo_dangling_err; + +#define MEMBEROF_CHK(mo,f) \ + (((mo)->mo_flags & (f)) == (f)) +#define MEMBEROF_DANGLING_CHECK(mo) \ + ((mo)->mo_flags & MEMBEROF_FDANGLING_MASK) +#define MEMBEROF_DANGLING_DROP(mo) \ + MEMBEROF_CHK((mo),MEMBEROF_FDANGLING_DROP) +#define MEMBEROF_DANGLING_ERROR(mo) \ + MEMBEROF_CHK((mo),MEMBEROF_FDANGLING_ERROR) +#define MEMBEROF_REFINT(mo) \ + MEMBEROF_CHK((mo),MEMBEROF_FREFINT) +#define MEMBEROF_REVERSE(mo) \ + MEMBEROF_CHK((mo),MEMBEROF_FREVERSE) +} memberof_t; + +typedef enum memberof_is_t { + MEMBEROF_IS_NONE = 0x00, + MEMBEROF_IS_GROUP = 0x01, + MEMBEROF_IS_MEMBER = 0x02, + MEMBEROF_IS_BOTH = (MEMBEROF_IS_GROUP|MEMBEROF_IS_MEMBER) +} memberof_is_t; + +typedef struct memberof_cookie_t { + AttributeDescription *ad; + BerVarray vals; + int foundit; +} memberof_cookie_t; + +typedef struct memberof_cbinfo_t { + slap_overinst *on; + BerVarray member; + BerVarray memberof; + memberof_is_t what; +} memberof_cbinfo_t; + +static void +memberof_set_backend( Operation *op_target, Operation *op, slap_overinst *on ) +{ + BackendInfo *bi = op->o_bd->bd_info; + + if ( bi->bi_type == memberof.on_bi.bi_type ) + op_target->o_bd->bd_info = (BackendInfo *)on->on_info; +} + +static int +memberof_isGroupOrMember_cb( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH ) { + memberof_cookie_t *mc; + + mc = (memberof_cookie_t *)op->o_callback->sc_private; + mc->foundit = 1; + } + + return 0; +} + +/* + * callback for internal search that saves the member attribute values + * of groups being deleted. + */ +static int +memberof_saveMember_cb( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH ) { + memberof_cookie_t *mc; + Attribute *a; + + mc = (memberof_cookie_t *)op->o_callback->sc_private; + mc->foundit = 1; + + assert( rs->sr_entry != NULL ); + assert( rs->sr_entry->e_attrs != NULL ); + + a = attr_find( rs->sr_entry->e_attrs, mc->ad ); + if ( a != NULL ) { + ber_bvarray_dup_x( &mc->vals, a->a_nvals, op->o_tmpmemctx ); + + assert( attr_find( a->a_next, mc->ad ) == NULL ); + } + } + + return 0; +} + +/* + * the delete hook performs an internal search that saves the member + * attribute values of groups being deleted. + */ +static int +memberof_isGroupOrMember( Operation *op, memberof_cbinfo_t *mci ) +{ + slap_overinst *on = mci->on; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + Operation op2 = *op; + slap_callback cb = { 0 }; + BackendInfo *bi = op->o_bd->bd_info; + AttributeName an[ 2 ]; + + memberof_is_t iswhat = MEMBEROF_IS_NONE; + memberof_cookie_t mc; + + assert( mci->what != MEMBEROF_IS_NONE ); + + cb.sc_private = &mc; + if ( op->o_tag == LDAP_REQ_DELETE ) { + cb.sc_response = memberof_saveMember_cb; + + } else { + cb.sc_response = memberof_isGroupOrMember_cb; + } + + op2.o_tag = LDAP_REQ_SEARCH; + op2.o_callback = &cb; + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + + op2.ors_scope = LDAP_SCOPE_BASE; + op2.ors_deref = LDAP_DEREF_NEVER; + BER_BVZERO( &an[ 1 ].an_name ); + op2.ors_attrs = an; + op2.ors_attrsonly = 0; + op2.ors_limit = NULL; + op2.ors_slimit = 1; + op2.ors_tlimit = SLAP_NO_LIMIT; + + if ( mci->what & MEMBEROF_IS_GROUP ) { + SlapReply rs2 = { REP_RESULT }; + + mc.ad = mo->mo_ad_member; + mc.foundit = 0; + mc.vals = NULL; + an[ 0 ].an_desc = mo->mo_ad_member; + an[ 0 ].an_name = an[ 0 ].an_desc->ad_cname; + op2.ors_filterstr = mo->mo_groupFilterstr; + op2.ors_filter = &mo->mo_groupFilter; + op2.o_do_not_cache = 1; /* internal search, don't log */ + + memberof_set_backend( &op2, op, on ); + (void)op->o_bd->be_search( &op2, &rs2 ); + op2.o_bd->bd_info = bi; + + if ( mc.foundit ) { + iswhat |= MEMBEROF_IS_GROUP; + if ( mc.vals ) mci->member = mc.vals; + + } + } + + if ( mci->what & MEMBEROF_IS_MEMBER ) { + SlapReply rs2 = { REP_RESULT }; + + mc.ad = mo->mo_ad_memberof; + mc.foundit = 0; + mc.vals = NULL; + an[ 0 ].an_desc = mo->mo_ad_memberof; + an[ 0 ].an_name = an[ 0 ].an_desc->ad_cname; + op2.ors_filterstr = mo->mo_memberFilterstr; + op2.ors_filter = &mo->mo_memberFilter; + op2.o_do_not_cache = 1; /* internal search, don't log */ + + memberof_set_backend( &op2, op, on ); + (void)op->o_bd->be_search( &op2, &rs2 ); + op2.o_bd->bd_info = bi; + + if ( mc.foundit ) { + iswhat |= MEMBEROF_IS_MEMBER; + if ( mc.vals ) mci->memberof = mc.vals; + + } + } + + mci->what = iswhat; + + return LDAP_SUCCESS; +} + +/* + * response callback that adds memberof values when a group is modified. + */ +static void +memberof_value_modify( + Operation *op, + struct berval *ndn, + AttributeDescription *ad, + struct berval *old_dn, + struct berval *old_ndn, + struct berval *new_dn, + struct berval *new_ndn ) +{ + memberof_cbinfo_t *mci = op->o_callback->sc_private; + slap_overinst *on = mci->on; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + Operation op2 = *op; + unsigned long opid = op->o_opid; + SlapReply rs2 = { REP_RESULT }; + slap_callback cb = { NULL, slap_null_cb, NULL, NULL }; + Modifications mod[ 2 ] = { { { 0 } } }, *ml; + struct berval values[ 4 ], nvalues[ 4 ]; + int mcnt = 0; + + if ( old_ndn != NULL && new_ndn != NULL && + ber_bvcmp( old_ndn, new_ndn ) == 0 ) { + /* DNs compare equal, it's a noop */ + return; + } + + op2.o_tag = LDAP_REQ_MODIFY; + + op2.o_req_dn = *ndn; + op2.o_req_ndn = *ndn; + + op2.o_callback = &cb; + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + op2.orm_modlist = NULL; + + /* Internal ops, never replicate these */ + op2.o_opid = 0; /* shared with op, saved above */ + op2.orm_no_opattrs = 1; + op2.o_dont_replicate = 1; + + if ( !BER_BVISNULL( &mo->mo_ndn ) ) { + ml = &mod[ mcnt ]; + ml->sml_numvals = 1; + ml->sml_values = &values[ 0 ]; + ml->sml_values[ 0 ] = mo->mo_dn; + BER_BVZERO( &ml->sml_values[ 1 ] ); + ml->sml_nvalues = &nvalues[ 0 ]; + ml->sml_nvalues[ 0 ] = mo->mo_ndn; + BER_BVZERO( &ml->sml_nvalues[ 1 ] ); + ml->sml_desc = slap_schema.si_ad_modifiersName; + ml->sml_type = ml->sml_desc->ad_cname; + ml->sml_op = LDAP_MOD_REPLACE; + ml->sml_flags = SLAP_MOD_INTERNAL; + ml->sml_next = op2.orm_modlist; + op2.orm_modlist = ml; + + mcnt++; + } + + ml = &mod[ mcnt ]; + ml->sml_numvals = 1; + ml->sml_values = &values[ 2 ]; + BER_BVZERO( &ml->sml_values[ 1 ] ); + ml->sml_nvalues = &nvalues[ 2 ]; + BER_BVZERO( &ml->sml_nvalues[ 1 ] ); + ml->sml_desc = ad; + ml->sml_type = ml->sml_desc->ad_cname; + ml->sml_flags = SLAP_MOD_INTERNAL; + ml->sml_next = op2.orm_modlist; + op2.orm_modlist = ml; + + if ( new_ndn != NULL ) { + BackendInfo *bi = op2.o_bd->bd_info; + OpExtra oex; + + assert( !BER_BVISNULL( new_dn ) ); + assert( !BER_BVISNULL( new_ndn ) ); + + ml = &mod[ mcnt ]; + ml->sml_op = LDAP_MOD_ADD; + + ml->sml_values[ 0 ] = *new_dn; + ml->sml_nvalues[ 0 ] = *new_ndn; + + oex.oe_key = (void *)&memberof; + LDAP_SLIST_INSERT_HEAD(&op2.o_extra, &oex, oe_next); + memberof_set_backend( &op2, op, on ); + (void)op->o_bd->be_modify( &op2, &rs2 ); + op2.o_bd->bd_info = bi; + LDAP_SLIST_REMOVE(&op2.o_extra, &oex, OpExtra, oe_next); + if ( rs2.sr_err != LDAP_SUCCESS ) { + char buf[ SLAP_TEXT_BUFLEN ]; + snprintf( buf, sizeof( buf ), + "memberof_value_modify DN=\"%s\" add %s=\"%s\" failed err=%d", + op2.o_req_dn.bv_val, ad->ad_cname.bv_val, new_dn->bv_val, rs2.sr_err ); + Debug( LDAP_DEBUG_ANY, "%s: %s\n", + op->o_log_prefix, buf, 0 ); + } + + assert( op2.orm_modlist == &mod[ mcnt ] ); + assert( mcnt == 0 || op2.orm_modlist->sml_next == &mod[ 0 ] ); + ml = op2.orm_modlist->sml_next; + if ( mcnt == 1 ) { + assert( ml == &mod[ 0 ] ); + ml = ml->sml_next; + } + if ( ml != NULL ) { + slap_mods_free( ml, 1 ); + } + + mod[ 0 ].sml_next = NULL; + } + + if ( old_ndn != NULL ) { + BackendInfo *bi = op2.o_bd->bd_info; + OpExtra oex; + + assert( !BER_BVISNULL( old_dn ) ); + assert( !BER_BVISNULL( old_ndn ) ); + + ml = &mod[ mcnt ]; + ml->sml_op = LDAP_MOD_DELETE; + + ml->sml_values[ 0 ] = *old_dn; + ml->sml_nvalues[ 0 ] = *old_ndn; + + oex.oe_key = (void *)&memberof; + LDAP_SLIST_INSERT_HEAD(&op2.o_extra, &oex, oe_next); + memberof_set_backend( &op2, op, on ); + (void)op->o_bd->be_modify( &op2, &rs2 ); + op2.o_bd->bd_info = bi; + LDAP_SLIST_REMOVE(&op2.o_extra, &oex, OpExtra, oe_next); + if ( rs2.sr_err != LDAP_SUCCESS ) { + char buf[ SLAP_TEXT_BUFLEN ]; + snprintf( buf, sizeof( buf ), + "memberof_value_modify DN=\"%s\" delete %s=\"%s\" failed err=%d", + op2.o_req_dn.bv_val, ad->ad_cname.bv_val, old_dn->bv_val, rs2.sr_err ); + Debug( LDAP_DEBUG_ANY, "%s: %s\n", + op->o_log_prefix, buf, 0 ); + } + + assert( op2.orm_modlist == &mod[ mcnt ] ); + ml = op2.orm_modlist->sml_next; + if ( mcnt == 1 ) { + assert( ml == &mod[ 0 ] ); + ml = ml->sml_next; + } + if ( ml != NULL ) { + slap_mods_free( ml, 1 ); + } + } + /* restore original opid */ + op->o_opid = opid; + + /* FIXME: if old_group_ndn doesn't exist, both delete __and__ + * add will fail; better split in two operations, although + * not optimal in terms of performance. At least it would + * move towards self-repairing capabilities. */ +} + +static int +memberof_cleanup( Operation *op, SlapReply *rs ) +{ + slap_callback *sc = op->o_callback; + memberof_cbinfo_t *mci = sc->sc_private; + + op->o_callback = sc->sc_next; + if ( mci->memberof ) + ber_bvarray_free_x( mci->memberof, op->o_tmpmemctx ); + if ( mci->member ) + ber_bvarray_free_x( mci->member, op->o_tmpmemctx ); + op->o_tmpfree( sc, op->o_tmpmemctx ); + return 0; +} + +static int memberof_res_add( Operation *op, SlapReply *rs ); +static int memberof_res_delete( Operation *op, SlapReply *rs ); +static int memberof_res_modify( Operation *op, SlapReply *rs ); +static int memberof_res_modrdn( Operation *op, SlapReply *rs ); + +static int +memberof_op_add( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + Attribute **ap, **map = NULL; + int rc = SLAP_CB_CONTINUE; + int i; + struct berval save_dn, save_ndn; + slap_callback *sc; + memberof_cbinfo_t *mci; + OpExtra *oex; + + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == (void *)&memberof ) + return SLAP_CB_CONTINUE; + } + + if ( op->ora_e->e_attrs == NULL ) { + /* FIXME: global overlay; need to deal with */ + Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): " + "consistency checks not implemented when overlay " + "is instantiated as global.\n", + op->o_log_prefix, op->o_req_dn.bv_val, 0 ); + return SLAP_CB_CONTINUE; + } + + if ( MEMBEROF_REVERSE( mo ) ) { + for ( ap = &op->ora_e->e_attrs; *ap; ap = &(*ap)->a_next ) { + Attribute *a = *ap; + + if ( a->a_desc == mo->mo_ad_memberof ) { + map = ap; + break; + } + } + } + + save_dn = op->o_dn; + save_ndn = op->o_ndn; + + if ( MEMBEROF_DANGLING_CHECK( mo ) + && !get_relax( op ) + && is_entry_objectclass_or_sub( op->ora_e, mo->mo_oc_group ) ) + { + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + op->o_bd->bd_info = (BackendInfo *)on->on_info; + + for ( ap = &op->ora_e->e_attrs; *ap; ) { + Attribute *a = *ap; + + if ( !is_ad_subtype( a->a_desc, mo->mo_ad_member ) ) { + ap = &a->a_next; + continue; + } + + assert( a->a_nvals != NULL ); + + for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) { + Entry *e = NULL; + + /* ITS#6670 Ignore member pointing to this entry */ + if ( dn_match( &a->a_nvals[i], &save_ndn )) + continue; + + rc = be_entry_get_rw( op, &a->a_nvals[ i ], + NULL, NULL, 0, &e ); + if ( rc == LDAP_SUCCESS ) { + be_entry_release_r( op, e ); + continue; + } + + if ( MEMBEROF_DANGLING_ERROR( mo ) ) { + rc = rs->sr_err = mo->mo_dangling_err; + rs->sr_text = "adding non-existing object " + "as group member"; + send_ldap_result( op, rs ); + goto done; + } + + if ( MEMBEROF_DANGLING_DROP( mo ) ) { + int j; + + Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): " + "member=\"%s\" does not exist (stripping...)\n", + op->o_log_prefix, op->ora_e->e_name.bv_val, + a->a_vals[ i ].bv_val ); + + for ( j = i + 1; !BER_BVISNULL( &a->a_nvals[ j ] ); j++ ); + ber_memfree( a->a_vals[ i ].bv_val ); + BER_BVZERO( &a->a_vals[ i ] ); + if ( a->a_nvals != a->a_vals ) { + ber_memfree( a->a_nvals[ i ].bv_val ); + BER_BVZERO( &a->a_nvals[ i ] ); + } + a->a_numvals--; + if ( j - i == 1 ) { + break; + } + + AC_MEMCPY( &a->a_vals[ i ], &a->a_vals[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + if ( a->a_nvals != a->a_vals ) { + AC_MEMCPY( &a->a_nvals[ i ], &a->a_nvals[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + } + i--; + } + } + + /* If all values have been removed, + * remove the attribute itself. */ + if ( BER_BVISNULL( &a->a_nvals[ 0 ] ) ) { + *ap = a->a_next; + attr_free( a ); + + } else { + ap = &a->a_next; + } + } + op->o_dn = save_dn; + op->o_ndn = save_ndn; + op->o_bd->bd_info = (BackendInfo *)on; + } + + if ( map != NULL ) { + Attribute *a = *map; + AccessControlState acl_state = ACL_STATE_INIT; + + for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) { + Entry *e; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + /* access is checked with the original identity */ + rc = access_allowed( op, op->ora_e, mo->mo_ad_memberof, + &a->a_nvals[ i ], ACL_WADD, + &acl_state ); + if ( rc == 0 ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = NULL; + send_ldap_result( op, rs ); + goto done; + } + /* ITS#6670 Ignore member pointing to this entry */ + if ( dn_match( &a->a_nvals[i], &save_ndn )) + continue; + + rc = be_entry_get_rw( op, &a->a_nvals[ i ], + NULL, NULL, 0, &e ); + op->o_bd->bd_info = (BackendInfo *)on; + if ( rc != LDAP_SUCCESS ) { + if ( get_relax( op ) ) { + continue; + } + + if ( MEMBEROF_DANGLING_ERROR( mo ) ) { + rc = rs->sr_err = mo->mo_dangling_err; + rs->sr_text = "adding non-existing object " + "as memberof"; + send_ldap_result( op, rs ); + goto done; + } + + if ( MEMBEROF_DANGLING_DROP( mo ) ) { + int j; + + Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): " + "memberof=\"%s\" does not exist (stripping...)\n", + op->o_log_prefix, op->ora_e->e_name.bv_val, + a->a_nvals[ i ].bv_val ); + + for ( j = i + 1; !BER_BVISNULL( &a->a_nvals[ j ] ); j++ ); + ber_memfree( a->a_vals[ i ].bv_val ); + BER_BVZERO( &a->a_vals[ i ] ); + if ( a->a_nvals != a->a_vals ) { + ber_memfree( a->a_nvals[ i ].bv_val ); + BER_BVZERO( &a->a_nvals[ i ] ); + } + if ( j - i == 1 ) { + break; + } + + AC_MEMCPY( &a->a_vals[ i ], &a->a_vals[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + if ( a->a_nvals != a->a_vals ) { + AC_MEMCPY( &a->a_nvals[ i ], &a->a_nvals[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + } + i--; + } + + continue; + } + + /* access is checked with the original identity */ + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = access_allowed( op, e, mo->mo_ad_member, + &op->o_req_ndn, ACL_WADD, NULL ); + be_entry_release_r( op, e ); + op->o_bd->bd_info = (BackendInfo *)on; + + if ( !rc ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "insufficient access to object referenced by memberof"; + send_ldap_result( op, rs ); + goto done; + } + } + + if ( BER_BVISNULL( &a->a_nvals[ 0 ] ) ) { + *map = a->a_next; + attr_free( a ); + } + } + + rc = SLAP_CB_CONTINUE; + + sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx ); + sc->sc_private = sc+1; + sc->sc_response = memberof_res_add; + sc->sc_cleanup = memberof_cleanup; + sc->sc_writewait = 0; + mci = sc->sc_private; + mci->on = on; + mci->member = NULL; + mci->memberof = NULL; + sc->sc_next = op->o_callback; + op->o_callback = sc; + +done:; + op->o_dn = save_dn; + op->o_ndn = save_ndn; + op->o_bd->bd_info = (BackendInfo *)on; + + return rc; +} + +static int +memberof_op_delete( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + slap_callback *sc; + memberof_cbinfo_t *mci; + OpExtra *oex; + + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == (void *)&memberof ) + return SLAP_CB_CONTINUE; + } + + sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx ); + sc->sc_private = sc+1; + sc->sc_response = memberof_res_delete; + sc->sc_cleanup = memberof_cleanup; + sc->sc_writewait = 0; + mci = sc->sc_private; + mci->on = on; + mci->member = NULL; + mci->memberof = NULL; + mci->what = MEMBEROF_IS_GROUP; + if ( MEMBEROF_REFINT( mo ) ) { + mci->what = MEMBEROF_IS_BOTH; + } + + memberof_isGroupOrMember( op, mci ); + + sc->sc_next = op->o_callback; + op->o_callback = sc; + + return SLAP_CB_CONTINUE; +} + +static int +memberof_op_modify( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + Modifications **mlp, **mmlp = NULL; + int rc = SLAP_CB_CONTINUE, save_member = 0; + struct berval save_dn, save_ndn; + slap_callback *sc; + memberof_cbinfo_t *mci, mcis; + OpExtra *oex; + + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == (void *)&memberof ) + return SLAP_CB_CONTINUE; + } + + if ( MEMBEROF_REVERSE( mo ) ) { + for ( mlp = &op->orm_modlist; *mlp; mlp = &(*mlp)->sml_next ) { + Modifications *ml = *mlp; + + if ( ml->sml_desc == mo->mo_ad_memberof ) { + mmlp = mlp; + break; + } + } + } + + save_dn = op->o_dn; + save_ndn = op->o_ndn; + mcis.on = on; + mcis.what = MEMBEROF_IS_GROUP; + + if ( memberof_isGroupOrMember( op, &mcis ) == LDAP_SUCCESS + && ( mcis.what & MEMBEROF_IS_GROUP ) ) + { + Modifications *ml; + + for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) { + if ( ml->sml_desc == mo->mo_ad_member ) { + switch ( ml->sml_op ) { + case LDAP_MOD_DELETE: + case LDAP_MOD_REPLACE: + case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */ + save_member = 1; + break; + } + } + } + + + if ( MEMBEROF_DANGLING_CHECK( mo ) + && !get_relax( op ) ) + { + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + op->o_bd->bd_info = (BackendInfo *)on->on_info; + + assert( op->orm_modlist != NULL ); + + for ( mlp = &op->orm_modlist; *mlp; ) { + Modifications *ml = *mlp; + int i; + + if ( !is_ad_subtype( ml->sml_desc, mo->mo_ad_member ) ) { + mlp = &ml->sml_next; + continue; + } + + switch ( ml->sml_op ) { + case LDAP_MOD_DELETE: + case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */ + /* we don't care about cancellations: if the value + * exists, fine; if it doesn't, we let the underlying + * database fail as appropriate; */ + mlp = &ml->sml_next; + break; + + case LDAP_MOD_REPLACE: + /* Handle this just like a delete (see above) */ + if ( !ml->sml_values ) { + mlp = &ml->sml_next; + break; + } + + case LDAP_MOD_ADD: + case SLAP_MOD_SOFTADD: /* ITS#7487 */ + case SLAP_MOD_ADD_IF_NOT_PRESENT: /* ITS#7487 */ + /* NOTE: right now, the attributeType we use + * for member must have a normalized value */ + assert( ml->sml_nvalues != NULL ); + + for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) { + Entry *e; + + /* ITS#6670 Ignore member pointing to this entry */ + if ( dn_match( &ml->sml_nvalues[i], &save_ndn )) + continue; + + if ( be_entry_get_rw( op, &ml->sml_nvalues[ i ], + NULL, NULL, 0, &e ) == LDAP_SUCCESS ) + { + be_entry_release_r( op, e ); + continue; + } + + if ( MEMBEROF_DANGLING_ERROR( mo ) ) { + rc = rs->sr_err = mo->mo_dangling_err; + rs->sr_text = "adding non-existing object " + "as group member"; + send_ldap_result( op, rs ); + goto done; + } + + if ( MEMBEROF_DANGLING_DROP( mo ) ) { + int j; + + Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): " + "member=\"%s\" does not exist (stripping...)\n", + op->o_log_prefix, op->o_req_dn.bv_val, + ml->sml_nvalues[ i ].bv_val ); + + for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ ); + ber_memfree( ml->sml_values[ i ].bv_val ); + BER_BVZERO( &ml->sml_values[ i ] ); + ber_memfree( ml->sml_nvalues[ i ].bv_val ); + BER_BVZERO( &ml->sml_nvalues[ i ] ); + ml->sml_numvals--; + if ( j - i == 1 ) { + break; + } + + AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + i--; + } + } + + if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) { + *mlp = ml->sml_next; + slap_mod_free( &ml->sml_mod, 0 ); + free( ml ); + + } else { + mlp = &ml->sml_next; + } + + break; + + default: + assert( 0 ); + } + } + } + } + + if ( mmlp != NULL ) { + Modifications *ml = *mmlp; + int i; + Entry *target; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, &op->o_req_ndn, + NULL, NULL, 0, &target ); + op->o_bd->bd_info = (BackendInfo *)on; + if ( rc != LDAP_SUCCESS ) { + rc = rs->sr_err = LDAP_NO_SUCH_OBJECT; + send_ldap_result( op, rs ); + goto done; + } + + switch ( ml->sml_op ) { + case LDAP_MOD_DELETE: + case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */ + if ( ml->sml_nvalues != NULL ) { + AccessControlState acl_state = ACL_STATE_INIT; + + for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) { + Entry *e; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + /* access is checked with the original identity */ + rc = access_allowed( op, target, + mo->mo_ad_memberof, + &ml->sml_nvalues[ i ], + ACL_WDEL, + &acl_state ); + if ( rc == 0 ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = NULL; + send_ldap_result( op, rs ); + goto done2; + } + + rc = be_entry_get_rw( op, &ml->sml_nvalues[ i ], + NULL, NULL, 0, &e ); + op->o_bd->bd_info = (BackendInfo *)on; + if ( rc != LDAP_SUCCESS ) { + if ( get_relax( op ) ) { + continue; + } + + if ( MEMBEROF_DANGLING_ERROR( mo ) ) { + rc = rs->sr_err = mo->mo_dangling_err; + rs->sr_text = "deleting non-existing object " + "as memberof"; + send_ldap_result( op, rs ); + goto done2; + } + + if ( MEMBEROF_DANGLING_DROP( mo ) ) { + int j; + + Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): " + "memberof=\"%s\" does not exist (stripping...)\n", + op->o_log_prefix, op->o_req_ndn.bv_val, + ml->sml_nvalues[ i ].bv_val ); + + for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ ); + ber_memfree( ml->sml_values[ i ].bv_val ); + BER_BVZERO( &ml->sml_values[ i ] ); + if ( ml->sml_nvalues != ml->sml_values ) { + ber_memfree( ml->sml_nvalues[ i ].bv_val ); + BER_BVZERO( &ml->sml_nvalues[ i ] ); + } + ml->sml_numvals--; + if ( j - i == 1 ) { + break; + } + + AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + if ( ml->sml_nvalues != ml->sml_values ) { + AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + } + i--; + } + + continue; + } + + /* access is checked with the original identity */ + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = access_allowed( op, e, mo->mo_ad_member, + &op->o_req_ndn, + ACL_WDEL, NULL ); + be_entry_release_r( op, e ); + op->o_bd->bd_info = (BackendInfo *)on; + + if ( !rc ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "insufficient access to object referenced by memberof"; + send_ldap_result( op, rs ); + goto done; + } + } + + if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) { + *mmlp = ml->sml_next; + slap_mod_free( &ml->sml_mod, 0 ); + free( ml ); + } + + break; + } + /* fall thru */ + + case LDAP_MOD_REPLACE: + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + /* access is checked with the original identity */ + rc = access_allowed( op, target, + mo->mo_ad_memberof, + NULL, + ACL_WDEL, NULL ); + op->o_bd->bd_info = (BackendInfo *)on; + if ( rc == 0 ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = NULL; + send_ldap_result( op, rs ); + goto done2; + } + + if ( ml->sml_op == LDAP_MOD_DELETE || ml->sml_op == SLAP_MOD_SOFTDEL || !ml->sml_values ) { + break; + } + /* fall thru */ + + case LDAP_MOD_ADD: + case SLAP_MOD_SOFTADD: /* ITS#7487 */ + case SLAP_MOD_ADD_IF_NOT_PRESENT: /* ITS#7487 */ + { + AccessControlState acl_state = ACL_STATE_INIT; + + for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) { + Entry *e; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + /* access is checked with the original identity */ + rc = access_allowed( op, target, + mo->mo_ad_memberof, + &ml->sml_nvalues[ i ], + ACL_WADD, + &acl_state ); + if ( rc == 0 ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = NULL; + send_ldap_result( op, rs ); + goto done2; + } + + /* ITS#6670 Ignore member pointing to this entry */ + if ( dn_match( &ml->sml_nvalues[i], &save_ndn )) + continue; + + rc = be_entry_get_rw( op, &ml->sml_nvalues[ i ], + NULL, NULL, 0, &e ); + op->o_bd->bd_info = (BackendInfo *)on; + if ( rc != LDAP_SUCCESS ) { + if ( MEMBEROF_DANGLING_ERROR( mo ) ) { + rc = rs->sr_err = mo->mo_dangling_err; + rs->sr_text = "adding non-existing object " + "as memberof"; + send_ldap_result( op, rs ); + goto done2; + } + + if ( MEMBEROF_DANGLING_DROP( mo ) ) { + int j; + + Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): " + "memberof=\"%s\" does not exist (stripping...)\n", + op->o_log_prefix, op->o_req_ndn.bv_val, + ml->sml_nvalues[ i ].bv_val ); + + for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ ); + ber_memfree( ml->sml_values[ i ].bv_val ); + BER_BVZERO( &ml->sml_values[ i ] ); + if ( ml->sml_nvalues != ml->sml_values ) { + ber_memfree( ml->sml_nvalues[ i ].bv_val ); + BER_BVZERO( &ml->sml_nvalues[ i ] ); + } + ml->sml_numvals--; + if ( j - i == 1 ) { + break; + } + + AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + if ( ml->sml_nvalues != ml->sml_values ) { + AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + } + i--; + } + + continue; + } + + /* access is checked with the original identity */ + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = access_allowed( op, e, mo->mo_ad_member, + &op->o_req_ndn, + ACL_WDEL, NULL ); + be_entry_release_r( op, e ); + op->o_bd->bd_info = (BackendInfo *)on; + + if ( !rc ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "insufficient access to object referenced by memberof"; + send_ldap_result( op, rs ); + goto done; + } + } + + if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) { + *mmlp = ml->sml_next; + slap_mod_free( &ml->sml_mod, 0 ); + free( ml ); + } + + } break; + + default: + assert( 0 ); + } + +done2:; + op->o_bd->bd_info = (BackendInfo *)on->on_info; + be_entry_release_r( op, target ); + op->o_bd->bd_info = (BackendInfo *)on; + } + + sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx ); + sc->sc_private = sc+1; + sc->sc_response = memberof_res_modify; + sc->sc_cleanup = memberof_cleanup; + sc->sc_writewait = 0; + mci = sc->sc_private; + mci->on = on; + mci->member = NULL; + mci->memberof = NULL; + mci->what = mcis.what; + + if ( save_member ) { + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = backend_attribute( op, NULL, &op->o_req_ndn, + mo->mo_ad_member, &mci->member, ACL_READ ); + op->o_bd->bd_info = (BackendInfo *)on; + } + + sc->sc_next = op->o_callback; + op->o_callback = sc; + + rc = SLAP_CB_CONTINUE; + +done:; + op->o_dn = save_dn; + op->o_ndn = save_ndn; + op->o_bd->bd_info = (BackendInfo *)on; + + return rc; +} + +static int +memberof_op_modrdn( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + slap_callback *sc; + memberof_cbinfo_t *mci; + OpExtra *oex; + + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == (void *)&memberof ) + return SLAP_CB_CONTINUE; + } + + sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx ); + sc->sc_private = sc+1; + sc->sc_response = memberof_res_modrdn; + sc->sc_cleanup = memberof_cleanup; + sc->sc_writewait = 0; + mci = sc->sc_private; + mci->on = on; + mci->member = NULL; + mci->memberof = NULL; + + sc->sc_next = op->o_callback; + op->o_callback = sc; + + return SLAP_CB_CONTINUE; +} + +/* + * response callback that adds memberof values when a group is added. + */ +static int +memberof_res_add( Operation *op, SlapReply *rs ) +{ + memberof_cbinfo_t *mci = op->o_callback->sc_private; + slap_overinst *on = mci->on; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + int i; + + if ( rs->sr_err != LDAP_SUCCESS ) { + return SLAP_CB_CONTINUE; + } + + if ( MEMBEROF_REVERSE( mo ) ) { + Attribute *ma; + + ma = attr_find( op->ora_e->e_attrs, mo->mo_ad_memberof ); + if ( ma != NULL ) { + /* relax is required to allow to add + * a non-existing member */ + op->o_relax = SLAP_CONTROL_CRITICAL; + + for ( i = 0; !BER_BVISNULL( &ma->a_nvals[ i ] ); i++ ) { + + /* ITS#6670 Ignore member pointing to this entry */ + if ( dn_match( &ma->a_nvals[i], &op->o_req_ndn )) + continue; + + /* the modification is attempted + * with the original identity */ + memberof_value_modify( op, + &ma->a_nvals[ i ], mo->mo_ad_member, + NULL, NULL, &op->o_req_dn, &op->o_req_ndn ); + } + } + } + + if ( is_entry_objectclass_or_sub( op->ora_e, mo->mo_oc_group ) ) { + Attribute *a; + + for ( a = attrs_find( op->ora_e->e_attrs, mo->mo_ad_member ); + a != NULL; + a = attrs_find( a->a_next, mo->mo_ad_member ) ) + { + for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) { + /* ITS#6670 Ignore member pointing to this entry */ + if ( dn_match( &a->a_nvals[i], &op->o_req_ndn )) + continue; + + memberof_value_modify( op, + &a->a_nvals[ i ], + mo->mo_ad_memberof, + NULL, NULL, + &op->o_req_dn, + &op->o_req_ndn ); + } + } + } + + return SLAP_CB_CONTINUE; +} + +/* + * response callback that deletes memberof values when a group is deleted. + */ +static int +memberof_res_delete( Operation *op, SlapReply *rs ) +{ + memberof_cbinfo_t *mci = op->o_callback->sc_private; + slap_overinst *on = mci->on; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + BerVarray vals; + int i; + + if ( rs->sr_err != LDAP_SUCCESS ) { + return SLAP_CB_CONTINUE; + } + + vals = mci->member; + if ( vals != NULL ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_memberof, + &op->o_req_dn, &op->o_req_ndn, + NULL, NULL ); + } + } + + if ( MEMBEROF_REFINT( mo ) ) { + vals = mci->memberof; + if ( vals != NULL ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_member, + &op->o_req_dn, &op->o_req_ndn, + NULL, NULL ); + } + } + } + + return SLAP_CB_CONTINUE; +} + +/* + * response callback that adds/deletes memberof values when a group + * is modified. + */ +static int +memberof_res_modify( Operation *op, SlapReply *rs ) +{ + memberof_cbinfo_t *mci = op->o_callback->sc_private; + slap_overinst *on = mci->on; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + int i, rc; + Modifications *ml, *mml = NULL; + BerVarray vals; + + if ( rs->sr_err != LDAP_SUCCESS ) { + return SLAP_CB_CONTINUE; + } + + if ( MEMBEROF_REVERSE( mo ) ) { + for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) { + if ( ml->sml_desc == mo->mo_ad_memberof ) { + mml = ml; + break; + } + } + } + + if ( mml != NULL ) { + BerVarray vals = mml->sml_nvalues; + + switch ( mml->sml_op ) { + case LDAP_MOD_DELETE: + case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */ + if ( vals != NULL ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_member, + &op->o_req_dn, &op->o_req_ndn, + NULL, NULL ); + } + break; + } + /* fall thru */ + + case LDAP_MOD_REPLACE: + /* delete all ... */ + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = backend_attribute( op, NULL, &op->o_req_ndn, + mo->mo_ad_memberof, &vals, ACL_READ ); + op->o_bd->bd_info = (BackendInfo *)on; + if ( rc == LDAP_SUCCESS ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_member, + &op->o_req_dn, &op->o_req_ndn, + NULL, NULL ); + } + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + + if ( ml->sml_op == LDAP_MOD_DELETE || !mml->sml_values ) { + break; + } + /* fall thru */ + + case LDAP_MOD_ADD: + case SLAP_MOD_SOFTADD: /* ITS#7487 */ + case SLAP_MOD_ADD_IF_NOT_PRESENT: /* ITS#7487 */ + assert( vals != NULL ); + + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_member, + NULL, NULL, + &op->o_req_dn, &op->o_req_ndn ); + } + break; + + default: + assert( 0 ); + } + } + + if ( mci->what & MEMBEROF_IS_GROUP ) + { + for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) { + if ( ml->sml_desc != mo->mo_ad_member ) { + continue; + } + + switch ( ml->sml_op ) { + case LDAP_MOD_DELETE: + case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */ + vals = ml->sml_nvalues; + if ( vals != NULL ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_memberof, + &op->o_req_dn, &op->o_req_ndn, + NULL, NULL ); + } + break; + } + /* fall thru */ + + case LDAP_MOD_REPLACE: + vals = mci->member; + + /* delete all ... */ + if ( vals != NULL ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_memberof, + &op->o_req_dn, &op->o_req_ndn, + NULL, NULL ); + } + } + + if ( ml->sml_op == LDAP_MOD_DELETE || ml->sml_op == SLAP_MOD_SOFTDEL || !ml->sml_values ) { + break; + } + /* fall thru */ + + case LDAP_MOD_ADD: + case SLAP_MOD_SOFTADD: /* ITS#7487 */ + case SLAP_MOD_ADD_IF_NOT_PRESENT : /* ITS#7487 */ + assert( ml->sml_nvalues != NULL ); + vals = ml->sml_nvalues; + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_memberof, + NULL, NULL, + &op->o_req_dn, &op->o_req_ndn ); + } + break; + + default: + assert( 0 ); + } + } + } + + return SLAP_CB_CONTINUE; +} + +/* + * response callback that adds/deletes member values when a group member + * is renamed. + */ +static int +memberof_res_modrdn( Operation *op, SlapReply *rs ) +{ + memberof_cbinfo_t *mci = op->o_callback->sc_private; + slap_overinst *on = mci->on; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + struct berval newPDN, newDN = BER_BVNULL, newPNDN, newNDN; + int i, rc; + BerVarray vals; + + struct berval save_dn, save_ndn; + + if ( rs->sr_err != LDAP_SUCCESS ) { + return SLAP_CB_CONTINUE; + } + + mci->what = MEMBEROF_IS_GROUP; + if ( MEMBEROF_REFINT( mo ) ) { + mci->what |= MEMBEROF_IS_MEMBER; + } + + if ( op->orr_nnewSup ) { + newPNDN = *op->orr_nnewSup; + + } else { + dnParent( &op->o_req_ndn, &newPNDN ); + } + + build_new_dn( &newNDN, &newPNDN, &op->orr_nnewrdn, op->o_tmpmemctx ); + + save_dn = op->o_req_dn; + save_ndn = op->o_req_ndn; + + op->o_req_dn = newNDN; + op->o_req_ndn = newNDN; + rc = memberof_isGroupOrMember( op, mci ); + op->o_req_dn = save_dn; + op->o_req_ndn = save_ndn; + + if ( rc != LDAP_SUCCESS || mci->what == MEMBEROF_IS_NONE ) { + goto done; + } + + if ( op->orr_newSup ) { + newPDN = *op->orr_newSup; + + } else { + dnParent( &op->o_req_dn, &newPDN ); + } + + build_new_dn( &newDN, &newPDN, &op->orr_newrdn, op->o_tmpmemctx ); + + if ( mci->what & MEMBEROF_IS_GROUP ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = backend_attribute( op, NULL, &newNDN, + mo->mo_ad_member, &vals, ACL_READ ); + op->o_bd->bd_info = (BackendInfo *)on; + + if ( rc == LDAP_SUCCESS ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_memberof, + &op->o_req_dn, &op->o_req_ndn, + &newDN, &newNDN ); + } + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + } + + if ( MEMBEROF_REFINT( mo ) && ( mci->what & MEMBEROF_IS_MEMBER ) ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = backend_attribute( op, NULL, &newNDN, + mo->mo_ad_memberof, &vals, ACL_READ ); + op->o_bd->bd_info = (BackendInfo *)on; + + if ( rc == LDAP_SUCCESS ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_member, + &op->o_req_dn, &op->o_req_ndn, + &newDN, &newNDN ); + } + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + } + +done:; + if ( !BER_BVISNULL( &newDN ) ) { + op->o_tmpfree( newDN.bv_val, op->o_tmpmemctx ); + } + op->o_tmpfree( newNDN.bv_val, op->o_tmpmemctx ); + + return SLAP_CB_CONTINUE; +} + + +static int +memberof_db_init( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + memberof_t *mo; + const char *text = NULL; + int rc; + + mo = (memberof_t *)ch_calloc( 1, sizeof( memberof_t ) ); + + /* safe default */ + mo->mo_dangling_err = LDAP_CONSTRAINT_VIOLATION; + + if ( !ad_memberOf ) { + rc = slap_str2ad( SLAPD_MEMBEROF_ATTR, &ad_memberOf, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "memberof_db_init: " + "unable to find attribute=\"%s\": %s (%d)\n", + SLAPD_MEMBEROF_ATTR, text, rc ); + return rc; + } + } + + if ( !ad_member ) { + rc = slap_str2ad( SLAPD_GROUP_ATTR, &ad_member, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "memberof_db_init: " + "unable to find attribute=\"%s\": %s (%d)\n", + SLAPD_GROUP_ATTR, text, rc ); + return rc; + } + } + + if ( !oc_group ) { + oc_group = oc_find( SLAPD_GROUP_CLASS ); + if ( oc_group == NULL ) { + Debug( LDAP_DEBUG_ANY, + "memberof_db_init: " + "unable to find objectClass=\"%s\"\n", + SLAPD_GROUP_CLASS, 0, 0 ); + return 1; + } + } + + on->on_bi.bi_private = (void *)mo; + + return 0; +} + +enum { + MO_DN = 1, + MO_DANGLING, + MO_REFINT, + MO_GROUP_OC, + MO_MEMBER_AD, + MO_MEMBER_OF_AD, +#if 0 + MO_REVERSE, +#endif + + MO_DANGLING_ERROR, + + MO_LAST +}; + +static ConfigDriver mo_cf_gen; + +#define OID "1.3.6.1.4.1.7136.2.666.4" +#define OIDAT OID ".1.1" +#define OIDCFGAT OID ".1.2" +#define OIDOC OID ".2.1" +#define OIDCFGOC OID ".2.2" + + +static ConfigTable mo_cfg[] = { + { "memberof-dn", "modifiersName", + 2, 2, 0, ARG_MAGIC|ARG_DN|MO_DN, mo_cf_gen, + "( OLcfgOvAt:18.0 NAME 'olcMemberOfDN' " + "DESC 'DN to be used as modifiersName' " + "SYNTAX OMsDN SINGLE-VALUE )", + NULL, NULL }, + + { "memberof-dangling", "ignore|drop|error", + 2, 2, 0, ARG_MAGIC|MO_DANGLING, mo_cf_gen, + "( OLcfgOvAt:18.1 NAME 'olcMemberOfDangling' " + "DESC 'Behavior with respect to dangling members, " + "constrained to ignore, drop, error' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL }, + + { "memberof-refint", "true|FALSE", + 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|MO_REFINT, mo_cf_gen, + "( OLcfgOvAt:18.2 NAME 'olcMemberOfRefInt' " + "DESC 'Take care of referential integrity' " + "SYNTAX OMsBoolean SINGLE-VALUE )", + NULL, NULL }, + + { "memberof-group-oc", "objectClass", + 2, 2, 0, ARG_MAGIC|MO_GROUP_OC, mo_cf_gen, + "( OLcfgOvAt:18.3 NAME 'olcMemberOfGroupOC' " + "DESC 'Group objectClass' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL }, + + { "memberof-member-ad", "member attribute", + 2, 2, 0, ARG_MAGIC|ARG_ATDESC|MO_MEMBER_AD, mo_cf_gen, + "( OLcfgOvAt:18.4 NAME 'olcMemberOfMemberAD' " + "DESC 'member attribute' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL }, + + { "memberof-memberof-ad", "memberOf attribute", + 2, 2, 0, ARG_MAGIC|ARG_ATDESC|MO_MEMBER_OF_AD, mo_cf_gen, + "( OLcfgOvAt:18.5 NAME 'olcMemberOfMemberOfAD' " + "DESC 'memberOf attribute' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL }, + +#if 0 + { "memberof-reverse", "true|FALSE", + 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|MO_REVERSE, mo_cf_gen, + "( OLcfgOvAt:18.6 NAME 'olcMemberOfReverse' " + "DESC 'Take care of referential integrity " + "also when directly modifying memberOf' " + "SYNTAX OMsBoolean SINGLE-VALUE )", + NULL, NULL }, +#endif + + { "memberof-dangling-error", "error code", + 2, 2, 0, ARG_MAGIC|MO_DANGLING_ERROR, mo_cf_gen, + "( OLcfgOvAt:18.7 NAME 'olcMemberOfDanglingError' " + "DESC 'Error code returned in case of dangling back reference' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL }, + + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs mo_ocs[] = { + { "( OLcfgOvOc:18.1 " + "NAME 'olcMemberOf' " + "DESC 'Member-of configuration' " + "SUP olcOverlayConfig " + "MAY ( " + "olcMemberOfDN " + "$ olcMemberOfDangling " + "$ olcMemberOfDanglingError" + "$ olcMemberOfRefInt " + "$ olcMemberOfGroupOC " + "$ olcMemberOfMemberAD " + "$ olcMemberOfMemberOfAD " +#if 0 + "$ olcMemberOfReverse " +#endif + ") " + ")", + Cft_Overlay, mo_cfg, NULL, NULL }, + { NULL, 0, NULL } +}; + +static slap_verbmasks dangling_mode[] = { + { BER_BVC( "ignore" ), MEMBEROF_NONE }, + { BER_BVC( "drop" ), MEMBEROF_FDANGLING_DROP }, + { BER_BVC( "error" ), MEMBEROF_FDANGLING_ERROR }, + { BER_BVNULL, 0 } +}; + +static int +memberof_make_group_filter( memberof_t *mo ) +{ + char *ptr; + + if ( !BER_BVISNULL( &mo->mo_groupFilterstr ) ) { + ch_free( mo->mo_groupFilterstr.bv_val ); + } + + mo->mo_groupFilter.f_choice = LDAP_FILTER_EQUALITY; + mo->mo_groupFilter.f_ava = &mo->mo_groupAVA; + + mo->mo_groupFilter.f_av_desc = slap_schema.si_ad_objectClass; + mo->mo_groupFilter.f_av_value = mo->mo_oc_group->soc_cname; + + mo->mo_groupFilterstr.bv_len = STRLENOF( "(=)" ) + + slap_schema.si_ad_objectClass->ad_cname.bv_len + + mo->mo_oc_group->soc_cname.bv_len; + ptr = mo->mo_groupFilterstr.bv_val = ch_malloc( mo->mo_groupFilterstr.bv_len + 1 ); + *ptr++ = '('; + ptr = lutil_strcopy( ptr, slap_schema.si_ad_objectClass->ad_cname.bv_val ); + *ptr++ = '='; + ptr = lutil_strcopy( ptr, mo->mo_oc_group->soc_cname.bv_val ); + *ptr++ = ')'; + *ptr = '\0'; + + return 0; +} + +static int +memberof_make_member_filter( memberof_t *mo ) +{ + char *ptr; + + if ( !BER_BVISNULL( &mo->mo_memberFilterstr ) ) { + ch_free( mo->mo_memberFilterstr.bv_val ); + } + + mo->mo_memberFilter.f_choice = LDAP_FILTER_PRESENT; + mo->mo_memberFilter.f_desc = mo->mo_ad_memberof; + + mo->mo_memberFilterstr.bv_len = STRLENOF( "(=*)" ) + + mo->mo_ad_memberof->ad_cname.bv_len; + ptr = mo->mo_memberFilterstr.bv_val = ch_malloc( mo->mo_memberFilterstr.bv_len + 1 ); + *ptr++ = '('; + ptr = lutil_strcopy( ptr, mo->mo_ad_memberof->ad_cname.bv_val ); + ptr = lutil_strcopy( ptr, "=*)" ); + + return 0; +} + +static int +mo_cf_gen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + int i, rc = 0; + + if ( c->op == SLAP_CONFIG_EMIT ) { + struct berval bv = BER_BVNULL; + + switch( c->type ) { + case MO_DN: + if ( mo->mo_dn.bv_val != NULL) { + value_add_one( &c->rvalue_vals, &mo->mo_dn ); + value_add_one( &c->rvalue_nvals, &mo->mo_ndn ); + } + break; + + case MO_DANGLING: + enum_to_verb( dangling_mode, (mo->mo_flags & MEMBEROF_FDANGLING_MASK), &bv ); + if ( BER_BVISNULL( &bv ) ) { + /* there's something wrong... */ + assert( 0 ); + rc = 1; + + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + break; + + case MO_DANGLING_ERROR: + if ( mo->mo_flags & MEMBEROF_FDANGLING_ERROR ) { + char buf[ SLAP_TEXT_BUFLEN ]; + enum_to_verb( slap_ldap_response_code, mo->mo_dangling_err, &bv ); + if ( BER_BVISNULL( &bv ) ) { + bv.bv_len = snprintf( buf, sizeof( buf ), "0x%x", mo->mo_dangling_err ); + if ( bv.bv_len < sizeof( buf ) ) { + bv.bv_val = buf; + } else { + rc = 1; + break; + } + } + value_add_one( &c->rvalue_vals, &bv ); + } else { + rc = 1; + } + break; + + case MO_REFINT: + c->value_int = MEMBEROF_REFINT( mo ); + break; + +#if 0 + case MO_REVERSE: + c->value_int = MEMBEROF_REVERSE( mo ); + break; +#endif + + case MO_GROUP_OC: + if ( mo->mo_oc_group != NULL ){ + value_add_one( &c->rvalue_vals, &mo->mo_oc_group->soc_cname ); + } + break; + + case MO_MEMBER_AD: + c->value_ad = mo->mo_ad_member; + break; + + case MO_MEMBER_OF_AD: + c->value_ad = mo->mo_ad_memberof; + break; + + default: + assert( 0 ); + return 1; + } + + return rc; + + } else if ( c->op == LDAP_MOD_DELETE ) { + switch( c->type ) { + case MO_DN: + if ( !BER_BVISNULL( &mo->mo_dn ) ) { + ber_memfree( mo->mo_dn.bv_val ); + ber_memfree( mo->mo_ndn.bv_val ); + BER_BVZERO( &mo->mo_dn ); + BER_BVZERO( &mo->mo_ndn ); + } + break; + + case MO_DANGLING: + mo->mo_flags &= ~MEMBEROF_FDANGLING_MASK; + break; + + case MO_DANGLING_ERROR: + mo->mo_dangling_err = LDAP_CONSTRAINT_VIOLATION; + break; + + case MO_REFINT: + mo->mo_flags &= ~MEMBEROF_FREFINT; + break; + +#if 0 + case MO_REVERSE: + mo->mo_flags &= ~MEMBEROF_FREVERSE; + break; +#endif + + case MO_GROUP_OC: + mo->mo_oc_group = oc_group; + memberof_make_group_filter( mo ); + break; + + case MO_MEMBER_AD: + mo->mo_ad_member = ad_member; + break; + + case MO_MEMBER_OF_AD: + mo->mo_ad_memberof = ad_memberOf; + memberof_make_member_filter( mo ); + break; + + default: + assert( 0 ); + return 1; + } + + } else { + switch( c->type ) { + case MO_DN: + if ( !BER_BVISNULL( &mo->mo_dn ) ) { + ber_memfree( mo->mo_dn.bv_val ); + ber_memfree( mo->mo_ndn.bv_val ); + } + mo->mo_dn = c->value_dn; + mo->mo_ndn = c->value_ndn; + break; + + case MO_DANGLING: + i = verb_to_mask( c->argv[ 1 ], dangling_mode ); + if ( BER_BVISNULL( &dangling_mode[ i ].word ) ) { + return 1; + } + + mo->mo_flags &= ~MEMBEROF_FDANGLING_MASK; + mo->mo_flags |= dangling_mode[ i ].mask; + break; + + case MO_DANGLING_ERROR: + i = verb_to_mask( c->argv[ 1 ], slap_ldap_response_code ); + if ( !BER_BVISNULL( &slap_ldap_response_code[ i ].word ) ) { + mo->mo_dangling_err = slap_ldap_response_code[ i ].mask; + } else if ( lutil_atoix( &mo->mo_dangling_err, c->argv[ 1 ], 0 ) ) { + return 1; + } + break; + + case MO_REFINT: + if ( c->value_int ) { + mo->mo_flags |= MEMBEROF_FREFINT; + + } else { + mo->mo_flags &= ~MEMBEROF_FREFINT; + } + break; + +#if 0 + case MO_REVERSE: + if ( c->value_int ) { + mo->mo_flags |= MEMBEROF_FREVERSE; + + } else { + mo->mo_flags &= ~MEMBEROF_FREVERSE; + } + break; +#endif + + case MO_GROUP_OC: { + ObjectClass *oc = NULL; + + oc = oc_find( c->argv[ 1 ] ); + if ( oc == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to find group objectClass=\"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + return 1; + } + + mo->mo_oc_group = oc; + memberof_make_group_filter( mo ); + } break; + + case MO_MEMBER_AD: { + AttributeDescription *ad = c->value_ad; + + if ( !is_at_syntax( ad->ad_type, SLAPD_DN_SYNTAX ) /* e.g. "member" */ + && !is_at_syntax( ad->ad_type, SLAPD_NAMEUID_SYNTAX ) ) /* e.g. "uniqueMember" */ + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "member attribute=\"%s\" must either " + "have DN (%s) or nameUID (%s) syntax", + c->argv[ 1 ], SLAPD_DN_SYNTAX, SLAPD_NAMEUID_SYNTAX ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + return 1; + } + + mo->mo_ad_member = ad; + } break; + + case MO_MEMBER_OF_AD: { + AttributeDescription *ad = c->value_ad; + + if ( !is_at_syntax( ad->ad_type, SLAPD_DN_SYNTAX ) /* e.g. "member" */ + && !is_at_syntax( ad->ad_type, SLAPD_NAMEUID_SYNTAX ) ) /* e.g. "uniqueMember" */ + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "memberof attribute=\"%s\" must either " + "have DN (%s) or nameUID (%s) syntax", + c->argv[ 1 ], SLAPD_DN_SYNTAX, SLAPD_NAMEUID_SYNTAX ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + return 1; + } + + mo->mo_ad_memberof = ad; + memberof_make_member_filter( mo ); + } break; + + default: + assert( 0 ); + return 1; + } + } + + return 0; +} + +static int +memberof_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + int rc; + + if ( !mo->mo_ad_memberof ) { + mo->mo_ad_memberof = ad_memberOf; + } + + if ( ! mo->mo_ad_member ) { + mo->mo_ad_member = ad_member; + } + + if ( ! mo->mo_oc_group ) { + mo->mo_oc_group = oc_group; + } + + if ( BER_BVISNULL( &mo->mo_dn ) && !BER_BVISNULL( &be->be_rootdn ) ) { + ber_dupbv( &mo->mo_dn, &be->be_rootdn ); + ber_dupbv( &mo->mo_ndn, &be->be_rootndn ); + } + + if ( BER_BVISNULL( &mo->mo_groupFilterstr ) ) { + memberof_make_group_filter( mo ); + } + + if ( BER_BVISNULL( &mo->mo_memberFilterstr ) ) { + memberof_make_member_filter( mo ); + } + + return 0; +} + +static int +memberof_db_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + if ( mo ) { + if ( !BER_BVISNULL( &mo->mo_dn ) ) { + ber_memfree( mo->mo_dn.bv_val ); + ber_memfree( mo->mo_ndn.bv_val ); + } + + if ( !BER_BVISNULL( &mo->mo_groupFilterstr ) ) { + ber_memfree( mo->mo_groupFilterstr.bv_val ); + } + + if ( !BER_BVISNULL( &mo->mo_memberFilterstr ) ) { + ber_memfree( mo->mo_memberFilterstr.bv_val ); + } + + ber_memfree( mo ); + } + + return 0; +} + +static struct { + char *desc; + AttributeDescription **adp; +} as[] = { + { "( 1.2.840.113556.1.2.102 " + "NAME 'memberOf' " + "DESC 'Group that the entry belongs to' " + "SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' " + "EQUALITY distinguishedNameMatch " /* added */ + "USAGE dSAOperation " /* added; questioned */ + /* "NO-USER-MODIFICATION " */ /* add? */ + "X-ORIGIN 'iPlanet Delegated Administrator' )", + &ad_memberOf }, + { NULL } +}; + +#if SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC +static +#endif /* SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC */ +int +memberof_initialize( void ) +{ + int code, i; + + for ( i = 0; as[ i ].desc != NULL; i++ ) { + code = register_at( as[ i ].desc, as[ i ].adp, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "memberof_initialize: register_at #%d failed\n", + i, 0, 0 ); + return code; + } + } + + memberof.on_bi.bi_type = "memberof"; + + memberof.on_bi.bi_db_init = memberof_db_init; + memberof.on_bi.bi_db_open = memberof_db_open; + memberof.on_bi.bi_db_destroy = memberof_db_destroy; + + memberof.on_bi.bi_op_add = memberof_op_add; + memberof.on_bi.bi_op_delete = memberof_op_delete; + memberof.on_bi.bi_op_modify = memberof_op_modify; + memberof.on_bi.bi_op_modrdn = memberof_op_modrdn; + + memberof.on_bi.bi_cf_ocs = mo_ocs; + + code = config_register_schema( mo_cfg, mo_ocs ); + if ( code ) return code; + + return overlay_register( &memberof ); +} + +#if SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return memberof_initialize(); +} +#endif /* SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC */ + +#endif /* SLAPD_OVER_MEMBEROF */ diff --git a/servers/slapd/overlays/overlays.c b/servers/slapd/overlays/overlays.c new file mode 100644 index 0000000..dff663f --- /dev/null +++ b/servers/slapd/overlays/overlays.c @@ -0,0 +1,44 @@ +/* overlays.c - Static overlay framework */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * Copyright 2003 by Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. + */ + +#include "portable.h" + +#include "slap.h" + +extern OverlayInit slap_oinfo[]; + +int +overlay_init(void) +{ + int i, rc = 0; + + for ( i= 0 ; slap_oinfo[i].ov_type; i++ ) { + rc = slap_oinfo[i].ov_init(); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "%s overlay setup failed, err %d\n", + slap_oinfo[i].ov_type, rc, 0 ); + break; + } + } + + return rc; +} diff --git a/servers/slapd/overlays/pcache.c b/servers/slapd/overlays/pcache.c new file mode 100644 index 0000000..f7b3f43 --- /dev/null +++ b/servers/slapd/overlays/pcache.c @@ -0,0 +1,5786 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * Portions Copyright 2003 IBM Corporation. + * Portions Copyright 2003-2009 Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Apurva Kumar for inclusion + * in OpenLDAP Software and subsequently rewritten by Howard Chu. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_PROXYCACHE + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/time.h> + +#include "slap.h" +#include "lutil.h" +#include "ldap_rq.h" +#include "avl.h" + +#include "../back-monitor/back-monitor.h" + +#include "config.h" + +#ifdef LDAP_DEVEL +/* + * Control that allows to access the private DB + * instead of the public one + */ +#define PCACHE_CONTROL_PRIVDB "1.3.6.1.4.1.4203.666.11.9.5.1" + +/* + * Extended Operation that allows to remove a query from the cache + */ +#define PCACHE_EXOP_QUERY_DELETE "1.3.6.1.4.1.4203.666.11.9.6.1" + +/* + * Monitoring + */ +#define PCACHE_MONITOR +#endif + +/* query cache structs */ +/* query */ + +typedef struct Query_s { + Filter* filter; /* Search Filter */ + struct berval base; /* Search Base */ + int scope; /* Search scope */ +} Query; + +struct query_template_s; + +typedef struct Qbase_s { + Avlnode *scopes[4]; /* threaded AVL trees of cached queries */ + struct berval base; + int queries; +} Qbase; + +/* struct representing a cached query */ +typedef struct cached_query_s { + Filter *filter; + Filter *first; + Qbase *qbase; + int scope; + struct berval q_uuid; /* query identifier */ + int q_sizelimit; + struct query_template_s *qtemp; /* template of the query */ + time_t expiry_time; /* time till the query is considered invalid */ + time_t refresh_time; /* time till the query is refreshed */ + time_t bindref_time; /* time till the bind is refreshed */ + int bind_refcnt; /* number of bind operation referencing this query */ + unsigned long answerable_cnt; /* how many times it was answerable */ + int refcnt; /* references since last refresh */ + ldap_pvt_thread_mutex_t answerable_cnt_mutex; + struct cached_query_s *next; /* next query in the template */ + struct cached_query_s *prev; /* previous query in the template */ + struct cached_query_s *lru_up; /* previous query in the LRU list */ + struct cached_query_s *lru_down; /* next query in the LRU list */ + ldap_pvt_thread_rdwr_t rwlock; +} CachedQuery; + +/* + * URL representation: + * + * ldap:///<base>??<scope>?<filter>?x-uuid=<uid>,x-template=<template>,x-attrset=<attrset>,x-expiry=<expiry>,x-refresh=<refresh> + * + * <base> ::= CachedQuery.qbase->base + * <scope> ::= CachedQuery.scope + * <filter> ::= filter2bv(CachedQuery.filter) + * <uuid> ::= CachedQuery.q_uuid + * <attrset> ::= CachedQuery.qtemp->attr_set_index + * <expiry> ::= CachedQuery.expiry_time + * <refresh> ::= CachedQuery.refresh_time + * + * quick hack: parse URI, call add_query() and then fix + * CachedQuery.expiry_time and CachedQuery.q_uuid + * + * NOTE: if the <attrset> changes, all stored URLs will be invalidated. + */ + +/* + * Represents a set of projected attributes. + */ + +struct attr_set { + struct query_template_s *templates; + AttributeName* attrs; /* specifies the set */ + unsigned flags; +#define PC_CONFIGURED (0x1) +#define PC_REFERENCED (0x2) +#define PC_GOT_OC (0x4) + int count; /* number of attributes */ +}; + +/* struct representing a query template + * e.g. template string = &(cn=)(mail=) + */ +typedef struct query_template_s { + struct query_template_s *qtnext; + struct query_template_s *qmnext; + + Avlnode* qbase; + CachedQuery* query; /* most recent query cached for the template */ + CachedQuery* query_last; /* oldest query cached for the template */ + ldap_pvt_thread_rdwr_t t_rwlock; /* Rd/wr lock for accessing queries in the template */ + struct berval querystr; /* Filter string corresponding to the QT */ + struct berval bindbase; /* base DN for Bind request */ + struct berval bindfilterstr; /* Filter string for Bind request */ + struct berval bindftemp; /* bind filter template */ + Filter *bindfilter; + AttributeDescription **bindfattrs; /* attrs to substitute in ftemp */ + + int bindnattrs; /* number of bindfattrs */ + int bindscope; + int attr_set_index; /* determines the projected attributes */ + int no_of_queries; /* Total number of queries in the template */ + time_t ttl; /* TTL for the queries of this template */ + time_t negttl; /* TTL for negative results */ + time_t limitttl; /* TTL for sizelimit exceeding results */ + time_t ttr; /* time to refresh */ + time_t bindttr; /* TTR for cached binds */ + struct attr_set t_attrs; /* filter attrs + attr_set */ +} QueryTemplate; + +typedef enum { + PC_IGNORE = 0, + PC_POSITIVE, + PC_NEGATIVE, + PC_SIZELIMIT +} pc_caching_reason_t; + +static const char *pc_caching_reason_str[] = { + "IGNORE", + "POSITIVE", + "NEGATIVE", + "SIZELIMIT", + + NULL +}; + +struct query_manager_s; + +/* prototypes for functions for 1) query containment + * 2) query addition, 3) cache replacement + */ +typedef CachedQuery *(QCfunc)(Operation *op, struct query_manager_s*, + Query*, QueryTemplate*); +typedef CachedQuery *(AddQueryfunc)(Operation *op, struct query_manager_s*, + Query*, QueryTemplate*, pc_caching_reason_t, int wlock); +typedef void (CRfunc)(struct query_manager_s*, struct berval*); + +/* LDAP query cache */ +typedef struct query_manager_s { + struct attr_set* attr_sets; /* possible sets of projected attributes */ + QueryTemplate* templates; /* cacheable templates */ + + CachedQuery* lru_top; /* top and bottom of LRU list */ + CachedQuery* lru_bottom; + + ldap_pvt_thread_mutex_t lru_mutex; /* mutex for accessing LRU list */ + + /* Query cache methods */ + QCfunc *qcfunc; /* Query containment*/ + CRfunc *crfunc; /* cache replacement */ + AddQueryfunc *addfunc; /* add query */ +} query_manager; + +/* LDAP query cache manager */ +typedef struct cache_manager_s { + BackendDB db; /* underlying database */ + unsigned long num_cached_queries; /* total number of cached queries */ + unsigned long max_queries; /* upper bound on # of cached queries */ + int save_queries; /* save cached queries across restarts */ + int check_cacheability; /* check whether a query is cacheable */ + int numattrsets; /* number of attribute sets */ + int cur_entries; /* current number of entries cached */ + int max_entries; /* max number of entries cached */ + int num_entries_limit; /* max # of entries in a cacheable query */ + + char response_cb; /* install the response callback + * at the tail of the callback list */ +#define PCACHE_RESPONSE_CB_HEAD 0 +#define PCACHE_RESPONSE_CB_TAIL 1 + char defer_db_open; /* defer open for online add */ + char cache_binds; /* cache binds or just passthru */ + + time_t cc_period; /* interval between successive consistency checks (sec) */ +#define PCACHE_CC_PAUSED 1 +#define PCACHE_CC_OFFLINE 2 + int cc_paused; + void *cc_arg; + + ldap_pvt_thread_mutex_t cache_mutex; + + query_manager* qm; /* query cache managed by the cache manager */ + +#ifdef PCACHE_MONITOR + void *monitor_cb; + struct berval monitor_ndn; +#endif /* PCACHE_MONITOR */ +} cache_manager; + +#ifdef PCACHE_MONITOR +static int pcache_monitor_db_init( BackendDB *be ); +static int pcache_monitor_db_open( BackendDB *be ); +static int pcache_monitor_db_close( BackendDB *be ); +static int pcache_monitor_db_destroy( BackendDB *be ); +#endif /* PCACHE_MONITOR */ + +static int pcache_debug; + +#ifdef PCACHE_CONTROL_PRIVDB +static int privDB_cid; +#endif /* PCACHE_CONTROL_PRIVDB */ + +static AttributeDescription *ad_queryId, *ad_cachedQueryURL; + +#ifdef PCACHE_MONITOR +static AttributeDescription *ad_numQueries, *ad_numEntries; +static ObjectClass *oc_olmPCache; +#endif /* PCACHE_MONITOR */ + +static struct { + char *name; + char *oid; +} s_oid[] = { + { "PCacheOID", "1.3.6.1.4.1.4203.666.11.9.1" }, + { "PCacheAttributes", "PCacheOID:1" }, + { "PCacheObjectClasses", "PCacheOID:2" }, + + { NULL } +}; + +static struct { + char *desc; + AttributeDescription **adp; +} s_ad[] = { + { "( PCacheAttributes:1 " + "NAME 'pcacheQueryID' " + "DESC 'ID of query the entry belongs to, formatted as a UUID' " + "EQUALITY octetStringMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{64} " + "NO-USER-MODIFICATION " + "USAGE directoryOperation )", + &ad_queryId }, + { "( PCacheAttributes:2 " + "NAME 'pcacheQueryURL' " + "DESC 'URI describing a cached query' " + "EQUALITY caseExactMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 " + "NO-USER-MODIFICATION " + "USAGE directoryOperation )", + &ad_cachedQueryURL }, +#ifdef PCACHE_MONITOR + { "( PCacheAttributes:3 " + "NAME 'pcacheNumQueries' " + "DESC 'Number of cached queries' " + "EQUALITY integerMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "NO-USER-MODIFICATION " + "USAGE directoryOperation )", + &ad_numQueries }, + { "( PCacheAttributes:4 " + "NAME 'pcacheNumEntries' " + "DESC 'Number of cached entries' " + "EQUALITY integerMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "NO-USER-MODIFICATION " + "USAGE directoryOperation )", + &ad_numEntries }, +#endif /* PCACHE_MONITOR */ + + { NULL } +}; + +static struct { + char *desc; + ObjectClass **ocp; +} s_oc[] = { +#ifdef PCACHE_MONITOR + /* augments an existing object, so it must be AUXILIARY */ + { "( PCacheObjectClasses:1 " + "NAME ( 'olmPCache' ) " + "SUP top AUXILIARY " + "MAY ( " + "pcacheQueryURL " + "$ pcacheNumQueries " + "$ pcacheNumEntries " + " ) )", + &oc_olmPCache }, +#endif /* PCACHE_MONITOR */ + + { NULL } +}; + +static int +filter2template( + Operation *op, + Filter *f, + struct berval *fstr ); + +static CachedQuery * +add_query( + Operation *op, + query_manager* qm, + Query* query, + QueryTemplate *templ, + pc_caching_reason_t why, + int wlock); + +static int +remove_query_data( + Operation *op, + struct berval *query_uuid ); + +/* + * Turn a cached query into its URL representation + */ +static int +query2url( Operation *op, CachedQuery *q, struct berval *urlbv, int dolock ) +{ + struct berval bv_scope, + bv_filter; + char attrset_buf[ LDAP_PVT_INTTYPE_CHARS( unsigned long ) ], + expiry_buf[ LDAP_PVT_INTTYPE_CHARS( unsigned long ) ], + refresh_buf[ LDAP_PVT_INTTYPE_CHARS( unsigned long ) ], + answerable_buf[ LDAP_PVT_INTTYPE_CHARS( unsigned long ) ], + *ptr; + ber_len_t attrset_len, + expiry_len, + refresh_len, + answerable_len; + + if ( dolock ) { + ldap_pvt_thread_rdwr_rlock( &q->rwlock ); + } + + ldap_pvt_scope2bv( q->scope, &bv_scope ); + filter2bv_x( op, q->filter, &bv_filter ); + attrset_len = sprintf( attrset_buf, + "%lu", (unsigned long)q->qtemp->attr_set_index ); + expiry_len = sprintf( expiry_buf, + "%lu", (unsigned long)q->expiry_time ); + answerable_len = snprintf( answerable_buf, sizeof( answerable_buf ), + "%lu", q->answerable_cnt ); + if ( q->refresh_time ) + refresh_len = sprintf( refresh_buf, + "%lu", (unsigned long)q->refresh_time ); + else + refresh_len = 0; + + urlbv->bv_len = STRLENOF( "ldap:///" ) + + q->qbase->base.bv_len + + STRLENOF( "??" ) + + bv_scope.bv_len + + STRLENOF( "?" ) + + bv_filter.bv_len + + STRLENOF( "?x-uuid=" ) + + q->q_uuid.bv_len + + STRLENOF( ",x-attrset=" ) + + attrset_len + + STRLENOF( ",x-expiry=" ) + + expiry_len + + STRLENOF( ",x-answerable=" ) + + answerable_len; + if ( refresh_len ) + urlbv->bv_len += STRLENOF( ",x-refresh=" ) + + refresh_len; + + ptr = urlbv->bv_val = ber_memalloc_x( urlbv->bv_len + 1, op->o_tmpmemctx ); + ptr = lutil_strcopy( ptr, "ldap:///" ); + ptr = lutil_strcopy( ptr, q->qbase->base.bv_val ); + ptr = lutil_strcopy( ptr, "??" ); + ptr = lutil_strcopy( ptr, bv_scope.bv_val ); + ptr = lutil_strcopy( ptr, "?" ); + ptr = lutil_strcopy( ptr, bv_filter.bv_val ); + ptr = lutil_strcopy( ptr, "?x-uuid=" ); + ptr = lutil_strcopy( ptr, q->q_uuid.bv_val ); + ptr = lutil_strcopy( ptr, ",x-attrset=" ); + ptr = lutil_strcopy( ptr, attrset_buf ); + ptr = lutil_strcopy( ptr, ",x-expiry=" ); + ptr = lutil_strcopy( ptr, expiry_buf ); + ptr = lutil_strcopy( ptr, ",x-answerable=" ); + ptr = lutil_strcopy( ptr, answerable_buf ); + if ( refresh_len ) { + ptr = lutil_strcopy( ptr, ",x-refresh=" ); + ptr = lutil_strcopy( ptr, refresh_buf ); + } + + ber_memfree_x( bv_filter.bv_val, op->o_tmpmemctx ); + + if ( dolock ) { + ldap_pvt_thread_rdwr_runlock( &q->rwlock ); + } + + return 0; +} + +/* Find and record the empty filter clauses */ + +static int +ftemp_attrs( struct berval *ftemp, struct berval *template, + AttributeDescription ***ret, const char **text ) +{ + int i; + int attr_cnt=0; + struct berval bv; + char *p1, *p2, *t1; + AttributeDescription *ad; + AttributeDescription **descs = NULL; + char *temp2; + + temp2 = ch_malloc( ftemp->bv_len + 1 ); + p1 = ftemp->bv_val; + t1 = temp2; + + *ret = NULL; + + for (;;) { + while ( *p1 == '(' || *p1 == '&' || *p1 == '|' || *p1 == ')' ) + *t1++ = *p1++; + + p2 = strchr( p1, '=' ); + if ( !p2 ) { + if ( !descs ) { + ch_free( temp2 ); + return -1; + } + break; + } + i = p2 - p1; + AC_MEMCPY( t1, p1, i ); + t1 += i; + *t1++ = '='; + + if ( p2[-1] == '<' || p2[-1] == '>' ) p2--; + bv.bv_val = p1; + bv.bv_len = p2 - p1; + ad = NULL; + i = slap_bv2ad( &bv, &ad, text ); + if ( i ) { + ch_free( temp2 ); + ch_free( descs ); + return -1; + } + if ( *p2 == '<' || *p2 == '>' ) p2++; + if ( p2[1] != ')' ) { + p2++; + while ( *p2 != ')' ) p2++; + p1 = p2; + continue; + } + + descs = (AttributeDescription **)ch_realloc(descs, + (attr_cnt + 2)*sizeof(AttributeDescription *)); + + descs[attr_cnt++] = ad; + + p1 = p2+1; + } + *t1 = '\0'; + descs[attr_cnt] = NULL; + *ret = descs; + template->bv_val = temp2; + template->bv_len = t1 - temp2; + return attr_cnt; +} + +static int +template_attrs( char *template, struct attr_set *set, AttributeName **ret, + const char **text ) +{ + int got_oc = 0; + int alluser = 0; + int allop = 0; + int i; + int attr_cnt; + int t_cnt = 0; + struct berval bv; + char *p1, *p2; + AttributeDescription *ad; + AttributeName *attrs; + + p1 = template; + + *ret = NULL; + + attrs = ch_calloc( set->count + 1, sizeof(AttributeName) ); + for ( i=0; i < set->count; i++ ) + attrs[i] = set->attrs[i]; + attr_cnt = i; + alluser = an_find( attrs, slap_bv_all_user_attrs ); + allop = an_find( attrs, slap_bv_all_operational_attrs ); + + for (;;) { + while ( *p1 == '(' || *p1 == '&' || *p1 == '|' || *p1 == ')' ) p1++; + p2 = strchr( p1, '=' ); + if ( !p2 ) + break; + if ( p2[-1] == '<' || p2[-1] == '>' ) p2--; + bv.bv_val = p1; + bv.bv_len = p2 - p1; + ad = NULL; + i = slap_bv2ad( &bv, &ad, text ); + if ( i ) { + ch_free( attrs ); + return -1; + } + t_cnt++; + + if ( ad == slap_schema.si_ad_objectClass ) + got_oc = 1; + + if ( is_at_operational(ad->ad_type)) { + if ( allop ) { + goto bottom; + } + } else if ( alluser ) { + goto bottom; + } + if ( !ad_inlist( ad, attrs )) { + attrs = (AttributeName *)ch_realloc(attrs, + (attr_cnt + 2)*sizeof(AttributeName)); + + attrs[attr_cnt].an_desc = ad; + attrs[attr_cnt].an_name = ad->ad_cname; + attrs[attr_cnt].an_oc = NULL; + attrs[attr_cnt].an_flags = 0; + BER_BVZERO( &attrs[attr_cnt+1].an_name ); + attr_cnt++; + } + +bottom: + p1 = p2+2; + } + if ( !t_cnt ) { + *text = "couldn't parse template"; + ch_free(attrs); + return -1; + } + if ( !got_oc && !( set->flags & PC_GOT_OC )) { + attrs = (AttributeName *)ch_realloc(attrs, + (attr_cnt + 2)*sizeof(AttributeName)); + + ad = slap_schema.si_ad_objectClass; + attrs[attr_cnt].an_desc = ad; + attrs[attr_cnt].an_name = ad->ad_cname; + attrs[attr_cnt].an_oc = NULL; + attrs[attr_cnt].an_flags = 0; + BER_BVZERO( &attrs[attr_cnt+1].an_name ); + attr_cnt++; + } + *ret = attrs; + return attr_cnt; +} + +/* + * Turn an URL representing a formerly cached query into a cached query, + * and try to cache it + */ +static int +url2query( + char *url, + Operation *op, + query_manager *qm ) +{ + Query query = { 0 }; + QueryTemplate *qt; + CachedQuery *cq; + LDAPURLDesc *lud = NULL; + struct berval base, + tempstr = BER_BVNULL, + uuid = BER_BVNULL; + int attrset; + time_t expiry_time; + time_t refresh_time; + unsigned long answerable_cnt; + int i, + got = 0, +#define GOT_UUID 0x1U +#define GOT_ATTRSET 0x2U +#define GOT_EXPIRY 0x4U +#define GOT_ANSWERABLE 0x8U +#define GOT_REFRESH 0x10U +#define GOT_ALL (GOT_UUID|GOT_ATTRSET|GOT_EXPIRY|GOT_ANSWERABLE) + rc = 0; + + rc = ldap_url_parse( url, &lud ); + if ( rc != LDAP_URL_SUCCESS ) { + return -1; + } + + /* non-allowed fields */ + if ( lud->lud_host != NULL ) { + rc = 1; + goto error; + } + + if ( lud->lud_attrs != NULL ) { + rc = 1; + goto error; + } + + /* be pedantic */ + if ( strcmp( lud->lud_scheme, "ldap" ) != 0 ) { + rc = 1; + goto error; + } + + /* required fields */ + if ( lud->lud_dn == NULL || lud->lud_dn[ 0 ] == '\0' ) { + rc = 1; + goto error; + } + + switch ( lud->lud_scope ) { + case LDAP_SCOPE_BASE: + case LDAP_SCOPE_ONELEVEL: + case LDAP_SCOPE_SUBTREE: + case LDAP_SCOPE_SUBORDINATE: + break; + + default: + rc = 1; + goto error; + } + + if ( lud->lud_filter == NULL || lud->lud_filter[ 0 ] == '\0' ) { + rc = 1; + goto error; + } + + if ( lud->lud_exts == NULL ) { + rc = 1; + goto error; + } + + for ( i = 0; lud->lud_exts[ i ] != NULL; i++ ) { + if ( strncmp( lud->lud_exts[ i ], "x-uuid=", STRLENOF( "x-uuid=" ) ) == 0 ) { + struct berval tmpUUID; + Syntax *syn_UUID = slap_schema.si_ad_entryUUID->ad_type->sat_syntax; + + if ( got & GOT_UUID ) { + rc = 1; + goto error; + } + + ber_str2bv( &lud->lud_exts[ i ][ STRLENOF( "x-uuid=" ) ], 0, 0, &tmpUUID ); + if ( !BER_BVISEMPTY( &tmpUUID ) ) { + rc = syn_UUID->ssyn_pretty( syn_UUID, &tmpUUID, &uuid, NULL ); + if ( rc != LDAP_SUCCESS ) { + goto error; + } + } + got |= GOT_UUID; + + } else if ( strncmp( lud->lud_exts[ i ], "x-attrset=", STRLENOF( "x-attrset=" ) ) == 0 ) { + if ( got & GOT_ATTRSET ) { + rc = 1; + goto error; + } + + rc = lutil_atoi( &attrset, &lud->lud_exts[ i ][ STRLENOF( "x-attrset=" ) ] ); + if ( rc ) { + goto error; + } + got |= GOT_ATTRSET; + + } else if ( strncmp( lud->lud_exts[ i ], "x-expiry=", STRLENOF( "x-expiry=" ) ) == 0 ) { + unsigned long l; + + if ( got & GOT_EXPIRY ) { + rc = 1; + goto error; + } + + rc = lutil_atoul( &l, &lud->lud_exts[ i ][ STRLENOF( "x-expiry=" ) ] ); + if ( rc ) { + goto error; + } + expiry_time = (time_t)l; + got |= GOT_EXPIRY; + + } else if ( strncmp( lud->lud_exts[ i ], "x-answerable=", STRLENOF( "x-answerable=" ) ) == 0 ) { + if ( got & GOT_ANSWERABLE ) { + rc = 1; + goto error; + } + + rc = lutil_atoul( &answerable_cnt, &lud->lud_exts[ i ][ STRLENOF( "x-answerable=" ) ] ); + if ( rc ) { + goto error; + } + got |= GOT_ANSWERABLE; + + } else if ( strncmp( lud->lud_exts[ i ], "x-refresh=", STRLENOF( "x-refresh=" ) ) == 0 ) { + unsigned long l; + + if ( got & GOT_REFRESH ) { + rc = 1; + goto error; + } + + rc = lutil_atoul( &l, &lud->lud_exts[ i ][ STRLENOF( "x-refresh=" ) ] ); + if ( rc ) { + goto error; + } + refresh_time = (time_t)l; + got |= GOT_REFRESH; + + } else { + rc = -1; + goto error; + } + } + + if ( got != GOT_ALL ) { + rc = 1; + goto error; + } + + if ( !(got & GOT_REFRESH )) + refresh_time = 0; + + /* ignore expired queries */ + if ( expiry_time <= slap_get_time()) { + Operation op2 = *op; + + memset( &op2.oq_search, 0, sizeof( op2.oq_search ) ); + + (void)remove_query_data( &op2, &uuid ); + + rc = 0; + + } else { + ber_str2bv( lud->lud_dn, 0, 0, &base ); + rc = dnNormalize( 0, NULL, NULL, &base, &query.base, NULL ); + if ( rc != LDAP_SUCCESS ) { + goto error; + } + query.scope = lud->lud_scope; + query.filter = str2filter( lud->lud_filter ); + if ( query.filter == NULL ) { + rc = -1; + goto error; + } + + tempstr.bv_val = ch_malloc( strlen( lud->lud_filter ) + 1 ); + tempstr.bv_len = 0; + if ( filter2template( op, query.filter, &tempstr ) ) { + ch_free( tempstr.bv_val ); + rc = -1; + goto error; + } + + /* check for query containment */ + qt = qm->attr_sets[attrset].templates; + for ( ; qt; qt = qt->qtnext ) { + /* find if template i can potentially answer tempstr */ + if ( bvmatch( &qt->querystr, &tempstr ) ) { + break; + } + } + + if ( qt == NULL ) { + rc = 1; + goto error; + } + + cq = add_query( op, qm, &query, qt, PC_POSITIVE, 0 ); + if ( cq != NULL ) { + cq->expiry_time = expiry_time; + cq->refresh_time = refresh_time; + cq->q_uuid = uuid; + cq->answerable_cnt = answerable_cnt; + cq->refcnt = 0; + + /* it's now into cq->filter */ + BER_BVZERO( &uuid ); + query.filter = NULL; + + } else { + rc = 1; + } + } + +error:; + if ( query.filter != NULL ) filter_free( query.filter ); + if ( !BER_BVISNULL( &tempstr ) ) ch_free( tempstr.bv_val ); + if ( !BER_BVISNULL( &query.base ) ) ch_free( query.base.bv_val ); + if ( !BER_BVISNULL( &uuid ) ) ch_free( uuid.bv_val ); + if ( lud != NULL ) ldap_free_urldesc( lud ); + + return rc; +} + +/* Return 1 for an added entry, else 0 */ +static int +merge_entry( + Operation *op, + Entry *e, + int dup, + struct berval* query_uuid ) +{ + int rc; + Modifications* modlist = NULL; + const char* text = NULL; + Attribute *attr; + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof(textbuf); + + SlapReply sreply = {REP_RESULT}; + + slap_callback cb = { NULL, slap_null_cb, NULL, NULL }; + + if ( dup ) + e = entry_dup( e ); + attr = e->e_attrs; + e->e_attrs = NULL; + + /* add queryId attribute */ + attr_merge_one( e, ad_queryId, query_uuid, NULL ); + + /* append the attribute list from the fetched entry */ + e->e_attrs->a_next = attr; + + op->o_tag = LDAP_REQ_ADD; + op->o_protocol = LDAP_VERSION3; + op->o_callback = &cb; + op->o_time = slap_get_time(); + op->o_do_not_cache = 1; + + op->ora_e = e; + op->o_req_dn = e->e_name; + op->o_req_ndn = e->e_nname; + rc = op->o_bd->be_add( op, &sreply ); + + if ( rc != LDAP_SUCCESS ) { + if ( rc == LDAP_ALREADY_EXISTS ) { + rs_reinit( &sreply, REP_RESULT ); + slap_entry2mods( e, &modlist, &text, textbuf, textlen ); + modlist->sml_op = LDAP_MOD_ADD; + op->o_tag = LDAP_REQ_MODIFY; + op->orm_modlist = modlist; + op->o_managedsait = SLAP_CONTROL_CRITICAL; + op->o_bd->be_modify( op, &sreply ); + slap_mods_free( modlist, 1 ); + } else if ( rc == LDAP_REFERRAL || + rc == LDAP_NO_SUCH_OBJECT ) { + syncrepl_add_glue( op, e ); + e = NULL; + rc = 1; + } + if ( e ) { + entry_free( e ); + rc = 0; + } + } else { + if ( op->ora_e == e ) + entry_free( e ); + rc = 1; + } + + return rc; +} + +/* Length-ordered sort on normalized DNs */ +static int pcache_dn_cmp( const void *v1, const void *v2 ) +{ + const Qbase *q1 = v1, *q2 = v2; + + int rc = q1->base.bv_len - q2->base.bv_len; + if ( rc == 0 ) + rc = strncmp( q1->base.bv_val, q2->base.bv_val, q1->base.bv_len ); + return rc; +} + +static int lex_bvcmp( struct berval *bv1, struct berval *bv2 ) +{ + int len, dif; + dif = bv1->bv_len - bv2->bv_len; + len = bv1->bv_len; + if ( dif > 0 ) len -= dif; + len = memcmp( bv1->bv_val, bv2->bv_val, len ); + if ( !len ) + len = dif; + return len; +} + +/* compare the current value in each filter */ +static int pcache_filter_cmp( Filter *f1, Filter *f2 ) +{ + int rc, weight1, weight2; + + switch( f1->f_choice ) { + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + weight1 = 0; + break; + case LDAP_FILTER_PRESENT: + weight1 = 1; + break; + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + weight1 = 2; + break; + default: + weight1 = 3; + } + switch( f2->f_choice ) { + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + weight2 = 0; + break; + case LDAP_FILTER_PRESENT: + weight2 = 1; + break; + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + weight2 = 2; + break; + default: + weight2 = 3; + } + rc = weight1 - weight2; + if ( !rc ) { + switch( weight1 ) { + case 0: + rc = pcache_filter_cmp( f1->f_and, f2->f_and ); + break; + case 1: + break; + case 2: + rc = lex_bvcmp( &f1->f_av_value, &f2->f_av_value ); + break; + case 3: + if ( f1->f_choice == LDAP_FILTER_SUBSTRINGS ) { + rc = 0; + if ( !BER_BVISNULL( &f1->f_sub_initial )) { + if ( !BER_BVISNULL( &f2->f_sub_initial )) { + rc = lex_bvcmp( &f1->f_sub_initial, + &f2->f_sub_initial ); + } else { + rc = 1; + } + } else if ( !BER_BVISNULL( &f2->f_sub_initial )) { + rc = -1; + } + if ( rc ) break; + if ( f1->f_sub_any ) { + if ( f2->f_sub_any ) { + rc = lex_bvcmp( f1->f_sub_any, + f2->f_sub_any ); + } else { + rc = 1; + } + } else if ( f2->f_sub_any ) { + rc = -1; + } + if ( rc ) break; + if ( !BER_BVISNULL( &f1->f_sub_final )) { + if ( !BER_BVISNULL( &f2->f_sub_final )) { + rc = lex_bvcmp( &f1->f_sub_final, + &f2->f_sub_final ); + } else { + rc = 1; + } + } else if ( !BER_BVISNULL( &f2->f_sub_final )) { + rc = -1; + } + } else { + rc = lex_bvcmp( &f1->f_mr_value, + &f2->f_mr_value ); + } + break; + } + while ( !rc ) { + f1 = f1->f_next; + f2 = f2->f_next; + if ( f1 || f2 ) { + if ( !f1 ) + rc = -1; + else if ( !f2 ) + rc = 1; + else { + rc = pcache_filter_cmp( f1, f2 ); + } + } else { + break; + } + } + } + return rc; +} + +/* compare filters in each query */ +static int pcache_query_cmp( const void *v1, const void *v2 ) +{ + const CachedQuery *q1 = v1, *q2 =v2; + return pcache_filter_cmp( q1->filter, q2->filter ); +} + +/* add query on top of LRU list */ +static void +add_query_on_top (query_manager* qm, CachedQuery* qc) +{ + CachedQuery* top = qm->lru_top; + + qm->lru_top = qc; + + if (top) + top->lru_up = qc; + else + qm->lru_bottom = qc; + + qc->lru_down = top; + qc->lru_up = NULL; + Debug( pcache_debug, "Base of added query = %s\n", + qc->qbase->base.bv_val, 0, 0 ); +} + +/* remove_query from LRU list */ + +static void +remove_query (query_manager* qm, CachedQuery* qc) +{ + CachedQuery* up; + CachedQuery* down; + + if (!qc) + return; + + up = qc->lru_up; + down = qc->lru_down; + + if (!up) + qm->lru_top = down; + + if (!down) + qm->lru_bottom = up; + + if (down) + down->lru_up = up; + + if (up) + up->lru_down = down; + + qc->lru_up = qc->lru_down = NULL; +} + +/* find and remove string2 from string1 + * from start if position = 1, + * from end if position = 3, + * from anywhere if position = 2 + * string1 is overwritten if position = 2. + */ + +static int +find_and_remove(struct berval* ber1, struct berval* ber2, int position) +{ + int ret=0; + + if ( !ber2->bv_val ) + return 1; + if ( !ber1->bv_val ) + return 0; + + switch( position ) { + case 1: + if ( ber1->bv_len >= ber2->bv_len && !memcmp( ber1->bv_val, + ber2->bv_val, ber2->bv_len )) { + ret = 1; + ber1->bv_val += ber2->bv_len; + ber1->bv_len -= ber2->bv_len; + } + break; + case 2: { + char *temp; + ber1->bv_val[ber1->bv_len] = '\0'; + temp = strstr( ber1->bv_val, ber2->bv_val ); + if ( temp ) { + strcpy( temp, temp+ber2->bv_len ); + ber1->bv_len -= ber2->bv_len; + ret = 1; + } + break; + } + case 3: + if ( ber1->bv_len >= ber2->bv_len && + !memcmp( ber1->bv_val+ber1->bv_len-ber2->bv_len, ber2->bv_val, + ber2->bv_len )) { + ret = 1; + ber1->bv_len -= ber2->bv_len; + } + break; + } + return ret; +} + + +static struct berval* +merge_init_final(Operation *op, struct berval* init, struct berval* any, + struct berval* final) +{ + struct berval* merged, *temp; + int i, any_count, count; + + for (any_count=0; any && any[any_count].bv_val; any_count++) + ; + + count = any_count; + + if (init->bv_val) + count++; + if (final->bv_val) + count++; + + merged = (struct berval*)op->o_tmpalloc( (count+1)*sizeof(struct berval), + op->o_tmpmemctx ); + temp = merged; + + if (init->bv_val) { + ber_dupbv_x( temp, init, op->o_tmpmemctx ); + temp++; + } + + for (i=0; i<any_count; i++) { + ber_dupbv_x( temp, any, op->o_tmpmemctx ); + temp++; any++; + } + + if (final->bv_val){ + ber_dupbv_x( temp, final, op->o_tmpmemctx ); + temp++; + } + BER_BVZERO( temp ); + return merged; +} + +/* Each element in stored must be found in incoming. Incoming is overwritten. + */ +static int +strings_containment(struct berval* stored, struct berval* incoming) +{ + struct berval* element; + int k=0; + int j, rc = 0; + + for ( element=stored; element->bv_val != NULL; element++ ) { + for (j = k; incoming[j].bv_val != NULL; j++) { + if (find_and_remove(&(incoming[j]), element, 2)) { + k = j; + rc = 1; + break; + } + rc = 0; + } + if ( rc ) { + continue; + } else { + return 0; + } + } + return 1; +} + +static int +substr_containment_substr(Operation *op, Filter* stored, Filter* incoming) +{ + int rc = 0; + + struct berval init_incoming; + struct berval final_incoming; + struct berval *remaining_incoming = NULL; + + if ((!(incoming->f_sub_initial.bv_val) && (stored->f_sub_initial.bv_val)) + || (!(incoming->f_sub_final.bv_val) && (stored->f_sub_final.bv_val))) + return 0; + + init_incoming = incoming->f_sub_initial; + final_incoming = incoming->f_sub_final; + + if (find_and_remove(&init_incoming, + &(stored->f_sub_initial), 1) && find_and_remove(&final_incoming, + &(stored->f_sub_final), 3)) + { + if (stored->f_sub_any == NULL) { + rc = 1; + goto final; + } + remaining_incoming = merge_init_final(op, &init_incoming, + incoming->f_sub_any, &final_incoming); + rc = strings_containment(stored->f_sub_any, remaining_incoming); + ber_bvarray_free_x( remaining_incoming, op->o_tmpmemctx ); + } +final: + return rc; +} + +static int +substr_containment_equality(Operation *op, Filter* stored, Filter* incoming) +{ + struct berval incoming_val[2]; + int rc = 0; + + incoming_val[1] = incoming->f_av_value; + + if (find_and_remove(incoming_val+1, + &(stored->f_sub_initial), 1) && find_and_remove(incoming_val+1, + &(stored->f_sub_final), 3)) { + if (stored->f_sub_any == NULL){ + rc = 1; + goto final; + } + ber_dupbv_x( incoming_val, incoming_val+1, op->o_tmpmemctx ); + BER_BVZERO( incoming_val+1 ); + rc = strings_containment(stored->f_sub_any, incoming_val); + op->o_tmpfree( incoming_val[0].bv_val, op->o_tmpmemctx ); + } +final: + return rc; +} + +static Filter * +filter_first( Filter *f ) +{ + while ( f->f_choice == LDAP_FILTER_OR || f->f_choice == LDAP_FILTER_AND ) + f = f->f_and; + return f; +} + +typedef struct fstack { + struct fstack *fs_next; + Filter *fs_fs; + Filter *fs_fi; +} fstack; + +static CachedQuery * +find_filter( Operation *op, Avlnode *root, Filter *inputf, Filter *first ) +{ + Filter* fs; + Filter* fi; + MatchingRule* mrule = NULL; + int res=0, eqpass= 0; + int ret, rc, dir; + Avlnode *ptr; + CachedQuery cq, *qc; + fstack *stack = NULL, *fsp; + + cq.filter = inputf; + cq.first = first; + + /* substring matches sort to the end, and we just have to + * walk the entire list. + */ + if ( first->f_choice == LDAP_FILTER_SUBSTRINGS ) { + ptr = tavl_end( root, 1 ); + dir = TAVL_DIR_LEFT; + } else { + ptr = tavl_find3( root, &cq, pcache_query_cmp, &ret ); + dir = (first->f_choice == LDAP_FILTER_GE) ? TAVL_DIR_LEFT : + TAVL_DIR_RIGHT; + } + + while (ptr) { + qc = ptr->avl_data; + fi = inputf; + fs = qc->filter; + + /* an incoming substr query can only be satisfied by a cached + * substr query. + */ + if ( first->f_choice == LDAP_FILTER_SUBSTRINGS && + qc->first->f_choice != LDAP_FILTER_SUBSTRINGS ) + break; + + /* an incoming eq query can be satisfied by a cached eq or substr + * query + */ + if ( first->f_choice == LDAP_FILTER_EQUALITY ) { + if ( eqpass == 0 ) { + if ( qc->first->f_choice != LDAP_FILTER_EQUALITY ) { +nextpass: eqpass = 1; + ptr = tavl_end( root, 1 ); + dir = TAVL_DIR_LEFT; + continue; + } + } else { + if ( qc->first->f_choice != LDAP_FILTER_SUBSTRINGS ) + break; + } + } + do { + res=0; + switch (fs->f_choice) { + case LDAP_FILTER_EQUALITY: + if (fi->f_choice == LDAP_FILTER_EQUALITY) + mrule = fs->f_ava->aa_desc->ad_type->sat_equality; + else + ret = 1; + break; + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + mrule = fs->f_ava->aa_desc->ad_type->sat_ordering; + break; + default: + mrule = NULL; + } + if (mrule) { + const char *text; + rc = value_match(&ret, fs->f_ava->aa_desc, mrule, + SLAP_MR_VALUE_OF_ASSERTION_SYNTAX, + &(fi->f_ava->aa_value), + &(fs->f_ava->aa_value), &text); + if (rc != LDAP_SUCCESS) { + return NULL; + } + if ( fi==first && fi->f_choice==LDAP_FILTER_EQUALITY && ret ) + goto nextpass; + } + switch (fs->f_choice) { + case LDAP_FILTER_OR: + case LDAP_FILTER_AND: + if ( fs->f_next ) { + /* save our stack position */ + fsp = op->o_tmpalloc(sizeof(fstack), op->o_tmpmemctx); + fsp->fs_next = stack; + fsp->fs_fs = fs->f_next; + fsp->fs_fi = fi->f_next; + stack = fsp; + } + fs = fs->f_and; + fi = fi->f_and; + res=1; + break; + case LDAP_FILTER_SUBSTRINGS: + /* check if the equality query can be + * answered with cached substring query */ + if ((fi->f_choice == LDAP_FILTER_EQUALITY) + && substr_containment_equality( op, + fs, fi)) + res=1; + /* check if the substring query can be + * answered with cached substring query */ + if ((fi->f_choice ==LDAP_FILTER_SUBSTRINGS + ) && substr_containment_substr( op, + fs, fi)) + res= 1; + fs=fs->f_next; + fi=fi->f_next; + break; + case LDAP_FILTER_PRESENT: + res=1; + fs=fs->f_next; + fi=fi->f_next; + break; + case LDAP_FILTER_EQUALITY: + if (ret == 0) + res = 1; + fs=fs->f_next; + fi=fi->f_next; + break; + case LDAP_FILTER_GE: + if (mrule && ret >= 0) + res = 1; + fs=fs->f_next; + fi=fi->f_next; + break; + case LDAP_FILTER_LE: + if (mrule && ret <= 0) + res = 1; + fs=fs->f_next; + fi=fi->f_next; + break; + case LDAP_FILTER_NOT: + res=0; + break; + default: + break; + } + if (!fs && !fi && stack) { + /* pop the stack */ + fsp = stack; + stack = fsp->fs_next; + fs = fsp->fs_fs; + fi = fsp->fs_fi; + op->o_tmpfree(fsp, op->o_tmpmemctx); + } + } while((res) && (fi != NULL) && (fs != NULL)); + + if ( res ) + return qc; + ptr = tavl_next( ptr, dir ); + } + return NULL; +} + +/* check whether query is contained in any of + * the cached queries in template + */ +static CachedQuery * +query_containment(Operation *op, query_manager *qm, + Query *query, + QueryTemplate *templa) +{ + CachedQuery* qc; + int depth = 0, tscope; + Qbase qbase, *qbptr = NULL; + struct berval pdn; + + if (query->filter != NULL) { + Filter *first; + + Debug( pcache_debug, "Lock QC index = %p\n", + (void *) templa, 0, 0 ); + qbase.base = query->base; + + first = filter_first( query->filter ); + + ldap_pvt_thread_rdwr_rlock(&templa->t_rwlock); + for( ;; ) { + /* Find the base */ + qbptr = avl_find( templa->qbase, &qbase, pcache_dn_cmp ); + if ( qbptr ) { + tscope = query->scope; + /* Find a matching scope: + * match at depth 0 OK + * scope is BASE, + * one at depth 1 OK + * subord at depth > 0 OK + * subtree at any depth OK + * scope is ONE, + * subtree or subord at any depth OK + * scope is SUBORD, + * subtree or subord at any depth OK + * scope is SUBTREE, + * subord at depth > 0 OK + * subtree at any depth OK + */ + for ( tscope = 0 ; tscope <= LDAP_SCOPE_CHILDREN; tscope++ ) { + switch ( query->scope ) { + case LDAP_SCOPE_BASE: + if ( tscope == LDAP_SCOPE_BASE && depth ) continue; + if ( tscope == LDAP_SCOPE_ONE && depth != 1) continue; + if ( tscope == LDAP_SCOPE_CHILDREN && !depth ) continue; + break; + case LDAP_SCOPE_ONE: + if ( tscope == LDAP_SCOPE_BASE ) + tscope = LDAP_SCOPE_ONE; + if ( tscope == LDAP_SCOPE_ONE && depth ) continue; + if ( !depth ) break; + if ( tscope < LDAP_SCOPE_SUBTREE ) + tscope = LDAP_SCOPE_SUBTREE; + break; + case LDAP_SCOPE_SUBTREE: + if ( tscope < LDAP_SCOPE_SUBTREE ) + tscope = LDAP_SCOPE_SUBTREE; + if ( tscope == LDAP_SCOPE_CHILDREN && !depth ) continue; + break; + case LDAP_SCOPE_CHILDREN: + if ( tscope < LDAP_SCOPE_SUBTREE ) + tscope = LDAP_SCOPE_SUBTREE; + break; + } + if ( !qbptr->scopes[tscope] ) continue; + + /* Find filter */ + qc = find_filter( op, qbptr->scopes[tscope], + query->filter, first ); + if ( qc ) { + if ( qc->q_sizelimit ) { + ldap_pvt_thread_rdwr_runlock(&templa->t_rwlock); + return NULL; + } + ldap_pvt_thread_mutex_lock(&qm->lru_mutex); + if (qm->lru_top != qc) { + remove_query(qm, qc); + add_query_on_top(qm, qc); + } + ldap_pvt_thread_mutex_unlock(&qm->lru_mutex); + return qc; + } + } + } + if ( be_issuffix( op->o_bd, &qbase.base )) + break; + /* Up a level */ + dnParent( &qbase.base, &pdn ); + qbase.base = pdn; + depth++; + } + + Debug( pcache_debug, + "Not answerable: Unlock QC index=%p\n", + (void *) templa, 0, 0 ); + ldap_pvt_thread_rdwr_runlock(&templa->t_rwlock); + } + return NULL; +} + +static void +free_query (CachedQuery* qc) +{ + free(qc->q_uuid.bv_val); + filter_free(qc->filter); + ldap_pvt_thread_mutex_destroy(&qc->answerable_cnt_mutex); + ldap_pvt_thread_rdwr_destroy( &qc->rwlock ); + memset(qc, 0, sizeof(*qc)); + free(qc); +} + + +/* Add query to query cache, the returned Query is locked for writing */ +static CachedQuery * +add_query( + Operation *op, + query_manager* qm, + Query* query, + QueryTemplate *templ, + pc_caching_reason_t why, + int wlock) +{ + CachedQuery* new_cached_query = (CachedQuery*) ch_malloc(sizeof(CachedQuery)); + Qbase *qbase, qb; + Filter *first; + int rc; + time_t ttl = 0, ttr = 0; + time_t now; + + new_cached_query->qtemp = templ; + BER_BVZERO( &new_cached_query->q_uuid ); + new_cached_query->q_sizelimit = 0; + + now = slap_get_time(); + switch ( why ) { + case PC_POSITIVE: + ttl = templ->ttl; + if ( templ->ttr ) + ttr = now + templ->ttr; + break; + + case PC_NEGATIVE: + ttl = templ->negttl; + break; + + case PC_SIZELIMIT: + ttl = templ->limitttl; + break; + + default: + assert( 0 ); + break; + } + new_cached_query->expiry_time = now + ttl; + new_cached_query->refresh_time = ttr; + new_cached_query->bindref_time = 0; + + new_cached_query->bind_refcnt = 0; + new_cached_query->answerable_cnt = 0; + new_cached_query->refcnt = 1; + ldap_pvt_thread_mutex_init(&new_cached_query->answerable_cnt_mutex); + + new_cached_query->lru_up = NULL; + new_cached_query->lru_down = NULL; + Debug( pcache_debug, "Added query expires at %ld (%s)\n", + (long) new_cached_query->expiry_time, + pc_caching_reason_str[ why ], 0 ); + + new_cached_query->scope = query->scope; + new_cached_query->filter = query->filter; + new_cached_query->first = first = filter_first( query->filter ); + + ldap_pvt_thread_rdwr_init(&new_cached_query->rwlock); + if (wlock) + ldap_pvt_thread_rdwr_wlock(&new_cached_query->rwlock); + + qb.base = query->base; + + /* Adding a query */ + Debug( pcache_debug, "Lock AQ index = %p\n", + (void *) templ, 0, 0 ); + ldap_pvt_thread_rdwr_wlock(&templ->t_rwlock); + qbase = avl_find( templ->qbase, &qb, pcache_dn_cmp ); + if ( !qbase ) { + qbase = ch_calloc( 1, sizeof(Qbase) + qb.base.bv_len + 1 ); + qbase->base.bv_len = qb.base.bv_len; + qbase->base.bv_val = (char *)(qbase+1); + memcpy( qbase->base.bv_val, qb.base.bv_val, qb.base.bv_len ); + qbase->base.bv_val[qbase->base.bv_len] = '\0'; + avl_insert( &templ->qbase, qbase, pcache_dn_cmp, avl_dup_error ); + } + new_cached_query->next = templ->query; + new_cached_query->prev = NULL; + new_cached_query->qbase = qbase; + rc = tavl_insert( &qbase->scopes[query->scope], new_cached_query, + pcache_query_cmp, avl_dup_error ); + if ( rc == 0 ) { + qbase->queries++; + if (templ->query == NULL) + templ->query_last = new_cached_query; + else + templ->query->prev = new_cached_query; + templ->query = new_cached_query; + templ->no_of_queries++; + } else { + ldap_pvt_thread_mutex_destroy(&new_cached_query->answerable_cnt_mutex); + if (wlock) + ldap_pvt_thread_rdwr_wunlock(&new_cached_query->rwlock); + ldap_pvt_thread_rdwr_destroy( &new_cached_query->rwlock ); + ch_free( new_cached_query ); + new_cached_query = find_filter( op, qbase->scopes[query->scope], + query->filter, first ); + filter_free( query->filter ); + query->filter = NULL; + } + Debug( pcache_debug, "TEMPLATE %p QUERIES++ %d\n", + (void *) templ, templ->no_of_queries, 0 ); + + /* Adding on top of LRU list */ + if ( rc == 0 ) { + ldap_pvt_thread_mutex_lock(&qm->lru_mutex); + add_query_on_top(qm, new_cached_query); + ldap_pvt_thread_mutex_unlock(&qm->lru_mutex); + } + Debug( pcache_debug, "Unlock AQ index = %p \n", + (void *) templ, 0, 0 ); + ldap_pvt_thread_rdwr_wunlock(&templ->t_rwlock); + + return rc == 0 ? new_cached_query : NULL; +} + +static void +remove_from_template (CachedQuery* qc, QueryTemplate* template) +{ + if (!qc->prev && !qc->next) { + template->query_last = template->query = NULL; + } else if (qc->prev == NULL) { + qc->next->prev = NULL; + template->query = qc->next; + } else if (qc->next == NULL) { + qc->prev->next = NULL; + template->query_last = qc->prev; + } else { + qc->next->prev = qc->prev; + qc->prev->next = qc->next; + } + tavl_delete( &qc->qbase->scopes[qc->scope], qc, pcache_query_cmp ); + qc->qbase->queries--; + if ( qc->qbase->queries == 0 ) { + avl_delete( &template->qbase, qc->qbase, pcache_dn_cmp ); + ch_free( qc->qbase ); + qc->qbase = NULL; + } + + template->no_of_queries--; +} + +/* remove bottom query of LRU list from the query cache */ +/* + * NOTE: slight change in functionality. + * + * - if result->bv_val is NULL, the query at the bottom of the LRU + * is removed + * - otherwise, the query whose UUID is *result is removed + * - if not found, result->bv_val is zeroed + */ +static void +cache_replacement(query_manager* qm, struct berval *result) +{ + CachedQuery* bottom; + QueryTemplate *temp; + + ldap_pvt_thread_mutex_lock(&qm->lru_mutex); + if ( BER_BVISNULL( result ) ) { + bottom = qm->lru_bottom; + + if (!bottom) { + Debug ( pcache_debug, + "Cache replacement invoked without " + "any query in LRU list\n", 0, 0, 0 ); + ldap_pvt_thread_mutex_unlock(&qm->lru_mutex); + return; + } + + } else { + for ( bottom = qm->lru_bottom; + bottom != NULL; + bottom = bottom->lru_up ) + { + if ( bvmatch( result, &bottom->q_uuid ) ) { + break; + } + } + + if ( !bottom ) { + Debug ( pcache_debug, + "Could not find query with uuid=\"%s\"" + "in LRU list\n", result->bv_val, 0, 0 ); + ldap_pvt_thread_mutex_unlock(&qm->lru_mutex); + BER_BVZERO( result ); + return; + } + } + + temp = bottom->qtemp; + remove_query(qm, bottom); + ldap_pvt_thread_mutex_unlock(&qm->lru_mutex); + + *result = bottom->q_uuid; + BER_BVZERO( &bottom->q_uuid ); + + Debug( pcache_debug, "Lock CR index = %p\n", (void *) temp, 0, 0 ); + ldap_pvt_thread_rdwr_wlock(&temp->t_rwlock); + remove_from_template(bottom, temp); + Debug( pcache_debug, "TEMPLATE %p QUERIES-- %d\n", + (void *) temp, temp->no_of_queries, 0 ); + Debug( pcache_debug, "Unlock CR index = %p\n", (void *) temp, 0, 0 ); + ldap_pvt_thread_rdwr_wunlock(&temp->t_rwlock); + free_query(bottom); +} + +struct query_info { + struct query_info *next; + struct berval xdn; + int del; +}; + +static int +remove_func ( + Operation *op, + SlapReply *rs +) +{ + Attribute *attr; + struct query_info *qi; + int count = 0; + + if ( rs->sr_type != REP_SEARCH ) return 0; + + attr = attr_find( rs->sr_entry->e_attrs, ad_queryId ); + if ( attr == NULL ) return 0; + + count = attr->a_numvals; + assert( count > 0 ); + qi = op->o_tmpalloc( sizeof( struct query_info ), op->o_tmpmemctx ); + qi->next = op->o_callback->sc_private; + op->o_callback->sc_private = qi; + ber_dupbv_x( &qi->xdn, &rs->sr_entry->e_nname, op->o_tmpmemctx ); + qi->del = ( count == 1 ); + + return 0; +} + +static int +remove_query_data( + Operation *op, + struct berval *query_uuid ) +{ + struct query_info *qi, *qnext; + char filter_str[ LDAP_LUTIL_UUIDSTR_BUFSIZE + STRLENOF( "(pcacheQueryID=)" ) ]; + AttributeAssertion ava = ATTRIBUTEASSERTION_INIT; + Filter filter = {LDAP_FILTER_EQUALITY}; + SlapReply sreply = {REP_RESULT}; + slap_callback cb = { NULL, remove_func, NULL, NULL }; + int deleted = 0; + + op->ors_filterstr.bv_len = snprintf(filter_str, sizeof(filter_str), + "(%s=%s)", ad_queryId->ad_cname.bv_val, query_uuid->bv_val); + filter.f_ava = &ava; + filter.f_av_desc = ad_queryId; + filter.f_av_value = *query_uuid; + + op->o_tag = LDAP_REQ_SEARCH; + op->o_protocol = LDAP_VERSION3; + op->o_callback = &cb; + op->o_time = slap_get_time(); + op->o_do_not_cache = 1; + + op->o_req_dn = op->o_bd->be_suffix[0]; + op->o_req_ndn = op->o_bd->be_nsuffix[0]; + op->ors_scope = LDAP_SCOPE_SUBTREE; + op->ors_deref = LDAP_DEREF_NEVER; + op->ors_slimit = SLAP_NO_LIMIT; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_limit = NULL; + op->ors_filter = &filter; + op->ors_filterstr.bv_val = filter_str; + op->ors_filterstr.bv_len = strlen(filter_str); + op->ors_attrs = NULL; + op->ors_attrsonly = 0; + + op->o_bd->be_search( op, &sreply ); + + for ( qi=cb.sc_private; qi; qi=qnext ) { + qnext = qi->next; + + op->o_req_dn = qi->xdn; + op->o_req_ndn = qi->xdn; + rs_reinit( &sreply, REP_RESULT ); + + if ( qi->del ) { + Debug( pcache_debug, "DELETING ENTRY TEMPLATE=%s\n", + query_uuid->bv_val, 0, 0 ); + + op->o_tag = LDAP_REQ_DELETE; + + if (op->o_bd->be_delete(op, &sreply) == LDAP_SUCCESS) { + deleted++; + } + + } else { + Modifications mod; + struct berval vals[2]; + + vals[0] = *query_uuid; + vals[1].bv_val = NULL; + vals[1].bv_len = 0; + mod.sml_op = LDAP_MOD_DELETE; + mod.sml_flags = 0; + mod.sml_desc = ad_queryId; + mod.sml_type = ad_queryId->ad_cname; + mod.sml_values = vals; + mod.sml_nvalues = NULL; + mod.sml_numvals = 1; + mod.sml_next = NULL; + Debug( pcache_debug, + "REMOVING TEMP ATTR : TEMPLATE=%s\n", + query_uuid->bv_val, 0, 0 ); + + op->orm_modlist = &mod; + + op->o_bd->be_modify( op, &sreply ); + } + op->o_tmpfree( qi->xdn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( qi, op->o_tmpmemctx ); + } + return deleted; +} + +static int +get_attr_set( + AttributeName* attrs, + query_manager* qm, + int num +); + +static int +filter2template( + Operation *op, + Filter *f, + struct berval *fstr ) +{ + AttributeDescription *ad; + int len, ret; + + switch ( f->f_choice ) { + case LDAP_FILTER_EQUALITY: + ad = f->f_av_desc; + len = STRLENOF( "(=)" ) + ad->ad_cname.bv_len; + ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s=)", ad->ad_cname.bv_val ); + assert( ret == len ); + fstr->bv_len += len; + break; + + case LDAP_FILTER_GE: + ad = f->f_av_desc; + len = STRLENOF( "(>=)" ) + ad->ad_cname.bv_len; + ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s>=)", ad->ad_cname.bv_val); + assert( ret == len ); + fstr->bv_len += len; + break; + + case LDAP_FILTER_LE: + ad = f->f_av_desc; + len = STRLENOF( "(<=)" ) + ad->ad_cname.bv_len; + ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s<=)", ad->ad_cname.bv_val); + assert( ret == len ); + fstr->bv_len += len; + break; + + case LDAP_FILTER_APPROX: + ad = f->f_av_desc; + len = STRLENOF( "(~=)" ) + ad->ad_cname.bv_len; + ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s~=)", ad->ad_cname.bv_val); + assert( ret == len ); + fstr->bv_len += len; + break; + + case LDAP_FILTER_SUBSTRINGS: + ad = f->f_sub_desc; + len = STRLENOF( "(=)" ) + ad->ad_cname.bv_len; + ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s=)", ad->ad_cname.bv_val ); + assert( ret == len ); + fstr->bv_len += len; + break; + + case LDAP_FILTER_PRESENT: + ad = f->f_desc; + len = STRLENOF( "(=*)" ) + ad->ad_cname.bv_len; + ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s=*)", ad->ad_cname.bv_val ); + assert( ret == len ); + fstr->bv_len += len; + break; + + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: { + int rc = 0; + fstr->bv_val[fstr->bv_len++] = '('; + switch ( f->f_choice ) { + case LDAP_FILTER_AND: + fstr->bv_val[fstr->bv_len] = '&'; + break; + case LDAP_FILTER_OR: + fstr->bv_val[fstr->bv_len] = '|'; + break; + case LDAP_FILTER_NOT: + fstr->bv_val[fstr->bv_len] = '!'; + break; + } + fstr->bv_len++; + + for ( f = f->f_list; f != NULL; f = f->f_next ) { + rc = filter2template( op, f, fstr ); + if ( rc ) break; + } + fstr->bv_val[fstr->bv_len++] = ')'; + fstr->bv_val[fstr->bv_len] = '\0'; + + return rc; + } + + default: + /* a filter should at least have room for "()", + * an "=" and for a 1-char attr */ + strcpy( fstr->bv_val, "(?=)" ); + fstr->bv_len += STRLENOF("(?=)"); + return -1; + } + + return 0; +} + +#define BI_HASHED 0x01 +#define BI_DIDCB 0x02 +#define BI_LOOKUP 0x04 + +struct search_info; + +typedef struct bindinfo { + cache_manager *bi_cm; + CachedQuery *bi_cq; + QueryTemplate *bi_templ; + struct search_info *bi_si; + int bi_flags; + slap_callback bi_cb; +} bindinfo; + +struct search_info { + slap_overinst *on; + Query query; + QueryTemplate *qtemp; + AttributeName* save_attrs; /* original attributes, saved for response */ + int swap_saved_attrs; + int max; + int over; + int count; + int slimit; + int slimit_exceeded; + pc_caching_reason_t caching_reason; + Entry *head, *tail; + bindinfo *pbi; +}; + +static void +remove_query_and_data( + Operation *op, + cache_manager *cm, + struct berval *uuid ) +{ + query_manager* qm = cm->qm; + + qm->crfunc( qm, uuid ); + if ( !BER_BVISNULL( uuid ) ) { + int return_val; + + Debug( pcache_debug, + "Removing query UUID %s\n", + uuid->bv_val, 0, 0 ); + return_val = remove_query_data( op, uuid ); + Debug( pcache_debug, + "QUERY REMOVED, SIZE=%d\n", + return_val, 0, 0); + ldap_pvt_thread_mutex_lock( &cm->cache_mutex ); + cm->cur_entries -= return_val; + cm->num_cached_queries--; + Debug( pcache_debug, + "STORED QUERIES = %lu\n", + cm->num_cached_queries, 0, 0 ); + ldap_pvt_thread_mutex_unlock( &cm->cache_mutex ); + Debug( pcache_debug, + "QUERY REMOVED, CACHE =" + "%d entries\n", + cm->cur_entries, 0, 0 ); + } +} + +/* + * Callback used to fetch queryId values based on entryUUID; + * used by pcache_remove_entries_from_cache() + */ +static int +fetch_queryId_cb( Operation *op, SlapReply *rs ) +{ + int rc = 0; + + /* only care about searchEntry responses */ + if ( rs->sr_type != REP_SEARCH ) { + return 0; + } + + /* allow only one response per entryUUID */ + if ( op->o_callback->sc_private != NULL ) { + rc = 1; + + } else { + Attribute *a; + + /* copy all queryId values into callback's private data */ + a = attr_find( rs->sr_entry->e_attrs, ad_queryId ); + if ( a != NULL ) { + BerVarray vals = NULL; + + ber_bvarray_dup_x( &vals, a->a_nvals, op->o_tmpmemctx ); + op->o_callback->sc_private = (void *)vals; + } + } + + /* clear entry if required */ + rs_flush_entry( op, rs, (slap_overinst *) op->o_bd->bd_info ); + + return rc; +} + +/* + * Call that allows to remove a set of entries from the cache, + * by forcing the removal of all the related queries. + */ +int +pcache_remove_entries_from_cache( + Operation *op, + cache_manager *cm, + BerVarray entryUUIDs ) +{ + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation op2; + slap_callback sc = { 0 }; + Filter f = { 0 }; + char filtbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE + STRLENOF( "(entryUUID=)" ) ]; + AttributeAssertion ava = ATTRIBUTEASSERTION_INIT; + AttributeName attrs[ 2 ] = {{{ 0 }}}; + int s, rc; + + if ( op == NULL ) { + void *thrctx = ldap_pvt_thread_pool_context(); + + connection_fake_init( &conn, &opbuf, thrctx ); + op = &opbuf.ob_op; + + } else { + op2 = *op; + op = &op2; + } + + memset( &op->oq_search, 0, sizeof( op->oq_search ) ); + op->ors_scope = LDAP_SCOPE_SUBTREE; + op->ors_deref = LDAP_DEREF_NEVER; + f.f_choice = LDAP_FILTER_EQUALITY; + f.f_ava = &ava; + ava.aa_desc = slap_schema.si_ad_entryUUID; + op->ors_filter = &f; + op->ors_slimit = 1; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_limit = NULL; + attrs[ 0 ].an_desc = ad_queryId; + attrs[ 0 ].an_name = ad_queryId->ad_cname; + op->ors_attrs = attrs; + op->ors_attrsonly = 0; + + op->o_req_dn = cm->db.be_suffix[ 0 ]; + op->o_req_ndn = cm->db.be_nsuffix[ 0 ]; + + op->o_tag = LDAP_REQ_SEARCH; + op->o_protocol = LDAP_VERSION3; + op->o_managedsait = SLAP_CONTROL_CRITICAL; + op->o_bd = &cm->db; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + sc.sc_response = fetch_queryId_cb; + op->o_callback = ≻ + + for ( s = 0; !BER_BVISNULL( &entryUUIDs[ s ] ); s++ ) { + BerVarray vals = NULL; + SlapReply rs = { REP_RESULT }; + + op->ors_filterstr.bv_len = snprintf( filtbuf, sizeof( filtbuf ), + "(entryUUID=%s)", entryUUIDs[ s ].bv_val ); + op->ors_filterstr.bv_val = filtbuf; + ava.aa_value = entryUUIDs[ s ]; + + rc = op->o_bd->be_search( op, &rs ); + if ( rc != LDAP_SUCCESS ) { + continue; + } + + vals = (BerVarray)op->o_callback->sc_private; + if ( vals != NULL ) { + int i; + + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + struct berval val = vals[ i ]; + + remove_query_and_data( op, cm, &val ); + + if ( !BER_BVISNULL( &val ) && val.bv_val != vals[ i ].bv_val ) { + ch_free( val.bv_val ); + } + } + + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + op->o_callback->sc_private = NULL; + } + } + + return 0; +} + +/* + * Call that allows to remove a query from the cache. + */ +int +pcache_remove_query_from_cache( + Operation *op, + cache_manager *cm, + struct berval *queryid ) +{ + Operation op2 = *op; + + op2.o_bd = &cm->db; + + /* remove the selected query */ + remove_query_and_data( &op2, cm, queryid ); + + return LDAP_SUCCESS; +} + +/* + * Call that allows to remove a set of queries related to an entry + * from the cache; if queryid is not null, the entry must belong to + * the query indicated by queryid. + */ +int +pcache_remove_entry_queries_from_cache( + Operation *op, + cache_manager *cm, + struct berval *ndn, + struct berval *queryid ) +{ + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation op2; + slap_callback sc = { 0 }; + SlapReply rs = { REP_RESULT }; + Filter f = { 0 }; + char filter_str[ LDAP_LUTIL_UUIDSTR_BUFSIZE + STRLENOF( "(pcacheQueryID=)" ) ]; + AttributeAssertion ava = ATTRIBUTEASSERTION_INIT; + AttributeName attrs[ 2 ] = {{{ 0 }}}; + int rc; + + BerVarray vals = NULL; + + if ( op == NULL ) { + void *thrctx = ldap_pvt_thread_pool_context(); + + connection_fake_init( &conn, &opbuf, thrctx ); + op = &opbuf.ob_op; + + } else { + op2 = *op; + op = &op2; + } + + memset( &op->oq_search, 0, sizeof( op->oq_search ) ); + op->ors_scope = LDAP_SCOPE_BASE; + op->ors_deref = LDAP_DEREF_NEVER; + if ( queryid == NULL || BER_BVISNULL( queryid ) ) { + BER_BVSTR( &op->ors_filterstr, "(objectClass=*)" ); + f.f_choice = LDAP_FILTER_PRESENT; + f.f_desc = slap_schema.si_ad_objectClass; + + } else { + op->ors_filterstr.bv_len = snprintf( filter_str, + sizeof( filter_str ), "(%s=%s)", + ad_queryId->ad_cname.bv_val, queryid->bv_val ); + f.f_choice = LDAP_FILTER_EQUALITY; + f.f_ava = &ava; + f.f_av_desc = ad_queryId; + f.f_av_value = *queryid; + } + op->ors_filter = &f; + op->ors_slimit = 1; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_limit = NULL; + attrs[ 0 ].an_desc = ad_queryId; + attrs[ 0 ].an_name = ad_queryId->ad_cname; + op->ors_attrs = attrs; + op->ors_attrsonly = 0; + + op->o_req_dn = *ndn; + op->o_req_ndn = *ndn; + + op->o_tag = LDAP_REQ_SEARCH; + op->o_protocol = LDAP_VERSION3; + op->o_managedsait = SLAP_CONTROL_CRITICAL; + op->o_bd = &cm->db; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + sc.sc_response = fetch_queryId_cb; + op->o_callback = ≻ + + rc = op->o_bd->be_search( op, &rs ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + vals = (BerVarray)op->o_callback->sc_private; + if ( vals != NULL ) { + int i; + + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + struct berval val = vals[ i ]; + + remove_query_and_data( op, cm, &val ); + + if ( !BER_BVISNULL( &val ) && val.bv_val != vals[ i ].bv_val ) { + ch_free( val.bv_val ); + } + } + + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + + return LDAP_SUCCESS; +} + +static int +cache_entries( + Operation *op, + struct berval *query_uuid ) +{ + struct search_info *si = op->o_callback->sc_private; + slap_overinst *on = si->on; + cache_manager *cm = on->on_bi.bi_private; + int return_val = 0; + Entry *e; + struct berval crp_uuid; + char uuidbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE ]; + Operation *op_tmp; + Connection conn = {0}; + OperationBuffer opbuf; + void *thrctx = ldap_pvt_thread_pool_context(); + + query_uuid->bv_len = lutil_uuidstr(uuidbuf, sizeof(uuidbuf)); + ber_str2bv(uuidbuf, query_uuid->bv_len, 1, query_uuid); + + connection_fake_init2( &conn, &opbuf, thrctx, 0 ); + op_tmp = &opbuf.ob_op; + op_tmp->o_bd = &cm->db; + op_tmp->o_dn = cm->db.be_rootdn; + op_tmp->o_ndn = cm->db.be_rootndn; + + Debug( pcache_debug, "UUID for query being added = %s\n", + uuidbuf, 0, 0 ); + + for ( e=si->head; e; e=si->head ) { + si->head = e->e_private; + e->e_private = NULL; + while ( cm->cur_entries > (cm->max_entries) ) { + BER_BVZERO( &crp_uuid ); + remove_query_and_data( op_tmp, cm, &crp_uuid ); + } + + return_val = merge_entry(op_tmp, e, 0, query_uuid); + ldap_pvt_thread_mutex_lock(&cm->cache_mutex); + cm->cur_entries += return_val; + Debug( pcache_debug, + "ENTRY ADDED/MERGED, CACHED ENTRIES=%d\n", + cm->cur_entries, 0, 0 ); + return_val = 0; + ldap_pvt_thread_mutex_unlock(&cm->cache_mutex); + } + + return return_val; +} + +static int +pcache_op_cleanup( Operation *op, SlapReply *rs ) { + slap_callback *cb = op->o_callback; + struct search_info *si = cb->sc_private; + slap_overinst *on = si->on; + cache_manager *cm = on->on_bi.bi_private; + query_manager* qm = cm->qm; + + if ( rs->sr_type == REP_RESULT || + op->o_abandon || rs->sr_err == SLAPD_ABANDON ) + { + if ( si->swap_saved_attrs ) { + rs->sr_attrs = si->save_attrs; + op->ors_attrs = si->save_attrs; + } + if ( (op->o_abandon || rs->sr_err == SLAPD_ABANDON) && + si->caching_reason == PC_IGNORE ) + { + filter_free( si->query.filter ); + if ( si->count ) { + /* duplicate query, free it */ + Entry *e; + for (;si->head; si->head=e) { + e = si->head->e_private; + si->head->e_private = NULL; + entry_free(si->head); + } + } + + } else if ( si->caching_reason != PC_IGNORE ) { + CachedQuery *qc = qm->addfunc(op, qm, &si->query, + si->qtemp, si->caching_reason, 1 ); + + if ( qc != NULL ) { + switch ( si->caching_reason ) { + case PC_POSITIVE: + cache_entries( op, &qc->q_uuid ); + if ( si->pbi ) { + qc->bind_refcnt++; + si->pbi->bi_cq = qc; + } + break; + + case PC_SIZELIMIT: + qc->q_sizelimit = rs->sr_nentries; + break; + + case PC_NEGATIVE: + break; + + default: + assert( 0 ); + break; + } + ldap_pvt_thread_rdwr_wunlock(&qc->rwlock); + ldap_pvt_thread_mutex_lock(&cm->cache_mutex); + cm->num_cached_queries++; + Debug( pcache_debug, "STORED QUERIES = %lu\n", + cm->num_cached_queries, 0, 0 ); + ldap_pvt_thread_mutex_unlock(&cm->cache_mutex); + + /* If the consistency checker suspended itself, + * wake it back up + */ + if ( cm->cc_paused == PCACHE_CC_PAUSED ) { + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( cm->cc_paused == PCACHE_CC_PAUSED ) { + cm->cc_paused = 0; + ldap_pvt_runqueue_resched( &slapd_rq, cm->cc_arg, 0 ); + } + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + + } else if ( si->count ) { + /* duplicate query, free it */ + Entry *e; + for (;si->head; si->head=e) { + e = si->head->e_private; + si->head->e_private = NULL; + entry_free(si->head); + } + } + + } else { + filter_free( si->query.filter ); + } + + op->o_callback = op->o_callback->sc_next; + op->o_tmpfree( cb, op->o_tmpmemctx ); + } + + return SLAP_CB_CONTINUE; +} + +static int +pcache_response( + Operation *op, + SlapReply *rs ) +{ + struct search_info *si = op->o_callback->sc_private; + + if ( si->swap_saved_attrs ) { + rs->sr_attrs = si->save_attrs; + rs->sr_attr_flags = slap_attr_flags( si->save_attrs ); + op->ors_attrs = si->save_attrs; + } + + if ( rs->sr_type == REP_SEARCH ) { + Entry *e; + + /* don't return more entries than requested by the client */ + if ( si->slimit > 0 && rs->sr_nentries >= si->slimit ) { + si->slimit_exceeded = 1; + } + + /* If we haven't exceeded the limit for this query, + * build a chain of answers to store. If we hit the + * limit, empty the chain and ignore the rest. + */ + if ( !si->over ) { + slap_overinst *on = si->on; + cache_manager *cm = on->on_bi.bi_private; + + /* check if the entry contains undefined + * attributes/objectClasses (ITS#5680) */ + if ( cm->check_cacheability && test_filter( op, rs->sr_entry, si->query.filter ) != LDAP_COMPARE_TRUE ) { + Debug( pcache_debug, "%s: query not cacheable because of schema issues in DN \"%s\"\n", + op->o_log_prefix, rs->sr_entry->e_name.bv_val, 0 ); + goto over; + } + + /* check for malformed entries: attrs with no values */ + { + Attribute *a = rs->sr_entry->e_attrs; + for (; a; a=a->a_next) { + if ( !a->a_numvals ) { + Debug( pcache_debug, "%s: query not cacheable because of attrs without values in DN \"%s\" (%s)\n", + op->o_log_prefix, rs->sr_entry->e_name.bv_val, + a->a_desc->ad_cname.bv_val ); + goto over; + } + } + } + + if ( si->count < si->max ) { + si->count++; + e = entry_dup( rs->sr_entry ); + if ( !si->head ) si->head = e; + if ( si->tail ) si->tail->e_private = e; + si->tail = e; + + } else { +over:; + si->over = 1; + si->count = 0; + for (;si->head; si->head=e) { + e = si->head->e_private; + si->head->e_private = NULL; + entry_free(si->head); + } + si->tail = NULL; + } + } + if ( si->slimit_exceeded ) { + return 0; + } + } else if ( rs->sr_type == REP_RESULT ) { + + if ( si->count ) { + if ( rs->sr_err == LDAP_SUCCESS ) { + si->caching_reason = PC_POSITIVE; + + } else if ( rs->sr_err == LDAP_SIZELIMIT_EXCEEDED + && si->qtemp->limitttl ) + { + Entry *e; + + si->caching_reason = PC_SIZELIMIT; + for (;si->head; si->head=e) { + e = si->head->e_private; + si->head->e_private = NULL; + entry_free(si->head); + } + } + + } else if ( si->qtemp->negttl && !si->count && !si->over && + rs->sr_err == LDAP_SUCCESS ) + { + si->caching_reason = PC_NEGATIVE; + } + + + if ( si->slimit_exceeded ) { + rs->sr_err = LDAP_SIZELIMIT_EXCEEDED; + } + } + + return SLAP_CB_CONTINUE; +} + +/* NOTE: this is a quick workaround to let pcache minimally interact + * with pagedResults. A more articulated solutions would be to + * perform the remote query without control and cache all results, + * performing the pagedResults search only within the client + * and the proxy. This requires pcache to understand pagedResults. */ +static int +pcache_chk_controls( + Operation *op, + SlapReply *rs ) +{ + const char *non = ""; + const char *stripped = ""; + + switch( op->o_pagedresults ) { + case SLAP_CONTROL_NONCRITICAL: + non = "non-"; + stripped = "; stripped"; + /* fallthru */ + + case SLAP_CONTROL_CRITICAL: + Debug( pcache_debug, "%s: " + "%scritical pagedResults control " + "disabled with proxy cache%s.\n", + op->o_log_prefix, non, stripped ); + + slap_remove_control( op, rs, slap_cids.sc_pagedResults, NULL ); + break; + + default: + rs->sr_err = SLAP_CB_CONTINUE; + break; + } + + return rs->sr_err; +} + +static int +pc_setpw( Operation *op, struct berval *pwd, cache_manager *cm ) +{ + struct berval vals[2]; + + { + const char *text = NULL; + BER_BVZERO( &vals[0] ); + slap_passwd_hash( pwd, &vals[0], &text ); + if ( BER_BVISEMPTY( &vals[0] )) { + Debug( pcache_debug, "pc_setpw: hash failed %s\n", + text, 0, 0 ); + return LDAP_OTHER; + } + } + + BER_BVZERO( &vals[1] ); + + { + Modifications mod; + SlapReply sr = { REP_RESULT }; + slap_callback cb = { 0, slap_null_cb, 0, 0 }; + int rc; + + mod.sml_op = LDAP_MOD_REPLACE; + mod.sml_flags = 0; + mod.sml_desc = slap_schema.si_ad_userPassword; + mod.sml_type = mod.sml_desc->ad_cname; + mod.sml_values = vals; + mod.sml_nvalues = NULL; + mod.sml_numvals = 1; + mod.sml_next = NULL; + + op->o_tag = LDAP_REQ_MODIFY; + op->orm_modlist = &mod; + op->o_bd = &cm->db; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + op->o_callback = &cb; + Debug( pcache_debug, "pc_setpw: CACHING BIND for %s\n", + op->o_req_dn.bv_val, 0, 0 ); + rc = op->o_bd->be_modify( op, &sr ); + ch_free( vals[0].bv_val ); + return rc; + } +} + +typedef struct bindcacheinfo { + slap_overinst *on; + CachedQuery *qc; +} bindcacheinfo; + +static int +pc_bind_save( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_err == LDAP_SUCCESS ) { + bindcacheinfo *bci = op->o_callback->sc_private; + slap_overinst *on = bci->on; + cache_manager *cm = on->on_bi.bi_private; + CachedQuery *qc = bci->qc; + int delete = 0; + + ldap_pvt_thread_rdwr_wlock( &qc->rwlock ); + if ( qc->bind_refcnt-- ) { + Operation op2 = *op; + if ( pc_setpw( &op2, &op->orb_cred, cm ) == LDAP_SUCCESS ) + bci->qc->bindref_time = op->o_time + bci->qc->qtemp->bindttr; + } else { + bci->qc = NULL; + delete = 1; + } + ldap_pvt_thread_rdwr_wunlock( &qc->rwlock ); + if ( delete ) free_query(qc); + } + return SLAP_CB_CONTINUE; +} + +static Filter * +pc_bind_attrs( Operation *op, Entry *e, QueryTemplate *temp, + struct berval *fbv ) +{ + int i, len = 0; + struct berval *vals, pres = BER_BVC("*"); + char *p1, *p2; + Attribute *a; + + vals = op->o_tmpalloc( temp->bindnattrs * sizeof( struct berval ), + op->o_tmpmemctx ); + + for ( i=0; i<temp->bindnattrs; i++ ) { + a = attr_find( e->e_attrs, temp->bindfattrs[i] ); + if ( a && a->a_vals ) { + vals[i] = a->a_vals[0]; + len += a->a_vals[0].bv_len; + } else { + vals[i] = pres; + } + } + fbv->bv_len = len + temp->bindftemp.bv_len; + fbv->bv_val = op->o_tmpalloc( fbv->bv_len + 1, op->o_tmpmemctx ); + + p1 = temp->bindftemp.bv_val; + p2 = fbv->bv_val; + i = 0; + while ( *p1 ) { + *p2++ = *p1; + if ( p1[0] == '=' && p1[1] == ')' ) { + AC_MEMCPY( p2, vals[i].bv_val, vals[i].bv_len ); + p2 += vals[i].bv_len; + i++; + } + p1++; + } + *p2 = '\0'; + op->o_tmpfree( vals, op->o_tmpmemctx ); + + /* FIXME: are we sure str2filter_x can't fail? + * caller needs to check */ + { + Filter *f = str2filter_x( op, fbv->bv_val ); + assert( f != NULL ); + return f; + } +} + +/* Check if the requested entry is from the cache and has a valid + * ttr and password hash + */ +static int +pc_bind_search( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH ) { + bindinfo *pbi = op->o_callback->sc_private; + + /* We only care if this is an already cached result and we're + * below the refresh time, or we're offline. + */ + if ( pbi->bi_cq ) { + if (( pbi->bi_cm->cc_paused & PCACHE_CC_OFFLINE ) || + op->o_time < pbi->bi_cq->bindref_time ) { + Attribute *a; + + /* See if a recognized password is hashed here */ + a = attr_find( rs->sr_entry->e_attrs, + slap_schema.si_ad_userPassword ); + if ( a && a->a_vals[0].bv_val[0] == '{' && + lutil_passwd_scheme( a->a_vals[0].bv_val )) + pbi->bi_flags |= BI_HASHED; + } else { + Debug( pcache_debug, "pc_bind_search: cache is stale, " + "reftime: %ld, current time: %ld\n", + pbi->bi_cq->bindref_time, op->o_time, 0 ); + } + } else if ( pbi->bi_si ) { + /* This search result is going into the cache */ + struct berval fbv; + Filter *f; + + filter_free( pbi->bi_si->query.filter ); + f = pc_bind_attrs( op, rs->sr_entry, pbi->bi_templ, &fbv ); + op->o_tmpfree( fbv.bv_val, op->o_tmpmemctx ); + pbi->bi_si->query.filter = filter_dup( f, NULL ); + filter_free_x( op, f, 1 ); + } + } + return 0; +} + +/* We always want pc_bind_search to run after the search handlers */ +static int +pc_bind_resp( Operation *op, SlapReply *rs ) +{ + bindinfo *pbi = op->o_callback->sc_private; + if ( !( pbi->bi_flags & BI_DIDCB )) { + slap_callback *sc = op->o_callback; + while ( sc && sc->sc_response != pcache_response ) + sc = sc->sc_next; + if ( !sc ) + sc = op->o_callback; + pbi->bi_cb.sc_next = sc->sc_next; + sc->sc_next = &pbi->bi_cb; + pbi->bi_flags |= BI_DIDCB; + } + return SLAP_CB_CONTINUE; +} + +#ifdef PCACHE_CONTROL_PRIVDB +static int +pcache_op_privdb( + Operation *op, + SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + cache_manager *cm = on->on_bi.bi_private; + slap_callback *save_cb; + slap_op_t type; + + /* skip if control is unset */ + if ( op->o_ctrlflag[ privDB_cid ] != SLAP_CONTROL_CRITICAL ) { + return SLAP_CB_CONTINUE; + } + + /* The cache DB isn't open yet */ + if ( cm->defer_db_open ) { + send_ldap_error( op, rs, LDAP_UNAVAILABLE, + "pcachePrivDB: cacheDB not available" ); + return rs->sr_err; + } + + /* FIXME: might be a little bit exaggerated... */ + if ( !be_isroot( op ) ) { + save_cb = op->o_callback; + op->o_callback = NULL; + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "pcachePrivDB: operation not allowed" ); + op->o_callback = save_cb; + + return rs->sr_err; + } + + /* map tag to operation */ + type = slap_req2op( op->o_tag ); + if ( type != SLAP_OP_LAST ) { + BI_op_func **func; + int rc; + + /* execute, if possible */ + func = &cm->db.be_bind; + if ( func[ type ] != NULL ) { + Operation op2 = *op; + + op2.o_bd = &cm->db; + + rc = func[ type ]( &op2, rs ); + if ( type == SLAP_OP_BIND && rc == LDAP_SUCCESS ) { + op->o_conn->c_authz_cookie = cm->db.be_private; + } + + return rs->sr_err; + } + } + + /* otherwise fall back to error */ + save_cb = op->o_callback; + op->o_callback = NULL; + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "operation not supported with pcachePrivDB control" ); + op->o_callback = save_cb; + + return rs->sr_err; +} +#endif /* PCACHE_CONTROL_PRIVDB */ + +static int +pcache_op_bind( + Operation *op, + SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + cache_manager *cm = on->on_bi.bi_private; + QueryTemplate *temp; + Entry *e; + slap_callback cb = { 0 }, *sc; + bindinfo bi = { 0 }; + bindcacheinfo *bci; + Operation op2; + int rc; + +#ifdef PCACHE_CONTROL_PRIVDB + if ( op->o_ctrlflag[ privDB_cid ] == SLAP_CONTROL_CRITICAL ) + return pcache_op_privdb( op, rs ); +#endif /* PCACHE_CONTROL_PRIVDB */ + + /* Skip if we're not configured for Binds, or cache DB isn't open yet */ + if ( !cm->cache_binds || cm->defer_db_open ) + return SLAP_CB_CONTINUE; + + /* First find a matching template with Bind info */ + for ( temp=cm->qm->templates; temp; temp=temp->qmnext ) { + if ( temp->bindttr && dnIsSuffix( &op->o_req_ndn, &temp->bindbase )) + break; + } + /* Didn't find a suitable template, just passthru */ + if ( !temp ) + return SLAP_CB_CONTINUE; + + /* See if the entry is already locally cached. If so, we can + * populate the query filter to retrieve the cached query. We + * need to check the bindrefresh time in the query. + */ + op2 = *op; + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + + op2.o_bd = &cm->db; + e = NULL; + rc = be_entry_get_rw( &op2, &op->o_req_ndn, NULL, NULL, 0, &e ); + if ( rc == LDAP_SUCCESS && e ) { + bi.bi_flags |= BI_LOOKUP; + op2.ors_filter = pc_bind_attrs( op, e, temp, &op2.ors_filterstr ); + be_entry_release_r( &op2, e ); + } else { + op2.ors_filter = temp->bindfilter; + op2.ors_filterstr = temp->bindfilterstr; + } + + op2.o_bd = op->o_bd; + op2.o_tag = LDAP_REQ_SEARCH; + op2.ors_scope = LDAP_SCOPE_BASE; + op2.ors_deref = LDAP_DEREF_NEVER; + op2.ors_slimit = 1; + op2.ors_tlimit = SLAP_NO_LIMIT; + op2.ors_limit = NULL; + op2.ors_attrs = cm->qm->attr_sets[temp->attr_set_index].attrs; + op2.ors_attrsonly = 0; + + /* We want to invoke search at the same level of the stack + * as we're already at... + */ + bi.bi_cm = cm; + bi.bi_templ = temp; + + bi.bi_cb.sc_response = pc_bind_search; + bi.bi_cb.sc_private = &bi; + cb.sc_private = &bi; + cb.sc_response = pc_bind_resp; + op2.o_callback = &cb; + overlay_op_walk( &op2, rs, op_search, on->on_info, on ); + + /* OK, just bind locally */ + if ( bi.bi_flags & BI_HASHED ) { + int delete = 0; + BackendDB *be = op->o_bd; + op->o_bd = &cm->db; + + Debug( pcache_debug, "pcache_op_bind: CACHED BIND for %s\n", + op->o_req_dn.bv_val, 0, 0 ); + + if ( op->o_bd->be_bind( op, rs ) == LDAP_SUCCESS ) { + op->o_conn->c_authz_cookie = cm->db.be_private; + } + op->o_bd = be; + ldap_pvt_thread_rdwr_wlock( &bi.bi_cq->rwlock ); + if ( !bi.bi_cq->bind_refcnt-- ) { + delete = 1; + } + ldap_pvt_thread_rdwr_wunlock( &bi.bi_cq->rwlock ); + if ( delete ) free_query( bi.bi_cq ); + return rs->sr_err; + } + + /* We have a cached query to work with */ + if ( bi.bi_cq ) { + sc = op->o_tmpalloc( sizeof(slap_callback) + sizeof(bindcacheinfo), + op->o_tmpmemctx ); + sc->sc_response = pc_bind_save; + sc->sc_cleanup = NULL; + sc->sc_private = sc+1; + sc->sc_writewait = NULL; + bci = sc->sc_private; + sc->sc_next = op->o_callback; + op->o_callback = sc; + bci->on = on; + bci->qc = bi.bi_cq; + } + return SLAP_CB_CONTINUE; +} + +static slap_response refresh_merge; + +static int +pcache_op_search( + Operation *op, + SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + cache_manager *cm = on->on_bi.bi_private; + query_manager* qm = cm->qm; + + int i = -1; + + Query query; + QueryTemplate *qtemp = NULL; + bindinfo *pbi = NULL; + + int attr_set = -1; + CachedQuery *answerable = NULL; + int cacheable = 0; + + struct berval tempstr; + +#ifdef PCACHE_CONTROL_PRIVDB + if ( op->o_ctrlflag[ privDB_cid ] == SLAP_CONTROL_CRITICAL ) { + return pcache_op_privdb( op, rs ); + } +#endif /* PCACHE_CONTROL_PRIVDB */ + + /* The cache DB isn't open yet */ + if ( cm->defer_db_open ) { + send_ldap_error( op, rs, LDAP_UNAVAILABLE, + "pcachePrivDB: cacheDB not available" ); + return rs->sr_err; + } + + /* pickup runtime ACL changes */ + cm->db.be_acl = op->o_bd->be_acl; + + { + /* See if we're processing a Bind request + * or a cache refresh */ + slap_callback *cb = op->o_callback; + + for ( ; cb; cb=cb->sc_next ) { + if ( cb->sc_response == pc_bind_resp ) { + pbi = cb->sc_private; + break; + } + if ( cb->sc_response == refresh_merge ) { + /* This is a refresh, do not search the cache */ + return SLAP_CB_CONTINUE; + } + } + } + + /* FIXME: cannot cache/answer requests with pagedResults control */ + + query.filter = op->ors_filter; + + if ( pbi ) { + query.base = pbi->bi_templ->bindbase; + query.scope = pbi->bi_templ->bindscope; + attr_set = pbi->bi_templ->attr_set_index; + cacheable = 1; + qtemp = pbi->bi_templ; + if ( pbi->bi_flags & BI_LOOKUP ) + answerable = qm->qcfunc(op, qm, &query, qtemp); + + } else { + tempstr.bv_val = op->o_tmpalloc( op->ors_filterstr.bv_len+1, + op->o_tmpmemctx ); + tempstr.bv_len = 0; + if ( filter2template( op, op->ors_filter, &tempstr )) + { + op->o_tmpfree( tempstr.bv_val, op->o_tmpmemctx ); + return SLAP_CB_CONTINUE; + } + + Debug( pcache_debug, "query template of incoming query = %s\n", + tempstr.bv_val, 0, 0 ); + + /* find attr set */ + attr_set = get_attr_set(op->ors_attrs, qm, cm->numattrsets); + + query.base = op->o_req_ndn; + query.scope = op->ors_scope; + + /* check for query containment */ + if (attr_set > -1) { + QueryTemplate *qt = qm->attr_sets[attr_set].templates; + for (; qt; qt = qt->qtnext ) { + /* find if template i can potentially answer tempstr */ + if ( ber_bvstrcasecmp( &qt->querystr, &tempstr ) != 0 ) + continue; + cacheable = 1; + qtemp = qt; + Debug( pcache_debug, "Entering QC, querystr = %s\n", + op->ors_filterstr.bv_val, 0, 0 ); + answerable = qm->qcfunc(op, qm, &query, qt); + + /* if != NULL, rlocks qtemp->t_rwlock */ + if (answerable) + break; + } + } + op->o_tmpfree( tempstr.bv_val, op->o_tmpmemctx ); + } + + if (answerable) { + BackendDB *save_bd = op->o_bd; + + ldap_pvt_thread_mutex_lock( &answerable->answerable_cnt_mutex ); + answerable->answerable_cnt++; + /* we only care about refcnts if we're refreshing */ + if ( answerable->refresh_time ) + answerable->refcnt++; + Debug( pcache_debug, "QUERY ANSWERABLE (answered %lu times)\n", + answerable->answerable_cnt, 0, 0 ); + ldap_pvt_thread_mutex_unlock( &answerable->answerable_cnt_mutex ); + + ldap_pvt_thread_rdwr_wlock(&answerable->rwlock); + if ( BER_BVISNULL( &answerable->q_uuid )) { + /* No entries cached, just an empty result set */ + i = rs->sr_err = 0; + send_ldap_result( op, rs ); + } else { + /* Let Bind know we used a cached query */ + if ( pbi ) { + answerable->bind_refcnt++; + pbi->bi_cq = answerable; + } + + op->o_bd = &cm->db; + if ( cm->response_cb == PCACHE_RESPONSE_CB_TAIL ) { + slap_callback cb; + /* The cached entry was already processed by any + * other overlays, so don't let it get processed again. + * + * This loop removes over_back_response from the stack. + */ + if ( overlay_callback_after_backover( op, &cb, 0) == 0 ) { + slap_callback **scp; + for ( scp = &op->o_callback; *scp != NULL; + scp = &(*scp)->sc_next ) { + if ( (*scp)->sc_next == &cb ) { + *scp = cb.sc_next; + break; + } + } + } + } + i = cm->db.bd_info->bi_op_search( op, rs ); + } + ldap_pvt_thread_rdwr_wunlock(&answerable->rwlock); + /* locked by qtemp->qcfunc (query_containment) */ + ldap_pvt_thread_rdwr_runlock(&qtemp->t_rwlock); + op->o_bd = save_bd; + return i; + } + + Debug( pcache_debug, "QUERY NOT ANSWERABLE\n", 0, 0, 0 ); + + ldap_pvt_thread_mutex_lock(&cm->cache_mutex); + if (cm->num_cached_queries >= cm->max_queries) { + cacheable = 0; + } + ldap_pvt_thread_mutex_unlock(&cm->cache_mutex); + + if (op->ors_attrsonly) + cacheable = 0; + + if (cacheable) { + slap_callback *cb; + struct search_info *si; + + Debug( pcache_debug, "QUERY CACHEABLE\n", 0, 0, 0 ); + query.filter = filter_dup(op->ors_filter, NULL); + + cb = op->o_tmpalloc( sizeof(*cb) + sizeof(*si), op->o_tmpmemctx ); + cb->sc_response = pcache_response; + cb->sc_cleanup = pcache_op_cleanup; + cb->sc_private = (cb+1); + cb->sc_writewait = 0; + si = cb->sc_private; + si->on = on; + si->query = query; + si->qtemp = qtemp; + si->max = cm->num_entries_limit ; + si->over = 0; + si->count = 0; + si->slimit = 0; + si->slimit_exceeded = 0; + si->caching_reason = PC_IGNORE; + if ( op->ors_slimit > 0 && op->ors_slimit < cm->num_entries_limit ) { + si->slimit = op->ors_slimit; + op->ors_slimit = cm->num_entries_limit; + } + si->head = NULL; + si->tail = NULL; + si->swap_saved_attrs = 1; + si->save_attrs = op->ors_attrs; + si->pbi = pbi; + if ( pbi ) + pbi->bi_si = si; + + op->ors_attrs = qtemp->t_attrs.attrs; + + if ( cm->response_cb == PCACHE_RESPONSE_CB_HEAD ) { + cb->sc_next = op->o_callback; + op->o_callback = cb; + + } else { + slap_callback **pcb; + + /* need to move the callback at the end, in case other + * overlays are present, so that the final entry is + * actually cached */ + cb->sc_next = NULL; + for ( pcb = &op->o_callback; *pcb; pcb = &(*pcb)->sc_next ); + *pcb = cb; + } + + } else { + Debug( pcache_debug, "QUERY NOT CACHEABLE\n", + 0, 0, 0); + } + + return SLAP_CB_CONTINUE; +} + +static int +get_attr_set( + AttributeName* attrs, + query_manager* qm, + int num ) +{ + int i = 0; + int count = 0; + + if ( attrs ) { + for ( ; attrs[i].an_name.bv_val; i++ ) { + /* only count valid attribute names + * (searches ignore others, this overlay does the same) */ + if ( attrs[i].an_desc ) { + count++; + } + } + } + + /* recognize default or explicit single "*" */ + if ( ! attrs || + ( i == 1 && bvmatch( &attrs[0].an_name, slap_bv_all_user_attrs ) ) ) + { + count = 1; + attrs = slap_anlist_all_user_attributes; + + /* recognize implicit (no valid attributes) or explicit single "1.1" */ + } else if ( count == 0 || + ( i == 1 && bvmatch( &attrs[0].an_name, slap_bv_no_attrs ) ) ) + { + count = 0; + attrs = NULL; + } + + for ( i = 0; i < num; i++ ) { + AttributeName *a2; + int found = 1; + + if ( count > qm->attr_sets[i].count ) { + if ( qm->attr_sets[i].count && + bvmatch( &qm->attr_sets[i].attrs[0].an_name, slap_bv_all_user_attrs )) { + break; + } + continue; + } + + if ( !count ) { + if ( !qm->attr_sets[i].count ) { + break; + } + continue; + } + + for ( a2 = attrs; a2->an_name.bv_val; a2++ ) { + if ( !a2->an_desc && !bvmatch( &a2->an_name, slap_bv_all_user_attrs ) ) continue; + + if ( !an_find( qm->attr_sets[i].attrs, &a2->an_name ) ) { + found = 0; + break; + } + } + + if ( found ) { + break; + } + } + + if ( i == num ) { + i = -1; + } + + return i; +} + +/* Refresh a cached query: + * 1: Replay the query on the remote DB and merge each entry into + * the local DB. Remember the DNs of each remote entry. + * 2: Search the local DB for all entries matching this queryID. + * Delete any entry whose DN is not in the list from (1). + */ +typedef struct dnlist { + struct dnlist *next; + struct berval dn; + char delete; +} dnlist; + +typedef struct refresh_info { + dnlist *ri_dns; + dnlist *ri_tail; + dnlist *ri_dels; + BackendDB *ri_be; + CachedQuery *ri_q; +} refresh_info; + +static dnlist *dnl_alloc( Operation *op, struct berval *bvdn ) +{ + dnlist *dn = op->o_tmpalloc( sizeof(dnlist) + bvdn->bv_len + 1, + op->o_tmpmemctx ); + dn->dn.bv_len = bvdn->bv_len; + dn->dn.bv_val = (char *)(dn+1); + AC_MEMCPY( dn->dn.bv_val, bvdn->bv_val, dn->dn.bv_len ); + dn->dn.bv_val[dn->dn.bv_len] = '\0'; + return dn; +} + +static int +refresh_merge( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH ) { + refresh_info *ri = op->o_callback->sc_private; + Entry *e; + dnlist *dnl; + slap_callback *ocb; + int rc; + + ocb = op->o_callback; + /* Find local entry, merge */ + op->o_bd = ri->ri_be; + rc = be_entry_get_rw( op, &rs->sr_entry->e_nname, NULL, NULL, 0, &e ); + if ( rc != LDAP_SUCCESS || e == NULL ) { + /* No local entry, just add it. FIXME: we are not checking + * the cache entry limit here + */ + merge_entry( op, rs->sr_entry, 1, &ri->ri_q->q_uuid ); + } else { + /* Entry exists, update it */ + Entry ne; + Attribute *a, **b; + Modifications *modlist, *mods = NULL; + const char* text = NULL; + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof(textbuf); + slap_callback cb = { NULL, slap_null_cb, NULL, NULL }; + + ne = *e; + b = &ne.e_attrs; + /* Get a copy of only the attrs we requested */ + for ( a=e->e_attrs; a; a=a->a_next ) { + if ( ad_inlist( a->a_desc, rs->sr_attrs )) { + *b = attr_alloc( a->a_desc ); + *(*b) = *a; + /* The actual values still belong to e */ + (*b)->a_flags |= SLAP_ATTR_DONT_FREE_VALS | + SLAP_ATTR_DONT_FREE_DATA; + b = &((*b)->a_next); + } + } + *b = NULL; + slap_entry2mods( rs->sr_entry, &modlist, &text, textbuf, textlen ); + syncrepl_diff_entry( op, ne.e_attrs, rs->sr_entry->e_attrs, + &mods, &modlist, 0 ); + be_entry_release_r( op, e ); + attrs_free( ne.e_attrs ); + slap_mods_free( modlist, 1 ); + /* mods is NULL if there are no changes */ + if ( mods ) { + SlapReply rs2 = { REP_RESULT }; + struct berval dn = op->o_req_dn; + struct berval ndn = op->o_req_ndn; + op->o_tag = LDAP_REQ_MODIFY; + op->orm_modlist = mods; + op->o_req_dn = rs->sr_entry->e_name; + op->o_req_ndn = rs->sr_entry->e_nname; + op->o_callback = &cb; + op->o_bd->be_modify( op, &rs2 ); + rs->sr_err = rs2.sr_err; + rs_assert_done( &rs2 ); + slap_mods_free( mods, 1 ); + op->o_req_dn = dn; + op->o_req_ndn = ndn; + } + } + + /* Add DN to list */ + dnl = dnl_alloc( op, &rs->sr_entry->e_nname ); + dnl->next = NULL; + if ( ri->ri_tail ) { + ri->ri_tail->next = dnl; + } else { + ri->ri_dns = dnl; + } + ri->ri_tail = dnl; + op->o_callback = ocb; + } + return 0; +} + +static int +refresh_purge( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH ) { + refresh_info *ri = op->o_callback->sc_private; + dnlist **dn; + int del = 1; + + /* Did the entry exist on the remote? */ + for ( dn=&ri->ri_dns; *dn; dn = &(*dn)->next ) { + if ( dn_match( &(*dn)->dn, &rs->sr_entry->e_nname )) { + dnlist *dnext = (*dn)->next; + op->o_tmpfree( *dn, op->o_tmpmemctx ); + *dn = dnext; + del = 0; + break; + } + } + /* No, so put it on the list to delete */ + if ( del ) { + Attribute *a; + dnlist *dnl = dnl_alloc( op, &rs->sr_entry->e_nname ); + dnl->next = ri->ri_dels; + ri->ri_dels = dnl; + a = attr_find( rs->sr_entry->e_attrs, ad_queryId ); + /* If ours is the only queryId, delete entry */ + dnl->delete = ( a->a_numvals == 1 ); + } + } + return 0; +} + +static int +refresh_query( Operation *op, CachedQuery *query, slap_overinst *on ) +{ + SlapReply rs = {REP_RESULT}; + slap_callback cb = { 0 }; + refresh_info ri = { 0 }; + char filter_str[ LDAP_LUTIL_UUIDSTR_BUFSIZE + STRLENOF( "(pcacheQueryID=)" ) ]; + AttributeAssertion ava = ATTRIBUTEASSERTION_INIT; + Filter filter = {LDAP_FILTER_EQUALITY}; + AttributeName attrs[ 2 ] = {{{ 0 }}}; + dnlist *dn; + int rc; + + ldap_pvt_thread_mutex_lock( &query->answerable_cnt_mutex ); + query->refcnt = 0; + ldap_pvt_thread_mutex_unlock( &query->answerable_cnt_mutex ); + + cb.sc_response = refresh_merge; + cb.sc_private = &ri; + + /* cache DB */ + ri.ri_be = op->o_bd; + ri.ri_q = query; + + op->o_tag = LDAP_REQ_SEARCH; + op->o_protocol = LDAP_VERSION3; + op->o_callback = &cb; + op->o_do_not_cache = 1; + + op->o_req_dn = query->qbase->base; + op->o_req_ndn = query->qbase->base; + op->ors_scope = query->scope; + op->ors_deref = LDAP_DEREF_NEVER; + op->ors_slimit = SLAP_NO_LIMIT; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_limit = NULL; + op->ors_filter = query->filter; + filter2bv_x( op, query->filter, &op->ors_filterstr ); + op->ors_attrs = query->qtemp->t_attrs.attrs; + op->ors_attrsonly = 0; + + op->o_bd = on->on_info->oi_origdb; + rc = op->o_bd->be_search( op, &rs ); + if ( rc ) { + op->o_bd = ri.ri_be; + goto leave; + } + + /* Get the DNs of all entries matching this query */ + cb.sc_response = refresh_purge; + + op->o_bd = ri.ri_be; + op->o_req_dn = op->o_bd->be_suffix[0]; + op->o_req_ndn = op->o_bd->be_nsuffix[0]; + op->ors_scope = LDAP_SCOPE_SUBTREE; + op->ors_deref = LDAP_DEREF_NEVER; + op->ors_filterstr.bv_len = snprintf(filter_str, sizeof(filter_str), + "(%s=%s)", ad_queryId->ad_cname.bv_val, query->q_uuid.bv_val); + filter.f_ava = &ava; + filter.f_av_desc = ad_queryId; + filter.f_av_value = query->q_uuid; + attrs[ 0 ].an_desc = ad_queryId; + attrs[ 0 ].an_name = ad_queryId->ad_cname; + op->ors_attrs = attrs; + op->ors_attrsonly = 0; + rs_reinit( &rs, REP_RESULT ); + rc = op->o_bd->be_search( op, &rs ); + if ( rc ) goto leave; + + while (( dn = ri.ri_dels )) { + op->o_req_dn = dn->dn; + op->o_req_ndn = dn->dn; + rs_reinit( &rs, REP_RESULT ); + if ( dn->delete ) { + op->o_tag = LDAP_REQ_DELETE; + op->o_bd->be_delete( op, &rs ); + } else { + Modifications mod; + struct berval vals[2]; + + vals[0] = query->q_uuid; + BER_BVZERO( &vals[1] ); + mod.sml_op = LDAP_MOD_DELETE; + mod.sml_flags = 0; + mod.sml_desc = ad_queryId; + mod.sml_type = ad_queryId->ad_cname; + mod.sml_values = vals; + mod.sml_nvalues = NULL; + mod.sml_numvals = 1; + mod.sml_next = NULL; + + op->o_tag = LDAP_REQ_MODIFY; + op->orm_modlist = &mod; + op->o_bd->be_modify( op, &rs ); + } + ri.ri_dels = dn->next; + op->o_tmpfree( dn, op->o_tmpmemctx ); + } + +leave: + /* reset our local heap, we're done with it */ + slap_sl_mem_create(SLAP_SLAB_SIZE, SLAP_SLAB_STACK, op->o_threadctx, 1 ); + return rc; +} + +static void* +consistency_check( + void *ctx, + void *arg ) +{ + struct re_s *rtask = arg; + slap_overinst *on = rtask->arg; + cache_manager *cm = on->on_bi.bi_private; + query_manager *qm = cm->qm; + Connection conn = {0}; + OperationBuffer opbuf; + Operation *op; + + CachedQuery *query, *qprev; + int return_val, pause = PCACHE_CC_PAUSED; + QueryTemplate *templ; + + /* Don't expire anything when we're offline */ + if ( cm->cc_paused & PCACHE_CC_OFFLINE ) { + pause = PCACHE_CC_OFFLINE; + goto leave; + } + + connection_fake_init( &conn, &opbuf, ctx ); + op = &opbuf.ob_op; + + op->o_bd = &cm->db; + op->o_dn = cm->db.be_rootdn; + op->o_ndn = cm->db.be_rootndn; + + cm->cc_arg = arg; + + for (templ = qm->templates; templ; templ=templ->qmnext) { + time_t ttl; + if ( !templ->query_last ) continue; + pause = 0; + op->o_time = slap_get_time(); + if ( !templ->ttr ) { + ttl = templ->ttl; + if ( templ->negttl && templ->negttl < ttl ) + ttl = templ->negttl; + if ( templ->limitttl && templ->limitttl < ttl ) + ttl = templ->limitttl; + /* The oldest timestamp that needs expiration checking */ + ttl += op->o_time; + } + + for ( query=templ->query_last; query; query=qprev ) { + qprev = query->prev; + if ( query->refresh_time && query->refresh_time < op->o_time ) { + /* A refresh will extend the expiry if the query has been + * referenced, but not if it's unreferenced. If the + * expiration has been hit, then skip the refresh since + * we're just going to discard the result anyway. + */ + if ( query->refcnt ) + query->expiry_time = op->o_time + templ->ttl; + if ( query->expiry_time > op->o_time ) { + refresh_query( op, query, on ); + continue; + } + } + + if (query->expiry_time < op->o_time) { + int rem = 0; + Debug( pcache_debug, "Lock CR index = %p\n", + (void *) templ, 0, 0 ); + ldap_pvt_thread_rdwr_wlock(&templ->t_rwlock); + if ( query == templ->query_last ) { + rem = 1; + remove_from_template(query, templ); + Debug( pcache_debug, "TEMPLATE %p QUERIES-- %d\n", + (void *) templ, templ->no_of_queries, 0 ); + Debug( pcache_debug, "Unlock CR index = %p\n", + (void *) templ, 0, 0 ); + } + if ( !rem ) { + ldap_pvt_thread_rdwr_wunlock(&templ->t_rwlock); + continue; + } + ldap_pvt_thread_mutex_lock(&qm->lru_mutex); + remove_query(qm, query); + ldap_pvt_thread_mutex_unlock(&qm->lru_mutex); + if ( BER_BVISNULL( &query->q_uuid )) + return_val = 0; + else + return_val = remove_query_data(op, &query->q_uuid); + Debug( pcache_debug, "STALE QUERY REMOVED, SIZE=%d\n", + return_val, 0, 0 ); + ldap_pvt_thread_mutex_lock(&cm->cache_mutex); + cm->cur_entries -= return_val; + cm->num_cached_queries--; + Debug( pcache_debug, "STORED QUERIES = %lu\n", + cm->num_cached_queries, 0, 0 ); + ldap_pvt_thread_mutex_unlock(&cm->cache_mutex); + Debug( pcache_debug, + "STALE QUERY REMOVED, CACHE =" + "%d entries\n", + cm->cur_entries, 0, 0 ); + ldap_pvt_thread_rdwr_wlock( &query->rwlock ); + if ( query->bind_refcnt-- ) { + rem = 0; + } else { + rem = 1; + } + ldap_pvt_thread_rdwr_wunlock( &query->rwlock ); + if ( rem ) free_query(query); + ldap_pvt_thread_rdwr_wunlock(&templ->t_rwlock); + } else if ( !templ->ttr && query->expiry_time > ttl ) { + /* We don't need to check for refreshes, and this + * query's expiry is too new, and all subsequent queries + * will be newer yet. So stop looking. + * + * If we have refreshes, then we always have to walk the + * entire query list. + */ + break; + } + } + } + +leave: + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, rtask )) { + ldap_pvt_runqueue_stoptask( &slapd_rq, rtask ); + } + /* If there were no queries, defer processing for a while */ + if ( cm->cc_paused != pause ) + cm->cc_paused = pause; + ldap_pvt_runqueue_resched( &slapd_rq, rtask, pause ); + + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + return NULL; +} + + +#define MAX_ATTR_SETS 500 + +enum { + PC_MAIN = 1, + PC_ATTR, + PC_TEMP, + PC_RESP, + PC_QUERIES, + PC_OFFLINE, + PC_BIND, + PC_PRIVATE_DB +}; + +static ConfigDriver pc_cf_gen; +static ConfigLDAPadd pc_ldadd; +static ConfigCfAdd pc_cfadd; + +static ConfigTable pccfg[] = { + { "pcache", "backend> <max_entries> <numattrsets> <entry limit> " + "<cycle_time", + 6, 6, 0, ARG_MAGIC|ARG_NO_DELETE|PC_MAIN, pc_cf_gen, + "( OLcfgOvAt:2.1 NAME ( 'olcPcache' 'olcProxyCache' ) " + "DESC 'Proxy Cache basic parameters' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "pcacheAttrset", "index> <attributes...", + 2, 0, 0, ARG_MAGIC|PC_ATTR, pc_cf_gen, + "( OLcfgOvAt:2.2 NAME ( 'olcPcacheAttrset' 'olcProxyAttrset' ) " + "DESC 'A set of attributes to cache' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "pcacheTemplate", "filter> <attrset-index> <TTL> <negTTL> " + "<limitTTL> <TTR", + 4, 7, 0, ARG_MAGIC|PC_TEMP, pc_cf_gen, + "( OLcfgOvAt:2.3 NAME ( 'olcPcacheTemplate' 'olcProxyCacheTemplate' ) " + "DESC 'Filter template, attrset, cache TTL, " + "optional negative TTL, optional sizelimit TTL, " + "optional TTR' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "pcachePosition", "head|tail(default)", + 2, 2, 0, ARG_MAGIC|PC_RESP, pc_cf_gen, + "( OLcfgOvAt:2.4 NAME 'olcPcachePosition' " + "DESC 'Response callback position in overlay stack' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "pcacheMaxQueries", "queries", + 2, 2, 0, ARG_INT|ARG_MAGIC|PC_QUERIES, pc_cf_gen, + "( OLcfgOvAt:2.5 NAME ( 'olcPcacheMaxQueries' 'olcProxyCacheQueries' ) " + "DESC 'Maximum number of queries to cache' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "pcachePersist", "TRUE|FALSE", + 2, 2, 0, ARG_ON_OFF|ARG_OFFSET, (void *)offsetof(cache_manager, save_queries), + "( OLcfgOvAt:2.6 NAME ( 'olcPcachePersist' 'olcProxySaveQueries' ) " + "DESC 'Save cached queries for hot restart' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "pcacheValidate", "TRUE|FALSE", + 2, 2, 0, ARG_ON_OFF|ARG_OFFSET, (void *)offsetof(cache_manager, check_cacheability), + "( OLcfgOvAt:2.7 NAME ( 'olcPcacheValidate' 'olcProxyCheckCacheability' ) " + "DESC 'Check whether the results of a query are cacheable, e.g. for schema issues' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "pcacheOffline", "TRUE|FALSE", + 2, 2, 0, ARG_ON_OFF|ARG_MAGIC|PC_OFFLINE, pc_cf_gen, + "( OLcfgOvAt:2.8 NAME 'olcPcacheOffline' " + "DESC 'Set cache to offline mode and disable expiration' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "pcacheBind", "filter> <attrset-index> <TTR> <scope> <base", + 6, 6, 0, ARG_MAGIC|PC_BIND, pc_cf_gen, + "( OLcfgOvAt:2.9 NAME 'olcPcacheBind' " + "DESC 'Parameters for caching Binds' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "pcache-", "private database args", + 1, 0, STRLENOF("pcache-"), ARG_MAGIC|PC_PRIVATE_DB, pc_cf_gen, + NULL, NULL, NULL }, + + /* Legacy keywords */ + { "proxycache", "backend> <max_entries> <numattrsets> <entry limit> " + "<cycle_time", + 6, 6, 0, ARG_MAGIC|ARG_NO_DELETE|PC_MAIN, pc_cf_gen, + NULL, NULL, NULL }, + { "proxyattrset", "index> <attributes...", + 2, 0, 0, ARG_MAGIC|PC_ATTR, pc_cf_gen, + NULL, NULL, NULL }, + { "proxytemplate", "filter> <attrset-index> <TTL> <negTTL", + 4, 7, 0, ARG_MAGIC|PC_TEMP, pc_cf_gen, + NULL, NULL, NULL }, + { "response-callback", "head|tail(default)", + 2, 2, 0, ARG_MAGIC|PC_RESP, pc_cf_gen, + NULL, NULL, NULL }, + { "proxyCacheQueries", "queries", + 2, 2, 0, ARG_INT|ARG_MAGIC|PC_QUERIES, pc_cf_gen, + NULL, NULL, NULL }, + { "proxySaveQueries", "TRUE|FALSE", + 2, 2, 0, ARG_ON_OFF|ARG_OFFSET, (void *)offsetof(cache_manager, save_queries), + NULL, NULL, NULL }, + { "proxyCheckCacheability", "TRUE|FALSE", + 2, 2, 0, ARG_ON_OFF|ARG_OFFSET, (void *)offsetof(cache_manager, check_cacheability), + NULL, NULL, NULL }, + + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs pcocs[] = { + { "( OLcfgOvOc:2.1 " + "NAME 'olcPcacheConfig' " + "DESC 'ProxyCache configuration' " + "SUP olcOverlayConfig " + "MUST ( olcPcache $ olcPcacheAttrset $ olcPcacheTemplate ) " + "MAY ( olcPcachePosition $ olcPcacheMaxQueries $ olcPcachePersist $ " + "olcPcacheValidate $ olcPcacheOffline $ olcPcacheBind ) )", + Cft_Overlay, pccfg, NULL, pc_cfadd }, + { "( OLcfgOvOc:2.2 " + "NAME 'olcPcacheDatabase' " + "DESC 'Cache database configuration' " + /* co_table is initialized in pcache_initialize */ + "AUXILIARY )", Cft_Misc, NULL, pc_ldadd }, + { NULL, 0, NULL } +}; + +static int pcache_db_open2( slap_overinst *on, ConfigReply *cr ); + +static int +pc_ldadd_cleanup( ConfigArgs *c ) +{ + slap_overinst *on = c->ca_private; + return pcache_db_open2( on, &c->reply ); +} + +static int +pc_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca ) +{ + slap_overinst *on; + cache_manager *cm; + + if ( p->ce_type != Cft_Overlay || !p->ce_bi || + p->ce_bi->bi_cf_ocs != pcocs ) + return LDAP_CONSTRAINT_VIOLATION; + + on = (slap_overinst *)p->ce_bi; + cm = on->on_bi.bi_private; + ca->be = &cm->db; + /* Defer open if this is an LDAPadd */ + if ( CONFIG_ONLINE_ADD( ca )) + ca->cleanup = pc_ldadd_cleanup; + else + cm->defer_db_open = 0; + ca->ca_private = on; + return LDAP_SUCCESS; +} + +static int +pc_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca ) +{ + CfEntryInfo *pe = p->e_private; + slap_overinst *on = (slap_overinst *)pe->ce_bi; + cache_manager *cm = on->on_bi.bi_private; + struct berval bv; + + /* FIXME: should not hardcode "olcDatabase" here */ + bv.bv_len = snprintf( ca->cr_msg, sizeof( ca->cr_msg ), + "olcDatabase=" SLAP_X_ORDERED_FMT "%s", + 0, cm->db.bd_info->bi_type ); + if ( bv.bv_len >= sizeof( ca->cr_msg ) ) { + return -1; + } + bv.bv_val = ca->cr_msg; + ca->be = &cm->db; + cm->defer_db_open = 0; + + /* We can only create this entry if the database is table-driven + */ + if ( cm->db.bd_info->bi_cf_ocs ) + config_build_entry( op, rs, pe, ca, &bv, cm->db.bd_info->bi_cf_ocs, + &pcocs[1] ); + + return 0; +} + +static int +pc_cf_gen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + cache_manager* cm = on->on_bi.bi_private; + query_manager* qm = cm->qm; + QueryTemplate* temp; + AttributeName* attr_name; + AttributeName* attrarray; + const char* text=NULL; + int i, num, rc = 0; + char *ptr; + unsigned long t; + + if ( c->op == SLAP_CONFIG_EMIT ) { + struct berval bv; + switch( c->type ) { + case PC_MAIN: + bv.bv_len = snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s %d %d %d %ld", + cm->db.bd_info->bi_type, cm->max_entries, cm->numattrsets, + cm->num_entries_limit, cm->cc_period ); + bv.bv_val = c->cr_msg; + value_add_one( &c->rvalue_vals, &bv ); + break; + case PC_ATTR: + for (i=0; i<cm->numattrsets; i++) { + if ( !qm->attr_sets[i].count ) continue; + + bv.bv_len = snprintf( c->cr_msg, sizeof( c->cr_msg ), "%d", i ); + + /* count the attr length */ + for ( attr_name = qm->attr_sets[i].attrs; + attr_name->an_name.bv_val; attr_name++ ) + { + bv.bv_len += attr_name->an_name.bv_len + 1; + if ( attr_name->an_desc && + ( attr_name->an_desc->ad_flags & SLAP_DESC_TEMPORARY ) ) { + bv.bv_len += STRLENOF("undef:"); + } + } + + bv.bv_val = ch_malloc( bv.bv_len+1 ); + ptr = lutil_strcopy( bv.bv_val, c->cr_msg ); + for ( attr_name = qm->attr_sets[i].attrs; + attr_name->an_name.bv_val; attr_name++ ) { + *ptr++ = ' '; + if ( attr_name->an_desc && + ( attr_name->an_desc->ad_flags & SLAP_DESC_TEMPORARY ) ) { + ptr = lutil_strcopy( ptr, "undef:" ); + } + ptr = lutil_strcopy( ptr, attr_name->an_name.bv_val ); + } + ber_bvarray_add( &c->rvalue_vals, &bv ); + } + if ( !c->rvalue_vals ) + rc = 1; + break; + case PC_TEMP: + for (temp=qm->templates; temp; temp=temp->qmnext) { + /* HEADS-UP: always print all; + * if optional == 0, ignore */ + bv.bv_len = snprintf( c->cr_msg, sizeof( c->cr_msg ), + " %d %ld %ld %ld %ld", + temp->attr_set_index, + temp->ttl, + temp->negttl, + temp->limitttl, + temp->ttr ); + bv.bv_len += temp->querystr.bv_len + 2; + bv.bv_val = ch_malloc( bv.bv_len+1 ); + ptr = bv.bv_val; + *ptr++ = '"'; + ptr = lutil_strcopy( ptr, temp->querystr.bv_val ); + *ptr++ = '"'; + strcpy( ptr, c->cr_msg ); + ber_bvarray_add( &c->rvalue_vals, &bv ); + } + if ( !c->rvalue_vals ) + rc = 1; + break; + case PC_BIND: + for (temp=qm->templates; temp; temp=temp->qmnext) { + if ( !temp->bindttr ) continue; + bv.bv_len = snprintf( c->cr_msg, sizeof( c->cr_msg ), + " %d %ld %s ", + temp->attr_set_index, + temp->bindttr, + ldap_pvt_scope2str( temp->bindscope )); + bv.bv_len += temp->bindbase.bv_len + temp->bindftemp.bv_len + 4; + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + ptr = bv.bv_val; + *ptr++ = '"'; + ptr = lutil_strcopy( ptr, temp->bindftemp.bv_val ); + *ptr++ = '"'; + ptr = lutil_strcopy( ptr, c->cr_msg ); + *ptr++ = '"'; + ptr = lutil_strcopy( ptr, temp->bindbase.bv_val ); + *ptr++ = '"'; + *ptr = '\0'; + ber_bvarray_add( &c->rvalue_vals, &bv ); + } + if ( !c->rvalue_vals ) + rc = 1; + break; + case PC_RESP: + if ( cm->response_cb == PCACHE_RESPONSE_CB_HEAD ) { + BER_BVSTR( &bv, "head" ); + } else { + BER_BVSTR( &bv, "tail" ); + } + value_add_one( &c->rvalue_vals, &bv ); + break; + case PC_QUERIES: + c->value_int = cm->max_queries; + break; + case PC_OFFLINE: + c->value_int = (cm->cc_paused & PCACHE_CC_OFFLINE) != 0; + break; + } + return rc; + } else if ( c->op == LDAP_MOD_DELETE ) { + rc = 1; + switch( c->type ) { + case PC_ATTR: /* FIXME */ + case PC_TEMP: + case PC_BIND: + break; + case PC_OFFLINE: + cm->cc_paused &= ~PCACHE_CC_OFFLINE; + /* If there were cached queries when we went offline, + * restart the checker now. + */ + if ( cm->num_cached_queries ) { + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + cm->cc_paused = 0; + ldap_pvt_runqueue_resched( &slapd_rq, cm->cc_arg, 0 ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + rc = 0; + break; + } + return rc; + } + + switch( c->type ) { + case PC_MAIN: + if ( cm->numattrsets > 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "\"pcache\" directive already provided" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + + if ( lutil_atoi( &cm->numattrsets, c->argv[3] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse num attrsets=\"%s\" (arg #3)", + c->argv[3] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + if ( cm->numattrsets <= 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "numattrsets (arg #3) must be positive" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + if ( cm->numattrsets > MAX_ATTR_SETS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "numattrsets (arg #3) must be <= %d", MAX_ATTR_SETS ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + + if ( !backend_db_init( c->argv[1], &cm->db, -1, NULL )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unknown backend type (arg #1)" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + + if ( lutil_atoi( &cm->max_entries, c->argv[2] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse max entries=\"%s\" (arg #2)", + c->argv[2] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + if ( cm->max_entries <= 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "max entries (arg #2) must be positive.\n" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + + if ( lutil_atoi( &cm->num_entries_limit, c->argv[4] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse entry limit=\"%s\" (arg #4)", + c->argv[4] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + if ( cm->num_entries_limit <= 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "entry limit (arg #4) must be positive" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + if ( cm->num_entries_limit > cm->max_entries ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "entry limit (arg #4) must be less than max entries %d (arg #2)", cm->max_entries ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + + if ( lutil_parse_time( c->argv[5], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse period=\"%s\" (arg #5)", + c->argv[5] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + + cm->cc_period = (time_t)t; + Debug( pcache_debug, + "Total # of attribute sets to be cached = %d.\n", + cm->numattrsets, 0, 0 ); + qm->attr_sets = ( struct attr_set * )ch_calloc( cm->numattrsets, + sizeof( struct attr_set ) ); + break; + case PC_ATTR: + if ( cm->numattrsets == 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "\"pcache\" directive not provided yet" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + if ( lutil_atoi( &num, c->argv[1] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse attrset #=\"%s\"", + c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + + if ( num < 0 || num >= cm->numattrsets ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "attrset index %d out of bounds (must be %s%d)", + num, cm->numattrsets > 1 ? "0->" : "", cm->numattrsets - 1 ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + qm->attr_sets[num].flags |= PC_CONFIGURED; + if ( c->argc == 2 ) { + /* assume "1.1" */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "need an explicit attr in attrlist; use \"*\" to indicate all attrs" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + + } else if ( c->argc == 3 ) { + if ( strcmp( c->argv[2], LDAP_ALL_USER_ATTRIBUTES ) == 0 ) { + qm->attr_sets[num].count = 1; + qm->attr_sets[num].attrs = (AttributeName*)ch_calloc( 2, + sizeof( AttributeName ) ); + BER_BVSTR( &qm->attr_sets[num].attrs[0].an_name, LDAP_ALL_USER_ATTRIBUTES ); + break; + + } else if ( strcmp( c->argv[2], LDAP_ALL_OPERATIONAL_ATTRIBUTES ) == 0 ) { + qm->attr_sets[num].count = 1; + qm->attr_sets[num].attrs = (AttributeName*)ch_calloc( 2, + sizeof( AttributeName ) ); + BER_BVSTR( &qm->attr_sets[num].attrs[0].an_name, LDAP_ALL_OPERATIONAL_ATTRIBUTES ); + break; + + } else if ( strcmp( c->argv[2], LDAP_NO_ATTRS ) == 0 ) { + break; + } + /* else: fallthru */ + + } else if ( c->argc == 4 ) { + if ( ( strcmp( c->argv[2], LDAP_ALL_USER_ATTRIBUTES ) == 0 && strcmp( c->argv[3], LDAP_ALL_OPERATIONAL_ATTRIBUTES ) == 0 ) + || ( strcmp( c->argv[2], LDAP_ALL_OPERATIONAL_ATTRIBUTES ) == 0 && strcmp( c->argv[3], LDAP_ALL_USER_ATTRIBUTES ) == 0 ) ) + { + qm->attr_sets[num].count = 2; + qm->attr_sets[num].attrs = (AttributeName*)ch_calloc( 3, + sizeof( AttributeName ) ); + BER_BVSTR( &qm->attr_sets[num].attrs[0].an_name, LDAP_ALL_USER_ATTRIBUTES ); + BER_BVSTR( &qm->attr_sets[num].attrs[1].an_name, LDAP_ALL_OPERATIONAL_ATTRIBUTES ); + break; + } + /* else: fallthru */ + } + + if ( c->argc > 2 ) { + int all_user = 0, all_op = 0; + + qm->attr_sets[num].count = c->argc - 2; + qm->attr_sets[num].attrs = (AttributeName*)ch_calloc( c->argc - 1, + sizeof( AttributeName ) ); + attr_name = qm->attr_sets[num].attrs; + for ( i = 2; i < c->argc; i++ ) { + attr_name->an_desc = NULL; + if ( strcmp( c->argv[i], LDAP_NO_ATTRS ) == 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "invalid attr #%d \"%s\" in attrlist", + i - 2, c->argv[i] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + ch_free( qm->attr_sets[num].attrs ); + qm->attr_sets[num].attrs = NULL; + qm->attr_sets[num].count = 0; + return 1; + } + if ( strcmp( c->argv[i], LDAP_ALL_USER_ATTRIBUTES ) == 0 ) { + all_user = 1; + BER_BVSTR( &attr_name->an_name, LDAP_ALL_USER_ATTRIBUTES ); + } else if ( strcmp( c->argv[i], LDAP_ALL_OPERATIONAL_ATTRIBUTES ) == 0 ) { + all_op = 1; + BER_BVSTR( &attr_name->an_name, LDAP_ALL_OPERATIONAL_ATTRIBUTES ); + } else { + if ( strncasecmp( c->argv[i], "undef:", STRLENOF("undef:") ) == 0 ) { + struct berval bv; + ber_str2bv( c->argv[i] + STRLENOF("undef:"), 0, 0, &bv ); + attr_name->an_desc = slap_bv2tmp_ad( &bv, NULL ); + + } else if ( slap_str2ad( c->argv[i], &attr_name->an_desc, &text ) ) { + strcpy( c->cr_msg, text ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + ch_free( qm->attr_sets[num].attrs ); + qm->attr_sets[num].attrs = NULL; + qm->attr_sets[num].count = 0; + return 1; + } + attr_name->an_name = attr_name->an_desc->ad_cname; + } + attr_name->an_oc = NULL; + attr_name->an_flags = 0; + if ( attr_name->an_desc == slap_schema.si_ad_objectClass ) + qm->attr_sets[num].flags |= PC_GOT_OC; + attr_name++; + BER_BVZERO( &attr_name->an_name ); + } + + /* warn if list contains both "*" and "+" */ + if ( i > 4 && all_user && all_op ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "warning: attribute list contains \"*\" and \"+\"" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + } + } + break; + case PC_TEMP: + if ( cm->numattrsets == 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "\"pcache\" directive not provided yet" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + if ( lutil_atoi( &i, c->argv[2] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse template #=\"%s\"", + c->argv[2] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + + if ( i < 0 || i >= cm->numattrsets || + !(qm->attr_sets[i].flags & PC_CONFIGURED )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "template index %d invalid (%s%d)", + i, cm->numattrsets > 1 ? "0->" : "", cm->numattrsets - 1 ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + { + AttributeName *attrs; + int cnt; + cnt = template_attrs( c->argv[1], &qm->attr_sets[i], &attrs, &text ); + if ( cnt < 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse template: %s", + text ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + temp = ch_calloc( 1, sizeof( QueryTemplate )); + temp->qmnext = qm->templates; + qm->templates = temp; + temp->t_attrs.attrs = attrs; + temp->t_attrs.count = cnt; + } + ldap_pvt_thread_rdwr_init( &temp->t_rwlock ); + temp->query = temp->query_last = NULL; + if ( lutil_parse_time( c->argv[3], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse template ttl=\"%s\"", + c->argv[3] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); +pc_temp_fail: + ch_free( temp->t_attrs.attrs ); + ch_free( temp ); + return( 1 ); + } + temp->ttl = (time_t)t; + temp->negttl = (time_t)0; + temp->limitttl = (time_t)0; + temp->ttr = (time_t)0; + switch ( c->argc ) { + case 7: + if ( lutil_parse_time( c->argv[6], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse template ttr=\"%s\"", + c->argv[6] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + goto pc_temp_fail; + } + temp->ttr = (time_t)t; + /* fallthru */ + + case 6: + if ( lutil_parse_time( c->argv[5], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse template sizelimit ttl=\"%s\"", + c->argv[5] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + goto pc_temp_fail; + } + temp->limitttl = (time_t)t; + /* fallthru */ + + case 5: + if ( lutil_parse_time( c->argv[4], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse template negative ttl=\"%s\"", + c->argv[4] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + goto pc_temp_fail; + } + temp->negttl = (time_t)t; + break; + } + + temp->no_of_queries = 0; + + ber_str2bv( c->argv[1], 0, 1, &temp->querystr ); + Debug( pcache_debug, "Template:\n", 0, 0, 0 ); + Debug( pcache_debug, " query template: %s\n", + temp->querystr.bv_val, 0, 0 ); + temp->attr_set_index = i; + qm->attr_sets[i].flags |= PC_REFERENCED; + temp->qtnext = qm->attr_sets[i].templates; + qm->attr_sets[i].templates = temp; + Debug( pcache_debug, " attributes: \n", 0, 0, 0 ); + if ( ( attrarray = qm->attr_sets[i].attrs ) != NULL ) { + for ( i=0; attrarray[i].an_name.bv_val; i++ ) + Debug( pcache_debug, "\t%s\n", + attrarray[i].an_name.bv_val, 0, 0 ); + } + break; + case PC_BIND: + if ( !qm->templates ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "\"pcacheTemplate\" directive not provided yet" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + if ( lutil_atoi( &i, c->argv[2] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse Bind index #=\"%s\"", + c->argv[2] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + + if ( i < 0 || i >= cm->numattrsets || + !(qm->attr_sets[i].flags & PC_CONFIGURED )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "Bind index %d invalid (%s%d)", + i, cm->numattrsets > 1 ? "0->" : "", cm->numattrsets - 1 ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + { struct berval bv, tempbv; + AttributeDescription **descs; + int ndescs; + ber_str2bv( c->argv[1], 0, 0, &bv ); + ndescs = ftemp_attrs( &bv, &tempbv, &descs, &text ); + if ( ndescs < 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse template: %s", + text ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + for ( temp = qm->templates; temp; temp=temp->qmnext ) { + if ( temp->attr_set_index == i && bvmatch( &tempbv, + &temp->querystr )) + break; + } + ch_free( tempbv.bv_val ); + if ( !temp ) { + ch_free( descs ); + snprintf( c->cr_msg, sizeof( c->cr_msg ), "Bind template %s %d invalid", + c->argv[1], i ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + ber_dupbv( &temp->bindftemp, &bv ); + temp->bindfattrs = descs; + temp->bindnattrs = ndescs; + } + if ( lutil_parse_time( c->argv[3], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse bind ttr=\"%s\"", + c->argv[3] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); +pc_bind_fail: + ch_free( temp->bindfattrs ); + temp->bindfattrs = NULL; + ch_free( temp->bindftemp.bv_val ); + BER_BVZERO( &temp->bindftemp ); + return( 1 ); + } + num = ldap_pvt_str2scope( c->argv[4] ); + if ( num < 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse bind scope=\"%s\"", + c->argv[4] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + goto pc_bind_fail; + } + { + struct berval dn, ndn; + ber_str2bv( c->argv[5], 0, 0, &dn ); + rc = dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL ); + if ( rc ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "invalid bind baseDN=\"%s\"", + c->argv[5] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + goto pc_bind_fail; + } + if ( temp->bindbase.bv_val ) + ch_free( temp->bindbase.bv_val ); + temp->bindbase = ndn; + } + { + /* convert the template into dummy filter */ + struct berval bv; + char *eq = temp->bindftemp.bv_val, *e2; + Filter *f; + i = 0; + while ((eq = strchr(eq, '=' ))) { + eq++; + if ( eq[0] == ')' ) + i++; + } + bv.bv_len = temp->bindftemp.bv_len + i; + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + for ( e2 = bv.bv_val, eq = temp->bindftemp.bv_val; + *eq; eq++ ) { + if ( *eq == '=' ) { + *e2++ = '='; + if ( eq[1] == ')' ) + *e2++ = '*'; + } else { + *e2++ = *eq; + } + } + *e2 = '\0'; + f = str2filter( bv.bv_val ); + if ( !f ) { + ch_free( bv.bv_val ); + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse bindfilter=\"%s\"", bv.bv_val ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + ch_free( temp->bindbase.bv_val ); + BER_BVZERO( &temp->bindbase ); + goto pc_bind_fail; + } + if ( temp->bindfilter ) + filter_free( temp->bindfilter ); + if ( temp->bindfilterstr.bv_val ) + ch_free( temp->bindfilterstr.bv_val ); + temp->bindfilterstr = bv; + temp->bindfilter = f; + } + temp->bindttr = (time_t)t; + temp->bindscope = num; + cm->cache_binds = 1; + break; + + case PC_RESP: + if ( strcasecmp( c->argv[1], "head" ) == 0 ) { + cm->response_cb = PCACHE_RESPONSE_CB_HEAD; + + } else if ( strcasecmp( c->argv[1], "tail" ) == 0 ) { + cm->response_cb = PCACHE_RESPONSE_CB_TAIL; + + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unknown specifier" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + break; + case PC_QUERIES: + if ( c->value_int <= 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "max queries must be positive" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + cm->max_queries = c->value_int; + break; + case PC_OFFLINE: + if ( c->value_int ) + cm->cc_paused |= PCACHE_CC_OFFLINE; + else + cm->cc_paused &= ~PCACHE_CC_OFFLINE; + break; + case PC_PRIVATE_DB: + if ( cm->db.be_private == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "private database must be defined before setting database specific options" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + + if ( cm->db.bd_info->bi_cf_ocs ) { + ConfigTable *ct; + ConfigArgs c2 = *c; + char *argv0 = c->argv[ 0 ]; + + c->argv[ 0 ] = &argv0[ STRLENOF( "pcache-" ) ]; + + ct = config_find_keyword( cm->db.bd_info->bi_cf_ocs->co_table, c ); + if ( ct == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "private database does not recognize specific option '%s'", + c->argv[ 0 ] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + rc = 1; + + } else { + c->table = cm->db.bd_info->bi_cf_ocs->co_type; + c->be = &cm->db; + c->bi = c->be->bd_info; + + rc = config_add_vals( ct, c ); + + c->bi = c2.bi; + c->be = c2.be; + c->table = c2.table; + } + + c->argv[ 0 ] = argv0; + + } else if ( cm->db.be_config != NULL ) { + char *argv0 = c->argv[ 0 ]; + + c->argv[ 0 ] = &argv0[ STRLENOF( "pcache-" ) ]; + rc = cm->db.be_config( &cm->db, c->fname, c->lineno, c->argc, c->argv ); + c->argv[ 0 ] = argv0; + + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "no means to set private database specific options" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + break; + default: + rc = SLAP_CONF_UNKNOWN; + break; + } + + return rc; +} + +static int +pcache_db_config( + BackendDB *be, + const char *fname, + int lineno, + int argc, + char **argv +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + cache_manager* cm = on->on_bi.bi_private; + + /* Something for the cache database? */ + if ( cm->db.bd_info && cm->db.bd_info->bi_db_config ) + return cm->db.bd_info->bi_db_config( &cm->db, fname, lineno, + argc, argv ); + return SLAP_CONF_UNKNOWN; +} + +static int +pcache_db_init( + BackendDB *be, + ConfigReply *cr) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + cache_manager *cm; + query_manager *qm; + + cm = (cache_manager *)ch_malloc(sizeof(cache_manager)); + on->on_bi.bi_private = cm; + + qm = (query_manager*)ch_malloc(sizeof(query_manager)); + + cm->db = *be; + cm->db.bd_info = NULL; + SLAP_DBFLAGS(&cm->db) |= SLAP_DBFLAG_NO_SCHEMA_CHECK; + cm->db.be_private = NULL; + cm->db.bd_self = &cm->db; + cm->db.be_pending_csn_list = NULL; + cm->qm = qm; + cm->numattrsets = 0; + cm->num_entries_limit = 5; + cm->num_cached_queries = 0; + cm->max_entries = 0; + cm->cur_entries = 0; + cm->max_queries = 10000; + cm->save_queries = 0; + cm->check_cacheability = 0; + cm->response_cb = PCACHE_RESPONSE_CB_TAIL; + cm->defer_db_open = 1; + cm->cache_binds = 0; + cm->cc_period = 1000; + cm->cc_paused = 0; + cm->cc_arg = NULL; +#ifdef PCACHE_MONITOR + cm->monitor_cb = NULL; +#endif /* PCACHE_MONITOR */ + + qm->attr_sets = NULL; + qm->templates = NULL; + qm->lru_top = NULL; + qm->lru_bottom = NULL; + + qm->qcfunc = query_containment; + qm->crfunc = cache_replacement; + qm->addfunc = add_query; + ldap_pvt_thread_mutex_init(&qm->lru_mutex); + + ldap_pvt_thread_mutex_init(&cm->cache_mutex); + +#ifndef PCACHE_MONITOR + return 0; +#else /* PCACHE_MONITOR */ + return pcache_monitor_db_init( be ); +#endif /* PCACHE_MONITOR */ +} + +static int +pcache_cachedquery_open_cb( Operation *op, SlapReply *rs ) +{ + assert( op->o_tag == LDAP_REQ_SEARCH ); + + if ( rs->sr_type == REP_SEARCH ) { + Attribute *a; + + a = attr_find( rs->sr_entry->e_attrs, ad_cachedQueryURL ); + if ( a != NULL ) { + BerVarray *valsp; + + assert( a->a_nvals != NULL ); + + valsp = op->o_callback->sc_private; + assert( *valsp == NULL ); + + ber_bvarray_dup_x( valsp, a->a_nvals, op->o_tmpmemctx ); + } + } + + return 0; +} + +static int +pcache_cachedquery_count_cb( Operation *op, SlapReply *rs ) +{ + assert( op->o_tag == LDAP_REQ_SEARCH ); + + if ( rs->sr_type == REP_SEARCH ) { + int *countp = (int *)op->o_callback->sc_private; + + (*countp)++; + } + + return 0; +} + +static int +pcache_db_open2( + slap_overinst *on, + ConfigReply *cr ) +{ + cache_manager *cm = on->on_bi.bi_private; + query_manager* qm = cm->qm; + int rc; + + rc = backend_startup_one( &cm->db, cr ); + if ( rc == 0 ) { + cm->defer_db_open = 0; + } + + /* There is no runqueue in TOOL mode */ + if (( slapMode & SLAP_SERVER_MODE ) && rc == 0 ) { + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + ldap_pvt_runqueue_insert( &slapd_rq, cm->cc_period, + consistency_check, on, + "pcache_consistency", cm->db.be_suffix[0].bv_val ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + /* Cached database must have the rootdn */ + if ( BER_BVISNULL( &cm->db.be_rootndn ) + || BER_BVISEMPTY( &cm->db.be_rootndn ) ) + { + Debug( LDAP_DEBUG_ANY, "pcache_db_open(): " + "underlying database of type \"%s\"\n" + " serving naming context \"%s\"\n" + " has no \"rootdn\", required by \"pcache\".\n", + on->on_info->oi_orig->bi_type, + cm->db.be_suffix[0].bv_val, 0 ); + return 1; + } + + if ( cm->save_queries ) { + void *thrctx = ldap_pvt_thread_pool_context(); + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation *op; + slap_callback cb = { 0 }; + SlapReply rs = { REP_RESULT }; + BerVarray vals = NULL; + Filter f = { 0 }, f2 = { 0 }; + AttributeAssertion ava = ATTRIBUTEASSERTION_INIT; + AttributeName attrs[ 2 ] = {{{ 0 }}}; + + connection_fake_init2( &conn, &opbuf, thrctx, 0 ); + op = &opbuf.ob_op; + + op->o_bd = &cm->db; + + op->o_tag = LDAP_REQ_SEARCH; + op->o_protocol = LDAP_VERSION3; + cb.sc_response = pcache_cachedquery_open_cb; + cb.sc_private = &vals; + op->o_callback = &cb; + op->o_time = slap_get_time(); + op->o_do_not_cache = 1; + op->o_managedsait = SLAP_CONTROL_CRITICAL; + + op->o_dn = cm->db.be_rootdn; + op->o_ndn = cm->db.be_rootndn; + op->o_req_dn = cm->db.be_suffix[ 0 ]; + op->o_req_ndn = cm->db.be_nsuffix[ 0 ]; + + op->ors_scope = LDAP_SCOPE_BASE; + op->ors_deref = LDAP_DEREF_NEVER; + op->ors_slimit = 1; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_limit = NULL; + ber_str2bv( "(pcacheQueryURL=*)", 0, 0, &op->ors_filterstr ); + f.f_choice = LDAP_FILTER_PRESENT; + f.f_desc = ad_cachedQueryURL; + op->ors_filter = &f; + attrs[ 0 ].an_desc = ad_cachedQueryURL; + attrs[ 0 ].an_name = ad_cachedQueryURL->ad_cname; + op->ors_attrs = attrs; + op->ors_attrsonly = 0; + + rc = op->o_bd->be_search( op, &rs ); + if ( rc == LDAP_SUCCESS && vals != NULL ) { + int i; + + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + if ( url2query( vals[ i ].bv_val, op, qm ) == 0 ) { + cm->num_cached_queries++; + } + } + + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + + /* count cached entries */ + f.f_choice = LDAP_FILTER_NOT; + f.f_not = &f2; + f2.f_choice = LDAP_FILTER_EQUALITY; + f2.f_ava = &ava; + f2.f_av_desc = slap_schema.si_ad_objectClass; + BER_BVSTR( &f2.f_av_value, "glue" ); + ber_str2bv( "(!(objectClass=glue))", 0, 0, &op->ors_filterstr ); + + op->ors_slimit = SLAP_NO_LIMIT; + op->ors_scope = LDAP_SCOPE_SUBTREE; + op->ors_attrs = slap_anlist_no_attrs; + + rs_reinit( &rs, REP_RESULT ); + op->o_callback->sc_response = pcache_cachedquery_count_cb; + op->o_callback->sc_private = &rs.sr_nentries; + + rc = op->o_bd->be_search( op, &rs ); + + cm->cur_entries = rs.sr_nentries; + + /* ignore errors */ + rc = 0; + } + } + return rc; +} + +static int +pcache_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + cache_manager *cm = on->on_bi.bi_private; + query_manager* qm = cm->qm; + int i, ncf = 0, rf = 0, nrf = 0, rc = 0; + + /* check attr sets */ + for ( i = 0; i < cm->numattrsets; i++) { + if ( !( qm->attr_sets[i].flags & PC_CONFIGURED ) ) { + if ( qm->attr_sets[i].flags & PC_REFERENCED ) { + Debug( LDAP_DEBUG_CONFIG, "pcache: attr set #%d not configured but referenced.\n", i, 0, 0 ); + rf++; + + } else { + Debug( LDAP_DEBUG_CONFIG, "pcache: warning, attr set #%d not configured.\n", i, 0, 0 ); + } + ncf++; + + } else if ( !( qm->attr_sets[i].flags & PC_REFERENCED ) ) { + Debug( LDAP_DEBUG_CONFIG, "pcache: attr set #%d configured but not referenced.\n", i, 0, 0 ); + nrf++; + } + } + + if ( ncf || rf || nrf ) { + Debug( LDAP_DEBUG_CONFIG, "pcache: warning, %d attr sets configured but not referenced.\n", nrf, 0, 0 ); + Debug( LDAP_DEBUG_CONFIG, "pcache: warning, %d attr sets not configured.\n", ncf, 0, 0 ); + Debug( LDAP_DEBUG_CONFIG, "pcache: %d attr sets not configured but referenced.\n", rf, 0, 0 ); + + if ( rf > 0 ) { + return 1; + } + } + + /* need to inherit something from the original database... */ + cm->db.be_def_limit = be->be_def_limit; + cm->db.be_limits = be->be_limits; + cm->db.be_acl = be->be_acl; + cm->db.be_dfltaccess = be->be_dfltaccess; + + if ( SLAP_DBMONITORING( be ) ) { + SLAP_DBFLAGS( &cm->db ) |= SLAP_DBFLAG_MONITORING; + + } else { + SLAP_DBFLAGS( &cm->db ) &= ~SLAP_DBFLAG_MONITORING; + } + + if ( !cm->defer_db_open ) { + rc = pcache_db_open2( on, cr ); + } + +#ifdef PCACHE_MONITOR + if ( rc == LDAP_SUCCESS ) { + rc = pcache_monitor_db_open( be ); + } +#endif /* PCACHE_MONITOR */ + + return rc; +} + +static void +pcache_free_qbase( void *v ) +{ + Qbase *qb = v; + int i; + + for (i=0; i<3; i++) + tavl_free( qb->scopes[i], NULL ); + ch_free( qb ); +} + +static int +pcache_db_close( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + cache_manager *cm = on->on_bi.bi_private; + query_manager *qm = cm->qm; + QueryTemplate *tm; + int rc = 0; + + /* stop the thread ... */ + if ( cm->cc_arg ) { + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, cm->cc_arg ) ) { + ldap_pvt_runqueue_stoptask( &slapd_rq, cm->cc_arg ); + } + ldap_pvt_runqueue_remove( &slapd_rq, cm->cc_arg ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + cm->cc_arg = NULL; + } + + if ( cm->save_queries ) { + CachedQuery *qc; + BerVarray vals = NULL; + + void *thrctx; + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation *op; + slap_callback cb = { 0 }; + + SlapReply rs = { REP_RESULT }; + Modifications mod = {{ 0 }}; + + thrctx = ldap_pvt_thread_pool_context(); + + connection_fake_init2( &conn, &opbuf, thrctx, 0 ); + op = &opbuf.ob_op; + + mod.sml_numvals = 0; + if ( qm->templates != NULL ) { + for ( tm = qm->templates; tm != NULL; tm = tm->qmnext ) { + for ( qc = tm->query; qc; qc = qc->next ) { + struct berval bv; + + if ( query2url( op, qc, &bv, 0 ) == 0 ) { + ber_bvarray_add_x( &vals, &bv, op->o_tmpmemctx ); + mod.sml_numvals++; + } + } + } + } + + op->o_bd = &cm->db; + op->o_dn = cm->db.be_rootdn; + op->o_ndn = cm->db.be_rootndn; + + op->o_tag = LDAP_REQ_MODIFY; + op->o_protocol = LDAP_VERSION3; + cb.sc_response = slap_null_cb; + op->o_callback = &cb; + op->o_time = slap_get_time(); + op->o_do_not_cache = 1; + op->o_managedsait = SLAP_CONTROL_CRITICAL; + + op->o_req_dn = op->o_bd->be_suffix[0]; + op->o_req_ndn = op->o_bd->be_nsuffix[0]; + + mod.sml_op = LDAP_MOD_REPLACE; + mod.sml_flags = 0; + mod.sml_desc = ad_cachedQueryURL; + mod.sml_type = ad_cachedQueryURL->ad_cname; + mod.sml_values = vals; + mod.sml_nvalues = NULL; + mod.sml_next = NULL; + Debug( pcache_debug, + "%sSETTING CACHED QUERY URLS\n", + vals == NULL ? "RE" : "", 0, 0 ); + + op->orm_modlist = &mod; + + op->o_bd->be_modify( op, &rs ); + + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + + /* cleanup stuff inherited from the original database... */ + cm->db.be_limits = NULL; + cm->db.be_acl = NULL; + + if ( cm->db.bd_info->bi_db_close ) { + rc = cm->db.bd_info->bi_db_close( &cm->db, NULL ); + } + +#ifdef PCACHE_MONITOR + if ( rc == LDAP_SUCCESS ) { + rc = pcache_monitor_db_close( be ); + } +#endif /* PCACHE_MONITOR */ + + return rc; +} + +static int +pcache_db_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + cache_manager *cm = on->on_bi.bi_private; + query_manager *qm = cm->qm; + QueryTemplate *tm; + int i; + + if ( cm->db.be_private != NULL ) { + backend_stopdown_one( &cm->db ); + } + + while ( (tm = qm->templates) != NULL ) { + CachedQuery *qc, *qn; + qm->templates = tm->qmnext; + for ( qc = tm->query; qc; qc = qn ) { + qn = qc->next; + free_query( qc ); + } + avl_free( tm->qbase, pcache_free_qbase ); + free( tm->querystr.bv_val ); + free( tm->bindfattrs ); + free( tm->bindftemp.bv_val ); + free( tm->bindfilterstr.bv_val ); + free( tm->bindbase.bv_val ); + filter_free( tm->bindfilter ); + ldap_pvt_thread_rdwr_destroy( &tm->t_rwlock ); + free( tm->t_attrs.attrs ); + free( tm ); + } + + for ( i = 0; i < cm->numattrsets; i++ ) { + int j; + + /* Account of LDAP_NO_ATTRS */ + if ( !qm->attr_sets[i].count ) continue; + + for ( j = 0; !BER_BVISNULL( &qm->attr_sets[i].attrs[j].an_name ); j++ ) { + if ( qm->attr_sets[i].attrs[j].an_desc && + ( qm->attr_sets[i].attrs[j].an_desc->ad_flags & + SLAP_DESC_TEMPORARY ) ) { + slap_sl_mfuncs.bmf_free( qm->attr_sets[i].attrs[j].an_desc, NULL ); + } + } + free( qm->attr_sets[i].attrs ); + } + free( qm->attr_sets ); + qm->attr_sets = NULL; + + ldap_pvt_thread_mutex_destroy( &qm->lru_mutex ); + ldap_pvt_thread_mutex_destroy( &cm->cache_mutex ); + free( qm ); + free( cm ); + +#ifdef PCACHE_MONITOR + pcache_monitor_db_destroy( be ); +#endif /* PCACHE_MONITOR */ + + return 0; +} + +#ifdef PCACHE_CONTROL_PRIVDB +/* + Control ::= SEQUENCE { + controlType LDAPOID, + criticality BOOLEAN DEFAULT FALSE, + controlValue OCTET STRING OPTIONAL } + + controlType ::= 1.3.6.1.4.1.4203.666.11.9.5.1 + + * criticality must be TRUE; controlValue must be absent. + */ +static int +parse_privdb_ctrl( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + if ( op->o_ctrlflag[ privDB_cid ] != SLAP_CONTROL_NONE ) { + rs->sr_text = "privateDB control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( !BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = "privateDB control value not absent"; + return LDAP_PROTOCOL_ERROR; + } + + if ( !ctrl->ldctl_iscritical ) { + rs->sr_text = "privateDB control criticality required"; + return LDAP_PROTOCOL_ERROR; + } + + op->o_ctrlflag[ privDB_cid ] = SLAP_CONTROL_CRITICAL; + + return LDAP_SUCCESS; +} + +static char *extops[] = { + LDAP_EXOP_MODIFY_PASSWD, + NULL +}; +#endif /* PCACHE_CONTROL_PRIVDB */ + +static struct berval pcache_exop_MODIFY_PASSWD = BER_BVC( LDAP_EXOP_MODIFY_PASSWD ); +#ifdef PCACHE_EXOP_QUERY_DELETE +static struct berval pcache_exop_QUERY_DELETE = BER_BVC( PCACHE_EXOP_QUERY_DELETE ); + +#define LDAP_TAG_EXOP_QUERY_DELETE_BASE ((LBER_CLASS_CONTEXT|LBER_CONSTRUCTED) + 0) +#define LDAP_TAG_EXOP_QUERY_DELETE_DN ((LBER_CLASS_CONTEXT|LBER_CONSTRUCTED) + 1) +#define LDAP_TAG_EXOP_QUERY_DELETE_UUID ((LBER_CLASS_CONTEXT|LBER_CONSTRUCTED) + 2) + +/* + ExtendedRequest ::= [APPLICATION 23] SEQUENCE { + requestName [0] LDAPOID, + requestValue [1] OCTET STRING OPTIONAL } + + requestName ::= 1.3.6.1.4.1.4203.666.11.9.6.1 + + requestValue ::= SEQUENCE { CHOICE { + baseDN [0] LDAPDN + entryDN [1] LDAPDN }, + queryID [2] OCTET STRING (SIZE(16)) + -- constrained to UUID } + + * Either baseDN or entryDN must be present, to allow database selection. + * + * 1. if baseDN and queryID are present, then the query corresponding + * to queryID is deleted; + * 2. if baseDN is present and queryID is absent, then all queries + * are deleted; + * 3. if entryDN is present and queryID is absent, then all queries + * corresponding to the queryID values present in entryDN are deleted; + * 4. if entryDN and queryID are present, then all queries + * corresponding to the queryID values present in entryDN are deleted, + * but only if the value of queryID is contained in the entry; + * + * Currently, only 1, 3 and 4 are implemented. 2 can be obtained by either + * recursively deleting the database (ldapdelete -r) with PRIVDB control, + * or by removing the database files. + + ExtendedResponse ::= [APPLICATION 24] SEQUENCE { + COMPONENTS OF LDAPResult, + responseName [10] LDAPOID OPTIONAL, + responseValue [11] OCTET STRING OPTIONAL } + + * responseName and responseValue must be absent. + */ + +/* + * - on success, *tagp is either LDAP_TAG_EXOP_QUERY_DELETE_BASE + * or LDAP_TAG_EXOP_QUERY_DELETE_DN. + * - if ndn != NULL, it is set to the normalized DN in the request + * corresponding to either the baseDN or the entryDN, according + * to *tagp; memory is malloc'ed on the Operation's slab, and must + * be freed by the caller. + * - if uuid != NULL, it is set to point to the normalized UUID; + * memory is malloc'ed on the Operation's slab, and must + * be freed by the caller. + */ +static int +pcache_parse_query_delete( + struct berval *in, + ber_tag_t *tagp, + struct berval *ndn, + struct berval *uuid, + const char **text, + void *ctx ) +{ + int rc = LDAP_SUCCESS; + ber_tag_t tag; + ber_len_t len = -1; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + struct berval reqdata = BER_BVNULL; + + *text = NULL; + + if ( ndn ) { + BER_BVZERO( ndn ); + } + + if ( uuid ) { + BER_BVZERO( uuid ); + } + + if ( in == NULL || in->bv_len == 0 ) { + *text = "empty request data field in queryDelete exop"; + return LDAP_PROTOCOL_ERROR; + } + + ber_dupbv_x( &reqdata, in, ctx ); + + /* ber_init2 uses reqdata directly, doesn't allocate new buffers */ + ber_init2( ber, &reqdata, 0 ); + + tag = ber_scanf( ber, "{" /*}*/ ); + + if ( tag == LBER_ERROR ) { + Debug( LDAP_DEBUG_TRACE, + "pcache_parse_query_delete: decoding error.\n", + 0, 0, 0 ); + goto decoding_error; + } + + tag = ber_peek_tag( ber, &len ); + if ( tag == LDAP_TAG_EXOP_QUERY_DELETE_BASE + || tag == LDAP_TAG_EXOP_QUERY_DELETE_DN ) + { + *tagp = tag; + + if ( ndn != NULL ) { + struct berval dn; + + tag = ber_scanf( ber, "m", &dn ); + if ( tag == LBER_ERROR ) { + Debug( LDAP_DEBUG_TRACE, + "pcache_parse_query_delete: DN parse failed.\n", + 0, 0, 0 ); + goto decoding_error; + } + + rc = dnNormalize( 0, NULL, NULL, &dn, ndn, ctx ); + if ( rc != LDAP_SUCCESS ) { + *text = "invalid DN in queryDelete exop request data"; + goto done; + } + + } else { + tag = ber_scanf( ber, "x" /* "m" */ ); + if ( tag == LBER_DEFAULT ) { + goto decoding_error; + } + } + + tag = ber_peek_tag( ber, &len ); + } + + if ( tag == LDAP_TAG_EXOP_QUERY_DELETE_UUID ) { + if ( uuid != NULL ) { + struct berval bv; + char uuidbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE ]; + + tag = ber_scanf( ber, "m", &bv ); + if ( tag == LBER_ERROR ) { + Debug( LDAP_DEBUG_TRACE, + "pcache_parse_query_delete: UUID parse failed.\n", + 0, 0, 0 ); + goto decoding_error; + } + + if ( bv.bv_len != 16 ) { + Debug( LDAP_DEBUG_TRACE, + "pcache_parse_query_delete: invalid UUID length %lu.\n", + (unsigned long)bv.bv_len, 0, 0 ); + goto decoding_error; + } + + rc = lutil_uuidstr_from_normalized( + bv.bv_val, bv.bv_len, + uuidbuf, sizeof( uuidbuf ) ); + if ( rc == -1 ) { + goto decoding_error; + } + ber_str2bv( uuidbuf, rc, 1, uuid ); + rc = LDAP_SUCCESS; + + } else { + tag = ber_skip_tag( ber, &len ); + if ( tag == LBER_DEFAULT ) { + goto decoding_error; + } + + if ( len != 16 ) { + Debug( LDAP_DEBUG_TRACE, + "pcache_parse_query_delete: invalid UUID length %lu.\n", + (unsigned long)len, 0, 0 ); + goto decoding_error; + } + } + + tag = ber_peek_tag( ber, &len ); + } + + if ( tag != LBER_DEFAULT || len != 0 ) { +decoding_error:; + Debug( LDAP_DEBUG_TRACE, + "pcache_parse_query_delete: decoding error\n", + 0, 0, 0 ); + rc = LDAP_PROTOCOL_ERROR; + *text = "queryDelete data decoding error"; + +done:; + if ( ndn && !BER_BVISNULL( ndn ) ) { + slap_sl_free( ndn->bv_val, ctx ); + BER_BVZERO( ndn ); + } + + if ( uuid && !BER_BVISNULL( uuid ) ) { + slap_sl_free( uuid->bv_val, ctx ); + BER_BVZERO( uuid ); + } + } + + if ( !BER_BVISNULL( &reqdata ) ) { + ber_memfree_x( reqdata.bv_val, ctx ); + } + + return rc; +} + +static int +pcache_exop_query_delete( + Operation *op, + SlapReply *rs ) +{ + BackendDB *bd = op->o_bd; + + struct berval uuid = BER_BVNULL, + *uuidp = NULL; + char buf[ SLAP_TEXT_BUFLEN ]; + unsigned len; + ber_tag_t tag = LBER_DEFAULT; + + if ( LogTest( LDAP_DEBUG_STATS ) ) { + uuidp = &uuid; + } + + rs->sr_err = pcache_parse_query_delete( op->ore_reqdata, + &tag, &op->o_req_ndn, uuidp, + &rs->sr_text, op->o_tmpmemctx ); + if ( rs->sr_err != LDAP_SUCCESS ) { + return rs->sr_err; + } + + if ( LogTest( LDAP_DEBUG_STATS ) ) { + assert( !BER_BVISNULL( &op->o_req_ndn ) ); + len = snprintf( buf, sizeof( buf ), " dn=\"%s\"", op->o_req_ndn.bv_val ); + + if ( !BER_BVISNULL( &uuid ) && len < sizeof( buf ) ) { + snprintf( &buf[ len ], sizeof( buf ) - len, " pcacheQueryId=\"%s\"", uuid.bv_val ); + } + + Debug( LDAP_DEBUG_STATS, "%s QUERY DELETE%s\n", + op->o_log_prefix, buf, 0 ); + } + op->o_req_dn = op->o_req_ndn; + + op->o_bd = select_backend( &op->o_req_ndn, 0 ); + if ( op->o_bd == NULL ) { + send_ldap_error( op, rs, LDAP_NO_SUCH_OBJECT, + "no global superior knowledge" ); + } + rs->sr_err = backend_check_restrictions( op, rs, + (struct berval *)&pcache_exop_QUERY_DELETE ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto done; + } + + if ( op->o_bd->be_extended == NULL ) { + send_ldap_error( op, rs, LDAP_UNAVAILABLE_CRITICAL_EXTENSION, + "backend does not support extended operations" ); + goto done; + } + + op->o_bd->be_extended( op, rs ); + +done:; + if ( !BER_BVISNULL( &op->o_req_ndn ) ) { + op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &op->o_req_ndn ); + BER_BVZERO( &op->o_req_dn ); + } + + if ( !BER_BVISNULL( &uuid ) ) { + op->o_tmpfree( uuid.bv_val, op->o_tmpmemctx ); + } + + op->o_bd = bd; + + return rs->sr_err; +} +#endif /* PCACHE_EXOP_QUERY_DELETE */ + +static int +pcache_op_extended( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + cache_manager *cm = on->on_bi.bi_private; + +#ifdef PCACHE_CONTROL_PRIVDB + if ( op->o_ctrlflag[ privDB_cid ] == SLAP_CONTROL_CRITICAL ) { + return pcache_op_privdb( op, rs ); + } +#endif /* PCACHE_CONTROL_PRIVDB */ + +#ifdef PCACHE_EXOP_QUERY_DELETE + if ( bvmatch( &op->ore_reqoid, &pcache_exop_QUERY_DELETE ) ) { + struct berval uuid = BER_BVNULL; + ber_tag_t tag = LBER_DEFAULT; + + rs->sr_err = pcache_parse_query_delete( op->ore_reqdata, + &tag, NULL, &uuid, &rs->sr_text, op->o_tmpmemctx ); + assert( rs->sr_err == LDAP_SUCCESS ); + + if ( tag == LDAP_TAG_EXOP_QUERY_DELETE_DN ) { + /* remove all queries related to the selected entry */ + rs->sr_err = pcache_remove_entry_queries_from_cache( op, + cm, &op->o_req_ndn, &uuid ); + + } else if ( tag == LDAP_TAG_EXOP_QUERY_DELETE_BASE ) { + if ( !BER_BVISNULL( &uuid ) ) { + /* remove the selected query */ + rs->sr_err = pcache_remove_query_from_cache( op, + cm, &uuid ); + + } else { + /* TODO: remove all queries */ + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "deletion of all queries not implemented"; + } + } + + op->o_tmpfree( uuid.bv_val, op->o_tmpmemctx ); + return rs->sr_err; + } +#endif /* PCACHE_EXOP_QUERY_DELETE */ + + /* We only care if we're configured for Bind caching */ + if ( bvmatch( &op->ore_reqoid, &pcache_exop_MODIFY_PASSWD ) && + cm->cache_binds ) { + /* See if the local entry exists and has a password. + * It's too much work to find the matching query, so + * we just see if there's a hashed password to update. + */ + Operation op2 = *op; + Entry *e = NULL; + int rc; + int doit = 0; + + op2.o_bd = &cm->db; + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + rc = be_entry_get_rw( &op2, &op->o_req_ndn, NULL, + slap_schema.si_ad_userPassword, 0, &e ); + if ( rc == LDAP_SUCCESS && e ) { + /* See if a recognized password is hashed here */ + Attribute *a = attr_find( e->e_attrs, + slap_schema.si_ad_userPassword ); + if ( a && a->a_vals[0].bv_val[0] == '{' && + lutil_passwd_scheme( a->a_vals[0].bv_val )) { + doit = 1; + } + be_entry_release_r( &op2, e ); + } + + if ( doit ) { + rc = overlay_op_walk( op, rs, op_extended, on->on_info, + on->on_next ); + if ( rc == LDAP_SUCCESS ) { + req_pwdexop_s *qpw = &op->oq_pwdexop; + + /* We don't care if it succeeds or not */ + pc_setpw( &op2, &qpw->rs_new, cm ); + } + return rc; + } + } + return SLAP_CB_CONTINUE; +} + +static int +pcache_entry_release( Operation *op, Entry *e, int rw ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + cache_manager *cm = on->on_bi.bi_private; + BackendDB *db = op->o_bd; + int rc; + + op->o_bd = &cm->db; + rc = be_entry_release_rw( op, e, rw ); + op->o_bd = db; + return rc; +} + +#ifdef PCACHE_MONITOR + +static int +pcache_monitor_update( + Operation *op, + SlapReply *rs, + Entry *e, + void *priv ) +{ + cache_manager *cm = (cache_manager *) priv; + query_manager *qm = cm->qm; + + CachedQuery *qc; + BerVarray vals = NULL; + + attr_delete( &e->e_attrs, ad_cachedQueryURL ); + if ( ( SLAP_OPATTRS( rs->sr_attr_flags ) || ad_inlist( ad_cachedQueryURL, rs->sr_attrs ) ) + && qm->templates != NULL ) + { + QueryTemplate *tm; + + for ( tm = qm->templates; tm != NULL; tm = tm->qmnext ) { + for ( qc = tm->query; qc; qc = qc->next ) { + struct berval bv; + + if ( query2url( op, qc, &bv, 1 ) == 0 ) { + ber_bvarray_add_x( &vals, &bv, op->o_tmpmemctx ); + } + } + } + + + if ( vals != NULL ) { + attr_merge_normalize( e, ad_cachedQueryURL, vals, NULL ); + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + } + + { + Attribute *a; + char buf[ SLAP_TEXT_BUFLEN ]; + struct berval bv; + + /* number of cached queries */ + a = attr_find( e->e_attrs, ad_numQueries ); + assert( a != NULL ); + + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", cm->num_cached_queries ); + + if ( a->a_nvals != a->a_vals ) { + ber_bvreplace( &a->a_nvals[ 0 ], &bv ); + } + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + + /* number of cached entries */ + a = attr_find( e->e_attrs, ad_numEntries ); + assert( a != NULL ); + + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%d", cm->cur_entries ); + + if ( a->a_nvals != a->a_vals ) { + ber_bvreplace( &a->a_nvals[ 0 ], &bv ); + } + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + } + + return SLAP_CB_CONTINUE; +} + +static int +pcache_monitor_free( + Entry *e, + void **priv ) +{ + struct berval values[ 2 ]; + Modification mod = { 0 }; + + const char *text; + char textbuf[ SLAP_TEXT_BUFLEN ]; + + int rc; + + /* NOTE: if slap_shutdown != 0, priv might have already been freed */ + *priv = NULL; + + /* Remove objectClass */ + mod.sm_op = LDAP_MOD_DELETE; + mod.sm_desc = slap_schema.si_ad_objectClass; + mod.sm_values = values; + mod.sm_numvals = 1; + values[ 0 ] = oc_olmPCache->soc_cname; + BER_BVZERO( &values[ 1 ] ); + + rc = modify_delete_values( e, &mod, 1, &text, + textbuf, sizeof( textbuf ) ); + /* don't care too much about return code... */ + + /* remove attrs */ + mod.sm_values = NULL; + mod.sm_desc = ad_cachedQueryURL; + mod.sm_numvals = 0; + rc = modify_delete_values( e, &mod, 1, &text, + textbuf, sizeof( textbuf ) ); + /* don't care too much about return code... */ + + /* remove attrs */ + mod.sm_values = NULL; + mod.sm_desc = ad_numQueries; + mod.sm_numvals = 0; + rc = modify_delete_values( e, &mod, 1, &text, + textbuf, sizeof( textbuf ) ); + /* don't care too much about return code... */ + + /* remove attrs */ + mod.sm_values = NULL; + mod.sm_desc = ad_numEntries; + mod.sm_numvals = 0; + rc = modify_delete_values( e, &mod, 1, &text, + textbuf, sizeof( textbuf ) ); + /* don't care too much about return code... */ + + return SLAP_CB_CONTINUE; +} + +/* + * call from within pcache_initialize() + */ +static int +pcache_monitor_initialize( void ) +{ + static int pcache_monitor_initialized = 0; + + if ( backend_info( "monitor" ) == NULL ) { + return -1; + } + + if ( pcache_monitor_initialized++ ) { + return 0; + } + + return 0; +} + +static int +pcache_monitor_db_init( BackendDB *be ) +{ + if ( pcache_monitor_initialize() == LDAP_SUCCESS ) { + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_MONITORING; + } + + return 0; +} + +static int +pcache_monitor_db_open( BackendDB *be ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + cache_manager *cm = on->on_bi.bi_private; + Attribute *a, *next; + monitor_callback_t *cb = NULL; + int rc = 0; + BackendInfo *mi; + monitor_extra_t *mbe; + + if ( !SLAP_DBMONITORING( be ) ) { + return 0; + } + + mi = backend_info( "monitor" ); + if ( !mi || !mi->bi_extra ) { + SLAP_DBFLAGS( be ) ^= SLAP_DBFLAG_MONITORING; + return 0; + } + mbe = mi->bi_extra; + + /* don't bother if monitor is not configured */ + if ( !mbe->is_configured() ) { + static int warning = 0; + + if ( warning++ == 0 ) { + Debug( LDAP_DEBUG_ANY, "pcache_monitor_db_open: " + "monitoring disabled; " + "configure monitor database to enable\n", + 0, 0, 0 ); + } + + return 0; + } + + /* alloc as many as required (plus 1 for objectClass) */ + a = attrs_alloc( 1 + 2 ); + if ( a == NULL ) { + rc = 1; + goto cleanup; + } + + a->a_desc = slap_schema.si_ad_objectClass; + attr_valadd( a, &oc_olmPCache->soc_cname, NULL, 1 ); + next = a->a_next; + + { + struct berval bv = BER_BVC( "0" ); + + next->a_desc = ad_numQueries; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + + next->a_desc = ad_numEntries; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + } + + cb = ch_calloc( sizeof( monitor_callback_t ), 1 ); + cb->mc_update = pcache_monitor_update; + cb->mc_free = pcache_monitor_free; + cb->mc_private = (void *)cm; + + /* make sure the database is registered; then add monitor attributes */ + BER_BVZERO( &cm->monitor_ndn ); + rc = mbe->register_overlay( be, on, &cm->monitor_ndn ); + if ( rc == 0 ) { + rc = mbe->register_entry_attrs( &cm->monitor_ndn, a, cb, + NULL, -1, NULL); + } + +cleanup:; + if ( rc != 0 ) { + if ( cb != NULL ) { + ch_free( cb ); + cb = NULL; + } + + if ( a != NULL ) { + attrs_free( a ); + a = NULL; + } + } + + /* store for cleanup */ + cm->monitor_cb = (void *)cb; + + /* we don't need to keep track of the attributes, because + * bdb_monitor_free() takes care of everything */ + if ( a != NULL ) { + attrs_free( a ); + } + + return rc; +} + +static int +pcache_monitor_db_close( BackendDB *be ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + cache_manager *cm = on->on_bi.bi_private; + + if ( cm->monitor_cb != NULL ) { + BackendInfo *mi = backend_info( "monitor" ); + monitor_extra_t *mbe; + + if ( mi && &mi->bi_extra ) { + mbe = mi->bi_extra; + mbe->unregister_entry_callback( &cm->monitor_ndn, + (monitor_callback_t *)cm->monitor_cb, + NULL, 0, NULL ); + } + } + + return 0; +} + +static int +pcache_monitor_db_destroy( BackendDB *be ) +{ + return 0; +} + +#endif /* PCACHE_MONITOR */ + +static slap_overinst pcache; + +static char *obsolete_names[] = { + "proxycache", + NULL +}; + +#if SLAPD_OVER_PROXYCACHE == SLAPD_MOD_DYNAMIC +static +#endif /* SLAPD_OVER_PROXYCACHE == SLAPD_MOD_DYNAMIC */ +int +pcache_initialize() +{ + int i, code; + struct berval debugbv = BER_BVC("pcache"); + ConfigArgs c; + char *argv[ 4 ]; + + /* olcDatabaseDummy is defined in slapd, and Windows + will not let us initialize a struct element with a data pointer + from another library, so we have to initialize this element + "by hand". */ + pcocs[1].co_table = olcDatabaseDummy; + + + code = slap_loglevel_get( &debugbv, &pcache_debug ); + if ( code ) { + return code; + } + +#ifdef PCACHE_CONTROL_PRIVDB + code = register_supported_control( PCACHE_CONTROL_PRIVDB, + SLAP_CTRL_BIND|SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE, extops, + parse_privdb_ctrl, &privDB_cid ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "pcache_initialize: failed to register control %s (%d)\n", + PCACHE_CONTROL_PRIVDB, code, 0 ); + return code; + } +#endif /* PCACHE_CONTROL_PRIVDB */ + +#ifdef PCACHE_EXOP_QUERY_DELETE + code = load_extop2( (struct berval *)&pcache_exop_QUERY_DELETE, + SLAP_EXOP_WRITES|SLAP_EXOP_HIDE, pcache_exop_query_delete, + 0 ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "pcache_initialize: unable to register queryDelete exop: %d.\n", + code, 0, 0 ); + return code; + } +#endif /* PCACHE_EXOP_QUERY_DELETE */ + + argv[ 0 ] = "back-bdb/back-hdb monitor"; + c.argv = argv; + c.argc = 3; + c.fname = argv[0]; + + for ( i = 0; s_oid[ i ].name; i++ ) { + c.lineno = i; + argv[ 1 ] = s_oid[ i ].name; + argv[ 2 ] = s_oid[ i ].oid; + + if ( parse_oidm( &c, 0, NULL ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "pcache_initialize: " + "unable to add objectIdentifier \"%s=%s\"\n", + s_oid[ i ].name, s_oid[ i ].oid, 0 ); + return 1; + } + } + + for ( i = 0; s_ad[i].desc != NULL; i++ ) { + code = register_at( s_ad[i].desc, s_ad[i].adp, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "pcache_initialize: register_at #%d failed\n", i, 0, 0 ); + return code; + } + (*s_ad[i].adp)->ad_type->sat_flags |= SLAP_AT_HIDE; + } + + for ( i = 0; s_oc[i].desc != NULL; i++ ) { + code = register_oc( s_oc[i].desc, s_oc[i].ocp, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "pcache_initialize: register_oc #%d failed\n", i, 0, 0 ); + return code; + } + (*s_oc[i].ocp)->soc_flags |= SLAP_OC_HIDE; + } + + pcache.on_bi.bi_type = "pcache"; + pcache.on_bi.bi_obsolete_names = obsolete_names; + pcache.on_bi.bi_db_init = pcache_db_init; + pcache.on_bi.bi_db_config = pcache_db_config; + pcache.on_bi.bi_db_open = pcache_db_open; + pcache.on_bi.bi_db_close = pcache_db_close; + pcache.on_bi.bi_db_destroy = pcache_db_destroy; + + pcache.on_bi.bi_op_search = pcache_op_search; + pcache.on_bi.bi_op_bind = pcache_op_bind; +#ifdef PCACHE_CONTROL_PRIVDB + pcache.on_bi.bi_op_compare = pcache_op_privdb; + pcache.on_bi.bi_op_modrdn = pcache_op_privdb; + pcache.on_bi.bi_op_modify = pcache_op_privdb; + pcache.on_bi.bi_op_add = pcache_op_privdb; + pcache.on_bi.bi_op_delete = pcache_op_privdb; +#endif /* PCACHE_CONTROL_PRIVDB */ + pcache.on_bi.bi_extended = pcache_op_extended; + + pcache.on_bi.bi_entry_release_rw = pcache_entry_release; + pcache.on_bi.bi_chk_controls = pcache_chk_controls; + + pcache.on_bi.bi_cf_ocs = pcocs; + + code = config_register_schema( pccfg, pcocs ); + if ( code ) return code; + + return overlay_register( &pcache ); +} + +#if SLAPD_OVER_PROXYCACHE == SLAPD_MOD_DYNAMIC +int init_module(int argc, char *argv[]) { + return pcache_initialize(); +} +#endif + +#endif /* defined(SLAPD_OVER_PROXYCACHE) */ diff --git a/servers/slapd/overlays/ppolicy.c b/servers/slapd/overlays/ppolicy.c new file mode 100644 index 0000000..7b2ed48 --- /dev/null +++ b/servers/slapd/overlays/ppolicy.c @@ -0,0 +1,2610 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2021 The OpenLDAP Foundation. + * Portions Copyright 2004-2005 Howard Chu, Symas Corporation. + * Portions Copyright 2004 Hewlett-Packard Company. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was developed by Howard Chu for inclusion in + * OpenLDAP Software, based on prior work by Neil Dunbar (HP). + * This work was sponsored by the Hewlett-Packard Company. + */ + +#include "portable.h" + +/* This file implements "Password Policy for LDAP Directories", + * based on draft behera-ldap-password-policy-09 + */ + +#ifdef SLAPD_OVER_PPOLICY + +#include <ldap.h> +#include "lutil.h" +#include "slap.h" +#ifdef SLAPD_MODULES +#define LIBLTDL_DLL_IMPORT /* Win32: don't re-export libltdl's symbols */ +#include <ltdl.h> +#endif +#include <ac/errno.h> +#include <ac/time.h> +#include <ac/string.h> +#include <ac/ctype.h> +#include "config.h" + +#ifndef MODULE_NAME_SZ +#define MODULE_NAME_SZ 256 +#endif + +#ifndef PPOLICY_DEFAULT_MAXRECORDED_FAILURE +#define PPOLICY_DEFAULT_MAXRECORDED_FAILURE 5 +#endif + +/* Per-instance configuration information */ +typedef struct pp_info { + struct berval def_policy; /* DN of default policy subentry */ + int use_lockout; /* send AccountLocked result? */ + int hash_passwords; /* transparently hash cleartext pwds */ + int forward_updates; /* use frontend for policy state updates */ + int send_netscape_controls; /* send netscape password controls */ + ldap_pvt_thread_mutex_t pwdFailureTime_mutex; +} pp_info; + +/* Our per-connection info - note, it is not per-instance, it is + * used by all instances + */ +typedef struct pw_conn { + struct berval dn; /* DN of restricted user */ +} pw_conn; + +static pw_conn *pwcons; +static int ppolicy_cid; +static int ov_count; + +typedef struct pass_policy { + AttributeDescription *ad; /* attribute to which the policy applies */ + int pwdMinAge; /* minimum time (seconds) until passwd can change */ + int pwdMaxAge; /* time in seconds until pwd will expire after change */ + int pwdInHistory; /* number of previous passwords kept */ + int pwdCheckQuality; /* 0 = don't check quality, 1 = check if possible, + 2 = check mandatory; fail if not possible */ + int pwdMinLength; /* minimum number of chars in password */ + int pwdExpireWarning; /* number of seconds that warning controls are + sent before a password expires */ + int pwdGraceAuthNLimit; /* number of times you can log in with an + expired password */ + int pwdLockout; /* 0 = do not lockout passwords, 1 = lock them out */ + int pwdLockoutDuration; /* time in seconds a password is locked out for */ + int pwdMaxFailure; /* number of failed binds allowed before lockout */ + int pwdMaxRecordedFailure; /* number of failed binds to store */ + int pwdFailureCountInterval; /* number of seconds before failure + counts are zeroed */ + int pwdMustChange; /* 0 = users can use admin set password + 1 = users must change password after admin set */ + int pwdAllowUserChange; /* 0 = users cannot change their passwords + 1 = users can change them */ + int pwdSafeModify; /* 0 = old password doesn't need to come + with password change request + 1 = password change must supply existing pwd */ + char pwdCheckModule[MODULE_NAME_SZ]; /* name of module to dynamically + load to check password */ +} PassPolicy; + +typedef struct pw_hist { + time_t t; /* timestamp of history entry */ + struct berval pw; /* old password hash */ + struct berval bv; /* text of entire entry */ + struct pw_hist *next; +} pw_hist; + +/* Operational attributes */ +static AttributeDescription *ad_pwdChangedTime, *ad_pwdAccountLockedTime, + *ad_pwdFailureTime, *ad_pwdHistory, *ad_pwdGraceUseTime, *ad_pwdReset, + *ad_pwdPolicySubentry; + +static struct schema_info { + char *def; + AttributeDescription **ad; +} pwd_OpSchema[] = { + { "( 1.3.6.1.4.1.42.2.27.8.1.16 " + "NAME ( 'pwdChangedTime' ) " + "DESC 'The time the password was last changed' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", + &ad_pwdChangedTime }, + { "( 1.3.6.1.4.1.42.2.27.8.1.17 " + "NAME ( 'pwdAccountLockedTime' ) " + "DESC 'The time an user account was locked' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "SINGLE-VALUE " +#if 0 + /* Not until Relax control is released */ + "NO-USER-MODIFICATION " +#endif + "USAGE directoryOperation )", + &ad_pwdAccountLockedTime }, + { "( 1.3.6.1.4.1.42.2.27.8.1.19 " + "NAME ( 'pwdFailureTime' ) " + "DESC 'The timestamps of the last consecutive authentication failures' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "NO-USER-MODIFICATION USAGE directoryOperation )", + &ad_pwdFailureTime }, + { "( 1.3.6.1.4.1.42.2.27.8.1.20 " + "NAME ( 'pwdHistory' ) " + "DESC 'The history of users passwords' " + "EQUALITY octetStringMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 " + "NO-USER-MODIFICATION USAGE directoryOperation )", + &ad_pwdHistory }, + { "( 1.3.6.1.4.1.42.2.27.8.1.21 " + "NAME ( 'pwdGraceUseTime' ) " + "DESC 'The timestamps of the grace login once the password has expired' " + "EQUALITY generalizedTimeMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "NO-USER-MODIFICATION USAGE directoryOperation )", + &ad_pwdGraceUseTime }, + { "( 1.3.6.1.4.1.42.2.27.8.1.22 " + "NAME ( 'pwdReset' ) " + "DESC 'The indication that the password has been reset' " + "EQUALITY booleanMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 " + "SINGLE-VALUE USAGE directoryOperation )", + &ad_pwdReset }, + { "( 1.3.6.1.4.1.42.2.27.8.1.23 " + "NAME ( 'pwdPolicySubentry' ) " + "DESC 'The pwdPolicy subentry in effect for this object' " + "EQUALITY distinguishedNameMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 " + "SINGLE-VALUE " +#if 0 + /* Not until Relax control is released */ + "NO-USER-MODIFICATION " +#endif + "USAGE directoryOperation )", + &ad_pwdPolicySubentry }, + { NULL, NULL } +}; + +/* User attributes */ +static AttributeDescription *ad_pwdMinAge, *ad_pwdMaxAge, *ad_pwdInHistory, + *ad_pwdCheckQuality, *ad_pwdMinLength, *ad_pwdMaxFailure, + *ad_pwdGraceAuthNLimit, *ad_pwdExpireWarning, *ad_pwdLockoutDuration, + *ad_pwdFailureCountInterval, *ad_pwdCheckModule, *ad_pwdLockout, + *ad_pwdMustChange, *ad_pwdAllowUserChange, *ad_pwdSafeModify, + *ad_pwdAttribute, *ad_pwdMaxRecordedFailure; + +#define TAB(name) { #name, &ad_##name } + +static struct schema_info pwd_UsSchema[] = { + TAB(pwdAttribute), + TAB(pwdMinAge), + TAB(pwdMaxAge), + TAB(pwdInHistory), + TAB(pwdCheckQuality), + TAB(pwdMinLength), + TAB(pwdMaxFailure), + TAB(pwdMaxRecordedFailure), + TAB(pwdGraceAuthNLimit), + TAB(pwdExpireWarning), + TAB(pwdLockout), + TAB(pwdLockoutDuration), + TAB(pwdFailureCountInterval), + TAB(pwdCheckModule), + TAB(pwdMustChange), + TAB(pwdAllowUserChange), + TAB(pwdSafeModify), + { NULL, NULL } +}; + +static ldap_pvt_thread_mutex_t chk_syntax_mutex; + +enum { + PPOLICY_DEFAULT = 1, + PPOLICY_HASH_CLEARTEXT, + PPOLICY_USE_LOCKOUT +}; + +static ConfigDriver ppolicy_cf_default; + +static ConfigTable ppolicycfg[] = { + { "ppolicy_default", "policyDN", 2, 2, 0, + ARG_DN|ARG_QUOTE|ARG_MAGIC|PPOLICY_DEFAULT, ppolicy_cf_default, + "( OLcfgOvAt:12.1 NAME 'olcPPolicyDefault' " + "DESC 'DN of a pwdPolicy object for uncustomized objects' " + "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL }, + { "ppolicy_hash_cleartext", "on|off", 1, 2, 0, + ARG_ON_OFF|ARG_OFFSET|PPOLICY_HASH_CLEARTEXT, + (void *)offsetof(pp_info,hash_passwords), + "( OLcfgOvAt:12.2 NAME 'olcPPolicyHashCleartext' " + "DESC 'Hash passwords on add or modify' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "ppolicy_forward_updates", "on|off", 1, 2, 0, + ARG_ON_OFF|ARG_OFFSET, + (void *)offsetof(pp_info,forward_updates), + "( OLcfgOvAt:12.4 NAME 'olcPPolicyForwardUpdates' " + "DESC 'Allow policy state updates to be forwarded via updateref' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "ppolicy_use_lockout", "on|off", 1, 2, 0, + ARG_ON_OFF|ARG_OFFSET|PPOLICY_USE_LOCKOUT, + (void *)offsetof(pp_info,use_lockout), + "( OLcfgOvAt:12.3 NAME 'olcPPolicyUseLockout' " + "DESC 'Warn clients with AccountLocked' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "ppolicy_send_netscape_controls", "on|off", 1, 2, 0, + ARG_ON_OFF|ARG_OFFSET, + (void *)offsetof(pp_info,send_netscape_controls), + "( OLcfgOvAt:12.6 NAME 'olcPPolicySendNetscapeControls' " + "DESC 'Send Netscape policy controls' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs ppolicyocs[] = { + { "( OLcfgOvOc:12.1 " + "NAME 'olcPPolicyConfig' " + "DESC 'Password Policy configuration' " + "SUP olcOverlayConfig " + "MAY ( olcPPolicyDefault $ olcPPolicyHashCleartext $ " + "olcPPolicyUseLockout $ olcPPolicyForwardUpdates $ " + "olcPPolicySendNetscapeControls ) )", + Cft_Overlay, ppolicycfg }, + { NULL, 0, NULL } +}; + +static int +ppolicy_cf_default( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + pp_info *pi = (pp_info *)on->on_bi.bi_private; + int rc = ARG_BAD_CONF; + + assert ( c->type == PPOLICY_DEFAULT ); + Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default\n", 0, 0, 0); + + switch ( c->op ) { + case SLAP_CONFIG_EMIT: + Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default emit\n", 0, 0, 0); + rc = 0; + if ( !BER_BVISEMPTY( &pi->def_policy )) { + rc = value_add_one( &c->rvalue_vals, + &pi->def_policy ); + if ( rc ) return rc; + rc = value_add_one( &c->rvalue_nvals, + &pi->def_policy ); + } + break; + case LDAP_MOD_DELETE: + Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default delete\n", 0, 0, 0); + if ( pi->def_policy.bv_val ) { + ber_memfree ( pi->def_policy.bv_val ); + pi->def_policy.bv_val = NULL; + } + pi->def_policy.bv_len = 0; + rc = 0; + break; + case SLAP_CONFIG_ADD: + /* fallthrough to LDAP_MOD_ADD */ + case LDAP_MOD_ADD: + Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default add\n", 0, 0, 0); + if ( pi->def_policy.bv_val ) { + ber_memfree ( pi->def_policy.bv_val ); + } + pi->def_policy = c->value_ndn; + ber_memfree( c->value_dn.bv_val ); + BER_BVZERO( &c->value_dn ); + BER_BVZERO( &c->value_ndn ); + rc = 0; + break; + default: + abort (); + } + + return rc; +} + +static time_t +parse_time( char *atm ) +{ + struct lutil_tm tm; + struct lutil_timet tt; + time_t ret = (time_t)-1; + + if ( lutil_parsetime( atm, &tm ) == 0) { + lutil_tm2time( &tm, &tt ); + ret = tt.tt_sec; + } + return ret; +} + +static int +account_locked( Operation *op, Entry *e, + PassPolicy *pp, Modifications **mod ) +{ + Attribute *la; + + assert(mod != NULL); + + if ( !pp->pwdLockout ) + return 0; + + if ( (la = attr_find( e->e_attrs, ad_pwdAccountLockedTime )) != NULL ) { + BerVarray vals = la->a_nvals; + + /* + * there is a lockout stamp - we now need to know if it's + * a valid one. + */ + if (vals[0].bv_val != NULL) { + time_t then, now; + Modifications *m; + + if (!pp->pwdLockoutDuration) + return 1; + + if ((then = parse_time( vals[0].bv_val )) == (time_t)0) + return 1; + + now = slap_get_time(); + + if (now < then + pp->pwdLockoutDuration) + return 1; + + m = ch_calloc( sizeof(Modifications), 1 ); + m->sml_op = LDAP_MOD_DELETE; + m->sml_flags = 0; + m->sml_type = ad_pwdAccountLockedTime->ad_cname; + m->sml_desc = ad_pwdAccountLockedTime; + m->sml_next = *mod; + *mod = m; + } + } + + return 0; +} + +/* IMPLICIT TAGS, all context-specific */ +#define PPOLICY_WARNING 0xa0L /* constructed + 0 */ +#define PPOLICY_ERROR 0x81L /* primitive + 1 */ + +#define PPOLICY_EXPIRE 0x80L /* primitive + 0 */ +#define PPOLICY_GRACE 0x81L /* primitive + 1 */ + +static const char ppolicy_ctrl_oid[] = LDAP_CONTROL_PASSWORDPOLICYRESPONSE; +static const char ppolicy_pwd_expired_oid[] = LDAP_CONTROL_X_PASSWORD_EXPIRED; +static const char ppolicy_pwd_expiring_oid[] = LDAP_CONTROL_X_PASSWORD_EXPIRING; + +static LDAPControl * +create_passcontrol( Operation *op, int exptime, int grace, LDAPPasswordPolicyError err ) +{ + BerElementBuffer berbuf, bb2; + BerElement *ber = (BerElement *) &berbuf, *b2 = (BerElement *) &bb2; + LDAPControl c = { 0 }, *cp; + struct berval bv; + int rc; + + BER_BVZERO( &c.ldctl_value ); + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_printf( ber, "{" /*}*/ ); + + if ( exptime >= 0 ) { + ber_init2( b2, NULL, LBER_USE_DER ); + ber_printf( b2, "ti", PPOLICY_EXPIRE, exptime ); + rc = ber_flatten2( b2, &bv, 1 ); + (void)ber_free_buf(b2); + if (rc == -1) { + cp = NULL; + goto fail; + } + ber_printf( ber, "tO", PPOLICY_WARNING, &bv ); + ch_free( bv.bv_val ); + } else if ( grace > 0 ) { + ber_init2( b2, NULL, LBER_USE_DER ); + ber_printf( b2, "ti", PPOLICY_GRACE, grace ); + rc = ber_flatten2( b2, &bv, 1 ); + (void)ber_free_buf(b2); + if (rc == -1) { + cp = NULL; + goto fail; + } + ber_printf( ber, "tO", PPOLICY_WARNING, &bv ); + ch_free( bv.bv_val ); + } + + if (err != PP_noError ) { + ber_printf( ber, "te", PPOLICY_ERROR, err ); + } + ber_printf( ber, /*{*/ "N}" ); + + if (ber_flatten2( ber, &c.ldctl_value, 0 ) == -1) { + return NULL; + } + cp = op->o_tmpalloc( sizeof( LDAPControl ) + c.ldctl_value.bv_len, op->o_tmpmemctx ); + cp->ldctl_oid = (char *)ppolicy_ctrl_oid; + cp->ldctl_iscritical = 0; + cp->ldctl_value.bv_val = (char *)&cp[1]; + cp->ldctl_value.bv_len = c.ldctl_value.bv_len; + AC_MEMCPY( cp->ldctl_value.bv_val, c.ldctl_value.bv_val, c.ldctl_value.bv_len ); +fail: + (void)ber_free_buf(ber); + + return cp; +} + +static LDAPControl * +create_passexpiry( Operation *op, int expired, int warn ) +{ + LDAPControl *cp; + char buf[sizeof("-2147483648")]; + struct berval bv = { .bv_val = buf, .bv_len = sizeof(buf) }; + + bv.bv_len = snprintf( bv.bv_val, bv.bv_len, "%d", warn ); + + cp = op->o_tmpalloc( sizeof( LDAPControl ) + bv.bv_len, op->o_tmpmemctx ); + if ( expired ) { + cp->ldctl_oid = (char *)ppolicy_pwd_expired_oid; + } else { + cp->ldctl_oid = (char *)ppolicy_pwd_expiring_oid; + } + cp->ldctl_iscritical = 0; + cp->ldctl_value.bv_val = (char *)&cp[1]; + cp->ldctl_value.bv_len = bv.bv_len; + AC_MEMCPY( cp->ldctl_value.bv_val, bv.bv_val, bv.bv_len ); + return cp; +} + +static LDAPControl ** +add_passcontrol( Operation *op, SlapReply *rs, LDAPControl *ctrl ) +{ + LDAPControl **ctrls, **oldctrls = rs->sr_ctrls; + int n; + + n = 0; + if ( oldctrls ) { + for ( ; oldctrls[n]; n++ ) + ; + } + n += 2; + + ctrls = op->o_tmpcalloc( sizeof( LDAPControl * ), n, op->o_tmpmemctx ); + + n = 0; + if ( oldctrls ) { + for ( ; oldctrls[n]; n++ ) { + ctrls[n] = oldctrls[n]; + } + } + ctrls[n] = ctrl; + ctrls[n+1] = NULL; + + rs->sr_ctrls = ctrls; + + return oldctrls; +} + +static void +ppolicy_get_default( PassPolicy *pp ) +{ + memset( pp, 0, sizeof(PassPolicy) ); + + pp->ad = slap_schema.si_ad_userPassword; + + /* Users can change their own password by default */ + pp->pwdAllowUserChange = 1; + if ( !pp->pwdMaxRecordedFailure ) + pp->pwdMaxRecordedFailure = PPOLICY_DEFAULT_MAXRECORDED_FAILURE; +} + + +static void +ppolicy_get( Operation *op, Entry *e, PassPolicy *pp ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + pp_info *pi = on->on_bi.bi_private; + Attribute *a; + BerVarray vals; + int rc; + Entry *pe = NULL; +#if 0 + const char *text; +#endif + + ppolicy_get_default( pp ); + + if ((a = attr_find( e->e_attrs, ad_pwdPolicySubentry )) == NULL) { + /* + * entry has no password policy assigned - use default + */ + vals = &pi->def_policy; + if ( !vals->bv_val ) + goto defaultpol; + } else { + vals = a->a_nvals; + if (vals[0].bv_val == NULL) { + Debug( LDAP_DEBUG_ANY, + "ppolicy_get: NULL value for policySubEntry\n", 0, 0, 0 ); + goto defaultpol; + } + } + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, vals, NULL, NULL, 0, &pe ); + op->o_bd->bd_info = (BackendInfo *)on; + + if ( rc ) goto defaultpol; + +#if 0 /* Only worry about userPassword for now */ + if ((a = attr_find( pe->e_attrs, ad_pwdAttribute ))) + slap_bv2ad( &a->a_vals[0], &pp->ad, &text ); +#endif + + if ( ( a = attr_find( pe->e_attrs, ad_pwdMinAge ) ) + && lutil_atoi( &pp->pwdMinAge, a->a_vals[0].bv_val ) != 0 ) + goto defaultpol; + if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxAge ) ) + && lutil_atoi( &pp->pwdMaxAge, a->a_vals[0].bv_val ) != 0 ) + goto defaultpol; + if ( ( a = attr_find( pe->e_attrs, ad_pwdInHistory ) ) + && lutil_atoi( &pp->pwdInHistory, a->a_vals[0].bv_val ) != 0 ) + goto defaultpol; + if ( ( a = attr_find( pe->e_attrs, ad_pwdCheckQuality ) ) + && lutil_atoi( &pp->pwdCheckQuality, a->a_vals[0].bv_val ) != 0 ) + goto defaultpol; + if ( ( a = attr_find( pe->e_attrs, ad_pwdMinLength ) ) + && lutil_atoi( &pp->pwdMinLength, a->a_vals[0].bv_val ) != 0 ) + goto defaultpol; + if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxFailure ) ) + && lutil_atoi( &pp->pwdMaxFailure, a->a_vals[0].bv_val ) != 0 ) + goto defaultpol; + if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxRecordedFailure ) ) + && lutil_atoi( &pp->pwdMaxRecordedFailure, a->a_vals[0].bv_val ) != 0 ) + goto defaultpol; + if ( ( a = attr_find( pe->e_attrs, ad_pwdGraceAuthNLimit ) ) + && lutil_atoi( &pp->pwdGraceAuthNLimit, a->a_vals[0].bv_val ) != 0 ) + goto defaultpol; + if ( ( a = attr_find( pe->e_attrs, ad_pwdExpireWarning ) ) + && lutil_atoi( &pp->pwdExpireWarning, a->a_vals[0].bv_val ) != 0 ) + goto defaultpol; + if ( ( a = attr_find( pe->e_attrs, ad_pwdFailureCountInterval ) ) + && lutil_atoi( &pp->pwdFailureCountInterval, a->a_vals[0].bv_val ) != 0 ) + goto defaultpol; + if ( ( a = attr_find( pe->e_attrs, ad_pwdLockoutDuration ) ) + && lutil_atoi( &pp->pwdLockoutDuration, a->a_vals[0].bv_val ) != 0 ) + goto defaultpol; + + if ( ( a = attr_find( pe->e_attrs, ad_pwdCheckModule ) ) ) { + strncpy( pp->pwdCheckModule, a->a_vals[0].bv_val, + sizeof(pp->pwdCheckModule) ); + pp->pwdCheckModule[sizeof(pp->pwdCheckModule)-1] = '\0'; + } + + if ((a = attr_find( pe->e_attrs, ad_pwdLockout ))) + pp->pwdLockout = bvmatch( &a->a_nvals[0], &slap_true_bv ); + if ((a = attr_find( pe->e_attrs, ad_pwdMustChange ))) + pp->pwdMustChange = bvmatch( &a->a_nvals[0], &slap_true_bv ); + if ((a = attr_find( pe->e_attrs, ad_pwdAllowUserChange ))) + pp->pwdAllowUserChange = bvmatch( &a->a_nvals[0], &slap_true_bv ); + if ((a = attr_find( pe->e_attrs, ad_pwdSafeModify ))) + pp->pwdSafeModify = bvmatch( &a->a_nvals[0], &slap_true_bv ); + + if ( pp->pwdMaxRecordedFailure < pp->pwdMaxFailure ) + pp->pwdMaxRecordedFailure = pp->pwdMaxFailure; + if ( !pp->pwdMaxRecordedFailure ) + pp->pwdMaxRecordedFailure = PPOLICY_DEFAULT_MAXRECORDED_FAILURE; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + be_entry_release_r( op, pe ); + op->o_bd->bd_info = (BackendInfo *)on; + + return; + +defaultpol: + if ( pe ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + be_entry_release_r( op, pe ); + op->o_bd->bd_info = (BackendInfo *)on; + } + + Debug( LDAP_DEBUG_TRACE, + "ppolicy_get: using default policy\n", 0, 0, 0 ); + + ppolicy_get_default( pp ); + + return; +} + +static int +password_scheme( struct berval *cred, struct berval *sch ) +{ + int e; + + assert( cred != NULL ); + + if (sch) { + sch->bv_val = NULL; + sch->bv_len = 0; + } + + if ((cred->bv_len == 0) || (cred->bv_val == NULL) || + (cred->bv_val[0] != '{')) return LDAP_OTHER; + + for(e = 1; cred->bv_val[e] && cred->bv_val[e] != '}'; e++); + if (cred->bv_val[e]) { + int rc; + rc = lutil_passwd_scheme( cred->bv_val ); + if (rc) { + if (sch) { + sch->bv_val = cred->bv_val; + sch->bv_len = e; + } + return LDAP_SUCCESS; + } + } + return LDAP_OTHER; +} + +static int +check_password_quality( struct berval *cred, PassPolicy *pp, LDAPPasswordPolicyError *err, Entry *e, char **txt ) +{ + int rc = LDAP_SUCCESS, ok = LDAP_SUCCESS; + char *ptr; + struct berval sch; + + assert( cred != NULL ); + assert( pp != NULL ); + assert( txt != NULL ); + + ptr = cred->bv_val; + + *txt = NULL; + + if ((cred->bv_len == 0) || (pp->pwdMinLength > cred->bv_len)) { + rc = LDAP_CONSTRAINT_VIOLATION; + if ( err ) *err = PP_passwordTooShort; + return rc; + } + + /* + * We need to know if the password is already hashed - if so + * what scheme is it. The reason being that the "hash" of + * {cleartext} still allows us to check the password. + */ + rc = password_scheme( cred, &sch ); + if (rc == LDAP_SUCCESS) { + if ((sch.bv_val) && (strncasecmp( sch.bv_val, "{cleartext}", + sch.bv_len ) == 0)) { + /* + * We can check the cleartext "hash" + */ + ptr = cred->bv_val + sch.bv_len; + } else { + /* everything else, we can't check */ + if (pp->pwdCheckQuality == 2) { + rc = LDAP_CONSTRAINT_VIOLATION; + if (err) *err = PP_insufficientPasswordQuality; + return rc; + } + /* + * We can't check the syntax of the password, but it's not + * mandatory (according to the policy), so we return success. + */ + + return LDAP_SUCCESS; + } + } + + rc = LDAP_SUCCESS; + + if (pp->pwdCheckModule[0]) { +#ifdef SLAPD_MODULES + lt_dlhandle mod; + const char *err; + + if ((mod = lt_dlopen( pp->pwdCheckModule )) == NULL) { + err = lt_dlerror(); + + Debug(LDAP_DEBUG_ANY, + "check_password_quality: lt_dlopen failed: (%s) %s.\n", + pp->pwdCheckModule, err, 0 ); + ok = LDAP_OTHER; /* internal error */ + } else { + /* FIXME: the error message ought to be passed thru a + * struct berval, with preallocated buffer and size + * passed in. Module can still allocate a buffer for + * it if the provided one is too small. + */ + int (*prog)( char *passwd, char **text, Entry *ent ); + + if ((prog = lt_dlsym( mod, "check_password" )) == NULL) { + err = lt_dlerror(); + + Debug(LDAP_DEBUG_ANY, + "check_password_quality: lt_dlsym failed: (%s) %s.\n", + pp->pwdCheckModule, err, 0 ); + ok = LDAP_OTHER; + } else { + ldap_pvt_thread_mutex_lock( &chk_syntax_mutex ); + ok = prog( ptr, txt, e ); + ldap_pvt_thread_mutex_unlock( &chk_syntax_mutex ); + if (ok != LDAP_SUCCESS) { + Debug(LDAP_DEBUG_ANY, + "check_password_quality: module error: (%s) %s.[%d]\n", + pp->pwdCheckModule, *txt ? *txt : "", ok ); + } + } + + lt_dlclose( mod ); + } +#else + Debug(LDAP_DEBUG_ANY, "check_password_quality: external modules not " + "supported. pwdCheckModule ignored.\n", 0, 0, 0); +#endif /* SLAPD_MODULES */ + } + + + if (ok != LDAP_SUCCESS) { + rc = LDAP_CONSTRAINT_VIOLATION; + if (err) *err = PP_insufficientPasswordQuality; + } + + return rc; +} + +static int +parse_pwdhistory( struct berval *bv, char **oid, time_t *oldtime, struct berval *oldpw ) +{ + char *ptr; + struct berval nv, npw; + ber_len_t i, j; + + assert (bv && (bv->bv_len > 0) && (bv->bv_val) && oldtime && oldpw ); + + if ( oid ) { + *oid = 0; + } + *oldtime = (time_t)-1; + BER_BVZERO( oldpw ); + + ber_dupbv( &nv, bv ); + + /* first get the time field */ + for ( i = 0; (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ ) + ; + if ( i == nv.bv_len ) { + goto exit_failure; /* couldn't locate the '#' separator */ + } + nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */ + ptr = nv.bv_val; + *oldtime = parse_time( ptr ); + if (*oldtime == (time_t)-1) { + goto exit_failure; + } + + /* get the OID field */ + for (ptr = &(nv.bv_val[i]); (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ ) + ; + if ( i == nv.bv_len ) { + goto exit_failure; /* couldn't locate the '#' separator */ + } + nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */ + if ( oid ) { + *oid = ber_strdup( ptr ); + } + + /* get the length field */ + for ( ptr = &(nv.bv_val[i]); (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ ) + ; + if ( i == nv.bv_len ) { + goto exit_failure; /* couldn't locate the '#' separator */ + } + nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */ + oldpw->bv_len = strtol( ptr, NULL, 10 ); + if (errno == ERANGE) { + goto exit_failure; + } + + /* lastly, get the octets of the string */ + for ( j = i, ptr = &(nv.bv_val[i]); i < nv.bv_len; i++ ) + ; + if ( i - j != oldpw->bv_len) { + goto exit_failure; /* length is wrong */ + } + + npw.bv_val = ptr; + npw.bv_len = oldpw->bv_len; + ber_dupbv( oldpw, &npw ); + ber_memfree( nv.bv_val ); + + return LDAP_SUCCESS; + +exit_failure:; + if ( oid && *oid ) { + ber_memfree(*oid); + *oid = NULL; + } + if ( oldpw->bv_val ) { + ber_memfree( oldpw->bv_val); + BER_BVZERO( oldpw ); + } + ber_memfree( nv.bv_val ); + + return LDAP_OTHER; +} + +static void +add_to_pwd_history( pw_hist **l, time_t t, + struct berval *oldpw, struct berval *bv ) +{ + pw_hist *p, *p1, *p2; + + if (!l) return; + + p = ch_malloc( sizeof( pw_hist )); + p->pw = *oldpw; + ber_dupbv( &p->bv, bv ); + p->t = t; + p->next = NULL; + + if (*l == NULL) { + /* degenerate case */ + *l = p; + return; + } + /* + * advance p1 and p2 such that p1 is the node before the + * new one, and p2 is the node after it + */ + for (p1 = NULL, p2 = *l; p2 && p2->t <= t; p1 = p2, p2=p2->next ); + p->next = p2; + if (p1 == NULL) { *l = p; return; } + p1->next = p; +} + +#ifndef MAX_PWD_HISTORY_SZ +#define MAX_PWD_HISTORY_SZ 1024 +#endif /* MAX_PWD_HISTORY_SZ */ + +static void +make_pwd_history_value( char *timebuf, struct berval *bv, Attribute *pa ) +{ + char str[ MAX_PWD_HISTORY_SZ ]; + int nlen; + + snprintf( str, MAX_PWD_HISTORY_SZ, + "%s#%s#%lu#", timebuf, + pa->a_desc->ad_type->sat_syntax->ssyn_oid, + (unsigned long) pa->a_nvals[0].bv_len ); + str[MAX_PWD_HISTORY_SZ-1] = 0; + nlen = strlen(str); + + /* + * We have to assume that the string is a string of octets, + * not readable characters. In reality, yes, it probably is + * a readable (ie, base64) string, but we can't count on that + * Hence, while the first 3 fields of the password history + * are definitely readable (a timestamp, an OID and an integer + * length), the remaining octets of the actual password + * are deemed to be binary data. + */ + AC_MEMCPY( str + nlen, pa->a_nvals[0].bv_val, pa->a_nvals[0].bv_len ); + nlen += pa->a_nvals[0].bv_len; + bv->bv_val = ch_malloc( nlen + 1 ); + AC_MEMCPY( bv->bv_val, str, nlen ); + bv->bv_val[nlen] = '\0'; + bv->bv_len = nlen; +} + +static void +free_pwd_history_list( pw_hist **l ) +{ + pw_hist *p; + + if (!l) return; + p = *l; + while (p) { + pw_hist *pp = p->next; + + free(p->pw.bv_val); + free(p->bv.bv_val); + free(p); + p = pp; + } + *l = NULL; +} + +typedef struct ppbind { + slap_overinst *on; + int send_ctrl; + int set_restrict; + LDAPControl **oldctrls; + Modifications *mod; + LDAPPasswordPolicyError pErr; + PassPolicy pp; +} ppbind; + +static void +ctrls_cleanup( Operation *op, SlapReply *rs, LDAPControl **oldctrls ) +{ + int n; + + assert( rs->sr_ctrls != NULL ); + assert( rs->sr_ctrls[0] != NULL ); + + for ( n = 0; rs->sr_ctrls[n]; n++ ) { + if ( rs->sr_ctrls[n]->ldctl_oid == ppolicy_ctrl_oid || + rs->sr_ctrls[n]->ldctl_oid == ppolicy_pwd_expired_oid || + rs->sr_ctrls[n]->ldctl_oid == ppolicy_pwd_expiring_oid ) { + op->o_tmpfree( rs->sr_ctrls[n], op->o_tmpmemctx ); + rs->sr_ctrls[n] = (LDAPControl *)(-1); + break; + } + } + + if ( rs->sr_ctrls[n] == NULL ) { + /* missed? */ + } + + op->o_tmpfree( rs->sr_ctrls, op->o_tmpmemctx ); + + rs->sr_ctrls = oldctrls; +} + +static int +ppolicy_ctrls_cleanup( Operation *op, SlapReply *rs ) +{ + ppbind *ppb = op->o_callback->sc_private; + if ( ppb->send_ctrl ) { + ctrls_cleanup( op, rs, ppb->oldctrls ); + } + return SLAP_CB_CONTINUE; +} + +static int +ppolicy_bind_response( Operation *op, SlapReply *rs ) +{ + ppbind *ppb = op->o_callback->sc_private; + slap_overinst *on = ppb->on; + pp_info *pi = on->on_bi.bi_private; + Modifications *mod = ppb->mod, *m; + int pwExpired = 0; + int ngut = -1, warn = -1, age, rc; + Attribute *a; + time_t now, pwtime = (time_t)-1; + struct lutil_tm now_tm; + struct lutil_timet now_usec; + char nowstr[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + char nowstr_usec[ LDAP_LUTIL_GENTIME_BUFSIZE+8 ]; + struct berval timestamp, timestamp_usec; + BackendInfo *bi = op->o_bd->bd_info; + LDAPControl *ctrl = NULL; + Entry *e; + + ldap_pvt_thread_mutex_lock( &pi->pwdFailureTime_mutex ); + /* If we already know it's locked, just get on with it */ + if ( ppb->pErr != PP_noError ) { + goto locked; + } + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e ); + op->o_bd->bd_info = bi; + + if ( rc != LDAP_SUCCESS ) { + ldap_pvt_thread_mutex_unlock( &pi->pwdFailureTime_mutex ); + return SLAP_CB_CONTINUE; + } + + ldap_pvt_gettime(&now_tm); /* stored for later consideration */ + lutil_tm2time(&now_tm, &now_usec); + now = now_usec.tt_sec; + timestamp.bv_val = nowstr; + timestamp.bv_len = sizeof(nowstr); + slap_timestamp( &now, ×tamp ); + + /* Separate timestamp for pwdFailureTime with microsecond granularity */ + strcpy(nowstr_usec, nowstr); + timestamp_usec.bv_val = nowstr_usec; + timestamp_usec.bv_len = timestamp.bv_len; + snprintf( timestamp_usec.bv_val + timestamp_usec.bv_len-1, sizeof(".123456Z"), ".%06dZ", now_usec.tt_usec ); + timestamp_usec.bv_len += STRLENOF(".123456"); + + if ( rs->sr_err == LDAP_INVALID_CREDENTIALS ) { + int i = 0, fc = 0; + + m = ch_calloc( sizeof(Modifications), 1 ); + m->sml_op = LDAP_MOD_ADD; + m->sml_flags = 0; + m->sml_type = ad_pwdFailureTime->ad_cname; + m->sml_desc = ad_pwdFailureTime; + m->sml_numvals = 1; + m->sml_values = ch_calloc( sizeof(struct berval), 2 ); + m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 ); + + ber_dupbv( &m->sml_values[0], ×tamp_usec ); + ber_dupbv( &m->sml_nvalues[0], ×tamp_usec ); + m->sml_next = mod; + mod = m; + + /* + * Count the pwdFailureTimes - if it's + * greater than the policy pwdMaxFailure, + * then lock the account. + */ + if ((a = attr_find( e->e_attrs, ad_pwdFailureTime )) != NULL) { + for(i=0; a->a_nvals[i].bv_val; i++) { + + /* + * If the interval is 0, then failures + * stay on the record until explicitly + * reset by successful authentication. + */ + if (ppb->pp.pwdFailureCountInterval == 0) { + fc++; + } else if (now <= + parse_time(a->a_nvals[i].bv_val) + + ppb->pp.pwdFailureCountInterval) { + + fc++; + } + /* + * We only count those failures + * which are not due to expire. + */ + } + /* Do we have too many timestamps? If so, delete some values. + * We don't bother to sort the values here. OpenLDAP keeps the + * values in order by default. Fundamentally, relying on the + * information here is wrong anyway; monitoring systems should + * be tracking Bind failures in syslog, not here. + */ + if (a->a_numvals >= ppb->pp.pwdMaxRecordedFailure) { + int j = ppb->pp.pwdMaxRecordedFailure-1; + /* If more than 2x, cheaper to perform a Replace */ + if (a->a_numvals >= 2 * ppb->pp.pwdMaxRecordedFailure) { + struct berval v, nv; + + /* Change the mod we constructed above */ + m->sml_op = LDAP_MOD_REPLACE; + m->sml_numvals = ppb->pp.pwdMaxRecordedFailure; + v = m->sml_values[0]; + nv = m->sml_nvalues[0]; + ch_free(m->sml_values); + ch_free(m->sml_nvalues); + m->sml_values = ch_calloc( sizeof(struct berval), ppb->pp.pwdMaxRecordedFailure+1 ); + m->sml_nvalues = ch_calloc( sizeof(struct berval), ppb->pp.pwdMaxRecordedFailure+1 ); + for (i=0; i<j; i++) { + ber_dupbv(&m->sml_values[i], &a->a_vals[a->a_numvals-j+i]); + ber_dupbv(&m->sml_nvalues[i], &a->a_nvals[a->a_numvals-j+i]); + } + m->sml_values[i] = v; + m->sml_nvalues[i] = nv; + } else { + /* else just delete some */ + m = ch_calloc( sizeof(Modifications), 1 ); + m->sml_op = LDAP_MOD_DELETE; + m->sml_type = ad_pwdFailureTime->ad_cname; + m->sml_desc = ad_pwdFailureTime; + m->sml_numvals = a->a_numvals - j; + m->sml_values = ch_calloc( sizeof(struct berval), m->sml_numvals+1 ); + m->sml_nvalues = ch_calloc( sizeof(struct berval), m->sml_numvals+1 ); + for (i=0; i<m->sml_numvals; i++) { + ber_dupbv(&m->sml_values[i], &a->a_vals[i]); + ber_dupbv(&m->sml_nvalues[i], &a->a_nvals[i]); + } + m->sml_next = mod; + mod = m; + } + } + } + + if ((ppb->pp.pwdMaxFailure > 0) && + (fc >= ppb->pp.pwdMaxFailure - 1)) { + + /* + * We subtract 1 from the failure max + * because the new failure entry hasn't + * made it to the entry yet. + */ + m = ch_calloc( sizeof(Modifications), 1 ); + m->sml_op = LDAP_MOD_REPLACE; + m->sml_flags = 0; + m->sml_type = ad_pwdAccountLockedTime->ad_cname; + m->sml_desc = ad_pwdAccountLockedTime; + m->sml_numvals = 1; + m->sml_values = ch_calloc( sizeof(struct berval), 2 ); + m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 ); + ber_dupbv( &m->sml_values[0], ×tamp ); + ber_dupbv( &m->sml_nvalues[0], ×tamp ); + m->sml_next = mod; + mod = m; + } + } else if ( rs->sr_err == LDAP_SUCCESS ) { + if ((a = attr_find( e->e_attrs, ad_pwdChangedTime )) != NULL) + pwtime = parse_time( a->a_nvals[0].bv_val ); + + /* delete all pwdFailureTimes */ + if ( attr_find( e->e_attrs, ad_pwdFailureTime )) { + m = ch_calloc( sizeof(Modifications), 1 ); + m->sml_op = LDAP_MOD_DELETE; + m->sml_flags = 0; + m->sml_type = ad_pwdFailureTime->ad_cname; + m->sml_desc = ad_pwdFailureTime; + m->sml_next = mod; + mod = m; + } + + /* + * check to see if the password must be changed + */ + if ( ppb->pp.pwdMustChange && + (a = attr_find( e->e_attrs, ad_pwdReset )) && + bvmatch( &a->a_nvals[0], &slap_true_bv ) ) + { + /* + * need to inject client controls here to give + * more information. For the moment, we ensure + * that we are disallowed from doing anything + * other than change password. + */ + if ( ppb->set_restrict ) { + ber_dupbv( &pwcons[op->o_conn->c_conn_idx].dn, + &op->o_conn->c_ndn ); + } + + ppb->pErr = PP_changeAfterReset; + + } else { + /* + * the password does not need to be changed, so + * we now check whether the password has expired. + * + * We can skip this bit if passwords don't age in + * the policy. Also, if there was no pwdChangedTime + * attribute in the entry, the password never expires. + */ + if (ppb->pp.pwdMaxAge == 0) goto grace; + + if (pwtime != (time_t)-1) { + /* + * Check: was the last change time of + * the password older than the maximum age + * allowed. (Ignore case 2 from I-D, it's just silly.) + */ + if (now - pwtime > ppb->pp.pwdMaxAge ) pwExpired = 1; + } + } + +grace: + if (!pwExpired) goto check_expiring_password; + + if ((a = attr_find( e->e_attrs, ad_pwdGraceUseTime )) == NULL) + ngut = ppb->pp.pwdGraceAuthNLimit; + else { + for(ngut=0; a->a_nvals[ngut].bv_val; ngut++); + ngut = ppb->pp.pwdGraceAuthNLimit - ngut; + } + + /* + * ngut is the number of remaining grace logins + */ + Debug( LDAP_DEBUG_ANY, + "ppolicy_bind: Entry %s has an expired password: %d grace logins\n", + e->e_name.bv_val, ngut, 0); + + if (ngut < 1) { + ppb->pErr = PP_passwordExpired; + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + /* + * Add a grace user time to the entry + */ + m = ch_calloc( sizeof(Modifications), 1 ); + m->sml_op = LDAP_MOD_ADD; + m->sml_flags = 0; + m->sml_type = ad_pwdGraceUseTime->ad_cname; + m->sml_desc = ad_pwdGraceUseTime; + m->sml_numvals = 1; + m->sml_values = ch_calloc( sizeof(struct berval), 2 ); + m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 ); + ber_dupbv( &m->sml_values[0], ×tamp ); + ber_dupbv( &m->sml_nvalues[0], ×tamp ); + m->sml_next = mod; + mod = m; + +check_expiring_password: + /* + * Now we need to check to see + * if it is about to expire, and if so, should the user + * be warned about it in the password policy control. + * + * If the password has expired, and we're in the grace period, then + * we don't need to do this bit. Similarly, if we don't have password + * aging, then there's no need to do this bit either. + */ + if ((ppb->pp.pwdMaxAge < 1) || (pwExpired) || (ppb->pp.pwdExpireWarning < 1)) + goto done; + + age = (int)(now - pwtime); + + /* + * We know that there is a password Change Time attribute - if + * there wasn't, then the pwdExpired value would be true, unless + * there is no password aging - and if there is no password aging, + * then this section isn't called anyway - you can't have an + * expiring password if there's no limit to expire. + */ + if (ppb->pp.pwdMaxAge - age < ppb->pp.pwdExpireWarning ) { + /* + * Set the warning value. + */ + warn = ppb->pp.pwdMaxAge - age; /* seconds left until expiry */ + if (warn < 0) warn = 0; /* something weird here - why is pwExpired not set? */ + + Debug( LDAP_DEBUG_ANY, + "ppolicy_bind: Setting warning for password expiry for %s = %d seconds\n", + op->o_req_dn.bv_val, warn, 0 ); + } + } + +done: + op->o_bd->bd_info = (BackendInfo *)on->on_info; + be_entry_release_r( op, e ); + +locked: + if ( mod ) { + Operation op2 = *op; + SlapReply r2 = { REP_RESULT }; + slap_callback cb = { NULL, slap_null_cb, NULL, NULL }; + LDAPControl c, *ca[2]; + + op2.o_tag = LDAP_REQ_MODIFY; + op2.o_callback = &cb; + op2.orm_modlist = mod; + op2.orm_no_opattrs = 0; + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + + /* If this server is a shadow and forward_updates is true, + * use the frontend to perform this modify. That will trigger + * the update referral, which can then be forwarded by the + * chain overlay. Obviously the updateref and chain overlay + * must be configured appropriately for this to be useful. + */ + if ( SLAP_SHADOW( op->o_bd ) && pi->forward_updates ) { + op2.o_bd = frontendDB; + + /* Must use Relax control since these are no-user-mod */ + op2.o_relax = SLAP_CONTROL_CRITICAL; + op2.o_ctrls = ca; + ca[0] = &c; + ca[1] = NULL; + BER_BVZERO( &c.ldctl_value ); + c.ldctl_iscritical = 1; + c.ldctl_oid = LDAP_CONTROL_RELAX; + } else { + /* If not forwarding, don't update opattrs and don't replicate */ + if ( SLAP_SINGLE_SHADOW( op->o_bd )) { + op2.orm_no_opattrs = 1; + op2.o_dont_replicate = 1; + } + op2.o_bd->bd_info = (BackendInfo *)on->on_info; + } + rc = op2.o_bd->be_modify( &op2, &r2 ); + slap_mods_free( mod, 1 ); + } + + if ( ppb->send_ctrl ) { + + /* Do we really want to tell that the account is locked? */ + if ( ppb->pErr == PP_accountLocked && !pi->use_lockout ) { + ppb->pErr = PP_noError; + } + ctrl = create_passcontrol( op, warn, ngut, ppb->pErr ); + } else if ( pi->send_netscape_controls ) { + if ( ppb->pErr != PP_noError || pwExpired ) { + ctrl = create_passexpiry( op, 1, 0 ); + } else if ( warn > 0 ) { + ctrl = create_passexpiry( op, 0, warn ); + } + } + if ( ctrl ) { + ppb->oldctrls = add_passcontrol( op, rs, ctrl ); + op->o_callback->sc_cleanup = ppolicy_ctrls_cleanup; + } + op->o_bd->bd_info = bi; + ldap_pvt_thread_mutex_unlock( &pi->pwdFailureTime_mutex ); + return SLAP_CB_CONTINUE; +} + +static int +ppolicy_bind( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + + /* Reset lockout status on all Bind requests */ + if ( !BER_BVISEMPTY( &pwcons[op->o_conn->c_conn_idx].dn )) { + ch_free( pwcons[op->o_conn->c_conn_idx].dn.bv_val ); + BER_BVZERO( &pwcons[op->o_conn->c_conn_idx].dn ); + } + + /* Root bypasses policy */ + if ( !be_isroot_dn( op->o_bd, &op->o_req_ndn )) { + Entry *e; + int rc; + ppbind *ppb; + slap_callback *cb; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e ); + + if ( rc != LDAP_SUCCESS ) { + return SLAP_CB_CONTINUE; + } + + cb = op->o_tmpcalloc( sizeof(ppbind)+sizeof(slap_callback), + 1, op->o_tmpmemctx ); + ppb = (ppbind *)(cb+1); + ppb->on = on; + ppb->pErr = PP_noError; + ppb->set_restrict = 1; + + /* Setup a callback so we can munge the result */ + + cb->sc_response = ppolicy_bind_response; + cb->sc_private = ppb; + overlay_callback_after_backover( op, cb, 1 ); + + /* Did we receive a password policy request control? */ + if ( op->o_ctrlflag[ppolicy_cid] ) { + ppb->send_ctrl = 1; + } + + op->o_bd->bd_info = (BackendInfo *)on; + ppolicy_get( op, e, &ppb->pp ); + + rc = account_locked( op, e, &ppb->pp, &ppb->mod ); + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + be_entry_release_r( op, e ); + + if ( rc ) { + ppb->pErr = PP_accountLocked; + send_ldap_error( op, rs, LDAP_INVALID_CREDENTIALS, NULL ); + return rs->sr_err; + } + + } + + return SLAP_CB_CONTINUE; +} + +/* Reset the restricted info for the next session on this connection */ +static int +ppolicy_connection_destroy( BackendDB *bd, Connection *conn ) +{ + if ( pwcons && !BER_BVISEMPTY( &pwcons[conn->c_conn_idx].dn )) { + ch_free( pwcons[conn->c_conn_idx].dn.bv_val ); + BER_BVZERO( &pwcons[conn->c_conn_idx].dn ); + } + return SLAP_CB_CONTINUE; +} + +/* Check if this connection is restricted */ +static int +ppolicy_restrict( + Operation *op, + SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + int send_ctrl = 0; + + /* Did we receive a password policy request control? */ + if ( op->o_ctrlflag[ppolicy_cid] ) { + send_ctrl = 1; + } + + if ( op->o_conn && !BER_BVISEMPTY( &pwcons[op->o_conn->c_conn_idx].dn )) { + LDAPControl **oldctrls; + /* if the current authcDN doesn't match the one we recorded, + * then an intervening Bind has succeeded and the restriction + * no longer applies. (ITS#4516) + */ + if ( !dn_match( &op->o_conn->c_ndn, + &pwcons[op->o_conn->c_conn_idx].dn )) { + ch_free( pwcons[op->o_conn->c_conn_idx].dn.bv_val ); + BER_BVZERO( &pwcons[op->o_conn->c_conn_idx].dn ); + return SLAP_CB_CONTINUE; + } + + Debug( LDAP_DEBUG_TRACE, + "connection restricted to password changing only\n", 0, 0, 0); + if ( send_ctrl ) { + LDAPControl *ctrl = NULL; + ctrl = create_passcontrol( op, -1, -1, PP_changeAfterReset ); + oldctrls = add_passcontrol( op, rs, ctrl ); + } + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, + "Operations are restricted to bind/unbind/abandon/StartTLS/modify password" ); + if ( send_ctrl ) { + ctrls_cleanup( op, rs, oldctrls ); + } + return rs->sr_err; + } + + return SLAP_CB_CONTINUE; +} + +static int +ppolicy_compare_response( + Operation *op, + SlapReply *rs ) +{ + /* map compare responses to bind responses */ + if ( rs->sr_err == LDAP_COMPARE_TRUE ) + rs->sr_err = LDAP_SUCCESS; + else if ( rs->sr_err == LDAP_COMPARE_FALSE ) + rs->sr_err = LDAP_INVALID_CREDENTIALS; + + ppolicy_bind_response( op, rs ); + + /* map back to compare */ + if ( rs->sr_err == LDAP_SUCCESS ) + rs->sr_err = LDAP_COMPARE_TRUE; + else if ( rs->sr_err == LDAP_INVALID_CREDENTIALS ) + rs->sr_err = LDAP_COMPARE_FALSE; + + return SLAP_CB_CONTINUE; +} + +static int +ppolicy_compare( + Operation *op, + SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + + if ( ppolicy_restrict( op, rs ) != SLAP_CB_CONTINUE ) + return rs->sr_err; + + /* Did we receive a password policy request control? + * Are we testing the userPassword? + */ + if ( op->o_ctrlflag[ppolicy_cid] && + op->orc_ava->aa_desc == slap_schema.si_ad_userPassword ) { + Entry *e; + int rc; + ppbind *ppb; + slap_callback *cb; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e ); + + if ( rc != LDAP_SUCCESS ) { + return SLAP_CB_CONTINUE; + } + + cb = op->o_tmpcalloc( sizeof(ppbind)+sizeof(slap_callback), + 1, op->o_tmpmemctx ); + ppb = (ppbind *)(cb+1); + ppb->on = on; + ppb->pErr = PP_noError; + ppb->send_ctrl = 1; + /* failures here don't lockout the connection */ + ppb->set_restrict = 0; + + /* Setup a callback so we can munge the result */ + + cb->sc_response = ppolicy_compare_response; + cb->sc_private = ppb; + overlay_callback_after_backover( op, cb, 1 ); + + op->o_bd->bd_info = (BackendInfo *)on; + ppolicy_get( op, e, &ppb->pp ); + + rc = account_locked( op, e, &ppb->pp, &ppb->mod ); + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + be_entry_release_r( op, e ); + + if ( rc ) { + ppb->pErr = PP_accountLocked; + send_ldap_error( op, rs, LDAP_COMPARE_FALSE, NULL ); + return rs->sr_err; + } + } + return SLAP_CB_CONTINUE; +} + +static int +ppolicy_add( + Operation *op, + SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + pp_info *pi = on->on_bi.bi_private; + PassPolicy pp; + Attribute *pa; + const char *txt; + + if ( ppolicy_restrict( op, rs ) != SLAP_CB_CONTINUE ) + return rs->sr_err; + + /* If this is a replica, assume the provider checked everything */ + if ( SLAPD_SYNC_IS_SYNCCONN( op->o_connid ) ) + return SLAP_CB_CONTINUE; + + /* Check for password in entry */ + if ((pa = attr_find( op->oq_add.rs_e->e_attrs, + slap_schema.si_ad_userPassword ))) + { + assert( pa->a_vals != NULL ); + assert( !BER_BVISNULL( &pa->a_vals[ 0 ] ) ); + + if ( !BER_BVISNULL( &pa->a_vals[ 1 ] ) ) { + send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION, "Password policy only allows one password value" ); + return rs->sr_err; + } + + /* + * new entry contains a password - if we're not the root user + * then we need to check that the password fits in with the + * security policy for the new entry. + */ + ppolicy_get( op, op->ora_e, &pp ); + if (pp.pwdCheckQuality > 0 && !be_isroot( op )) { + struct berval *bv = &(pa->a_vals[0]); + int rc, send_ctrl = 0; + LDAPPasswordPolicyError pErr = PP_noError; + char *txt; + + /* Did we receive a password policy request control? */ + if ( op->o_ctrlflag[ppolicy_cid] ) { + send_ctrl = 1; + } + rc = check_password_quality( bv, &pp, &pErr, op->ora_e, &txt ); + if (rc != LDAP_SUCCESS) { + LDAPControl **oldctrls = NULL; + op->o_bd->bd_info = (BackendInfo *)on->on_info; + if ( send_ctrl ) { + LDAPControl *ctrl = NULL; + ctrl = create_passcontrol( op, -1, -1, pErr ); + oldctrls = add_passcontrol( op, rs, ctrl ); + } + send_ldap_error( op, rs, rc, txt ? txt : "Password fails quality checking policy" ); + if ( txt ) { + free( txt ); + } + if ( send_ctrl ) { + ctrls_cleanup( op, rs, oldctrls ); + } + return rs->sr_err; + } + } + /* + * A controversial bit. We hash cleartext + * passwords provided via add and modify operations + * You're not really supposed to do this, since + * the X.500 model says "store attributes" as they + * get provided. By default, this is what we do + * + * But if the hash_passwords flag is set, we hash + * any cleartext password attribute values via the + * default password hashing scheme. + */ + if ((pi->hash_passwords) && + (password_scheme( &(pa->a_vals[0]), NULL ) != LDAP_SUCCESS)) { + struct berval hpw; + + slap_passwd_hash( &(pa->a_vals[0]), &hpw, &txt ); + if (hpw.bv_val == NULL) { + /* + * hashing didn't work. Emit an error. + */ + rs->sr_err = LDAP_OTHER; + rs->sr_text = txt; + send_ldap_error( op, rs, LDAP_OTHER, "Password hashing failed" ); + return rs->sr_err; + } + + memset( pa->a_vals[0].bv_val, 0, pa->a_vals[0].bv_len); + ber_memfree( pa->a_vals[0].bv_val ); + pa->a_vals[0].bv_val = hpw.bv_val; + pa->a_vals[0].bv_len = hpw.bv_len; + } + + /* If password aging is in effect, set the pwdChangedTime */ + if ( pp.pwdMaxAge || pp.pwdMinAge ) { + struct berval timestamp; + char timebuf[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + time_t now = slap_get_time(); + + timestamp.bv_val = timebuf; + timestamp.bv_len = sizeof(timebuf); + slap_timestamp( &now, ×tamp ); + + attr_merge_one( op->ora_e, ad_pwdChangedTime, ×tamp, ×tamp ); + } + } + return SLAP_CB_CONTINUE; +} + +static int +ppolicy_mod_cb( Operation *op, SlapReply *rs ) +{ + slap_callback *sc = op->o_callback; + op->o_callback = sc->sc_next; + if ( rs->sr_err == LDAP_SUCCESS ) { + ch_free( pwcons[op->o_conn->c_conn_idx].dn.bv_val ); + BER_BVZERO( &pwcons[op->o_conn->c_conn_idx].dn ); + } + op->o_tmpfree( sc, op->o_tmpmemctx ); + return SLAP_CB_CONTINUE; +} + +static int +ppolicy_modify( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + pp_info *pi = on->on_bi.bi_private; + int i, rc, mod_pw_only, pwmod, pwmop = -1, deladd, + hsize = 0, hskip; + PassPolicy pp; + Modifications *mods = NULL, *modtail = NULL, + *ml, *delmod, *addmod; + Attribute *pa, *ha, at; + const char *txt; + pw_hist *tl = NULL, *p; + int zapReset, send_ctrl = 0, free_txt = 0; + Entry *e; + struct berval newpw = BER_BVNULL, oldpw = BER_BVNULL, + *bv, cr[2]; + LDAPPasswordPolicyError pErr = PP_noError; + LDAPControl *ctrl = NULL; + LDAPControl **oldctrls = NULL; + int is_pwdexop = 0; + int got_del_grace = 0, got_del_lock = 0, got_pw = 0, got_del_fail = 0; + int got_changed = 0, got_history = 0; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e ); + op->o_bd->bd_info = (BackendInfo *)on; + + if ( rc != LDAP_SUCCESS ) return SLAP_CB_CONTINUE; + + /* If this is a replica, we may need to tweak some of the + * provider's modifications. Otherwise, just pass it through. + */ + if ( SLAPD_SYNC_IS_SYNCCONN( op->o_connid ) ) { + Modifications **prev; + Attribute *a_grace, *a_lock, *a_fail; + + a_grace = attr_find( e->e_attrs, ad_pwdGraceUseTime ); + a_lock = attr_find( e->e_attrs, ad_pwdAccountLockedTime ); + a_fail = attr_find( e->e_attrs, ad_pwdFailureTime ); + + for( prev = &op->orm_modlist, ml = *prev; ml; ml = *prev ) { + + if ( ml->sml_desc == slap_schema.si_ad_userPassword ) + got_pw = 1; + + /* If we're deleting an attr that didn't exist, + * drop this delete op + */ + if ( ml->sml_op == LDAP_MOD_DELETE || + ml->sml_op == SLAP_MOD_SOFTDEL ) { + int drop = 0; + + if ( ml->sml_desc == ad_pwdGraceUseTime ) { + if ( !a_grace || got_del_grace ) { + drop = ml->sml_op == LDAP_MOD_DELETE; + } else { + got_del_grace = 1; + } + } else + if ( ml->sml_desc == ad_pwdAccountLockedTime ) { + if ( !a_lock || got_del_lock ) { + drop = ml->sml_op == LDAP_MOD_DELETE; + } else { + got_del_lock = 1; + } + } else + if ( ml->sml_desc == ad_pwdFailureTime ) { + if ( !a_fail || got_del_fail ) { + drop = ml->sml_op == LDAP_MOD_DELETE; + } else { + got_del_fail = 1; + } + } + if ( drop ) { + *prev = ml->sml_next; + ml->sml_next = NULL; + slap_mods_free( ml, 1 ); + continue; + } + } + prev = &ml->sml_next; + } + + /* If we're resetting the password, make sure grace, accountlock, + * and failure also get removed. + */ + if ( got_pw ) { + if ( a_grace && !got_del_grace ) { + ml = (Modifications *) ch_malloc( sizeof( Modifications ) ); + ml->sml_op = LDAP_MOD_DELETE; + ml->sml_flags = SLAP_MOD_INTERNAL; + ml->sml_type.bv_val = NULL; + ml->sml_desc = ad_pwdGraceUseTime; + ml->sml_numvals = 0; + ml->sml_values = NULL; + ml->sml_nvalues = NULL; + ml->sml_next = NULL; + *prev = ml; + prev = &ml->sml_next; + } + if ( a_lock && !got_del_lock ) { + ml = (Modifications *) ch_malloc( sizeof( Modifications ) ); + ml->sml_op = LDAP_MOD_DELETE; + ml->sml_flags = SLAP_MOD_INTERNAL; + ml->sml_type.bv_val = NULL; + ml->sml_desc = ad_pwdAccountLockedTime; + ml->sml_numvals = 0; + ml->sml_values = NULL; + ml->sml_nvalues = NULL; + ml->sml_next = NULL; + *prev = ml; + } + if ( a_fail && !got_del_fail ) { + ml = (Modifications *) ch_malloc( sizeof( Modifications ) ); + ml->sml_op = LDAP_MOD_DELETE; + ml->sml_flags = SLAP_MOD_INTERNAL; + ml->sml_type.bv_val = NULL; + ml->sml_desc = ad_pwdFailureTime; + ml->sml_numvals = 0; + ml->sml_values = NULL; + ml->sml_nvalues = NULL; + ml->sml_next = NULL; + *prev = ml; + } + } + op->o_bd->bd_info = (BackendInfo *)on->on_info; + be_entry_release_r( op, e ); + return SLAP_CB_CONTINUE; + } + + /* Did we receive a password policy request control? */ + if ( op->o_ctrlflag[ppolicy_cid] ) { + send_ctrl = 1; + } + + /* See if this is a pwdModify exop. If so, we can + * access the plaintext passwords from that request. + */ + { + slap_callback *sc; + + for ( sc = op->o_callback; sc; sc=sc->sc_next ) { + if ( sc->sc_response == slap_null_cb && + sc->sc_private ) { + req_pwdexop_s *qpw = sc->sc_private; + newpw = qpw->rs_new; + oldpw = qpw->rs_old; + is_pwdexop = 1; + break; + } + } + } + + ppolicy_get( op, e, &pp ); + + for ( ml = op->orm_modlist, + pwmod = 0, mod_pw_only = 1, + deladd = 0, delmod = NULL, + addmod = NULL, + zapReset = 1; + ml != NULL; modtail = ml, ml = ml->sml_next ) + { + if ( ml->sml_desc == pp.ad ) { + pwmod = 1; + pwmop = ml->sml_op; + if ((deladd == 0) && (ml->sml_op == LDAP_MOD_DELETE) && + (ml->sml_values) && !BER_BVISNULL( &ml->sml_values[0] )) + { + deladd = 1; + delmod = ml; + } + + if ((ml->sml_op == LDAP_MOD_ADD) || + (ml->sml_op == LDAP_MOD_REPLACE)) + { + if ( ml->sml_values && !BER_BVISNULL( &ml->sml_values[0] )) { + if ( deladd == 1 ) + deladd = 2; + + /* FIXME: there's no easy way to ensure + * that add does not cause multiple + * userPassword values; one way (that + * would be consistent with the single + * password constraint) would be to turn + * add into replace); another would be + * to disallow add. + * + * Let's check at least that a single value + * is being added + */ + if ( addmod || !BER_BVISNULL( &ml->sml_values[ 1 ] ) ) { + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + rs->sr_text = "Password policy only allows one password value"; + goto return_results; + } + + addmod = ml; + } else { + /* replace can have no values, add cannot */ + assert( ml->sml_op == LDAP_MOD_REPLACE ); + } + } + + } else if ( !(ml->sml_flags & SLAP_MOD_INTERNAL) && !is_at_operational( ml->sml_desc->ad_type ) ) { + mod_pw_only = 0; + /* modifying something other than password */ + } + + /* + * If there is a request to explicitly add a pwdReset + * attribute, then we suppress the normal behaviour on + * password change, which is to remove the pwdReset + * attribute. + * + * This enables an administrator to assign a new password + * and place a "must reset" flag on the entry, which will + * stay until the user explicitly changes his/her password. + */ + if (ml->sml_desc == ad_pwdReset ) { + if ((ml->sml_op == LDAP_MOD_ADD) || + (ml->sml_op == LDAP_MOD_REPLACE)) + zapReset = 0; + } + if ( ml->sml_op == LDAP_MOD_DELETE ) { + if ( ml->sml_desc == ad_pwdGraceUseTime ) { + got_del_grace = 1; + } else if ( ml->sml_desc == ad_pwdAccountLockedTime ) { + got_del_lock = 1; + } else if ( ml->sml_desc == ad_pwdFailureTime ) { + got_del_fail = 1; + } + } + if ( ml->sml_desc == ad_pwdChangedTime ) { + got_changed = 1; + } else if (ml->sml_desc == ad_pwdHistory ) { + got_history = 1; + } + } + + if (!BER_BVISEMPTY( &pwcons[op->o_conn->c_conn_idx].dn ) && !mod_pw_only ) { + if ( dn_match( &op->o_conn->c_ndn, + &pwcons[op->o_conn->c_conn_idx].dn )) { + Debug( LDAP_DEBUG_TRACE, + "connection restricted to password changing only\n", 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "Operations are restricted to bind/unbind/abandon/StartTLS/modify password"; + pErr = PP_changeAfterReset; + goto return_results; + } else { + ch_free( pwcons[op->o_conn->c_conn_idx].dn.bv_val ); + BER_BVZERO( &pwcons[op->o_conn->c_conn_idx].dn ); + } + } + + /* + * if we have a "safe password modify policy", then we need to check if we're doing + * a delete (with the old password), followed by an add (with the new password). + * + * If we got just a delete with nothing else, just let it go. We also skip all the checks if + * the root user is bound. Root can do anything, including avoid the policies. + */ + + if (!pwmod) goto do_modify; + + /* + * Build the password history list in ascending time order + * We need this, even if the user is root, in order to maintain + * the pwdHistory operational attributes properly. + */ + if (addmod && pp.pwdInHistory > 0 && (ha = attr_find( e->e_attrs, ad_pwdHistory ))) { + struct berval oldpw; + time_t oldtime; + + for(i=0; ha->a_nvals[i].bv_val; i++) { + rc = parse_pwdhistory( &(ha->a_nvals[i]), NULL, + &oldtime, &oldpw ); + + if (rc != LDAP_SUCCESS) continue; /* invalid history entry */ + + if (oldpw.bv_val) { + add_to_pwd_history( &tl, oldtime, &oldpw, + &(ha->a_nvals[i]) ); + oldpw.bv_val = NULL; + oldpw.bv_len = 0; + } + } + for(p=tl; p; p=p->next, hsize++); /* count history size */ + } + + if (be_isroot( op )) goto do_modify; + + /* NOTE: according to draft-behera-ldap-password-policy + * pwdAllowUserChange == FALSE must only prevent pwd changes + * by the user the pwd belongs to (ITS#7021) */ + if (!pp.pwdAllowUserChange && dn_match(&op->o_req_ndn, &op->o_ndn)) { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "User alteration of password is not allowed"; + pErr = PP_passwordModNotAllowed; + goto return_results; + } + + /* Just deleting? */ + if (!addmod) { + /* skip everything else */ + pwmod = 0; + goto do_modify; + } + + /* This is a pwdModify exop that provided the old pw. + * We need to create a Delete mod for this old pw and + * let the matching value get found later + */ + if (pp.pwdSafeModify && oldpw.bv_val ) { + ml = (Modifications *)ch_calloc( sizeof( Modifications ), 1 ); + ml->sml_op = LDAP_MOD_DELETE; + ml->sml_flags = SLAP_MOD_INTERNAL; + ml->sml_desc = pp.ad; + ml->sml_type = pp.ad->ad_cname; + ml->sml_numvals = 1; + ml->sml_values = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) ); + ber_dupbv( &ml->sml_values[0], &oldpw ); + BER_BVZERO( &ml->sml_values[1] ); + ml->sml_next = op->orm_modlist; + op->orm_modlist = ml; + delmod = ml; + deladd = 2; + } + + if (pp.pwdSafeModify && deladd != 2) { + Debug( LDAP_DEBUG_TRACE, + "change password must use DELETE followed by ADD/REPLACE\n", + 0, 0, 0 ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "Must supply old password to be changed as well as new one"; + pErr = PP_mustSupplyOldPassword; + goto return_results; + } + + /* Check age, but only if pwdReset is not TRUE */ + pa = attr_find( e->e_attrs, ad_pwdReset ); + if ((!pa || !bvmatch( &pa->a_nvals[0], &slap_true_bv )) && + pp.pwdMinAge > 0) { + time_t pwtime = (time_t)-1, now; + int age; + + if ((pa = attr_find( e->e_attrs, ad_pwdChangedTime )) != NULL) + pwtime = parse_time( pa->a_nvals[0].bv_val ); + now = slap_get_time(); + age = (int)(now - pwtime); + if ((pwtime != (time_t)-1) && (age < pp.pwdMinAge)) { + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + rs->sr_text = "Password is too young to change"; + pErr = PP_passwordTooYoung; + goto return_results; + } + } + + /* pa is used in password history check below, be sure it's set */ + if ((pa = attr_find( e->e_attrs, pp.ad )) != NULL && delmod) { + /* + * we have a password to check + */ + bv = oldpw.bv_val ? &oldpw : delmod->sml_values; + /* FIXME: no access checking? */ + rc = slap_passwd_check( op, NULL, pa, bv, &txt ); + if (rc != LDAP_SUCCESS) { + Debug( LDAP_DEBUG_TRACE, + "old password check failed: %s\n", txt, 0, 0 ); + + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "Must supply correct old password to change to new one"; + pErr = PP_mustSupplyOldPassword; + goto return_results; + + } else { + int i; + + /* + * replace the delete value with the (possibly hashed) + * value which is currently in the password. + */ + for ( i = 0; !BER_BVISNULL( &delmod->sml_values[i] ); i++ ) { + free( delmod->sml_values[i].bv_val ); + BER_BVZERO( &delmod->sml_values[i] ); + } + free( delmod->sml_values ); + delmod->sml_values = ch_calloc( sizeof(struct berval), 2 ); + BER_BVZERO( &delmod->sml_values[1] ); + ber_dupbv( &(delmod->sml_values[0]), &(pa->a_nvals[0]) ); + } + } + + bv = newpw.bv_val ? &newpw : &addmod->sml_values[0]; + if (pp.pwdCheckQuality > 0) { + + rc = check_password_quality( bv, &pp, &pErr, e, (char **)&txt ); + if (rc != LDAP_SUCCESS) { + rs->sr_err = rc; + if ( txt ) { + rs->sr_text = txt; + free_txt = 1; + } else { + rs->sr_text = "Password fails quality checking policy"; + } + goto return_results; + } + } + + /* If pwdInHistory is zero, passwords may be reused */ + if (pa && pp.pwdInHistory > 0) { + /* + * Last check - the password history. + */ + /* FIXME: no access checking? */ + if (slap_passwd_check( op, NULL, pa, bv, &txt ) == LDAP_SUCCESS) { + /* + * This is bad - it means that the user is attempting + * to set the password to the same as the old one. + */ + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + rs->sr_text = "Password is not being changed from existing value"; + pErr = PP_passwordInHistory; + goto return_results; + } + + /* We need this when reduce pwdInHistory */ + hskip = hsize - pp.pwdInHistory; + + /* + * Iterate through the password history, and fail on any + * password matches. + */ + at = *pa; + at.a_vals = cr; + cr[1].bv_val = NULL; + for(p=tl; p; p=p->next) { + if(hskip > 0){ + hskip--; + continue; + } + cr[0] = p->pw; + /* FIXME: no access checking? */ + rc = slap_passwd_check( op, NULL, &at, bv, &txt ); + + if (rc != LDAP_SUCCESS) continue; + + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + rs->sr_text = "Password is in history of old passwords"; + pErr = PP_passwordInHistory; + goto return_results; + } + } + +do_modify: + if (pwmod) { + struct berval timestamp; + char timebuf[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + time_t now = slap_get_time(); + + /* If the conn is restricted, set a callback to clear it + * if the pwmod succeeds + */ + if (!BER_BVISEMPTY( &pwcons[op->o_conn->c_conn_idx].dn )) { + slap_callback *sc = op->o_tmpcalloc( 1, sizeof( slap_callback ), + op->o_tmpmemctx ); + sc->sc_next = op->o_callback; + /* Must use sc_response to insure we reset on success, before + * the client sees the response. Must use sc_cleanup to insure + * that it gets cleaned up if sc_response is not called. + */ + sc->sc_response = ppolicy_mod_cb; + sc->sc_cleanup = ppolicy_mod_cb; + op->o_callback = sc; + } + + /* + * keep the necessary pwd.. operational attributes + * up to date. + */ + + if (!got_changed) { + timestamp.bv_val = timebuf; + timestamp.bv_len = sizeof(timebuf); + slap_timestamp( &now, ×tamp ); + + mods = NULL; + if (pwmop != LDAP_MOD_DELETE) { + mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 ); + mods->sml_op = LDAP_MOD_REPLACE; + mods->sml_numvals = 1; + mods->sml_values = (BerVarray) ch_calloc( sizeof( struct berval ), 2 ); + mods->sml_nvalues = (BerVarray) ch_calloc( sizeof( struct berval ), 2 ); + + ber_dupbv( &mods->sml_values[0], ×tamp ); + ber_dupbv( &mods->sml_nvalues[0], ×tamp ); + } else if (attr_find(e->e_attrs, ad_pwdChangedTime )) { + mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 ); + mods->sml_op = LDAP_MOD_DELETE; + } + if (mods) { + mods->sml_desc = ad_pwdChangedTime; + mods->sml_flags = SLAP_MOD_INTERNAL; + mods->sml_next = NULL; + modtail->sml_next = mods; + modtail = mods; + } + } + + if (!got_del_grace && attr_find(e->e_attrs, ad_pwdGraceUseTime )) { + mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 ); + mods->sml_op = LDAP_MOD_DELETE; + mods->sml_desc = ad_pwdGraceUseTime; + mods->sml_flags = SLAP_MOD_INTERNAL; + mods->sml_next = NULL; + modtail->sml_next = mods; + modtail = mods; + } + + if (!got_del_lock && attr_find(e->e_attrs, ad_pwdAccountLockedTime )) { + mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 ); + mods->sml_op = LDAP_MOD_DELETE; + mods->sml_desc = ad_pwdAccountLockedTime; + mods->sml_flags = SLAP_MOD_INTERNAL; + mods->sml_next = NULL; + modtail->sml_next = mods; + modtail = mods; + } + + if (!got_del_fail && attr_find(e->e_attrs, ad_pwdFailureTime )) { + mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 ); + mods->sml_op = LDAP_MOD_DELETE; + mods->sml_desc = ad_pwdFailureTime; + mods->sml_flags = SLAP_MOD_INTERNAL; + mods->sml_next = NULL; + modtail->sml_next = mods; + modtail = mods; + } + + /* Delete the pwdReset attribute, since it's being reset */ + if ((zapReset) && (attr_find(e->e_attrs, ad_pwdReset ))) { + mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 ); + mods->sml_op = LDAP_MOD_DELETE; + mods->sml_desc = ad_pwdReset; + mods->sml_flags = SLAP_MOD_INTERNAL; + mods->sml_next = NULL; + modtail->sml_next = mods; + modtail = mods; + } + + /* Delete all pwdInHistory attribute */ + if (!got_history && pp.pwdInHistory == 0 && + attr_find(e->e_attrs, ad_pwdHistory )){ + mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 ); + mods->sml_op = LDAP_MOD_DELETE; + mods->sml_flags = SLAP_MOD_INTERNAL; + mods->sml_desc = ad_pwdHistory; + mods->sml_next = NULL; + modtail->sml_next = mods; + modtail = mods; + } + + if (!got_history && pp.pwdInHistory > 0){ + if (hsize >= pp.pwdInHistory) { + /* + * We use the >= operator, since we are going to add + * the existing password attribute value into the + * history - thus the cardinality of history values is + * about to rise by one. + * + * If this would push it over the limit of history + * values (remembering - the password policy could have + * changed since the password was last altered), we must + * delete at least 1 value from the pwdHistory list. + * + * In fact, we delete '(#pwdHistory attrs - max pwd + * history length) + 1' values, starting with the oldest. + * This is easily evaluated, since the linked list is + * created in ascending time order. + */ + mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 ); + mods->sml_op = LDAP_MOD_DELETE; + mods->sml_flags = SLAP_MOD_INTERNAL; + mods->sml_desc = ad_pwdHistory; + mods->sml_numvals = hsize - pp.pwdInHistory + 1; + mods->sml_values = ch_calloc( sizeof( struct berval ), + hsize - pp.pwdInHistory + 2 ); + BER_BVZERO( &mods->sml_values[ hsize - pp.pwdInHistory + 1 ] ); + for(i=0,p=tl; i < (hsize - pp.pwdInHistory + 1); i++, p=p->next) { + BER_BVZERO( &mods->sml_values[i] ); + ber_dupbv( &(mods->sml_values[i]), &p->bv ); + } + mods->sml_next = NULL; + modtail->sml_next = mods; + modtail = mods; + } + free_pwd_history_list( &tl ); + + /* + * Now add the existing password into the history list. + * This will be executed even if the operation is to delete + * the password entirely. + * + * This isn't in the spec explicitly, but it seems to make + * sense that the password history list is the list of all + * previous passwords - even if they were deleted. Thus, if + * someone tries to add a historical password at some future + * point, it will fail. + */ + if ((pa = attr_find( e->e_attrs, pp.ad )) != NULL) { + mods = (Modifications *) ch_malloc( sizeof( Modifications ) ); + mods->sml_op = LDAP_MOD_ADD; + mods->sml_flags = SLAP_MOD_INTERNAL; + mods->sml_type.bv_val = NULL; + mods->sml_desc = ad_pwdHistory; + mods->sml_nvalues = NULL; + mods->sml_numvals = 1; + mods->sml_values = ch_calloc( sizeof( struct berval ), 2 ); + mods->sml_values[ 1 ].bv_val = NULL; + mods->sml_values[ 1 ].bv_len = 0; + make_pwd_history_value( timebuf, &mods->sml_values[0], pa ); + mods->sml_next = NULL; + modtail->sml_next = mods; + modtail = mods; + + } else { + Debug( LDAP_DEBUG_TRACE, + "ppolicy_modify: password attr lookup failed\n", 0, 0, 0 ); + } + } + + /* + * Controversial bit here. If the new password isn't hashed + * (ie, is cleartext), we probably should hash it according + * to the default hash. The reason for this is that we want + * to use the policy if possible, but if we hash the password + * before, then we're going to run into trouble when it + * comes time to check the password. + * + * Now, the right thing to do is to use the extended password + * modify operation, but not all software can do this, + * therefore it makes sense to hash the new password, now + * we know it passes the policy requirements. + * + * Of course, if the password is already hashed, then we + * leave it alone. + */ + + if ((pi->hash_passwords) && (addmod) && !newpw.bv_val && + (password_scheme( &(addmod->sml_values[0]), NULL ) != LDAP_SUCCESS)) + { + struct berval hpw, bv; + + slap_passwd_hash( &(addmod->sml_values[0]), &hpw, &txt ); + if (hpw.bv_val == NULL) { + /* + * hashing didn't work. Emit an error. + */ + rs->sr_err = LDAP_OTHER; + rs->sr_text = txt; + goto return_results; + } + bv = addmod->sml_values[0]; + /* clear and discard the clear password */ + memset(bv.bv_val, 0, bv.bv_len); + ber_memfree(bv.bv_val); + addmod->sml_values[0] = hpw; + } + } + op->o_bd->bd_info = (BackendInfo *)on->on_info; + be_entry_release_r( op, e ); + return SLAP_CB_CONTINUE; + +return_results: + free_pwd_history_list( &tl ); + op->o_bd->bd_info = (BackendInfo *)on->on_info; + be_entry_release_r( op, e ); + if ( send_ctrl ) { + ctrl = create_passcontrol( op, -1, -1, pErr ); + oldctrls = add_passcontrol( op, rs, ctrl ); + } + send_ldap_result( op, rs ); + if ( free_txt ) { + free( (char *)txt ); + rs->sr_text = NULL; + } + if ( send_ctrl ) { + if ( is_pwdexop ) { + if ( rs->sr_flags & REP_CTRLS_MUSTBEFREED ) { + op->o_tmpfree( oldctrls, op->o_tmpmemctx ); + } + oldctrls = NULL; + rs->sr_flags |= REP_CTRLS_MUSTBEFREED; + + } else { + ctrls_cleanup( op, rs, oldctrls ); + } + } + return rs->sr_err; +} + +static int +ppolicy_parseCtrl( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + if ( !BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = "passwordPolicyRequest control value not absent"; + return LDAP_PROTOCOL_ERROR; + } + op->o_ctrlflag[ppolicy_cid] = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + return LDAP_SUCCESS; +} + +static int +attrPretty( + Syntax *syntax, + struct berval *val, + struct berval *out, + void *ctx ) +{ + AttributeDescription *ad = NULL; + const char *err; + int code; + + code = slap_bv2ad( val, &ad, &err ); + if ( !code ) { + ber_dupbv_x( out, &ad->ad_type->sat_cname, ctx ); + } + return code; +} + +static int +attrNormalize( + slap_mask_t use, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *out, + void *ctx ) +{ + AttributeDescription *ad = NULL; + const char *err; + int code; + + code = slap_bv2ad( val, &ad, &err ); + if ( !code ) { + ber_str2bv_x( ad->ad_type->sat_oid, 0, 1, out, ctx ); + } + return code; +} + +static int +ppolicy_db_init( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + pp_info *pi; + + if ( SLAP_ISGLOBALOVERLAY( be ) ) { + /* do not allow slapo-ppolicy to be global by now (ITS#5858) */ + if ( cr ){ + snprintf( cr->msg, sizeof(cr->msg), + "slapo-ppolicy cannot be global" ); + Debug( LDAP_DEBUG_ANY, "%s\n", cr->msg, 0, 0 ); + } + return 1; + } + + /* Has User Schema been initialized yet? */ + if ( !pwd_UsSchema[0].ad[0] ) { + const char *err; + int i, code; + + for (i=0; pwd_UsSchema[i].def; i++) { + code = slap_str2ad( pwd_UsSchema[i].def, pwd_UsSchema[i].ad, &err ); + if ( code ) { + if ( cr ){ + snprintf( cr->msg, sizeof(cr->msg), + "User Schema load failed for attribute \"%s\". Error code %d: %s", + pwd_UsSchema[i].def, code, err ); + Debug( LDAP_DEBUG_ANY, "%s\n", cr->msg, 0, 0 ); + } + return code; + } + } + { + Syntax *syn; + MatchingRule *mr; + + syn = ch_malloc( sizeof( Syntax )); + *syn = *ad_pwdAttribute->ad_type->sat_syntax; + syn->ssyn_pretty = attrPretty; + ad_pwdAttribute->ad_type->sat_syntax = syn; + + mr = ch_malloc( sizeof( MatchingRule )); + *mr = *ad_pwdAttribute->ad_type->sat_equality; + mr->smr_normalize = attrNormalize; + ad_pwdAttribute->ad_type->sat_equality = mr; + } + } + + pi = on->on_bi.bi_private = ch_calloc( sizeof(pp_info), 1 ); + + if ( !pwcons ) { + /* accommodate for c_conn_idx == -1 */ + pwcons = ch_calloc( sizeof(pw_conn), dtblsize + 1 ); + pwcons++; + } + + ov_count++; + + ldap_pvt_thread_mutex_init( &pi->pwdFailureTime_mutex ); + + return 0; +} + +static int +ppolicy_db_open( + BackendDB *be, + ConfigReply *cr +) +{ + return overlay_register_control( be, LDAP_CONTROL_PASSWORDPOLICYREQUEST ); +} + +static int +ppolicy_db_close( + BackendDB *be, + ConfigReply *cr +) +{ +#ifdef SLAP_CONFIG_DELETE + overlay_unregister_control( be, LDAP_CONTROL_PASSWORDPOLICYREQUEST ); +#endif /* SLAP_CONFIG_DELETE */ + + return 0; +} + +static int +ppolicy_db_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + pp_info *pi = on->on_bi.bi_private; + + on->on_bi.bi_private = NULL; + ldap_pvt_thread_mutex_destroy( &pi->pwdFailureTime_mutex ); + free( pi->def_policy.bv_val ); + free( pi ); + + ov_count--; + if ( ov_count <=0 && pwcons ) { + pw_conn *pwc = pwcons; + pwcons = NULL; + pwc--; + ch_free( pwc ); + } + return 0; +} + +static char *extops[] = { + LDAP_EXOP_MODIFY_PASSWD, + NULL +}; + +static slap_overinst ppolicy; + +int ppolicy_initialize() +{ + int i, code; + + for (i=0; pwd_OpSchema[i].def; i++) { + code = register_at( pwd_OpSchema[i].def, pwd_OpSchema[i].ad, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "ppolicy_initialize: register_at failed\n", 0, 0, 0 ); + return code; + } + /* Allow Manager to set these as needed */ + if ( is_at_no_user_mod( (*pwd_OpSchema[i].ad)->ad_type )) { + (*pwd_OpSchema[i].ad)->ad_type->sat_flags |= + SLAP_AT_MANAGEABLE; + } + } + + code = register_supported_control( LDAP_CONTROL_PASSWORDPOLICYREQUEST, + SLAP_CTRL_ADD|SLAP_CTRL_BIND|SLAP_CTRL_MODIFY, extops, + ppolicy_parseCtrl, &ppolicy_cid ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "Failed to register control %d\n", code, 0, 0 ); + return code; + } + + /* We don't expect to receive these controls, only send them */ + code = register_supported_control( LDAP_CONTROL_X_PASSWORD_EXPIRED, + 0, NULL, NULL, NULL ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "Failed to register control %d\n", code, 0, 0 ); + return code; + } + + code = register_supported_control( LDAP_CONTROL_X_PASSWORD_EXPIRING, + 0, NULL, NULL, NULL ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "Failed to register control %d\n", code, 0, 0 ); + return code; + } + + ldap_pvt_thread_mutex_init( &chk_syntax_mutex ); + + ppolicy.on_bi.bi_type = "ppolicy"; + ppolicy.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + ppolicy.on_bi.bi_db_init = ppolicy_db_init; + ppolicy.on_bi.bi_db_open = ppolicy_db_open; + ppolicy.on_bi.bi_db_close = ppolicy_db_close; + ppolicy.on_bi.bi_db_destroy = ppolicy_db_destroy; + + ppolicy.on_bi.bi_op_add = ppolicy_add; + ppolicy.on_bi.bi_op_bind = ppolicy_bind; + ppolicy.on_bi.bi_op_compare = ppolicy_compare; + ppolicy.on_bi.bi_op_delete = ppolicy_restrict; + ppolicy.on_bi.bi_op_modify = ppolicy_modify; + ppolicy.on_bi.bi_op_search = ppolicy_restrict; + ppolicy.on_bi.bi_connection_destroy = ppolicy_connection_destroy; + + ppolicy.on_bi.bi_cf_ocs = ppolicyocs; + code = config_register_schema( ppolicycfg, ppolicyocs ); + if ( code ) return code; + + return overlay_register( &ppolicy ); +} + +#if SLAPD_OVER_PPOLICY == SLAPD_MOD_DYNAMIC +int init_module(int argc, char *argv[]) { + return ppolicy_initialize(); +} +#endif + +#endif /* defined(SLAPD_OVER_PPOLICY) */ diff --git a/servers/slapd/overlays/refint.c b/servers/slapd/overlays/refint.c new file mode 100644 index 0000000..d6cbe95 --- /dev/null +++ b/servers/slapd/overlays/refint.c @@ -0,0 +1,1087 @@ +/* refint.c - referential integrity module */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2021 The OpenLDAP Foundation. + * Portions Copyright 2004 Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Symas Corp. for inclusion in + * OpenLDAP Software. This work was sponsored by Hewlett-Packard. + */ + +#include "portable.h" + +/* This module maintains referential integrity for a set of + * DN-valued attributes by searching for all references to a given + * DN whenever the DN is changed or its entry is deleted, and making + * the appropriate update. + * + * Updates are performed using the database rootdn in a separate task + * to allow the original operation to complete immediately. + */ + +#ifdef SLAPD_OVER_REFINT + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "config.h" +#include "ldap_rq.h" + +static slap_overinst refint; + +/* The DN to use in the ModifiersName for all refint updates */ +static BerValue refint_dn = BER_BVC("cn=Referential Integrity Overlay"); +static BerValue refint_ndn = BER_BVC("cn=referential integrity overlay"); + +typedef struct refint_attrs_s { + struct refint_attrs_s *next; + AttributeDescription *attr; + BerVarray old_vals; + BerVarray old_nvals; + BerVarray new_vals; + BerVarray new_nvals; + int ra_numvals; + int dont_empty; +} refint_attrs; + +typedef struct dependents_s { + struct dependents_s *next; + BerValue dn; /* target dn */ + BerValue ndn; + refint_attrs *attrs; +} dependent_data; + +typedef struct refint_q { + struct refint_q *next; + struct refint_data_s *rdata; + dependent_data *attrs; /* entries and attrs returned from callback */ + BackendDB *db; + BerValue olddn; + BerValue oldndn; + BerValue newdn; + BerValue newndn; + int do_sub; +} refint_q; + +typedef struct refint_data_s { + struct refint_attrs_s *attrs; /* list of known attrs */ + BerValue dn; /* basedn in parent, */ + BerValue nothing; /* the nothing value, if needed */ + BerValue nnothing; /* normalized nothingness */ + BerValue refint_dn; /* modifier's name */ + BerValue refint_ndn; /* normalized modifier's name */ + struct re_s *qtask; + refint_q *qhead; + refint_q *qtail; + BackendDB *db; + ldap_pvt_thread_mutex_t qmutex; +} refint_data; + +typedef struct refint_pre_s { + slap_overinst *on; + int do_sub; +} refint_pre; + +#define RUNQ_INTERVAL 36000 /* a long time */ + +static MatchingRule *mr_dnSubtreeMatch; + +enum { + REFINT_ATTRS = 1, + REFINT_NOTHING, + REFINT_MODIFIERSNAME +}; + +static ConfigDriver refint_cf_gen; + +static ConfigTable refintcfg[] = { + { "refint_attributes", "attribute...", 2, 0, 0, + ARG_MAGIC|REFINT_ATTRS, refint_cf_gen, + "( OLcfgOvAt:11.1 NAME 'olcRefintAttribute' " + "DESC 'Attributes for referential integrity' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "refint_nothing", "string", 2, 2, 0, + ARG_DN|ARG_MAGIC|REFINT_NOTHING, refint_cf_gen, + "( OLcfgOvAt:11.2 NAME 'olcRefintNothing' " + "DESC 'Replacement DN to supply when needed' " + "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL }, + { "refint_modifiersName", "DN", 2, 2, 0, + ARG_DN|ARG_MAGIC|REFINT_MODIFIERSNAME, refint_cf_gen, + "( OLcfgOvAt:11.3 NAME 'olcRefintModifiersName' " + "DESC 'The DN to use as modifiersName' " + "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs refintocs[] = { + { "( OLcfgOvOc:11.1 " + "NAME 'olcRefintConfig' " + "DESC 'Referential integrity configuration' " + "SUP olcOverlayConfig " + "MAY ( olcRefintAttribute " + "$ olcRefintNothing " + "$ olcRefintModifiersName " + ") )", + Cft_Overlay, refintcfg }, + { NULL, 0, NULL } +}; + +static int +refint_cf_gen(ConfigArgs *c) +{ + slap_overinst *on = (slap_overinst *)c->bi; + refint_data *dd = (refint_data *)on->on_bi.bi_private; + refint_attrs *ip, *pip, **pipp = NULL; + AttributeDescription *ad; + const char *text; + int rc = ARG_BAD_CONF; + int i; + + switch ( c->op ) { + case SLAP_CONFIG_EMIT: + switch ( c->type ) { + case REFINT_ATTRS: + ip = dd->attrs; + while ( ip ) { + value_add_one( &c->rvalue_vals, + &ip->attr->ad_cname ); + ip = ip->next; + } + rc = 0; + break; + case REFINT_NOTHING: + if ( !BER_BVISEMPTY( &dd->nothing )) { + rc = value_add_one( &c->rvalue_vals, + &dd->nothing ); + if ( rc ) return rc; + rc = value_add_one( &c->rvalue_nvals, + &dd->nnothing ); + return rc; + } + rc = 0; + break; + case REFINT_MODIFIERSNAME: + if ( !BER_BVISEMPTY( &dd->refint_dn )) { + rc = value_add_one( &c->rvalue_vals, + &dd->refint_dn ); + if ( rc ) return rc; + rc = value_add_one( &c->rvalue_nvals, + &dd->refint_ndn ); + return rc; + } + rc = 0; + break; + default: + abort (); + } + break; + case LDAP_MOD_DELETE: + switch ( c->type ) { + case REFINT_ATTRS: + pipp = &dd->attrs; + if ( c->valx < 0 ) { + ip = *pipp; + *pipp = NULL; + while ( ip ) { + pip = ip; + ip = ip->next; + ch_free ( pip ); + } + } else { + /* delete from linked list */ + for ( i=0; i < c->valx; ++i ) { + pipp = &(*pipp)->next; + } + ip = *pipp; + *pipp = (*pipp)->next; + + /* AttributeDescriptions are global so + * shouldn't be freed here... */ + ch_free ( ip ); + } + rc = 0; + break; + case REFINT_NOTHING: + ch_free( dd->nothing.bv_val ); + ch_free( dd->nnothing.bv_val ); + BER_BVZERO( &dd->nothing ); + BER_BVZERO( &dd->nnothing ); + rc = 0; + break; + case REFINT_MODIFIERSNAME: + ch_free( dd->refint_dn.bv_val ); + ch_free( dd->refint_ndn.bv_val ); + BER_BVZERO( &dd->refint_dn ); + BER_BVZERO( &dd->refint_ndn ); + rc = 0; + break; + default: + abort (); + } + break; + case SLAP_CONFIG_ADD: + /* fallthrough to LDAP_MOD_ADD */ + case LDAP_MOD_ADD: + switch ( c->type ) { + case REFINT_ATTRS: + rc = 0; + for ( i=1; i < c->argc; ++i ) { + ad = NULL; + if ( slap_str2ad ( c->argv[i], &ad, &text ) + == LDAP_SUCCESS) { + ip = ch_malloc ( + sizeof ( refint_attrs ) ); + ip->attr = ad; + ip->next = dd->attrs; + dd->attrs = ip; + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s <%s>: %s", c->argv[0], c->argv[i], text ); + Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg, 0 ); + rc = ARG_BAD_CONF; + } + } + break; + case REFINT_NOTHING: + if ( !BER_BVISNULL( &c->value_ndn )) { + ch_free ( dd->nothing.bv_val ); + ch_free ( dd->nnothing.bv_val ); + dd->nothing = c->value_dn; + dd->nnothing = c->value_ndn; + rc = 0; + } else { + rc = ARG_BAD_CONF; + } + break; + case REFINT_MODIFIERSNAME: + if ( !BER_BVISNULL( &c->value_ndn )) { + ch_free( dd->refint_dn.bv_val ); + ch_free( dd->refint_ndn.bv_val ); + dd->refint_dn = c->value_dn; + dd->refint_ndn = c->value_ndn; + rc = 0; + } else { + rc = ARG_BAD_CONF; + } + break; + default: + abort (); + } + break; + default: + abort (); + } + + return rc; +} + +/* +** allocate new refint_data; +** store in on_bi.bi_private; +** +*/ + +static int +refint_db_init( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + refint_data *id = ch_calloc(1,sizeof(refint_data)); + + on->on_bi.bi_private = id; + ldap_pvt_thread_mutex_init( &id->qmutex ); + return(0); +} + +static int +refint_db_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + + if ( on->on_bi.bi_private ) { + refint_data *id = on->on_bi.bi_private; + refint_attrs *ii, *ij; + + on->on_bi.bi_private = NULL; + ldap_pvt_thread_mutex_destroy( &id->qmutex ); + + for(ii = id->attrs; ii; ii = ij) { + ij = ii->next; + ch_free(ii); + } + + ch_free( id->nothing.bv_val ); + BER_BVZERO( &id->nothing ); + ch_free( id->nnothing.bv_val ); + BER_BVZERO( &id->nnothing ); + + ch_free( id ); + } + return(0); +} + +/* +** initialize, copy basedn if not already set +** +*/ + +static int +refint_open( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + refint_data *id = on->on_bi.bi_private; + + if ( BER_BVISNULL( &id->dn )) { + if ( BER_BVISNULL( &be->be_nsuffix[0] )) + return -1; + ber_dupbv( &id->dn, &be->be_nsuffix[0] ); + } + if ( BER_BVISNULL( &id->refint_dn ) ) { + ber_dupbv( &id->refint_dn, &refint_dn ); + ber_dupbv( &id->refint_ndn, &refint_ndn ); + } + + /* + ** find the backend that matches our configured basedn; + ** make sure it exists and has search and modify methods; + ** + */ + + if ( on->on_info->oi_origdb != frontendDB ) { + BackendDB *db = select_backend(&id->dn, 1); + + if ( db ) { + BackendInfo *bi; + if ( db == be ) + bi = on->on_info->oi_orig; + else + bi = db->bd_info; + if ( !bi->bi_op_search || !bi->bi_op_modify ) { + Debug( LDAP_DEBUG_CONFIG, + "refint_response: backend missing search and/or modify\n", + 0, 0, 0 ); + return -1; + } + id->db = db; + } else { + Debug( LDAP_DEBUG_CONFIG, + "refint_response: no backend for our baseDN %s??\n", + id->dn.bv_val, 0, 0 ); + return -1; + } + } + return(0); +} + + +/* +** free our basedn; +** free our refintdn +** +*/ + +static int +refint_close( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + refint_data *id = on->on_bi.bi_private; + + ch_free( id->dn.bv_val ); + BER_BVZERO( &id->dn ); + ch_free( id->refint_dn.bv_val ); + BER_BVZERO( &id->refint_dn ); + ch_free( id->refint_ndn.bv_val ); + BER_BVZERO( &id->refint_ndn ); + + return(0); +} + +/* +** search callback +** generates a list of Attributes from search results +*/ + +static int +refint_search_cb( + Operation *op, + SlapReply *rs +) +{ + Attribute *a; + BerVarray b = NULL; + refint_q *rq = op->o_callback->sc_private; + refint_data *dd = rq->rdata; + refint_attrs *ia, *da = dd->attrs, *na; + dependent_data *ip; + int i; + + Debug(LDAP_DEBUG_TRACE, "refint_search_cb <%s>\n", + rs->sr_entry ? rs->sr_entry->e_name.bv_val : "NOTHING", 0, 0); + + if (rs->sr_type != REP_SEARCH || !rs->sr_entry) return(0); + + /* + ** foreach configured attribute type: + ** if this attr exists in the search result, + ** and it has a value matching the target: + ** allocate an attr; + ** save/build DNs of any subordinate matches; + ** handle special case: found exact + subordinate match; + ** handle olcRefintNothing; + ** + */ + + ip = op->o_tmpalloc(sizeof(dependent_data), op->o_tmpmemctx ); + ber_dupbv_x( &ip->dn, &rs->sr_entry->e_name, op->o_tmpmemctx ); + ber_dupbv_x( &ip->ndn, &rs->sr_entry->e_nname, op->o_tmpmemctx ); + ip->next = rq->attrs; + rq->attrs = ip; + ip->attrs = NULL; + for(ia = da; ia; ia = ia->next) { + if ( (a = attr_find(rs->sr_entry->e_attrs, ia->attr) ) ) { + int exact = -1, is_exact; + + na = NULL; + + /* Are we doing subtree matching or simple equality? */ + if ( rq->do_sub ) { + for(i = 0, b = a->a_nvals; b[i].bv_val; i++) { + if(dnIsSuffix(&b[i], &rq->oldndn)) { + is_exact = b[i].bv_len == rq->oldndn.bv_len; + + /* Paranoia: skip buggy duplicate exact match, + * it would break ra_numvals + */ + if ( is_exact && exact >= 0 ) + continue; + + /* first match? create structure */ + if ( na == NULL ) { + na = op->o_tmpcalloc( 1, + sizeof( refint_attrs ), + op->o_tmpmemctx ); + na->next = ip->attrs; + ip->attrs = na; + na->attr = ia->attr; + } + + na->ra_numvals++; + + if ( is_exact ) { + /* Exact match: refint_repair will deduce the DNs */ + exact = i; + + } else { + /* Subordinate match */ + struct berval newsub, newdn, olddn, oldndn; + + /* Save old DN */ + ber_dupbv_x( &olddn, &a->a_vals[i], op->o_tmpmemctx ); + ber_bvarray_add_x( &na->old_vals, &olddn, op->o_tmpmemctx ); + + ber_dupbv_x( &oldndn, &a->a_nvals[i], op->o_tmpmemctx ); + ber_bvarray_add_x( &na->old_nvals, &oldndn, op->o_tmpmemctx ); + + if ( BER_BVISEMPTY( &rq->newdn ) ) + continue; + + /* Rename subordinate match: Build new DN */ + newsub = a->a_vals[i]; + newsub.bv_len -= rq->olddn.bv_len + 1; + build_new_dn( &newdn, &rq->newdn, &newsub, op->o_tmpmemctx ); + ber_bvarray_add_x( &na->new_vals, &newdn, op->o_tmpmemctx ); + + newsub = a->a_nvals[i]; + newsub.bv_len -= rq->oldndn.bv_len + 1; + build_new_dn( &newdn, &rq->newndn, &newsub, op->o_tmpmemctx ); + ber_bvarray_add_x( &na->new_nvals, &newdn, op->o_tmpmemctx ); + } + } + } + + /* If we got both subordinate and exact match, + * refint_repair won't special-case the exact match */ + if ( exact >= 0 && na->old_vals ) { + struct berval dn; + + ber_dupbv_x( &dn, &a->a_vals[exact], op->o_tmpmemctx ); + ber_bvarray_add_x( &na->old_vals, &dn, op->o_tmpmemctx ); + ber_dupbv_x( &dn, &a->a_nvals[exact], op->o_tmpmemctx ); + ber_bvarray_add_x( &na->old_nvals, &dn, op->o_tmpmemctx ); + + if ( !BER_BVISEMPTY( &rq->newdn ) ) { + ber_dupbv_x( &dn, &rq->newdn, op->o_tmpmemctx ); + ber_bvarray_add_x( &na->new_vals, &dn, op->o_tmpmemctx ); + ber_dupbv_x( &dn, &rq->newndn, op->o_tmpmemctx ); + ber_bvarray_add_x( &na->new_nvals, &dn, op->o_tmpmemctx ); + } + } + } else { + /* entry has no children, just equality matching */ + is_exact = attr_valfind( a, + SLAP_MR_EQUALITY|SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH| + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH, &rq->oldndn, &i, NULL ); + if ( is_exact == LDAP_SUCCESS ) { + na = op->o_tmpcalloc( 1, + sizeof( refint_attrs ), + op->o_tmpmemctx ); + na->next = ip->attrs; + ip->attrs = na; + na->attr = ia->attr; + na->ra_numvals = 1; + } + } + + /* Deleting/replacing all values and a nothing DN is configured? */ + if ( na && na->ra_numvals == a->a_numvals && !BER_BVISNULL(&dd->nothing) ) + na->dont_empty = 1; + + Debug( LDAP_DEBUG_TRACE, "refint_search_cb: %s: %s (#%d)\n", + a->a_desc->ad_cname.bv_val, rq->olddn.bv_val, i ); + } + } + + return(0); +} + +static int +refint_repair( + Operation *op, + refint_data *id, + refint_q *rq ) +{ + dependent_data *dp; + SlapReply rs = {REP_RESULT}; + Operation op2; + unsigned long opid; + int rc; + int cache; + + op->o_callback->sc_response = refint_search_cb; + op->o_req_dn = op->o_bd->be_suffix[ 0 ]; + op->o_req_ndn = op->o_bd->be_nsuffix[ 0 ]; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + cache = op->o_do_not_cache; + op->o_do_not_cache = 1; + + /* search */ + rc = op->o_bd->be_search( op, &rs ); + op->o_do_not_cache = cache; + + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "refint_repair: search failed: %d\n", + rc, 0, 0 ); + return rc; + } + + /* safety? paranoid just in case */ + if ( op->o_callback->sc_private == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "refint_repair: callback wiped out sc_private?!\n", + 0, 0, 0 ); + return 0; + } + + /* Set up the Modify requests */ + op->o_callback->sc_response = &slap_null_cb; + + /* + * [our search callback builds a list of attrs] + * foreach attr: + * make sure its dn has a backend; + * build Modification* chain; + * call the backend modify function; + * + */ + + opid = op->o_opid; + op2 = *op; + for ( dp = rq->attrs; dp; dp = dp->next ) { + SlapReply rs2 = {REP_RESULT}; + refint_attrs *ra; + Modifications *m; + + if ( dp->attrs == NULL ) continue; /* TODO: Is this needed? */ + + op2.o_bd = select_backend( &dp->ndn, 1 ); + if ( !op2.o_bd ) { + Debug( LDAP_DEBUG_TRACE, + "refint_repair: no backend for DN %s!\n", + dp->dn.bv_val, 0, 0 ); + continue; + } + op2.o_tag = LDAP_REQ_MODIFY; + op2.orm_modlist = NULL; + op2.o_req_dn = dp->dn; + op2.o_req_ndn = dp->ndn; + /* Internal ops, never replicate these */ + op2.orm_no_opattrs = 1; + op2.o_dont_replicate = 1; + op2.o_opid = 0; + + /* Set our ModifiersName */ + if ( SLAP_LASTMOD( op->o_bd ) ) { + m = op2.o_tmpalloc( sizeof(Modifications) + + 4*sizeof(BerValue), op2.o_tmpmemctx ); + m->sml_next = op2.orm_modlist; + op2.orm_modlist = m; + m->sml_op = LDAP_MOD_REPLACE; + m->sml_flags = SLAP_MOD_INTERNAL; + m->sml_desc = slap_schema.si_ad_modifiersName; + m->sml_type = m->sml_desc->ad_cname; + m->sml_numvals = 1; + m->sml_values = (BerVarray)(m+1); + m->sml_nvalues = m->sml_values+2; + BER_BVZERO( &m->sml_values[1] ); + BER_BVZERO( &m->sml_nvalues[1] ); + m->sml_values[0] = id->refint_dn; + m->sml_nvalues[0] = id->refint_ndn; + } + + for ( ra = dp->attrs; ra; ra = ra->next ) { + size_t len; + + /* Add values */ + if ( ra->dont_empty || !BER_BVISEMPTY( &rq->newdn ) ) { + len = sizeof(Modifications); + + if ( ra->new_vals == NULL ) { + len += 4*sizeof(BerValue); + } + + m = op2.o_tmpalloc( len, op2.o_tmpmemctx ); + m->sml_next = op2.orm_modlist; + op2.orm_modlist = m; + m->sml_op = LDAP_MOD_ADD; + m->sml_flags = 0; + m->sml_desc = ra->attr; + m->sml_type = ra->attr->ad_cname; + if ( ra->new_vals == NULL ) { + m->sml_values = (BerVarray)(m+1); + m->sml_nvalues = m->sml_values+2; + BER_BVZERO( &m->sml_values[1] ); + BER_BVZERO( &m->sml_nvalues[1] ); + m->sml_numvals = 1; + if ( BER_BVISEMPTY( &rq->newdn ) ) { + m->sml_values[0] = id->nothing; + m->sml_nvalues[0] = id->nnothing; + } else { + m->sml_values[0] = rq->newdn; + m->sml_nvalues[0] = rq->newndn; + } + } else { + m->sml_values = ra->new_vals; + m->sml_nvalues = ra->new_nvals; + m->sml_numvals = ra->ra_numvals; + } + } + + /* Delete values */ + len = sizeof(Modifications); + if ( ra->old_vals == NULL ) { + len += 4*sizeof(BerValue); + } + m = op2.o_tmpalloc( len, op2.o_tmpmemctx ); + m->sml_next = op2.orm_modlist; + op2.orm_modlist = m; + m->sml_op = LDAP_MOD_DELETE; + m->sml_flags = 0; + m->sml_desc = ra->attr; + m->sml_type = ra->attr->ad_cname; + if ( ra->old_vals == NULL ) { + m->sml_numvals = 1; + m->sml_values = (BerVarray)(m+1); + m->sml_nvalues = m->sml_values+2; + m->sml_values[0] = rq->olddn; + m->sml_nvalues[0] = rq->oldndn; + BER_BVZERO( &m->sml_values[1] ); + BER_BVZERO( &m->sml_nvalues[1] ); + } else { + m->sml_values = ra->old_vals; + m->sml_nvalues = ra->old_nvals; + m->sml_numvals = ra->ra_numvals; + } + } + + op2.o_dn = op2.o_bd->be_rootdn; + op2.o_ndn = op2.o_bd->be_rootndn; + rc = op2.o_bd->be_modify( &op2, &rs2 ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "refint_repair: dependent modify failed: %d\n", + rs2.sr_err, 0, 0 ); + } + + while ( ( m = op2.orm_modlist ) ) { + op2.orm_modlist = m->sml_next; + op2.o_tmpfree( m, op2.o_tmpmemctx ); + } + } + op2.o_opid = opid; + + return 0; +} + +static void * +refint_qtask( void *ctx, void *arg ) +{ + struct re_s *rtask = arg; + refint_data *id = rtask->arg; + Connection conn = {0}; + OperationBuffer opbuf; + Operation *op; + slap_callback cb = { NULL, NULL, NULL, NULL }; + Filter ftop, *fptr; + refint_q *rq; + refint_attrs *ip; + int pausing = 0, rc = 0; + + connection_fake_init( &conn, &opbuf, ctx ); + op = &opbuf.ob_op; + + /* + ** build a search filter for all configured attributes; + ** populate our Operation; + ** pass our data (attr list, dn) to backend via sc_private; + ** call the backend search function; + ** nb: (|(one=thing)) is valid, but do smart formatting anyway; + ** nb: 16 is arbitrarily a dozen or so extra bytes; + ** + */ + + ftop.f_choice = LDAP_FILTER_OR; + ftop.f_next = NULL; + ftop.f_or = NULL; + op->ors_filter = &ftop; + for(ip = id->attrs; ip; ip = ip->next) { + /* this filter can be either EQUALITY or EXT */ + fptr = op->o_tmpcalloc( sizeof(Filter) + sizeof(MatchingRuleAssertion), + 1, op->o_tmpmemctx ); + fptr->f_mra = (MatchingRuleAssertion *)(fptr+1); + fptr->f_mr_rule = mr_dnSubtreeMatch; + fptr->f_mr_rule_text = mr_dnSubtreeMatch->smr_bvoid; + fptr->f_mr_desc = ip->attr; + fptr->f_mr_dnattrs = 0; + fptr->f_next = ftop.f_or; + ftop.f_or = fptr; + } + + for (;;) { + dependent_data *dp, *dp_next; + refint_attrs *ra, *ra_next; + + if ( ldap_pvt_thread_pool_pausing( &connection_pool ) > 0 ) { + pausing = 1; + break; + } + + /* Dequeue an op */ + ldap_pvt_thread_mutex_lock( &id->qmutex ); + rq = id->qhead; + if ( rq ) { + id->qhead = rq->next; + if ( !id->qhead ) + id->qtail = NULL; + } + ldap_pvt_thread_mutex_unlock( &id->qmutex ); + if ( !rq ) + break; + + for (fptr = ftop.f_or; fptr; fptr = fptr->f_next ) { + fptr->f_mr_value = rq->oldndn; + /* Use (attr:dnSubtreeMatch:=value) to catch subtree rename + * and subtree delete where supported */ + if (rq->do_sub) + fptr->f_choice = LDAP_FILTER_EXT; + else + fptr->f_choice = LDAP_FILTER_EQUALITY; + } + + filter2bv_x( op, op->ors_filter, &op->ors_filterstr ); + + /* callback gets the searched dn instead */ + cb.sc_private = rq; + cb.sc_response = refint_search_cb; + op->o_callback = &cb; + op->o_tag = LDAP_REQ_SEARCH; + op->ors_scope = LDAP_SCOPE_SUBTREE; + op->ors_deref = LDAP_DEREF_NEVER; + op->ors_limit = NULL; + op->ors_slimit = SLAP_NO_LIMIT; + op->ors_tlimit = SLAP_NO_LIMIT; + + /* no attrs! */ + op->ors_attrs = slap_anlist_no_attrs; + + slap_op_time( &op->o_time, &op->o_tincr ); + + if ( rq->db != NULL ) { + op->o_bd = rq->db; + rc = refint_repair( op, id, rq ); + + } else { + BackendDB *be; + + LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) { + /* we may want to skip cn=config */ + if ( be == LDAP_STAILQ_FIRST(&backendDB) ) { + continue; + } + + if ( be->be_search && be->be_modify ) { + op->o_bd = be; + rc = refint_repair( op, id, rq ); + } + } + } + + for ( dp = rq->attrs; dp; dp = dp_next ) { + dp_next = dp->next; + for ( ra = dp->attrs; ra; ra = ra_next ) { + ra_next = ra->next; + ber_bvarray_free_x( ra->new_nvals, op->o_tmpmemctx ); + ber_bvarray_free_x( ra->new_vals, op->o_tmpmemctx ); + ber_bvarray_free_x( ra->old_nvals, op->o_tmpmemctx ); + ber_bvarray_free_x( ra->old_vals, op->o_tmpmemctx ); + op->o_tmpfree( ra, op->o_tmpmemctx ); + } + op->o_tmpfree( dp->ndn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( dp->dn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( dp, op->o_tmpmemctx ); + } + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + if ( rc == LDAP_BUSY ) { + pausing = 1; + /* re-queue this op */ + ldap_pvt_thread_mutex_lock( &id->qmutex ); + rq->next = id->qhead; + id->qhead = rq; + if ( !id->qtail ) + id->qtail = rq; + ldap_pvt_thread_mutex_unlock( &id->qmutex ); + break; + } + + if ( !BER_BVISNULL( &rq->newndn )) { + ch_free( rq->newndn.bv_val ); + ch_free( rq->newdn.bv_val ); + } + ch_free( rq->oldndn.bv_val ); + ch_free( rq->olddn.bv_val ); + ch_free( rq ); + } + + /* free filter */ + for ( fptr = ftop.f_or; fptr; ) { + Filter *f_next = fptr->f_next; + op->o_tmpfree( fptr, op->o_tmpmemctx ); + fptr = f_next; + } + + /* wait until we get explicitly scheduled again */ + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + ldap_pvt_runqueue_stoptask( &slapd_rq, id->qtask ); + if ( pausing ) { + /* try to run again as soon as the pause is done */ + id->qtask->interval.tv_sec = 0; + ldap_pvt_runqueue_resched( &slapd_rq, id->qtask, 0 ); + id->qtask->interval.tv_sec = RUNQ_INTERVAL; + } else { + ldap_pvt_runqueue_resched( &slapd_rq,id->qtask, 1 ); + } + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + return NULL; +} + +/* +** refint_response +** search for matching records and modify them +*/ + +static int +refint_response( + Operation *op, + SlapReply *rs +) +{ + refint_pre *rp; + slap_overinst *on; + refint_data *id; + BerValue pdn; + refint_q *rq; + refint_attrs *ip; + int ac; + + /* If the main op failed or is not a Delete or ModRdn, ignore it */ + if (( op->o_tag != LDAP_REQ_DELETE && op->o_tag != LDAP_REQ_MODRDN ) || + rs->sr_err != LDAP_SUCCESS ) + return SLAP_CB_CONTINUE; + + rp = op->o_callback->sc_private; + on = rp->on; + id = on->on_bi.bi_private; + + rq = ch_calloc( 1, sizeof( refint_q )); + ber_dupbv( &rq->olddn, &op->o_req_dn ); + ber_dupbv( &rq->oldndn, &op->o_req_ndn ); + rq->db = id->db; + rq->rdata = id; + rq->do_sub = rp->do_sub; + + if ( op->o_tag == LDAP_REQ_MODRDN ) { + if ( op->oq_modrdn.rs_newSup ) { + pdn = *op->oq_modrdn.rs_newSup; + } else { + dnParent( &op->o_req_dn, &pdn ); + } + build_new_dn( &rq->newdn, &pdn, &op->orr_newrdn, NULL ); + if ( op->oq_modrdn.rs_nnewSup ) { + pdn = *op->oq_modrdn.rs_nnewSup; + } else { + dnParent( &op->o_req_ndn, &pdn ); + } + build_new_dn( &rq->newndn, &pdn, &op->orr_nnewrdn, NULL ); + } + + ldap_pvt_thread_mutex_lock( &id->qmutex ); + if ( id->qtail ) { + id->qtail->next = rq; + } else { + id->qhead = rq; + } + id->qtail = rq; + ldap_pvt_thread_mutex_unlock( &id->qmutex ); + + ac = 0; + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( !id->qtask ) { + id->qtask = ldap_pvt_runqueue_insert( &slapd_rq, RUNQ_INTERVAL, + refint_qtask, id, "refint_qtask", + op->o_bd->be_suffix[0].bv_val ); + ac = 1; + } else { + if ( !ldap_pvt_runqueue_isrunning( &slapd_rq, id->qtask ) && + !id->qtask->next_sched.tv_sec ) { + id->qtask->interval.tv_sec = 0; + ldap_pvt_runqueue_resched( &slapd_rq, id->qtask, 0 ); + id->qtask->interval.tv_sec = RUNQ_INTERVAL; + ac = 1; + } + } + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + if ( ac ) + slap_wake_listener(); + + return SLAP_CB_CONTINUE; +} + +/* Check if the target entry exists and has children. + * Do nothing if target doesn't exist. + */ +static int +refint_preop( + Operation *op, + SlapReply *rs +) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + refint_data *id = on->on_bi.bi_private; + Entry *e; + int rc; + + /* are any attrs configured? */ + if ( !id->attrs ) + return SLAP_CB_CONTINUE; + + rc = overlay_entry_get_ov( op, &op->o_req_ndn, NULL, NULL, 0, &e, on ); + if ( rc == LDAP_SUCCESS ) { + slap_callback *sc = op->o_tmpcalloc( 1, + sizeof(slap_callback)+sizeof(refint_pre), op->o_tmpmemctx ); + refint_pre *rp = (refint_pre *)(sc+1); + rp->on = on; + rp->do_sub = 1; /* assume there are children */ + if ( op->o_bd->be_has_subordinates ) { + int has = 0; + rc = op->o_bd->be_has_subordinates( op, e, &has ); + /* there definitely are not children */ + if ( rc == LDAP_SUCCESS && has == LDAP_COMPARE_FALSE ) + rp->do_sub = 0; + } + overlay_entry_release_ov( op, e, 0, on ); + sc->sc_response = refint_response; + sc->sc_private = rp; + sc->sc_next = op->o_callback; + op->o_callback = sc; + } + return SLAP_CB_CONTINUE; +} + +/* +** init_module is last so the symbols resolve "for free" -- +** it expects to be called automagically during dynamic module initialization +*/ + +int refint_initialize() { + int rc; + + mr_dnSubtreeMatch = mr_find( "dnSubtreeMatch" ); + if ( mr_dnSubtreeMatch == NULL ) { + Debug( LDAP_DEBUG_ANY, "refint_initialize: " + "unable to find MatchingRule 'dnSubtreeMatch'.\n", + 0, 0, 0 ); + return 1; + } + + /* statically declared just after the #includes at top */ + refint.on_bi.bi_type = "refint"; + refint.on_bi.bi_db_init = refint_db_init; + refint.on_bi.bi_db_destroy = refint_db_destroy; + refint.on_bi.bi_db_open = refint_open; + refint.on_bi.bi_db_close = refint_close; + refint.on_bi.bi_op_delete = refint_preop; + refint.on_bi.bi_op_modrdn = refint_preop; + + refint.on_bi.bi_cf_ocs = refintocs; + rc = config_register_schema ( refintcfg, refintocs ); + if ( rc ) return rc; + + return(overlay_register(&refint)); +} + +#if SLAPD_OVER_REFINT == SLAPD_MOD_DYNAMIC && defined(PIC) +int init_module(int argc, char *argv[]) { + return refint_initialize(); +} +#endif + +#endif /* SLAPD_OVER_REFINT */ diff --git a/servers/slapd/overlays/retcode.c b/servers/slapd/overlays/retcode.c new file mode 100644 index 0000000..ad40ceb --- /dev/null +++ b/servers/slapd/overlays/retcode.c @@ -0,0 +1,1571 @@ +/* retcode.c - customizable response for client testing purposes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2021 The OpenLDAP Foundation. + * Portions Copyright 2005 Pierangelo Masarati <ando@sys-net.it> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_RETCODE + +#include <stdio.h> + +#include <ac/unistd.h> +#include <ac/string.h> +#include <ac/ctype.h> +#include <ac/socket.h> + +#include "slap.h" +#include "config.h" +#include "lutil.h" +#include "ldif.h" + +static slap_overinst retcode; + +static AttributeDescription *ad_errCode; +static AttributeDescription *ad_errText; +static AttributeDescription *ad_errOp; +static AttributeDescription *ad_errSleepTime; +static AttributeDescription *ad_errMatchedDN; +static AttributeDescription *ad_errUnsolicitedOID; +static AttributeDescription *ad_errUnsolicitedData; +static AttributeDescription *ad_errDisconnect; + +static ObjectClass *oc_errAbsObject; +static ObjectClass *oc_errObject; +static ObjectClass *oc_errAuxObject; + +typedef enum retcode_op_e { + SN_DG_OP_NONE = 0x0000, + SN_DG_OP_ADD = 0x0001, + SN_DG_OP_BIND = 0x0002, + SN_DG_OP_COMPARE = 0x0004, + SN_DG_OP_DELETE = 0x0008, + SN_DG_OP_MODIFY = 0x0010, + SN_DG_OP_RENAME = 0x0020, + SN_DG_OP_SEARCH = 0x0040, + SN_DG_EXTENDED = 0x0080, + SN_DG_OP_AUTH = SN_DG_OP_BIND, + SN_DG_OP_READ = (SN_DG_OP_COMPARE|SN_DG_OP_SEARCH), + SN_DG_OP_WRITE = (SN_DG_OP_ADD|SN_DG_OP_DELETE|SN_DG_OP_MODIFY|SN_DG_OP_RENAME), + SN_DG_OP_ALL = (SN_DG_OP_AUTH|SN_DG_OP_READ|SN_DG_OP_WRITE|SN_DG_EXTENDED) +} retcode_op_e; + +typedef struct retcode_item_t { + struct berval rdi_line; + struct berval rdi_dn; + struct berval rdi_ndn; + struct berval rdi_text; + struct berval rdi_matched; + int rdi_err; + BerVarray rdi_ref; + int rdi_sleeptime; + Entry rdi_e; + slap_mask_t rdi_mask; + struct berval rdi_unsolicited_oid; + struct berval rdi_unsolicited_data; + + unsigned rdi_flags; +#define RDI_PRE_DISCONNECT (0x1U) +#define RDI_POST_DISCONNECT (0x2U) + + struct retcode_item_t *rdi_next; +} retcode_item_t; + +typedef struct retcode_t { + struct berval rd_pdn; + struct berval rd_npdn; + + int rd_sleep; + + retcode_item_t *rd_item; + + int rd_indir; +#define RETCODE_FINDIR 0x01 +#define RETCODE_INDIR( rd ) ( (rd)->rd_indir ) +} retcode_t; + +static int +retcode_entry_response( Operation *op, SlapReply *rs, BackendInfo *bi, Entry *e ); + +static unsigned int +retcode_sleep( int s ) +{ + unsigned int r = 0; + + /* sleep as required */ + if ( s < 0 ) { +#if 0 /* use high-order bits for better randomness (Numerical Recipes in "C") */ + r = rand() % (-s); +#endif + r = ((double)(-s))*rand()/(RAND_MAX + 1.0); + } else if ( s > 0 ) { + r = (unsigned int)s; + } + if ( r ) { + sleep( r ); + } + + return r; +} + +static int +retcode_cleanup_cb( Operation *op, SlapReply *rs ) +{ + rs->sr_matched = NULL; + rs->sr_text = NULL; + + if ( rs->sr_ref != NULL ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + + ch_free( op->o_callback ); + op->o_callback = NULL; + + return SLAP_CB_CONTINUE; +} + +static int +retcode_send_onelevel( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + retcode_t *rd = (retcode_t *)on->on_bi.bi_private; + + retcode_item_t *rdi; + + for ( rdi = rd->rd_item; rdi != NULL; rdi = rdi->rdi_next ) { + if ( op->o_abandon ) { + return rs->sr_err = SLAPD_ABANDON; + } + + rs->sr_err = test_filter( op, &rdi->rdi_e, op->ors_filter ); + if ( rs->sr_err == LDAP_COMPARE_TRUE ) { + /* safe default */ + rs->sr_attrs = op->ors_attrs; + rs->sr_operational_attrs = NULL; + rs->sr_ctrls = NULL; + rs->sr_flags = 0; + rs->sr_err = LDAP_SUCCESS; + rs->sr_entry = &rdi->rdi_e; + + rs->sr_err = send_search_entry( op, rs ); + rs->sr_flags = 0; + rs->sr_entry = NULL; + rs->sr_attrs = NULL; + + switch ( rs->sr_err ) { + case LDAP_UNAVAILABLE: /* connection closed */ + rs->sr_err = LDAP_OTHER; + /* fallthru */ + case LDAP_SIZELIMIT_EXCEEDED: + goto done; + } + } + rs->sr_err = LDAP_SUCCESS; + } + +done:; + + send_ldap_result( op, rs ); + + return rs->sr_err; +} + +static int +retcode_op_add( Operation *op, SlapReply *rs ) +{ + return retcode_entry_response( op, rs, NULL, op->ora_e ); +} + +typedef struct retcode_cb_t { + BackendInfo *rdc_info; + unsigned rdc_flags; + ber_tag_t rdc_tag; + AttributeName *rdc_attrs; +} retcode_cb_t; + +static int +retcode_cb_response( Operation *op, SlapReply *rs ) +{ + retcode_cb_t *rdc = (retcode_cb_t *)op->o_callback->sc_private; + + op->o_tag = rdc->rdc_tag; + if ( rs->sr_type == REP_SEARCH ) { + ber_tag_t o_tag = op->o_tag; + int rc; + + if ( op->o_tag == LDAP_REQ_SEARCH ) { + rs->sr_attrs = rdc->rdc_attrs; + } + rc = retcode_entry_response( op, rs, rdc->rdc_info, rs->sr_entry ); + op->o_tag = o_tag; + + return rc; + } + + switch ( rs->sr_err ) { + case LDAP_SUCCESS: + case LDAP_NO_SUCH_OBJECT: + /* in case of noSuchObject, stop the internal search + * for in-directory error stuff */ + if ( !op->o_abandon ) { + rdc->rdc_flags = SLAP_CB_CONTINUE; + } + return 0; + } + + return SLAP_CB_CONTINUE; +} + +static int +retcode_op_internal( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + + Operation op2 = *op; + BackendDB db = *op->o_bd; + slap_callback sc = { 0 }; + retcode_cb_t rdc; + + int rc; + + op2.o_tag = LDAP_REQ_SEARCH; + op2.ors_scope = LDAP_SCOPE_BASE; + op2.ors_deref = LDAP_DEREF_NEVER; + op2.ors_tlimit = SLAP_NO_LIMIT; + op2.ors_slimit = SLAP_NO_LIMIT; + op2.ors_limit = NULL; + op2.ors_attrsonly = 0; + op2.ors_attrs = slap_anlist_all_attributes; + + ber_str2bv_x( "(objectClass=errAbsObject)", + STRLENOF( "(objectClass=errAbsObject)" ), + 1, &op2.ors_filterstr, op2.o_tmpmemctx ); + op2.ors_filter = str2filter_x( &op2, op2.ors_filterstr.bv_val ); + + /* errAbsObject is defined by this overlay! */ + assert( op2.ors_filter != NULL ); + + db.bd_info = on->on_info->oi_orig; + op2.o_bd = &db; + + rdc.rdc_info = on->on_info->oi_orig; + rdc.rdc_flags = RETCODE_FINDIR; + if ( op->o_tag == LDAP_REQ_SEARCH ) { + rdc.rdc_attrs = op->ors_attrs; + } + rdc.rdc_tag = op->o_tag; + sc.sc_response = retcode_cb_response; + sc.sc_private = &rdc; + op2.o_callback = ≻ + + rc = op2.o_bd->be_search( &op2, rs ); + op->o_abandon = op2.o_abandon; + + filter_free_x( &op2, op2.ors_filter, 1 ); + ber_memfree_x( op2.ors_filterstr.bv_val, op2.o_tmpmemctx ); + + if ( rdc.rdc_flags == SLAP_CB_CONTINUE ) { + return SLAP_CB_CONTINUE; + } + + return rc; +} + +static int +retcode_op_func( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + retcode_t *rd = (retcode_t *)on->on_bi.bi_private; + + retcode_item_t *rdi; + struct berval nrdn, npdn; + + slap_callback *cb = NULL; + + /* sleep as required */ + retcode_sleep( rd->rd_sleep ); + + if ( !dnIsSuffix( &op->o_req_ndn, &rd->rd_npdn ) ) { + if ( RETCODE_INDIR( rd ) ) { + switch ( op->o_tag ) { + case LDAP_REQ_ADD: + return retcode_op_add( op, rs ); + + case LDAP_REQ_BIND: + /* skip if rootdn */ + /* FIXME: better give the db a chance? */ + if ( be_isroot_pw( op ) ) { + return LDAP_SUCCESS; + } + return retcode_op_internal( op, rs ); + + case LDAP_REQ_SEARCH: + if ( op->ors_scope == LDAP_SCOPE_BASE ) { + rs->sr_err = retcode_op_internal( op, rs ); + switch ( rs->sr_err ) { + case SLAP_CB_CONTINUE: + if ( rs->sr_nentries == 0 ) { + break; + } + rs->sr_err = LDAP_SUCCESS; + /* fallthru */ + + default: + send_ldap_result( op, rs ); + break; + } + return rs->sr_err; + } + break; + + case LDAP_REQ_MODIFY: + case LDAP_REQ_DELETE: + case LDAP_REQ_MODRDN: + case LDAP_REQ_COMPARE: + return retcode_op_internal( op, rs ); + } + } + + return SLAP_CB_CONTINUE; + } + + if ( op->o_tag == LDAP_REQ_SEARCH + && op->ors_scope != LDAP_SCOPE_BASE + && op->o_req_ndn.bv_len == rd->rd_npdn.bv_len ) + { + return retcode_send_onelevel( op, rs ); + } + + dnParent( &op->o_req_ndn, &npdn ); + if ( npdn.bv_len != rd->rd_npdn.bv_len ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_matched = rd->rd_pdn.bv_val; + send_ldap_result( op, rs ); + rs->sr_matched = NULL; + return rs->sr_err; + } + + dnRdn( &op->o_req_ndn, &nrdn ); + + for ( rdi = rd->rd_item; rdi != NULL; rdi = rdi->rdi_next ) { + struct berval rdi_nrdn; + + dnRdn( &rdi->rdi_ndn, &rdi_nrdn ); + if ( dn_match( &nrdn, &rdi_nrdn ) ) { + break; + } + } + + if ( rdi != NULL && rdi->rdi_mask != SN_DG_OP_ALL ) { + retcode_op_e o_tag = SN_DG_OP_NONE; + + switch ( op->o_tag ) { + case LDAP_REQ_ADD: + o_tag = SN_DG_OP_ADD; + break; + + case LDAP_REQ_BIND: + o_tag = SN_DG_OP_BIND; + break; + + case LDAP_REQ_COMPARE: + o_tag = SN_DG_OP_COMPARE; + break; + + case LDAP_REQ_DELETE: + o_tag = SN_DG_OP_DELETE; + break; + + case LDAP_REQ_MODIFY: + o_tag = SN_DG_OP_MODIFY; + break; + + case LDAP_REQ_MODRDN: + o_tag = SN_DG_OP_RENAME; + break; + + case LDAP_REQ_SEARCH: + o_tag = SN_DG_OP_SEARCH; + break; + + case LDAP_REQ_EXTENDED: + o_tag = SN_DG_EXTENDED; + break; + + default: + /* Should not happen */ + break; + } + + if ( !( o_tag & rdi->rdi_mask ) ) { + return SLAP_CB_CONTINUE; + } + } + + if ( rdi == NULL ) { + rs->sr_matched = rd->rd_pdn.bv_val; + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = "retcode not found"; + + } else { + if ( rdi->rdi_flags & RDI_PRE_DISCONNECT ) { + return rs->sr_err = SLAPD_DISCONNECT; + } + + rs->sr_err = rdi->rdi_err; + rs->sr_text = rdi->rdi_text.bv_val; + rs->sr_matched = rdi->rdi_matched.bv_val; + + /* FIXME: we only honor the rdi_ref field in case rdi_err + * is LDAP_REFERRAL otherwise send_ldap_result() bails out */ + if ( rs->sr_err == LDAP_REFERRAL ) { + BerVarray ref; + + if ( rdi->rdi_ref != NULL ) { + ref = rdi->rdi_ref; + } else { + ref = default_referral; + } + + if ( ref != NULL ) { + rs->sr_ref = referral_rewrite( ref, + NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + + } else { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "bad referral object"; + } + } + + retcode_sleep( rdi->rdi_sleeptime ); + } + + switch ( op->o_tag ) { + case LDAP_REQ_EXTENDED: + if ( rdi == NULL ) { + break; + } + cb = ( slap_callback * )ch_malloc( sizeof( slap_callback ) ); + memset( cb, 0, sizeof( slap_callback ) ); + cb->sc_cleanup = retcode_cleanup_cb; + op->o_callback = cb; + break; + + default: + if ( rdi && !BER_BVISNULL( &rdi->rdi_unsolicited_oid ) ) { + ber_int_t msgid = op->o_msgid; + + /* RFC 4511 unsolicited response */ + + op->o_msgid = 0; + if ( strcmp( rdi->rdi_unsolicited_oid.bv_val, "0" ) == 0 ) { + send_ldap_result( op, rs ); + + } else { + ber_tag_t tag = op->o_tag; + + op->o_tag = LDAP_REQ_EXTENDED; + rs->sr_rspoid = rdi->rdi_unsolicited_oid.bv_val; + if ( !BER_BVISNULL( &rdi->rdi_unsolicited_data ) ) { + rs->sr_rspdata = &rdi->rdi_unsolicited_data; + } + send_ldap_extended( op, rs ); + rs->sr_rspoid = NULL; + rs->sr_rspdata = NULL; + op->o_tag = tag; + + } + op->o_msgid = msgid; + + } else { + send_ldap_result( op, rs ); + } + + if ( rs->sr_ref != NULL ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + rs->sr_matched = NULL; + rs->sr_text = NULL; + + if ( rdi && rdi->rdi_flags & RDI_POST_DISCONNECT ) { + return rs->sr_err = SLAPD_DISCONNECT; + } + break; + } + + return rs->sr_err; +} + +static int +retcode_op2str( ber_tag_t op, struct berval *bv ) +{ + switch ( op ) { + case LDAP_REQ_BIND: + BER_BVSTR( bv, "bind" ); + return 0; + case LDAP_REQ_ADD: + BER_BVSTR( bv, "add" ); + return 0; + case LDAP_REQ_DELETE: + BER_BVSTR( bv, "delete" ); + return 0; + case LDAP_REQ_MODRDN: + BER_BVSTR( bv, "modrdn" ); + return 0; + case LDAP_REQ_MODIFY: + BER_BVSTR( bv, "modify" ); + return 0; + case LDAP_REQ_COMPARE: + BER_BVSTR( bv, "compare" ); + return 0; + case LDAP_REQ_SEARCH: + BER_BVSTR( bv, "search" ); + return 0; + case LDAP_REQ_EXTENDED: + BER_BVSTR( bv, "extended" ); + return 0; + } + return -1; +} + +static int +retcode_entry_response( Operation *op, SlapReply *rs, BackendInfo *bi, Entry *e ) +{ + Attribute *a; + int err; + char *next; + int disconnect = 0; + + if ( get_manageDSAit( op ) ) { + return SLAP_CB_CONTINUE; + } + + if ( !is_entry_objectclass_or_sub( e, oc_errAbsObject ) ) { + return SLAP_CB_CONTINUE; + } + + /* operation */ + a = attr_find( e->e_attrs, ad_errOp ); + if ( a != NULL ) { + int i, + gotit = 0; + struct berval bv = BER_BVNULL; + + (void)retcode_op2str( op->o_tag, &bv ); + + if ( BER_BVISNULL( &bv ) ) { + return SLAP_CB_CONTINUE; + } + + for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) { + if ( bvmatch( &a->a_nvals[ i ], &bv ) ) { + gotit = 1; + break; + } + } + + if ( !gotit ) { + return SLAP_CB_CONTINUE; + } + } + + /* disconnect */ + a = attr_find( e->e_attrs, ad_errDisconnect ); + if ( a != NULL ) { + if ( bvmatch( &a->a_nvals[ 0 ], &slap_true_bv ) ) { + return rs->sr_err = SLAPD_DISCONNECT; + } + disconnect = 1; + } + + /* error code */ + a = attr_find( e->e_attrs, ad_errCode ); + if ( a == NULL ) { + return SLAP_CB_CONTINUE; + } + err = strtol( a->a_nvals[ 0 ].bv_val, &next, 0 ); + if ( next == a->a_nvals[ 0 ].bv_val || next[ 0 ] != '\0' ) { + return SLAP_CB_CONTINUE; + } + rs->sr_err = err; + + /* sleep time */ + a = attr_find( e->e_attrs, ad_errSleepTime ); + if ( a != NULL && a->a_nvals[ 0 ].bv_val[ 0 ] != '-' ) { + int sleepTime; + + if ( lutil_atoi( &sleepTime, a->a_nvals[ 0 ].bv_val ) == 0 ) { + retcode_sleep( sleepTime ); + } + } + + if ( rs->sr_err != LDAP_SUCCESS && !LDAP_API_ERROR( rs->sr_err )) { + BackendDB db = *op->o_bd, + *o_bd = op->o_bd; + void *o_callback = op->o_callback; + + /* message text */ + a = attr_find( e->e_attrs, ad_errText ); + if ( a != NULL ) { + rs->sr_text = a->a_vals[ 0 ].bv_val; + } + + /* matched DN */ + a = attr_find( e->e_attrs, ad_errMatchedDN ); + if ( a != NULL ) { + rs->sr_matched = a->a_vals[ 0 ].bv_val; + } + + if ( bi == NULL ) { + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + + bi = on->on_info->oi_orig; + } + + db.bd_info = bi; + op->o_bd = &db; + op->o_callback = NULL; + + /* referral */ + if ( rs->sr_err == LDAP_REFERRAL ) { + BerVarray refs = default_referral; + + a = attr_find( e->e_attrs, slap_schema.si_ad_ref ); + if ( a != NULL ) { + refs = a->a_vals; + } + rs->sr_ref = referral_rewrite( refs, + NULL, &op->o_req_dn, op->oq_search.rs_scope ); + + send_search_reference( op, rs ); + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + + } else { + a = attr_find( e->e_attrs, ad_errUnsolicitedOID ); + if ( a != NULL ) { + struct berval oid = BER_BVNULL, + data = BER_BVNULL; + ber_int_t msgid = op->o_msgid; + + /* RFC 4511 unsolicited response */ + + op->o_msgid = 0; + + oid = a->a_nvals[ 0 ]; + + a = attr_find( e->e_attrs, ad_errUnsolicitedData ); + if ( a != NULL ) { + data = a->a_nvals[ 0 ]; + } + + if ( strcmp( oid.bv_val, "0" ) == 0 ) { + send_ldap_result( op, rs ); + + } else { + ber_tag_t tag = op->o_tag; + + op->o_tag = LDAP_REQ_EXTENDED; + rs->sr_rspoid = oid.bv_val; + if ( !BER_BVISNULL( &data ) ) { + rs->sr_rspdata = &data; + } + send_ldap_extended( op, rs ); + rs->sr_rspoid = NULL; + rs->sr_rspdata = NULL; + op->o_tag = tag; + } + op->o_msgid = msgid; + + } else { + send_ldap_result( op, rs ); + } + } + + rs->sr_text = NULL; + rs->sr_matched = NULL; + op->o_bd = o_bd; + op->o_callback = o_callback; + } + + if ( rs->sr_err != LDAP_SUCCESS ) { + if ( disconnect ) { + return rs->sr_err = SLAPD_DISCONNECT; + } + + op->o_abandon = 1; + return rs->sr_err; + } + + return SLAP_CB_CONTINUE; +} + +static int +retcode_response( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + retcode_t *rd = (retcode_t *)on->on_bi.bi_private; + + if ( rs->sr_type != REP_SEARCH || !RETCODE_INDIR( rd ) ) { + return SLAP_CB_CONTINUE; + } + + return retcode_entry_response( op, rs, NULL, rs->sr_entry ); +} + +static int +retcode_db_init( BackendDB *be, ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + retcode_t *rd; + + srand( getpid() ); + + rd = (retcode_t *)ch_malloc( sizeof( retcode_t ) ); + memset( rd, 0, sizeof( retcode_t ) ); + + on->on_bi.bi_private = (void *)rd; + + return 0; +} + +static void +retcode_item_destroy( retcode_item_t *rdi ) +{ + ber_memfree( rdi->rdi_line.bv_val ); + + ber_memfree( rdi->rdi_dn.bv_val ); + ber_memfree( rdi->rdi_ndn.bv_val ); + + if ( !BER_BVISNULL( &rdi->rdi_text ) ) { + ber_memfree( rdi->rdi_text.bv_val ); + } + + if ( !BER_BVISNULL( &rdi->rdi_matched ) ) { + ber_memfree( rdi->rdi_matched.bv_val ); + } + + if ( rdi->rdi_ref ) { + ber_bvarray_free( rdi->rdi_ref ); + } + + BER_BVZERO( &rdi->rdi_e.e_name ); + BER_BVZERO( &rdi->rdi_e.e_nname ); + + entry_clean( &rdi->rdi_e ); + + if ( !BER_BVISNULL( &rdi->rdi_unsolicited_oid ) ) { + ber_memfree( rdi->rdi_unsolicited_oid.bv_val ); + if ( !BER_BVISNULL( &rdi->rdi_unsolicited_data ) ) + ber_memfree( rdi->rdi_unsolicited_data.bv_val ); + } + + ch_free( rdi ); +} + +enum { + RC_PARENT = 1, + RC_ITEM +}; + +static ConfigDriver rc_cf_gen; + +static ConfigTable rccfg[] = { + { "retcode-parent", "dn", + 2, 2, 0, ARG_MAGIC|ARG_DN|RC_PARENT, rc_cf_gen, + "( OLcfgOvAt:20.1 NAME 'olcRetcodeParent' " + "DESC '' " + "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL }, + { "retcode-item", "rdn> <retcode> <...", + 3, 0, 0, ARG_MAGIC|RC_ITEM, rc_cf_gen, + "( OLcfgOvAt:20.2 NAME 'olcRetcodeItem' " + "DESC '' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "X-ORDERED 'VALUES' )", NULL, NULL }, + { "retcode-indir", "on|off", + 1, 2, 0, ARG_OFFSET|ARG_ON_OFF, + (void *)offsetof(retcode_t, rd_indir), + "( OLcfgOvAt:20.3 NAME 'olcRetcodeInDir' " + "DESC '' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + + { "retcode-sleep", "sleeptime", + 2, 2, 0, ARG_OFFSET|ARG_INT, + (void *)offsetof(retcode_t, rd_sleep), + "( OLcfgOvAt:20.4 NAME 'olcRetcodeSleep' " + "DESC '' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs rcocs[] = { + { "( OLcfgOvOc:20.1 " + "NAME 'olcRetcodeConfig' " + "DESC 'Retcode configuration' " + "SUP olcOverlayConfig " + "MAY ( olcRetcodeParent " + "$ olcRetcodeItem " + "$ olcRetcodeInDir " + "$ olcRetcodeSleep " + ") )", + Cft_Overlay, rccfg, NULL, NULL }, + { NULL, 0, NULL } +}; + +static int +rc_cf_gen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + retcode_t *rd = (retcode_t *)on->on_bi.bi_private; + int rc = ARG_BAD_CONF; + + if ( c->op == SLAP_CONFIG_EMIT ) { + switch( c->type ) { + case RC_PARENT: + if ( !BER_BVISEMPTY( &rd->rd_pdn )) { + rc = value_add_one( &c->rvalue_vals, + &rd->rd_pdn ); + if ( rc == 0 ) { + rc = value_add_one( &c->rvalue_nvals, + &rd->rd_npdn ); + } + return rc; + } + rc = 0; + break; + + case RC_ITEM: { + retcode_item_t *rdi; + int i; + + for ( rdi = rd->rd_item, i = 0; rdi; rdi = rdi->rdi_next, i++ ) { + char buf[4096]; + struct berval bv; + char *ptr; + + bv.bv_len = snprintf( buf, sizeof( buf ), SLAP_X_ORDERED_FMT, i ); + bv.bv_len += rdi->rdi_line.bv_len; + ptr = bv.bv_val = ch_malloc( bv.bv_len + 1 ); + ptr = lutil_strcopy( ptr, buf ); + ptr = lutil_strncopy( ptr, rdi->rdi_line.bv_val, rdi->rdi_line.bv_len ); + ber_bvarray_add( &c->rvalue_vals, &bv ); + } + rc = 0; + } break; + + default: + assert( 0 ); + break; + } + + return rc; + + } else if ( c->op == LDAP_MOD_DELETE ) { + switch( c->type ) { + case RC_PARENT: + if ( rd->rd_pdn.bv_val ) { + ber_memfree ( rd->rd_pdn.bv_val ); + rc = 0; + } + if ( rd->rd_npdn.bv_val ) { + ber_memfree ( rd->rd_npdn.bv_val ); + } + break; + + case RC_ITEM: + if ( c->valx == -1 ) { + retcode_item_t *rdi, *next; + + for ( rdi = rd->rd_item; rdi != NULL; rdi = next ) { + next = rdi->rdi_next; + retcode_item_destroy( rdi ); + } + + } else { + retcode_item_t **rdip, *rdi; + int i; + + for ( rdip = &rd->rd_item, i = 0; i <= c->valx && *rdip; i++, rdip = &(*rdip)->rdi_next ) + ; + if ( *rdip == NULL ) { + return 1; + } + rdi = *rdip; + *rdip = rdi->rdi_next; + + retcode_item_destroy( rdi ); + } + rc = 0; + break; + + default: + assert( 0 ); + break; + } + return rc; /* FIXME */ + } + + switch( c->type ) { + case RC_PARENT: + if ( rd->rd_pdn.bv_val ) { + ber_memfree ( rd->rd_pdn.bv_val ); + } + if ( rd->rd_npdn.bv_val ) { + ber_memfree ( rd->rd_npdn.bv_val ); + } + rd->rd_pdn = c->value_dn; + rd->rd_npdn = c->value_ndn; + rc = 0; + break; + + case RC_ITEM: { + retcode_item_t rdi = { BER_BVNULL }, **rdip; + struct berval bv, rdn, nrdn; + char *next = NULL; + int i; + + if ( c->argc < 3 ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "\"retcode-item <RDN> <retcode> [<text>]\": " + "missing args" ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + + ber_str2bv( c->argv[ 1 ], 0, 0, &bv ); + + rc = dnPrettyNormal( NULL, &bv, &rdn, &nrdn, NULL ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "unable to normalize RDN \"%s\": %d", + c->argv[ 1 ], rc ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + + if ( !dnIsOneLevelRDN( &nrdn ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "value \"%s\" is not a RDN", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + + if ( BER_BVISNULL( &rd->rd_npdn ) ) { + /* FIXME: we use the database suffix */ + if ( c->be->be_nsuffix == NULL ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "either \"retcode-parent\" " + "or \"suffix\" must be defined" ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + + ber_dupbv( &rd->rd_pdn, &c->be->be_suffix[ 0 ] ); + ber_dupbv( &rd->rd_npdn, &c->be->be_nsuffix[ 0 ] ); + } + + build_new_dn( &rdi.rdi_dn, &rd->rd_pdn, &rdn, NULL ); + build_new_dn( &rdi.rdi_ndn, &rd->rd_npdn, &nrdn, NULL ); + + ch_free( rdn.bv_val ); + ch_free( nrdn.bv_val ); + + rdi.rdi_err = strtol( c->argv[ 2 ], &next, 0 ); + if ( next == c->argv[ 2 ] || next[ 0 ] != '\0' ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "unable to parse return code \"%s\"", + c->argv[ 2 ] ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + + rdi.rdi_mask = SN_DG_OP_ALL; + + if ( c->argc > 3 ) { + for ( i = 3; i < c->argc; i++ ) { + if ( strncasecmp( c->argv[ i ], "op=", STRLENOF( "op=" ) ) == 0 ) + { + char **ops; + int j; + + ops = ldap_str2charray( &c->argv[ i ][ STRLENOF( "op=" ) ], "," ); + assert( ops != NULL ); + + rdi.rdi_mask = SN_DG_OP_NONE; + + for ( j = 0; ops[ j ] != NULL; j++ ) { + if ( strcasecmp( ops[ j ], "add" ) == 0 ) { + rdi.rdi_mask |= SN_DG_OP_ADD; + + } else if ( strcasecmp( ops[ j ], "bind" ) == 0 ) { + rdi.rdi_mask |= SN_DG_OP_BIND; + + } else if ( strcasecmp( ops[ j ], "compare" ) == 0 ) { + rdi.rdi_mask |= SN_DG_OP_COMPARE; + + } else if ( strcasecmp( ops[ j ], "delete" ) == 0 ) { + rdi.rdi_mask |= SN_DG_OP_DELETE; + + } else if ( strcasecmp( ops[ j ], "modify" ) == 0 ) { + rdi.rdi_mask |= SN_DG_OP_MODIFY; + + } else if ( strcasecmp( ops[ j ], "rename" ) == 0 + || strcasecmp( ops[ j ], "modrdn" ) == 0 ) + { + rdi.rdi_mask |= SN_DG_OP_RENAME; + + } else if ( strcasecmp( ops[ j ], "search" ) == 0 ) { + rdi.rdi_mask |= SN_DG_OP_SEARCH; + + } else if ( strcasecmp( ops[ j ], "extended" ) == 0 ) { + rdi.rdi_mask |= SN_DG_EXTENDED; + + } else if ( strcasecmp( ops[ j ], "auth" ) == 0 ) { + rdi.rdi_mask |= SN_DG_OP_AUTH; + + } else if ( strcasecmp( ops[ j ], "read" ) == 0 ) { + rdi.rdi_mask |= SN_DG_OP_READ; + + } else if ( strcasecmp( ops[ j ], "write" ) == 0 ) { + rdi.rdi_mask |= SN_DG_OP_WRITE; + + } else if ( strcasecmp( ops[ j ], "all" ) == 0 ) { + rdi.rdi_mask |= SN_DG_OP_ALL; + + } else { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "unknown op \"%s\"", + ops[ j ] ); + ldap_charray_free( ops ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + } + + ldap_charray_free( ops ); + + } else if ( strncasecmp( c->argv[ i ], "text=", STRLENOF( "text=" ) ) == 0 ) + { + if ( !BER_BVISNULL( &rdi.rdi_text ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "\"text\" already provided" ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + ber_str2bv( &c->argv[ i ][ STRLENOF( "text=" ) ], 0, 1, &rdi.rdi_text ); + + } else if ( strncasecmp( c->argv[ i ], "matched=", STRLENOF( "matched=" ) ) == 0 ) + { + struct berval dn; + + if ( !BER_BVISNULL( &rdi.rdi_matched ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "\"matched\" already provided" ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + ber_str2bv( &c->argv[ i ][ STRLENOF( "matched=" ) ], 0, 0, &dn ); + if ( dnPretty( NULL, &dn, &rdi.rdi_matched, NULL ) != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "unable to prettify matched DN \"%s\"", + &c->argv[ i ][ STRLENOF( "matched=" ) ] ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + + } else if ( strncasecmp( c->argv[ i ], "ref=", STRLENOF( "ref=" ) ) == 0 ) + { + char **refs; + int j; + + if ( rdi.rdi_ref != NULL ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "\"ref\" already provided" ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + + if ( rdi.rdi_err != LDAP_REFERRAL ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "providing \"ref\" " + "along with a non-referral " + "resultCode may cause slapd failures " + "related to internal checks" ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg, 0 ); + } + + refs = ldap_str2charray( &c->argv[ i ][ STRLENOF( "ref=" ) ], " " ); + assert( refs != NULL ); + + for ( j = 0; refs[ j ] != NULL; j++ ) { + struct berval bv; + + ber_str2bv( refs[ j ], 0, 1, &bv ); + ber_bvarray_add( &rdi.rdi_ref, &bv ); + } + + ldap_charray_free( refs ); + + } else if ( strncasecmp( c->argv[ i ], "sleeptime=", STRLENOF( "sleeptime=" ) ) == 0 ) + { + if ( rdi.rdi_sleeptime != 0 ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "\"sleeptime\" already provided" ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + + if ( lutil_atoi( &rdi.rdi_sleeptime, &c->argv[ i ][ STRLENOF( "sleeptime=" ) ] ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "unable to parse \"sleeptime=%s\"", + &c->argv[ i ][ STRLENOF( "sleeptime=" ) ] ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + + } else if ( strncasecmp( c->argv[ i ], "unsolicited=", STRLENOF( "unsolicited=" ) ) == 0 ) + { + char *data; + + if ( !BER_BVISNULL( &rdi.rdi_unsolicited_oid ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "\"unsolicited\" already provided" ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + + data = strchr( &c->argv[ i ][ STRLENOF( "unsolicited=" ) ], ':' ); + if ( data != NULL ) { + struct berval oid; + + if ( ldif_parse_line2( &c->argv[ i ][ STRLENOF( "unsolicited=" ) ], + &oid, &rdi.rdi_unsolicited_data, NULL ) ) + { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "unable to parse \"unsolicited\"" ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + + ber_dupbv( &rdi.rdi_unsolicited_oid, &oid ); + + } else { + ber_str2bv( &c->argv[ i ][ STRLENOF( "unsolicited=" ) ], 0, 1, + &rdi.rdi_unsolicited_oid ); + } + + } else if ( strncasecmp( c->argv[ i ], "flags=", STRLENOF( "flags=" ) ) == 0 ) + { + char *arg = &c->argv[ i ][ STRLENOF( "flags=" ) ]; + if ( strcasecmp( arg, "disconnect" ) == 0 ) { + rdi.rdi_flags |= RDI_PRE_DISCONNECT; + + } else if ( strcasecmp( arg, "pre-disconnect" ) == 0 ) { + rdi.rdi_flags |= RDI_PRE_DISCONNECT; + + } else if ( strcasecmp( arg, "post-disconnect" ) == 0 ) { + rdi.rdi_flags |= RDI_POST_DISCONNECT; + + } else { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "unknown flag \"%s\"", arg ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + + } else { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "unknown option \"%s\"", + c->argv[ i ] ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + } + } + + rdi.rdi_line.bv_len = 2*(c->argc - 1) + c->argc - 2; + for ( i = 1; i < c->argc; i++ ) { + rdi.rdi_line.bv_len += strlen( c->argv[ i ] ); + } + next = rdi.rdi_line.bv_val = ch_malloc( rdi.rdi_line.bv_len + 1 ); + + for ( i = 1; i < c->argc; i++ ) { + *next++ = '"'; + next = lutil_strcopy( next, c->argv[ i ] ); + *next++ = '"'; + *next++ = ' '; + } + *--next = '\0'; + + for ( rdip = &rd->rd_item; *rdip; rdip = &(*rdip)->rdi_next ) + /* go to last */ ; + + + *rdip = ( retcode_item_t * )ch_malloc( sizeof( retcode_item_t ) ); + *(*rdip) = rdi; + + rc = 0; + } break; + + default: + rc = SLAP_CONF_UNKNOWN; + break; + } + + return rc; +} + +static int +retcode_db_open( BackendDB *be, ConfigReply *cr) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + retcode_t *rd = (retcode_t *)on->on_bi.bi_private; + + retcode_item_t *rdi; + + for ( rdi = rd->rd_item; rdi; rdi = rdi->rdi_next ) { + LDAPRDN rdn = NULL; + int rc, j; + char* p; + struct berval val[ 3 ]; + char buf[ SLAP_TEXT_BUFLEN ]; + + /* DN */ + rdi->rdi_e.e_name = rdi->rdi_dn; + rdi->rdi_e.e_nname = rdi->rdi_ndn; + + /* objectClass */ + val[ 0 ] = oc_errObject->soc_cname; + val[ 1 ] = slap_schema.si_oc_extensibleObject->soc_cname; + BER_BVZERO( &val[ 2 ] ); + + attr_merge( &rdi->rdi_e, slap_schema.si_ad_objectClass, val, NULL ); + + /* RDN avas */ + rc = ldap_bv2rdn( &rdi->rdi_dn, &rdn, (char **) &p, + LDAP_DN_FORMAT_LDAP ); + + assert( rc == LDAP_SUCCESS ); + + for ( j = 0; rdn[ j ]; j++ ) { + LDAPAVA *ava = rdn[ j ]; + AttributeDescription *ad = NULL; + const char *text; + + rc = slap_bv2ad( &ava->la_attr, &ad, &text ); + assert( rc == LDAP_SUCCESS ); + + attr_merge_normalize_one( &rdi->rdi_e, ad, + &ava->la_value, NULL ); + } + + ldap_rdnfree( rdn ); + + /* error code */ + snprintf( buf, sizeof( buf ), "%d", rdi->rdi_err ); + ber_str2bv( buf, 0, 0, &val[ 0 ] ); + + attr_merge_one( &rdi->rdi_e, ad_errCode, &val[ 0 ], NULL ); + + if ( rdi->rdi_ref != NULL ) { + attr_merge_normalize( &rdi->rdi_e, slap_schema.si_ad_ref, + rdi->rdi_ref, NULL ); + } + + /* text */ + if ( !BER_BVISNULL( &rdi->rdi_text ) ) { + val[ 0 ] = rdi->rdi_text; + + attr_merge_normalize_one( &rdi->rdi_e, ad_errText, &val[ 0 ], NULL ); + } + + /* matched */ + if ( !BER_BVISNULL( &rdi->rdi_matched ) ) { + val[ 0 ] = rdi->rdi_matched; + + attr_merge_normalize_one( &rdi->rdi_e, ad_errMatchedDN, &val[ 0 ], NULL ); + } + + /* sleep time */ + if ( rdi->rdi_sleeptime ) { + snprintf( buf, sizeof( buf ), "%d", rdi->rdi_sleeptime ); + ber_str2bv( buf, 0, 0, &val[ 0 ] ); + + attr_merge_one( &rdi->rdi_e, ad_errSleepTime, &val[ 0 ], NULL ); + } + + /* operations */ + if ( rdi->rdi_mask & SN_DG_OP_ADD ) { + BER_BVSTR( &val[ 0 ], "add" ); + attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL ); + } + + if ( rdi->rdi_mask & SN_DG_OP_BIND ) { + BER_BVSTR( &val[ 0 ], "bind" ); + attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL ); + } + + if ( rdi->rdi_mask & SN_DG_OP_COMPARE ) { + BER_BVSTR( &val[ 0 ], "compare" ); + attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL ); + } + + if ( rdi->rdi_mask & SN_DG_OP_DELETE ) { + BER_BVSTR( &val[ 0 ], "delete" ); + attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL ); + } + + if ( rdi->rdi_mask & SN_DG_EXTENDED ) { + BER_BVSTR( &val[ 0 ], "extended" ); + attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL ); + } + + if ( rdi->rdi_mask & SN_DG_OP_MODIFY ) { + BER_BVSTR( &val[ 0 ], "modify" ); + attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL ); + } + + if ( rdi->rdi_mask & SN_DG_OP_RENAME ) { + BER_BVSTR( &val[ 0 ], "rename" ); + attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL ); + } + + if ( rdi->rdi_mask & SN_DG_OP_SEARCH ) { + BER_BVSTR( &val[ 0 ], "search" ); + attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL ); + } + } + + return 0; +} + +static int +retcode_db_destroy( BackendDB *be, ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + retcode_t *rd = (retcode_t *)on->on_bi.bi_private; + + if ( rd ) { + retcode_item_t *rdi, *next; + + for ( rdi = rd->rd_item; rdi != NULL; rdi = next ) { + next = rdi->rdi_next; + retcode_item_destroy( rdi ); + } + + if ( !BER_BVISNULL( &rd->rd_pdn ) ) { + ber_memfree( rd->rd_pdn.bv_val ); + } + + if ( !BER_BVISNULL( &rd->rd_npdn ) ) { + ber_memfree( rd->rd_npdn.bv_val ); + } + + ber_memfree( rd ); + } + + return 0; +} + +#if SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC +static +#endif /* SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC */ +int +retcode_initialize( void ) +{ + int i, code; + + static struct { + char *desc; + AttributeDescription **ad; + } retcode_at[] = { + { "( 1.3.6.1.4.1.4203.666.11.4.1.1 " + "NAME ( 'errCode' ) " + "DESC 'LDAP error code' " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "SINGLE-VALUE )", + &ad_errCode }, + { "( 1.3.6.1.4.1.4203.666.11.4.1.2 " + "NAME ( 'errOp' ) " + "DESC 'Operations the errObject applies to' " + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + &ad_errOp}, + { "( 1.3.6.1.4.1.4203.666.11.4.1.3 " + "NAME ( 'errText' ) " + "DESC 'LDAP error textual description' " + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 " + "SINGLE-VALUE )", + &ad_errText }, + { "( 1.3.6.1.4.1.4203.666.11.4.1.4 " + "NAME ( 'errSleepTime' ) " + "DESC 'Time to wait before returning the error' " + "EQUALITY integerMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "SINGLE-VALUE )", + &ad_errSleepTime }, + { "( 1.3.6.1.4.1.4203.666.11.4.1.5 " + "NAME ( 'errMatchedDN' ) " + "DESC 'Value to be returned as matched DN' " + "EQUALITY distinguishedNameMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 " + "SINGLE-VALUE )", + &ad_errMatchedDN }, + { "( 1.3.6.1.4.1.4203.666.11.4.1.6 " + "NAME ( 'errUnsolicitedOID' ) " + "DESC 'OID to be returned within unsolicited response' " + "EQUALITY objectIdentifierMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 " + "SINGLE-VALUE )", + &ad_errUnsolicitedOID }, + { "( 1.3.6.1.4.1.4203.666.11.4.1.7 " + "NAME ( 'errUnsolicitedData' ) " + "DESC 'Data to be returned within unsolicited response' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 " + "SINGLE-VALUE )", + &ad_errUnsolicitedData }, + { "( 1.3.6.1.4.1.4203.666.11.4.1.8 " + "NAME ( 'errDisconnect' ) " + "DESC 'Disconnect without notice' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 " + "SINGLE-VALUE )", + &ad_errDisconnect }, + { NULL } + }; + + static struct { + char *desc; + ObjectClass **oc; + } retcode_oc[] = { + { "( 1.3.6.1.4.1.4203.666.11.4.3.0 " + "NAME ( 'errAbsObject' ) " + "SUP top ABSTRACT " + "MUST ( errCode ) " + "MAY ( " + "cn " + "$ description " + "$ errOp " + "$ errText " + "$ errSleepTime " + "$ errMatchedDN " + "$ errUnsolicitedOID " + "$ errUnsolicitedData " + "$ errDisconnect " + ") )", + &oc_errAbsObject }, + { "( 1.3.6.1.4.1.4203.666.11.4.3.1 " + "NAME ( 'errObject' ) " + "SUP errAbsObject STRUCTURAL " + ")", + &oc_errObject }, + { "( 1.3.6.1.4.1.4203.666.11.4.3.2 " + "NAME ( 'errAuxObject' ) " + "SUP errAbsObject AUXILIARY " + ")", + &oc_errAuxObject }, + { NULL } + }; + + + for ( i = 0; retcode_at[ i ].desc != NULL; i++ ) { + code = register_at( retcode_at[ i ].desc, retcode_at[ i ].ad, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "retcode: register_at failed\n", 0, 0, 0 ); + return code; + } + + (*retcode_at[ i ].ad)->ad_type->sat_flags |= SLAP_AT_HIDE; + } + + for ( i = 0; retcode_oc[ i ].desc != NULL; i++ ) { + code = register_oc( retcode_oc[ i ].desc, retcode_oc[ i ].oc, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "retcode: register_oc failed\n", 0, 0, 0 ); + return code; + } + + (*retcode_oc[ i ].oc)->soc_flags |= SLAP_OC_HIDE; + } + + retcode.on_bi.bi_type = "retcode"; + + retcode.on_bi.bi_db_init = retcode_db_init; + retcode.on_bi.bi_db_open = retcode_db_open; + retcode.on_bi.bi_db_destroy = retcode_db_destroy; + + retcode.on_bi.bi_op_add = retcode_op_func; + retcode.on_bi.bi_op_bind = retcode_op_func; + retcode.on_bi.bi_op_compare = retcode_op_func; + retcode.on_bi.bi_op_delete = retcode_op_func; + retcode.on_bi.bi_op_modify = retcode_op_func; + retcode.on_bi.bi_op_modrdn = retcode_op_func; + retcode.on_bi.bi_op_search = retcode_op_func; + + retcode.on_bi.bi_extended = retcode_op_func; + + retcode.on_response = retcode_response; + + retcode.on_bi.bi_cf_ocs = rcocs; + + code = config_register_schema( rccfg, rcocs ); + if ( code ) { + return code; + } + + return overlay_register( &retcode ); +} + +#if SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return retcode_initialize(); +} +#endif /* SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC */ + +#endif /* SLAPD_OVER_RETCODE */ diff --git a/servers/slapd/overlays/rwm.c b/servers/slapd/overlays/rwm.c new file mode 100644 index 0000000..4bedbf6 --- /dev/null +++ b/servers/slapd/overlays/rwm.c @@ -0,0 +1,2763 @@ +/* rwm.c - rewrite/remap operations */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * Portions Copyright 2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_RWM + +#include <stdio.h> + +#include <ac/string.h> + +#include "slap.h" +#include "config.h" +#include "lutil.h" +#include "rwm.h" + +typedef struct rwm_op_state { + ber_tag_t r_tag; + struct berval ro_dn; + struct berval ro_ndn; + struct berval r_dn; + struct berval r_ndn; + struct berval rx_dn; + struct berval rx_ndn; + AttributeName *mapped_attrs; + OpRequest o_request; +} rwm_op_state; + +typedef struct rwm_op_cb { + slap_callback cb; + rwm_op_state ros; +} rwm_op_cb; + +static int +rwm_db_destroy( BackendDB *be, ConfigReply *cr ); + +static int +rwm_send_entry( Operation *op, SlapReply *rs ); + +static void +rwm_op_rollback( Operation *op, SlapReply *rs, rwm_op_state *ros ) +{ + /* in case of successful extended operation cleanup + * gets called *after* (ITS#6632); this hack counts + * on others to cleanup our o_req_dn/o_req_ndn, + * while we cleanup theirs. */ + if ( ros->r_tag == LDAP_REQ_EXTENDED && rs->sr_err == LDAP_SUCCESS ) { + if ( !BER_BVISNULL( &ros->rx_dn ) ) { + ch_free( ros->rx_dn.bv_val ); + } + if ( !BER_BVISNULL( &ros->rx_ndn ) ) { + ch_free( ros->rx_ndn.bv_val ); + } + + } else { + if ( !BER_BVISNULL( &ros->ro_dn ) ) { + op->o_req_dn = ros->ro_dn; + } + if ( !BER_BVISNULL( &ros->ro_ndn ) ) { + op->o_req_ndn = ros->ro_ndn; + } + + if ( !BER_BVISNULL( &ros->r_dn ) + && ros->r_dn.bv_val != ros->ro_dn.bv_val ) + { + assert( ros->r_dn.bv_val != ros->r_ndn.bv_val ); + ch_free( ros->r_dn.bv_val ); + } + + if ( !BER_BVISNULL( &ros->r_ndn ) + && ros->r_ndn.bv_val != ros->ro_ndn.bv_val ) + { + ch_free( ros->r_ndn.bv_val ); + } + } + + BER_BVZERO( &ros->r_dn ); + BER_BVZERO( &ros->r_ndn ); + BER_BVZERO( &ros->ro_dn ); + BER_BVZERO( &ros->ro_ndn ); + BER_BVZERO( &ros->rx_dn ); + BER_BVZERO( &ros->rx_ndn ); + + switch( ros->r_tag ) { + case LDAP_REQ_COMPARE: + if ( op->orc_ava->aa_value.bv_val != ros->orc_ava->aa_value.bv_val ) + op->o_tmpfree( op->orc_ava->aa_value.bv_val, op->o_tmpmemctx ); + op->orc_ava = ros->orc_ava; + break; + case LDAP_REQ_MODIFY: + slap_mods_free( op->orm_modlist, 1 ); + op->orm_modlist = ros->orm_modlist; + break; + case LDAP_REQ_MODRDN: + if ( op->orr_newSup != ros->orr_newSup ) { + if ( op->orr_newSup ) { + ch_free( op->orr_newSup->bv_val ); + ch_free( op->orr_nnewSup->bv_val ); + op->o_tmpfree( op->orr_newSup, op->o_tmpmemctx ); + op->o_tmpfree( op->orr_nnewSup, op->o_tmpmemctx ); + } + op->orr_newSup = ros->orr_newSup; + op->orr_nnewSup = ros->orr_nnewSup; + } + if ( op->orr_newrdn.bv_val != ros->orr_newrdn.bv_val ) { + ch_free( op->orr_newrdn.bv_val ); + ch_free( op->orr_nnewrdn.bv_val ); + op->orr_newrdn = ros->orr_newrdn; + op->orr_nnewrdn = ros->orr_nnewrdn; + } + break; + case LDAP_REQ_SEARCH: + op->o_tmpfree( ros->mapped_attrs, op->o_tmpmemctx ); + op->ors_attrs = ros->ors_attrs; + if ( op->ors_filter != ros->ors_filter ) { + filter_free_x( op, op->ors_filter, 1 ); + op->ors_filter = ros->ors_filter; + } + if ( op->ors_filterstr.bv_val != ros->ors_filterstr.bv_val ) { + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + op->ors_filterstr = ros->ors_filterstr; + } + break; + case LDAP_REQ_EXTENDED: + if ( op->ore_reqdata != ros->ore_reqdata ) { + ber_bvfree( op->ore_reqdata ); + op->ore_reqdata = ros->ore_reqdata; + } + break; + case LDAP_REQ_BIND: + if ( rs->sr_err == LDAP_SUCCESS ) { +#if 0 + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + /* too late, c_mutex released */ + Debug( LDAP_DEBUG_ANY, "*** DN: \"%s\" => \"%s\"\n", + op->o_conn->c_ndn.bv_val, + op->o_req_ndn.bv_val ); + ber_bvreplace( &op->o_conn->c_ndn, + &op->o_req_ndn ); + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); +#endif + } + break; + default: break; + } +} + +static int +rwm_op_cleanup( Operation *op, SlapReply *rs ) +{ + slap_callback *cb = op->o_callback; + rwm_op_state *ros = cb->sc_private; + + if ( rs->sr_type == REP_RESULT || rs->sr_type == REP_EXTENDED || + op->o_abandon || rs->sr_err == SLAPD_ABANDON ) + { + rwm_op_rollback( op, rs, ros ); + + op->o_callback = op->o_callback->sc_next; + op->o_tmpfree( cb, op->o_tmpmemctx ); + } + + return SLAP_CB_CONTINUE; +} + +static rwm_op_cb * +rwm_callback_get( Operation *op ) +{ + rwm_op_cb *roc; + + roc = op->o_tmpcalloc( 1, sizeof( struct rwm_op_cb ), op->o_tmpmemctx ); + roc->cb.sc_cleanup = rwm_op_cleanup; + roc->cb.sc_response = NULL; + roc->cb.sc_next = op->o_callback; + roc->cb.sc_private = &roc->ros; + roc->ros.r_tag = op->o_tag; + roc->ros.ro_dn = op->o_req_dn; + roc->ros.ro_ndn = op->o_req_ndn; + BER_BVZERO( &roc->ros.r_dn ); + BER_BVZERO( &roc->ros.r_ndn ); + BER_BVZERO( &roc->ros.rx_dn ); + BER_BVZERO( &roc->ros.rx_ndn ); + roc->ros.mapped_attrs = NULL; + roc->ros.o_request = op->o_request; + + return roc; +} + + +static int +rwm_op_dn_massage( Operation *op, SlapReply *rs, void *cookie, + rwm_op_state *ros ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + struct berval dn = BER_BVNULL, + ndn = BER_BVNULL; + int rc = 0; + dncookie dc; + + /* + * Rewrite the dn if needed + */ + dc.rwmap = rwmap; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = (char *)cookie; + + /* NOTE: in those cases where only the ndn is available, + * and the caller sets op->o_req_dn = op->o_req_ndn, + * only rewrite the op->o_req_ndn and use it as + * op->o_req_dn as well */ + ndn = op->o_req_ndn; + if ( op->o_req_dn.bv_val != op->o_req_ndn.bv_val ) { + dn = op->o_req_dn; + rc = rwm_dn_massage_pretty_normalize( &dc, &op->o_req_dn, &dn, &ndn ); + } else { + rc = rwm_dn_massage_normalize( &dc, &op->o_req_ndn, &ndn ); + } + + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( ( op->o_req_dn.bv_val != op->o_req_ndn.bv_val && dn.bv_val == op->o_req_dn.bv_val ) + || ndn.bv_val == op->o_req_ndn.bv_val ) + { + return LDAP_SUCCESS; + } + + if ( op->o_req_dn.bv_val != op->o_req_ndn.bv_val ) { + op->o_req_dn = dn; + assert( BER_BVISNULL( &ros->r_dn ) ); + ros->r_dn = dn; + } else { + op->o_req_dn = ndn; + } + op->o_req_ndn = ndn; + assert( BER_BVISNULL( &ros->r_ndn ) ); + ros->r_ndn = ndn; + + if ( ros->r_tag == LDAP_REQ_EXTENDED ) { + ros->rx_dn = ros->r_dn; + ros->rx_ndn = ros->r_ndn; + } + + return LDAP_SUCCESS; +} + +static int +rwm_op_add( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + int rc, + i; + Attribute **ap = NULL; + char *olddn = op->o_req_dn.bv_val; + int isupdate; + + rwm_op_cb *roc = rwm_callback_get( op ); + + rc = rwm_op_dn_massage( op, rs, "addDN", &roc->ros ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "addDN massage error" ); + return -1; + } + + if ( olddn != op->o_req_dn.bv_val ) { + ber_bvreplace( &op->ora_e->e_name, &op->o_req_dn ); + ber_bvreplace( &op->ora_e->e_nname, &op->o_req_ndn ); + } + + /* Count number of attributes in entry */ + isupdate = be_shadow_update( op ); + for ( i = 0, ap = &op->oq_add.rs_e->e_attrs; *ap; ) { + Attribute *a; + + if ( (*ap)->a_desc == slap_schema.si_ad_objectClass || + (*ap)->a_desc == slap_schema.si_ad_structuralObjectClass ) + { + int j, last; + + last = (*ap)->a_numvals - 1; + for ( j = 0; !BER_BVISNULL( &(*ap)->a_vals[ j ] ); j++ ) { + struct ldapmapping *mapping = NULL; + + ( void )rwm_mapping( &rwmap->rwm_oc, &(*ap)->a_vals[ j ], + &mapping, RWM_MAP ); + if ( mapping == NULL ) { + if ( rwmap->rwm_at.drop_missing ) { + /* FIXME: we allow to remove objectClasses as well; + * if the resulting entry is inconsistent, that's + * the relayed database's business... + */ + ch_free( (*ap)->a_vals[ j ].bv_val ); + if ( last > j ) { + (*ap)->a_vals[ j ] = (*ap)->a_vals[ last ]; + } + BER_BVZERO( &(*ap)->a_vals[ last ] ); + (*ap)->a_numvals--; + last--; + j--; + } + + } else { + ch_free( (*ap)->a_vals[ j ].bv_val ); + ber_dupbv( &(*ap)->a_vals[ j ], &mapping->m_dst ); + } + } + + } else if ( !isupdate && !get_relax( op ) && (*ap)->a_desc->ad_type->sat_no_user_mod ) + { + goto next_attr; + + } else { + struct ldapmapping *mapping = NULL; + + ( void )rwm_mapping( &rwmap->rwm_at, &(*ap)->a_desc->ad_cname, + &mapping, RWM_MAP ); + if ( mapping == NULL ) { + if ( rwmap->rwm_at.drop_missing ) { + goto cleanup_attr; + } + } + + if ( (*ap)->a_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName + || ( mapping != NULL && mapping->m_dst_ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) ) + { + /* + * FIXME: rewrite could fail; in this case + * the operation should give up, right? + */ + rc = rwm_dnattr_rewrite( op, rs, "addAttrDN", + (*ap)->a_vals, + (*ap)->a_nvals ? &(*ap)->a_nvals : NULL ); + if ( rc ) { + goto cleanup_attr; + } + + } else if ( (*ap)->a_desc == slap_schema.si_ad_ref ) { + rc = rwm_referral_rewrite( op, rs, "referralAttrDN", + (*ap)->a_vals, + (*ap)->a_nvals ? &(*ap)->a_nvals : NULL ); + if ( rc != LDAP_SUCCESS ) { + goto cleanup_attr; + } + } + + if ( mapping != NULL ) { + assert( mapping->m_dst_ad != NULL ); + (*ap)->a_desc = mapping->m_dst_ad; + } + } + +next_attr:; + ap = &(*ap)->a_next; + continue; + +cleanup_attr:; + /* FIXME: leaking attribute/values? */ + a = *ap; + + *ap = (*ap)->a_next; + attr_free( a ); + } + + op->o_callback = &roc->cb; + + return SLAP_CB_CONTINUE; +} + +static int +rwm_conn_init( BackendDB *be, Connection *conn ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + ( void )rewrite_session_init( rwmap->rwm_rw, conn ); + + return SLAP_CB_CONTINUE; +} + +static int +rwm_conn_destroy( BackendDB *be, Connection *conn ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + ( void )rewrite_session_delete( rwmap->rwm_rw, conn ); + + return SLAP_CB_CONTINUE; +} + +static int +rwm_op_bind( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + int rc; + + rwm_op_cb *roc = rwm_callback_get( op ); + + rc = rwm_op_dn_massage( op, rs, "bindDN", &roc->ros ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "bindDN massage error" ); + return -1; + } + + overlay_callback_after_backover( op, &roc->cb, 1 ); + + return SLAP_CB_CONTINUE; +} + +static int +rwm_op_unbind( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + rewrite_session_delete( rwmap->rwm_rw, op->o_conn ); + + return SLAP_CB_CONTINUE; +} + +static int +rwm_op_compare( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + int rc; + struct berval mapped_vals[2] = { BER_BVNULL, BER_BVNULL }; + + rwm_op_cb *roc = rwm_callback_get( op ); + + rc = rwm_op_dn_massage( op, rs, "compareDN", &roc->ros ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "compareDN massage error" ); + return -1; + } + + /* if the attribute is an objectClass, try to remap its value */ + if ( op->orc_ava->aa_desc == slap_schema.si_ad_objectClass + || op->orc_ava->aa_desc == slap_schema.si_ad_structuralObjectClass ) + { + rwm_map( &rwmap->rwm_oc, &op->orc_ava->aa_value, + &mapped_vals[0], RWM_MAP ); + if ( BER_BVISNULL( &mapped_vals[0] ) || BER_BVISEMPTY( &mapped_vals[0] ) ) + { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, LDAP_OTHER, "compare objectClass map error" ); + return -1; + + } else if ( mapped_vals[0].bv_val != op->orc_ava->aa_value.bv_val ) { + ber_dupbv_x( &op->orc_ava->aa_value, &mapped_vals[0], + op->o_tmpmemctx ); + } + + } else { + struct ldapmapping *mapping = NULL; + AttributeDescription *ad = op->orc_ava->aa_desc; + + ( void )rwm_mapping( &rwmap->rwm_at, &op->orc_ava->aa_desc->ad_cname, + &mapping, RWM_MAP ); + if ( mapping == NULL ) { + if ( rwmap->rwm_at.drop_missing ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, LDAP_OTHER, "compare attributeType map error" ); + return -1; + } + + } else { + assert( mapping->m_dst_ad != NULL ); + ad = mapping->m_dst_ad; + } + + if ( op->orc_ava->aa_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName + || ( mapping != NULL && mapping->m_dst_ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) ) + { + struct berval *mapped_valsp[2]; + + mapped_valsp[0] = &mapped_vals[0]; + mapped_valsp[1] = &mapped_vals[1]; + + mapped_vals[0] = op->orc_ava->aa_value; + + rc = rwm_dnattr_rewrite( op, rs, "compareAttrDN", NULL, mapped_valsp ); + + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "compareAttrDN massage error" ); + return -1; + } + + if ( mapped_vals[ 0 ].bv_val != op->orc_ava->aa_value.bv_val ) { + /* NOTE: if we get here, rwm_dnattr_rewrite() + * already freed the old value, so now + * it's invalid */ + ber_dupbv_x( &op->orc_ava->aa_value, &mapped_vals[0], + op->o_tmpmemctx ); + ber_memfree_x( mapped_vals[ 0 ].bv_val, NULL ); + } + } + op->orc_ava->aa_desc = ad; + } + + op->o_callback = &roc->cb; + + return SLAP_CB_CONTINUE; +} + +static int +rwm_op_delete( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + int rc; + + rwm_op_cb *roc = rwm_callback_get( op ); + + rc = rwm_op_dn_massage( op, rs, "deleteDN", &roc->ros ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "deleteDN massage error" ); + return -1; + } + + op->o_callback = &roc->cb; + + return SLAP_CB_CONTINUE; +} + +static int +rwm_op_modify( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + int isupdate; + Modifications **mlp; + int rc; + + rwm_op_cb *roc = rwm_callback_get( op ); + + rc = rwm_op_dn_massage( op, rs, "modifyDN", &roc->ros ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "modifyDN massage error" ); + return -1; + } + + isupdate = be_shadow_update( op ); + for ( mlp = &op->orm_modlist; *mlp; ) { + int is_oc = 0; + Modifications *ml = *mlp; + struct ldapmapping *mapping = NULL; + + /* ml points to a temporary mod until needs duplication */ + if ( ml->sml_desc == slap_schema.si_ad_objectClass + || ml->sml_desc == slap_schema.si_ad_structuralObjectClass ) + { + is_oc = 1; + + } else if ( !isupdate && !get_relax( op ) && ml->sml_desc->ad_type->sat_no_user_mod ) + { + ml = ch_malloc( sizeof( Modifications ) ); + *ml = **mlp; + if ( (*mlp)->sml_values ) { + ber_bvarray_dup_x( &ml->sml_values, (*mlp)->sml_values, NULL ); + if ( (*mlp)->sml_nvalues ) { + ber_bvarray_dup_x( &ml->sml_nvalues, (*mlp)->sml_nvalues, NULL ); + } + } + *mlp = ml; + goto next_mod; + + } else { + int drop_missing; + + drop_missing = rwm_mapping( &rwmap->rwm_at, + &ml->sml_desc->ad_cname, + &mapping, RWM_MAP ); + if ( drop_missing || ( mapping != NULL && BER_BVISNULL( &mapping->m_dst ) ) ) + { + goto skip_mod; + } + } + + /* duplicate the modlist */ + ml = ch_malloc( sizeof( Modifications )); + *ml = **mlp; + *mlp = ml; + + if ( ml->sml_values != NULL ) { + int i, num; + struct berval *bva; + + for ( num = 0; !BER_BVISNULL( &ml->sml_values[ num ] ); num++ ) + /* count values */ ; + + bva = ch_malloc( (num+1) * sizeof( struct berval )); + for (i=0; i<num; i++) + ber_dupbv( &bva[i], &ml->sml_values[i] ); + BER_BVZERO( &bva[i] ); + ml->sml_values = bva; + + if ( ml->sml_nvalues ) { + bva = ch_malloc( (num+1) * sizeof( struct berval )); + for (i=0; i<num; i++) + ber_dupbv( &bva[i], &ml->sml_nvalues[i] ); + BER_BVZERO( &bva[i] ); + ml->sml_nvalues = bva; + } + + if ( is_oc ) { + int last, j; + + last = num-1; + + for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ ) { + struct ldapmapping *oc_mapping = NULL; + + ( void )rwm_mapping( &rwmap->rwm_oc, &ml->sml_values[ j ], + &oc_mapping, RWM_MAP ); + if ( oc_mapping == NULL ) { + if ( rwmap->rwm_at.drop_missing ) { + /* FIXME: we allow to remove objectClasses as well; + * if the resulting entry is inconsistent, that's + * the relayed database's business... + */ + if ( last > j ) { + ch_free( ml->sml_values[ j ].bv_val ); + ml->sml_values[ j ] = ml->sml_values[ last ]; + } + BER_BVZERO( &ml->sml_values[ last ] ); + last--; + j--; + } + + } else { + ch_free( ml->sml_values[ j ].bv_val ); + ber_dupbv( &ml->sml_values[ j ], &oc_mapping->m_dst ); + } + } + + } else { + if ( ml->sml_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName + || ( mapping != NULL && mapping->m_dst_ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) ) + { + rc = rwm_dnattr_rewrite( op, rs, "modifyAttrDN", + ml->sml_values, + ml->sml_nvalues ? &ml->sml_nvalues : NULL ); + + } else if ( ml->sml_desc == slap_schema.si_ad_ref ) { + rc = rwm_referral_rewrite( op, rs, + "referralAttrDN", + ml->sml_values, + ml->sml_nvalues ? &ml->sml_nvalues : NULL ); + if ( rc != LDAP_SUCCESS ) { + goto cleanup_mod; + } + } + + if ( rc != LDAP_SUCCESS ) { + goto cleanup_mod; + } + } + } + +next_mod:; + if ( mapping != NULL ) { + /* use new attribute description */ + assert( mapping->m_dst_ad != NULL ); + ml->sml_desc = mapping->m_dst_ad; + } + + mlp = &ml->sml_next; + continue; + +skip_mod:; + *mlp = (*mlp)->sml_next; + continue; + +cleanup_mod:; + ml = *mlp; + *mlp = (*mlp)->sml_next; + slap_mod_free( &ml->sml_mod, 0 ); + free( ml ); + } + + op->o_callback = &roc->cb; + + return SLAP_CB_CONTINUE; +} + +static int +rwm_op_modrdn( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + int rc; + dncookie dc; + + rwm_op_cb *roc = rwm_callback_get( op ); + + if ( op->orr_newSup ) { + struct berval nnewSup = BER_BVNULL; + struct berval newSup = BER_BVNULL; + + /* + * Rewrite the new superior, if defined and required + */ + dc.rwmap = rwmap; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "newSuperiorDN"; + newSup = *op->orr_newSup; + nnewSup = *op->orr_nnewSup; + rc = rwm_dn_massage_pretty_normalize( &dc, op->orr_newSup, &newSup, &nnewSup ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "newSuperiorDN massage error" ); + return -1; + } + + if ( op->orr_newSup->bv_val != newSup.bv_val ) { + op->orr_newSup = op->o_tmpalloc( sizeof( struct berval ), + op->o_tmpmemctx ); + op->orr_nnewSup = op->o_tmpalloc( sizeof( struct berval ), + op->o_tmpmemctx ); + *op->orr_newSup = newSup; + *op->orr_nnewSup = nnewSup; + } + } + + /* + * Rewrite the newRDN, if needed + */ + { + struct berval newrdn = BER_BVNULL; + struct berval nnewrdn = BER_BVNULL; + + dc.rwmap = rwmap; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "newRDN"; + newrdn = op->orr_newrdn; + nnewrdn = op->orr_nnewrdn; + rc = rwm_dn_massage_pretty_normalize( &dc, &op->orr_newrdn, &newrdn, &nnewrdn ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "newRDN massage error" ); + goto err; + } + + if ( op->orr_newrdn.bv_val != newrdn.bv_val ) { + op->orr_newrdn = newrdn; + op->orr_nnewrdn = nnewrdn; + } + } + + /* + * Rewrite the dn, if needed + */ + rc = rwm_op_dn_massage( op, rs, "renameDN", &roc->ros ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "renameDN massage error" ); + goto err; + } + + op->o_callback = &roc->cb; + + rc = SLAP_CB_CONTINUE; + + if ( 0 ) { +err:; + if ( op->orr_newSup != roc->ros.orr_newSup ) { + ch_free( op->orr_newSup->bv_val ); + ch_free( op->orr_nnewSup->bv_val ); + op->o_tmpfree( op->orr_newSup, op->o_tmpmemctx ); + op->o_tmpfree( op->orr_nnewSup, op->o_tmpmemctx ); + op->orr_newSup = roc->ros.orr_newSup; + op->orr_nnewSup = roc->ros.orr_nnewSup; + } + + if ( op->orr_newrdn.bv_val != roc->ros.orr_newrdn.bv_val ) { + ch_free( op->orr_newrdn.bv_val ); + ch_free( op->orr_nnewrdn.bv_val ); + op->orr_newrdn = roc->ros.orr_newrdn; + op->orr_nnewrdn = roc->ros.orr_nnewrdn; + } + } + + return rc; +} + + +static int +rwm_swap_attrs( Operation *op, SlapReply *rs ) +{ + slap_callback *cb = op->o_callback; + rwm_op_state *ros = cb->sc_private; + + rs->sr_attrs = ros->ors_attrs; + + /* other overlays might have touched op->ors_attrs, + * so we restore the original version here, otherwise + * attribute-mapping might fail */ + op->ors_attrs = ros->mapped_attrs; + + return SLAP_CB_CONTINUE; +} + +/* + * NOTE: this implementation of get/release entry is probably far from + * optimal. The rationale consists in intercepting the request directed + * to the underlying database, in order to rewrite/remap the request, + * perform it using the modified data, duplicate the resulting entry + * and finally free it when release is called. + * This implies that subsequent overlays are not called, as the request + * is directly shunted to the underlying database. + */ +static int +rwm_entry_release_rw( Operation *op, Entry *e, int rw ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + + /* can't be ours */ + if ( ((BackendInfo *)on->on_info->oi_orig)->bi_entry_get_rw == NULL ) { + return SLAP_CB_CONTINUE; + } + + /* just free entry if (probably) ours */ + if ( e->e_private == NULL && BER_BVISNULL( &e->e_bv ) ) { + entry_free( e ); + return LDAP_SUCCESS; + } + + return SLAP_CB_CONTINUE; +} + +static int +rwm_entry_get_rw( Operation *op, struct berval *ndn, + ObjectClass *oc, AttributeDescription *at, int rw, Entry **ep ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + int rc; + BackendDB db; + Operation op2; + SlapReply rs = { REP_SEARCH }; + + rwm_op_state ros = { 0 }; + struct berval mndn = BER_BVNULL; + + if ( ((BackendInfo *)on->on_info->oi_orig)->bi_entry_get_rw == NULL ) { + return SLAP_CB_CONTINUE; + } + + /* massage DN */ + op2.o_tag = LDAP_REQ_SEARCH; + op2 = *op; + op2.o_req_dn = *ndn; + op2.o_req_ndn = *ndn; + rc = rwm_op_dn_massage( &op2, &rs, "searchDN", &ros ); + if ( rc != LDAP_SUCCESS ) { + return LDAP_OTHER; + } + + mndn = BER_BVISNULL( &ros.r_ndn ) ? *ndn : ros.r_ndn; + + /* map attribute & objectClass */ + if ( at != NULL ) { + } + + if ( oc != NULL ) { + } + + /* fetch entry */ + db = *op->o_bd; + op2.o_bd = &db; + op2.o_bd->bd_info = (BackendInfo *)on->on_info->oi_orig; + op2.ors_attrs = slap_anlist_all_attributes; + rc = op2.o_bd->bd_info->bi_entry_get_rw( &op2, &mndn, oc, at, rw, ep ); + if ( rc == LDAP_SUCCESS && *ep != NULL ) { + /* we assume be_entry_release() needs to be called */ + rs.sr_flags = REP_ENTRY_MUSTRELEASE; + rs.sr_entry = *ep; + + /* duplicate & release */ + op2.o_bd->bd_info = (BackendInfo *)on; + rc = rwm_send_entry( &op2, &rs ); + RS_ASSERT( rs.sr_flags & REP_ENTRY_MUSTFLUSH ); + if ( rc == SLAP_CB_CONTINUE ) { + *ep = rs.sr_entry; + rc = LDAP_SUCCESS; + } else { + assert( rc != LDAP_SUCCESS && rs.sr_entry == *ep ); + *ep = NULL; + op2.o_bd->bd_info = (BackendInfo *)on->on_info; + be_entry_release_r( &op2, rs.sr_entry ); + op2.o_bd->bd_info = (BackendInfo *)on; + } + } + + if ( !BER_BVISNULL( &ros.r_ndn) && ros.r_ndn.bv_val != ndn->bv_val ) { + op->o_tmpfree( ros.r_ndn.bv_val, op->o_tmpmemctx ); + } + + return rc; +} + +static int +rwm_op_search( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + int rc; + dncookie dc; + + struct berval fstr = BER_BVNULL; + Filter *f = NULL; + + AttributeName *an = NULL; + + char *text = NULL; + + rwm_op_cb *roc = rwm_callback_get( op ); + + rc = rewrite_session_var_set( rwmap->rwm_rw, op->o_conn, + "searchFilter", op->ors_filterstr.bv_val ); + if ( rc == LDAP_SUCCESS ) + rc = rwm_op_dn_massage( op, rs, "searchDN", &roc->ros ); + if ( rc != LDAP_SUCCESS ) { + text = "searchDN massage error"; + goto error_return; + } + + /* + * Rewrite the dn if needed + */ + dc.rwmap = rwmap; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "searchFilterAttrDN"; + + rc = rwm_filter_map_rewrite( op, &dc, op->ors_filter, &fstr ); + if ( rc != LDAP_SUCCESS ) { + text = "searchFilter/searchFilterAttrDN massage error"; + goto error_return; + } + + f = str2filter_x( op, fstr.bv_val ); + + if ( f == NULL ) { + text = "massaged filter parse error"; + goto error_return; + } + + op->ors_filter = f; + op->ors_filterstr = fstr; + + rc = rwm_map_attrnames( op, &rwmap->rwm_at, &rwmap->rwm_oc, + op->ors_attrs, &an, RWM_MAP ); + if ( rc != LDAP_SUCCESS ) { + text = "attribute list mapping error"; + goto error_return; + } + + op->ors_attrs = an; + /* store the mapped Attributes for later usage, in + * the case that other overlays change op->ors_attrs */ + roc->ros.mapped_attrs = an; + roc->cb.sc_response = rwm_swap_attrs; + + op->o_callback = &roc->cb; + + return SLAP_CB_CONTINUE; + +error_return:; + if ( an != NULL ) { + ch_free( an ); + } + + if ( f != NULL ) { + filter_free_x( op, f, 1 ); + } + + if ( !BER_BVISNULL( &fstr ) ) { + op->o_tmpfree( fstr.bv_val, op->o_tmpmemctx ); + } + + rwm_op_rollback( op, rs, &roc->ros ); + op->oq_search = roc->ros.oq_search; + op->o_tmpfree( roc, op->o_tmpmemctx ); + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, text ); + + return -1; + +} + +static int +rwm_exop_passwd( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + int rc; + rwm_op_cb *roc; + + struct berval id = BER_BVNULL, + pwold = BER_BVNULL, + pwnew = BER_BVNULL; + BerElement *ber = NULL; + + if ( !BER_BVISNULL( &op->o_req_ndn ) ) { + return LDAP_SUCCESS; + } + + if ( !SLAP_ISGLOBALOVERLAY( op->o_bd ) ) { + rs->sr_err = LDAP_OTHER; + return rs->sr_err; + } + + rs->sr_err = slap_passwd_parse( op->ore_reqdata, &id, + &pwold, &pwnew, &rs->sr_text ); + if ( rs->sr_err != LDAP_SUCCESS ) { + return rs->sr_err; + } + + if ( !BER_BVISNULL( &id ) ) { + char idNul = id.bv_val[id.bv_len]; + id.bv_val[id.bv_len] = '\0'; + rs->sr_err = dnPrettyNormal( NULL, &id, &op->o_req_dn, + &op->o_req_ndn, op->o_tmpmemctx ); + id.bv_val[id.bv_len] = idNul; + if ( rs->sr_err != LDAP_SUCCESS ) { + rs->sr_text = "Invalid DN"; + return rs->sr_err; + } + + } else { + ber_dupbv_x( &op->o_req_dn, &op->o_dn, op->o_tmpmemctx ); + ber_dupbv_x( &op->o_req_ndn, &op->o_ndn, op->o_tmpmemctx ); + } + + roc = rwm_callback_get( op ); + + rc = rwm_op_dn_massage( op, rs, "extendedDN", &roc->ros ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "extendedDN massage error" ); + return -1; + } + + ber = ber_alloc_t( LBER_USE_DER ); + if ( !ber ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "No memory"; + return rs->sr_err; + } + ber_printf( ber, "{" ); + if ( !BER_BVISNULL( &id )) { + ber_printf( ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_ID, + &op->o_req_dn ); + } + if ( !BER_BVISNULL( &pwold )) { + ber_printf( ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_OLD, &pwold ); + } + if ( !BER_BVISNULL( &pwnew )) { + ber_printf( ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_NEW, &pwnew ); + } + ber_printf( ber, "N}" ); + ber_flatten( ber, &op->ore_reqdata ); + ber_free( ber, 1 ); + + op->o_callback = &roc->cb; + + return SLAP_CB_CONTINUE; +} + +static struct exop { + struct berval oid; + BI_op_extended *extended; +} exop_table[] = { + { BER_BVC(LDAP_EXOP_MODIFY_PASSWD), rwm_exop_passwd }, + { BER_BVNULL, NULL } +}; + +static int +rwm_extended( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + int rc; + rwm_op_cb *roc; + + int i; + + for ( i = 0; exop_table[i].extended != NULL; i++ ) { + if ( bvmatch( &exop_table[i].oid, &op->oq_extended.rs_reqoid ) ) + { + rc = exop_table[i].extended( op, rs ); + switch ( rc ) { + case LDAP_SUCCESS: + break; + + case SLAP_CB_CONTINUE: + case SLAPD_ABANDON: + return rc; + + default: + send_ldap_result( op, rs ); + return rc; + } + break; + } + } + + roc = rwm_callback_get( op ); + + rc = rwm_op_dn_massage( op, rs, "extendedDN", &roc->ros ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "extendedDN massage error" ); + return -1; + } + + /* TODO: rewrite/map extended data ? ... */ + op->o_callback = &roc->cb; + + return SLAP_CB_CONTINUE; +} + +static void +rwm_matched( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + struct berval dn, mdn; + dncookie dc; + int rc; + + if ( rs->sr_matched == NULL ) { + return; + } + + dc.rwmap = rwmap; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "matchedDN"; + ber_str2bv( rs->sr_matched, 0, 0, &dn ); + mdn = dn; + rc = rwm_dn_massage_pretty( &dc, &dn, &mdn ); + if ( rc != LDAP_SUCCESS ) { + rs->sr_err = rc; + rs->sr_text = "Rewrite error"; + + } else if ( mdn.bv_val != dn.bv_val ) { + if ( rs->sr_flags & REP_MATCHED_MUSTBEFREED ) { + ch_free( (void *)rs->sr_matched ); + + } else { + rs->sr_flags |= REP_MATCHED_MUSTBEFREED; + } + rs->sr_matched = mdn.bv_val; + } +} + +static int +rwm_attrs( Operation *op, SlapReply *rs, Attribute** a_first, int stripEntryDN ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + dncookie dc; + int rc; + Attribute **ap; + int isupdate; + int check_duplicate_attrs = 0; + + /* + * Rewrite the dn attrs, if needed + */ + dc.rwmap = rwmap; + dc.conn = op->o_conn; + dc.rs = NULL; + + /* FIXME: the entries are in the remote mapping form; + * so we need to select those attributes we are willing + * to return, and remap them accordingly */ + + /* FIXME: in principle, one could map an attribute + * on top of another, which already exists. + * As such, in the end there might exist more than + * one instance of an attribute. + * We should at least check if this occurs, and issue + * an error (because multiple instances of attrs in + * response are not valid), or merge the values (what + * about duplicate values?) */ + isupdate = be_shadow_update( op ); + for ( ap = a_first; *ap; ) { + struct ldapmapping *mapping = NULL; + int drop_missing; + int last = -1; + Attribute *a; + + if ( ( rwmap->rwm_flags & RWM_F_DROP_UNREQUESTED_ATTRS ) && + op->ors_attrs != NULL && + !SLAP_USERATTRS( rs->sr_attr_flags ) && + !ad_inlist( (*ap)->a_desc, op->ors_attrs ) ) + { + goto cleanup_attr; + } + + drop_missing = rwm_mapping( &rwmap->rwm_at, + &(*ap)->a_desc->ad_cname, &mapping, RWM_REMAP ); + if ( drop_missing || ( mapping != NULL && BER_BVISEMPTY( &mapping->m_dst ) ) ) + { + goto cleanup_attr; + } + if ( mapping != NULL ) { + assert( mapping->m_dst_ad != NULL ); + + /* try to normalize mapped Attributes if the original + * AttributeType was not normalized */ + if ( (!(*ap)->a_desc->ad_type->sat_equality || + !(*ap)->a_desc->ad_type->sat_equality->smr_normalize) && + mapping->m_dst_ad->ad_type->sat_equality && + mapping->m_dst_ad->ad_type->sat_equality->smr_normalize ) + { + if ((rwmap->rwm_flags & RWM_F_NORMALIZE_MAPPED_ATTRS)) + { + int i = 0; + + last = (*ap)->a_numvals; + if ( last ) + { + (*ap)->a_nvals = ch_malloc( (last+1) * sizeof(struct berval) ); + + for ( i = 0; !BER_BVISNULL( &(*ap)->a_vals[i]); i++ ) { + int rc; + /* + * check that each value is valid per syntax + * and pretty if appropriate + */ + rc = mapping->m_dst_ad->ad_type->sat_equality->smr_normalize( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + mapping->m_dst_ad->ad_type->sat_syntax, + mapping->m_dst_ad->ad_type->sat_equality, + &(*ap)->a_vals[i], &(*ap)->a_nvals[i], + NULL ); + + if ( rc != LDAP_SUCCESS ) { + /* FIXME: this is wrong, putting a non-normalized value + * into nvals. But when a proxy sends us bogus data, + * we still need to give it to the client, even if it + * violates the syntax. I.e., we don't want to silently + * drop things and trigger an apparent data loss. + */ + ber_dupbv( &(*ap)->a_nvals[i], &(*ap)->a_vals[i] ); + } + } + BER_BVZERO( &(*ap)->a_nvals[i] ); + } + + } else { + assert( (*ap)->a_nvals == (*ap)->a_vals ); + (*ap)->a_nvals = NULL; + ber_bvarray_dup_x( &(*ap)->a_nvals, (*ap)->a_vals, NULL ); + } + } + + /* rewrite the attribute description */ + (*ap)->a_desc = mapping->m_dst_ad; + + /* will need to check for duplicate attrs */ + check_duplicate_attrs++; + } + + if ( (*ap)->a_desc == slap_schema.si_ad_entryDN ) { + if ( stripEntryDN ) { + /* will be generated by frontend */ + goto cleanup_attr; + } + + } else if ( !isupdate + && !get_relax( op ) + && (*ap)->a_desc->ad_type->sat_no_user_mod + && (*ap)->a_desc->ad_type != slap_schema.si_at_undefined ) + { + goto next_attr; + } + + if ( last == -1 ) { /* not yet counted */ + last = (*ap)->a_numvals; + } + + if ( last == 0 ) { + /* empty? leave it in place because of attrsonly and vlv */ + goto next_attr; + } + last--; + + if ( (*ap)->a_desc == slap_schema.si_ad_objectClass + || (*ap)->a_desc == slap_schema.si_ad_structuralObjectClass ) + { + struct berval *bv; + + for ( bv = (*ap)->a_vals; !BER_BVISNULL( bv ); bv++ ) { + struct berval mapped; + + rwm_map( &rwmap->rwm_oc, &bv[0], &mapped, RWM_REMAP ); + if ( BER_BVISNULL( &mapped ) || BER_BVISEMPTY( &mapped ) ) { +remove_oc:; + ch_free( bv[0].bv_val ); + BER_BVZERO( &bv[0] ); + if ( &(*ap)->a_vals[last] > &bv[0] ) { + bv[0] = (*ap)->a_vals[last]; + BER_BVZERO( &(*ap)->a_vals[last] ); + } + last--; + bv--; + + } else if ( mapped.bv_val != bv[0].bv_val + && ber_bvstrcasecmp( &mapped, &bv[0] ) != 0 ) + { + int i; + + for ( i = 0; !BER_BVISNULL( &(*ap)->a_vals[ i ] ); i++ ) { + if ( &(*ap)->a_vals[ i ] == bv ) { + continue; + } + + if ( ber_bvstrcasecmp( &mapped, &(*ap)->a_vals[ i ] ) == 0 ) { + break; + } + } + + if ( !BER_BVISNULL( &(*ap)->a_vals[ i ] ) ) { + goto remove_oc; + } + + /* + * FIXME: after LBER_FREEing + * the value is replaced by + * ch_alloc'ed memory + */ + ber_bvreplace( &bv[0], &mapped ); + + /* FIXME: will need to check + * if the structuralObjectClass + * changed */ + } + } + + /* + * It is necessary to try to rewrite attributes with + * dn syntax because they might be used in ACLs as + * members of groups; since ACLs are applied to the + * rewritten stuff, no dn-based subject clause could + * be used at the ldap backend side (see + * http://www.OpenLDAP.org/faq/data/cache/452.html) + * The problem can be overcome by moving the dn-based + * ACLs to the target directory server, and letting + * everything pass thru the ldap backend. */ + /* FIXME: handle distinguishedName-like syntaxes, like + * nameAndOptionalUID */ + } else if ( (*ap)->a_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName + || ( mapping != NULL && mapping->m_src_ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) ) + { + dc.ctx = "searchAttrDN"; + rc = rwm_dnattr_result_rewrite( &dc, (*ap)->a_vals, (*ap)->a_nvals ); + if ( rc != LDAP_SUCCESS ) { + goto cleanup_attr; + } + + } else if ( (*ap)->a_desc == slap_schema.si_ad_ref ) { + dc.ctx = "searchAttrDN"; + rc = rwm_referral_result_rewrite( &dc, (*ap)->a_vals ); + if ( rc != LDAP_SUCCESS ) { + goto cleanup_attr; + } + } + + +next_attr:; + ap = &(*ap)->a_next; + continue; + +cleanup_attr:; + a = *ap; + *ap = (*ap)->a_next; + + attr_free( a ); + } + + /* only check if some mapping occurred */ + if ( check_duplicate_attrs ) { + for ( ap = a_first; *ap != NULL; ap = &(*ap)->a_next ) { + Attribute **tap; + + for ( tap = &(*ap)->a_next; *tap != NULL; ) { + if ( (*tap)->a_desc == (*ap)->a_desc ) { + Entry e = { 0 }; + Modification mod = { 0 }; + const char *text = NULL; + char textbuf[ SLAP_TEXT_BUFLEN ]; + Attribute *next = (*tap)->a_next; + + BER_BVSTR( &e.e_name, "" ); + BER_BVSTR( &e.e_nname, "" ); + e.e_attrs = *ap; + mod.sm_op = LDAP_MOD_ADD; + mod.sm_desc = (*ap)->a_desc; + mod.sm_type = mod.sm_desc->ad_cname; + mod.sm_numvals = (*tap)->a_numvals; + mod.sm_values = (*tap)->a_vals; + if ( (*tap)->a_nvals != (*tap)->a_vals ) { + mod.sm_nvalues = (*tap)->a_nvals; + } + + (void)modify_add_values( &e, &mod, + /* permissive */ 1, + &text, textbuf, sizeof( textbuf ) ); + + /* should not insert new attrs! */ + assert( e.e_attrs == *ap ); + + attr_free( *tap ); + *tap = next; + + } else { + tap = &(*tap)->a_next; + } + } + } + } + + return 0; +} + +/* Should return SLAP_CB_CONTINUE or failure, never LDAP_SUCCESS. */ +static int +rwm_send_entry( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + Entry *e = NULL; + struct berval dn = BER_BVNULL, + ndn = BER_BVNULL; + dncookie dc; + int rc; + + assert( rs->sr_entry != NULL ); + + /* + * Rewrite the dn of the result, if needed + */ + dc.rwmap = rwmap; + dc.conn = op->o_conn; + dc.rs = NULL; + dc.ctx = "searchEntryDN"; + + e = rs->sr_entry; + if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE ) ) { + /* FIXME: all we need to duplicate are: + * - dn + * - ndn + * - attributes that are requested + * - no values if attrsonly is set + */ + e = entry_dup( e ); + if ( e == NULL ) { + rc = LDAP_NO_MEMORY; + goto fail; + } + } else if ( rs->sr_flags & REP_ENTRY_MUSTRELEASE ) { + /* ITS#6423: REP_ENTRY_MUSTRELEASE incompatible + * with REP_ENTRY_MODIFIABLE */ + RS_ASSERT( 0 ); + rc = 1; + goto fail; + } + + /* + * Note: this may fail if the target host(s) schema differs + * from the one known to the meta, and a DN with unknown + * attributes is returned. + */ + dn = e->e_name; + ndn = e->e_nname; + rc = rwm_dn_massage_pretty_normalize( &dc, &e->e_name, &dn, &ndn ); + if ( rc != LDAP_SUCCESS ) { + rc = 1; + goto fail; + } + + if ( e->e_name.bv_val != dn.bv_val ) { + ch_free( e->e_name.bv_val ); + ch_free( e->e_nname.bv_val ); + + e->e_name = dn; + e->e_nname = ndn; + } + + /* TODO: map entry attribute types, objectclasses + * and dn-valued attribute values */ + + /* FIXME: the entries are in the remote mapping form; + * so we need to select those attributes we are willing + * to return, and remap them accordingly */ + (void)rwm_attrs( op, rs, &e->e_attrs, 1 ); + + if ( e != rs->sr_entry ) { + /* Reimplementing rs_replace_entry(), I suppose to + * bypass our own dubious rwm_entry_release_rw() */ + if ( rs->sr_flags & REP_ENTRY_MUSTRELEASE ) { + rs->sr_flags ^= REP_ENTRY_MUSTRELEASE; + op->o_bd->bd_info = (BackendInfo *)on->on_info; + be_entry_release_r( op, rs->sr_entry ); + op->o_bd->bd_info = (BackendInfo *)on; + } else if ( rs->sr_flags & REP_ENTRY_MUSTBEFREED ) { + entry_free( rs->sr_entry ); + } + rs->sr_entry = e; + rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED; + } + + return SLAP_CB_CONTINUE; + +fail:; + if ( e != NULL && e != rs->sr_entry ) { + if ( e->e_name.bv_val == dn.bv_val ) { + BER_BVZERO( &e->e_name ); + } + + if ( e->e_nname.bv_val == ndn.bv_val ) { + BER_BVZERO( &e->e_nname ); + } + + entry_free( e ); + } + + if ( !BER_BVISNULL( &dn ) ) { + ch_free( dn.bv_val ); + } + + if ( !BER_BVISNULL( &ndn ) ) { + ch_free( ndn.bv_val ); + } + + return rc; +} + +static int +rwm_operational( Operation *op, SlapReply *rs ) +{ + /* FIXME: the entries are in the remote mapping form; + * so we need to select those attributes we are willing + * to return, and remap them accordingly */ + if ( rs->sr_operational_attrs ) { + rwm_attrs( op, rs, &rs->sr_operational_attrs, 1 ); + } + + return SLAP_CB_CONTINUE; +} + +#if 0 +/* don't use this; it cannot be reverted, and leaves op->o_req_dn + * rewritten for subsequent operations; fine for plain suffixmassage, + * but destroys everything else */ +static int +rwm_chk_referrals( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + int rc; + + rc = rwm_op_dn_massage( op, rs, "referralCheckDN" ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "referralCheckDN massage error" ); + return -1; + } + + return SLAP_CB_CONTINUE; +} +#endif + +static int +rwm_rw_config( + BackendDB *be, + const char *fname, + int lineno, + int argc, + char **argv ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + return rewrite_parse( rwmap->rwm_rw, + fname, lineno, argc, argv ); + + return 0; +} + +static int +rwm_suffixmassage_config( + BackendDB *be, + const char *fname, + int lineno, + int argc, + char **argv ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + struct berval bvnc, nvnc, pvnc, brnc, nrnc, prnc; + int massaged; + int rc; + + /* + * syntax: + * + * suffixmassage [<suffix>] <massaged suffix> + * + * the [<suffix>] field must be defined as a valid suffix + * for the current database; + * the <massaged suffix> shouldn't have already been + * defined as a valid suffix for the current server + */ + if ( argc == 2 ) { + if ( be->be_suffix == NULL ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + " \"suffixMassage [<suffix>]" + " <massaged suffix>\" without " + "<suffix> part requires database " + "suffix be defined first.\n", + fname, lineno, 0 ); + return 1; + } + bvnc = be->be_suffix[ 0 ]; + massaged = 1; + + } else if ( argc == 3 ) { + ber_str2bv( argv[ 1 ], 0, 0, &bvnc ); + massaged = 2; + + } else { + Debug( LDAP_DEBUG_ANY, "%s: line %d: syntax is" + " \"suffixMassage [<suffix>]" + " <massaged suffix>\"\n", + fname, lineno, 0 ); + return 1; + } + + if ( dnPrettyNormal( NULL, &bvnc, &pvnc, &nvnc, NULL ) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: suffix DN %s is invalid\n", + fname, lineno, bvnc.bv_val ); + return 1; + } + + ber_str2bv( argv[ massaged ], 0, 0, &brnc ); + if ( dnPrettyNormal( NULL, &brnc, &prnc, &nrnc, NULL ) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: suffix DN %s is invalid\n", + fname, lineno, brnc.bv_val ); + free( nvnc.bv_val ); + free( pvnc.bv_val ); + return 1; + } + + /* + * The suffix massaging is emulated + * by means of the rewrite capabilities + */ + rc = rwm_suffix_massage_config( rwmap->rwm_rw, + &pvnc, &nvnc, &prnc, &nrnc ); + free( nvnc.bv_val ); + free( pvnc.bv_val ); + free( nrnc.bv_val ); + free( prnc.bv_val ); + + return rc; +} + +static int +rwm_m_config( + BackendDB *be, + const char *fname, + int lineno, + int argc, + char **argv ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + /* objectclass/attribute mapping */ + return rwm_map_config( &rwmap->rwm_oc, + &rwmap->rwm_at, + fname, lineno, argc, argv ); +} + +static int +rwm_response( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + int rc; + + if ( op->o_tag == LDAP_REQ_SEARCH && rs->sr_type == REP_SEARCH ) { + return rwm_send_entry( op, rs ); + } + + switch( op->o_tag ) { + case LDAP_REQ_SEARCH: + case LDAP_REQ_BIND: + case LDAP_REQ_ADD: + case LDAP_REQ_DELETE: + case LDAP_REQ_MODRDN: + case LDAP_REQ_MODIFY: + case LDAP_REQ_COMPARE: + case LDAP_REQ_EXTENDED: + if ( rs->sr_ref ) { + dncookie dc; + + /* + * Rewrite the dn of the referrals, if needed + */ + dc.rwmap = rwmap; + dc.conn = op->o_conn; + dc.rs = NULL; + dc.ctx = "referralDN"; + rc = rwm_referral_result_rewrite( &dc, rs->sr_ref ); + /* FIXME: impossible, so far */ + if ( rc != LDAP_SUCCESS ) { + rs->sr_err = rc; + break; + } + } + + rwm_matched( op, rs ); + break; + } + + return SLAP_CB_CONTINUE; +} + +static int +rwm_db_config( + BackendDB *be, + const char *fname, + int lineno, + int argc, + char **argv ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + int rc = 0; + char *argv0 = NULL; + + if ( strncasecmp( argv[ 0 ], "rwm-", STRLENOF( "rwm-" ) ) == 0 ) { + argv0 = argv[ 0 ]; + argv[ 0 ] = &argv0[ STRLENOF( "rwm-" ) ]; + } + + if ( strncasecmp( argv[0], "rewrite", STRLENOF("rewrite") ) == 0 ) { + rc = rwm_rw_config( be, fname, lineno, argc, argv ); + + } else if ( strcasecmp( argv[0], "map" ) == 0 ) { + rc = rwm_m_config( be, fname, lineno, argc, argv ); + + } else if ( strcasecmp( argv[0], "suffixmassage" ) == 0 ) { + rc = rwm_suffixmassage_config( be, fname, lineno, argc, argv ); + + } else if ( strcasecmp( argv[0], "t-f-support" ) == 0 ) { + if ( argc != 2 ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: \"t-f-support {no|yes|discover}\" needs 1 argument.\n", + fname, lineno, 0 ); + return( 1 ); + } + + if ( strcasecmp( argv[ 1 ], "no" ) == 0 ) { + rwmap->rwm_flags &= ~(RWM_F_SUPPORT_T_F_MASK2); + + } else if ( strcasecmp( argv[ 1 ], "yes" ) == 0 ) { + rwmap->rwm_flags |= RWM_F_SUPPORT_T_F; + + /* TODO: not implemented yet */ + } else if ( strcasecmp( argv[ 1 ], "discover" ) == 0 ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: \"discover\" not supported yet " + "in \"t-f-support {no|yes|discover}\".\n", + fname, lineno, 0 ); + return( 1 ); +#if 0 + rwmap->rwm_flags |= RWM_F_SUPPORT_T_F_DISCOVER; +#endif + + } else { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: unknown value \"%s\" for \"t-f-support {no|yes|discover}\".\n", + fname, lineno, argv[ 1 ] ); + return 1; + } + + } else if ( strcasecmp( argv[0], "normalize-mapped-attrs" ) == 0 ) { + if ( argc !=2 ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: \"normalize-mapped-attrs {no|yes}\" needs 1 argument.\n", + fname, lineno, 0 ); + return( 1 ); + } + + if ( strcasecmp( argv[ 1 ], "no" ) == 0 ) { + rwmap->rwm_flags &= ~(RWM_F_NORMALIZE_MAPPED_ATTRS); + + } else if ( strcasecmp( argv[ 1 ], "yes" ) == 0 ) { + rwmap->rwm_flags |= RWM_F_NORMALIZE_MAPPED_ATTRS; + } + + } else { + rc = SLAP_CONF_UNKNOWN; + } + + if ( argv0 ) { + argv[ 0 ] = argv0; + } + + return rc; +} + +/* + * dynamic configuration... + */ + +enum { + /* rewrite */ + RWM_CF_REWRITE = 1, + + /* map */ + RWM_CF_MAP, + RWM_CF_T_F_SUPPORT, + RWM_CF_NORMALIZE_MAPPED, + RWM_CF_DROP_UNREQUESTED, + + RWM_CF_LAST +}; + +static slap_verbmasks t_f_mode[] = { + { BER_BVC( "true" ), RWM_F_SUPPORT_T_F }, + { BER_BVC( "yes" ), RWM_F_SUPPORT_T_F }, + { BER_BVC( "discover" ), RWM_F_SUPPORT_T_F_DISCOVER }, + { BER_BVC( "false" ), RWM_F_NONE }, + { BER_BVC( "no" ), RWM_F_NONE }, + { BER_BVNULL, 0 } +}; + +static ConfigDriver rwm_cf_gen; + +static ConfigTable rwmcfg[] = { + { "rwm-rewrite", "rewrite", + 2, 0, STRLENOF("rwm-rewrite"), + ARG_MAGIC|RWM_CF_REWRITE, rwm_cf_gen, + "( OLcfgOvAt:16.1 NAME 'olcRwmRewrite' " + "DESC 'Rewrites strings' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "X-ORDERED 'VALUES' )", + NULL, NULL }, + + { "rwm-suffixmassage", "[virtual]> <real", + 2, 3, 0, ARG_MAGIC|RWM_CF_REWRITE, rwm_cf_gen, + NULL, NULL, NULL }, + + { "rwm-t-f-support", "true|false|discover", + 2, 2, 0, ARG_MAGIC|RWM_CF_T_F_SUPPORT, rwm_cf_gen, + "( OLcfgOvAt:16.2 NAME 'olcRwmTFSupport' " + "DESC 'Absolute filters support' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + + { "rwm-map", "{objectClass|attribute}", + 2, 4, 0, ARG_MAGIC|RWM_CF_MAP, rwm_cf_gen, + "( OLcfgOvAt:16.3 NAME 'olcRwmMap' " + "DESC 'maps attributes/objectClasses' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "X-ORDERED 'VALUES' )", + NULL, NULL }, + + { "rwm-normalize-mapped-attrs", "true|false", + 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|RWM_CF_NORMALIZE_MAPPED, rwm_cf_gen, + "( OLcfgOvAt:16.4 NAME 'olcRwmNormalizeMapped' " + "DESC 'Normalize mapped attributes/objectClasses' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + + { "rwm-drop-unrequested-attrs", "true|false", + 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|RWM_CF_DROP_UNREQUESTED, rwm_cf_gen, + "( OLcfgOvAt:16.5 NAME 'olcRwmDropUnrequested' " + "DESC 'Drop unrequested attributes' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs rwmocs[] = { + { "( OLcfgOvOc:16.1 " + "NAME 'olcRwmConfig' " + "DESC 'Rewrite/remap configuration' " + "SUP olcOverlayConfig " + "MAY ( " + "olcRwmRewrite $ " + "olcRwmTFSupport $ " + "olcRwmMap $ " + "olcRwmNormalizeMapped $ " + "olcRwmDropUnrequested" + ") )", + Cft_Overlay, rwmcfg, NULL, NULL }, + { NULL, 0, NULL } +}; + +static void +slap_bv_x_ordered_unparse( BerVarray in, BerVarray *out ) +{ + int i; + BerVarray bva = NULL; + char ibuf[32], *ptr; + struct berval idx; + + assert( in != NULL ); + + for ( i = 0; !BER_BVISNULL( &in[i] ); i++ ) + /* count'em */ ; + + if ( i == 0 ) { + return; + } + + idx.bv_val = ibuf; + + bva = ch_malloc( ( i + 1 ) * sizeof(struct berval) ); + BER_BVZERO( &bva[ 0 ] ); + + for ( i = 0; !BER_BVISNULL( &in[i] ); i++ ) { + idx.bv_len = snprintf( idx.bv_val, sizeof( ibuf ), "{%d}", i ); + if ( idx.bv_len >= sizeof( ibuf ) ) { + ber_bvarray_free( bva ); + return; + } + + bva[i].bv_len = idx.bv_len + in[i].bv_len; + bva[i].bv_val = ch_malloc( bva[i].bv_len + 1 ); + ptr = lutil_strcopy( bva[i].bv_val, ibuf ); + ptr = lutil_strcopy( ptr, in[i].bv_val ); + *ptr = '\0'; + BER_BVZERO( &bva[ i + 1 ] ); + } + + *out = bva; +} + +static int +rwm_bva_add( + BerVarray *bva, + int idx, + char **argv ) +{ + char *line; + struct berval bv; + + line = ldap_charray2str( argv, "\" \"" ); + if ( line != NULL ) { + int len = strlen( argv[ 0 ] ); + + ber_str2bv( line, 0, 0, &bv ); + AC_MEMCPY( &bv.bv_val[ len ], &bv.bv_val[ len + 1 ], + bv.bv_len - ( len + 1 ) ); + bv.bv_val[ bv.bv_len - 1 ] = '"'; + + if ( idx == -1 ) { + ber_bvarray_add( bva, &bv ); + + } else { + (*bva)[ idx ] = bv; + } + + return 0; + } + + return -1; +} + +static int +rwm_bva_rewrite_add( + struct ldaprwmap *rwmap, + int idx, + char **argv ) +{ + return rwm_bva_add( &rwmap->rwm_bva_rewrite, idx, argv ); +} + +#ifdef unused +static int +rwm_bva_map_add( + struct ldaprwmap *rwmap, + int idx, + char **argv ) +{ + return rwm_bva_add( &rwmap->rwm_bva_map, idx, argv ); +} +#endif /* unused */ + +static int +rwm_info_init( struct rewrite_info ** rwm_rw ) +{ + char *rargv[ 3 ]; + + *rwm_rw = rewrite_info_init( REWRITE_MODE_USE_DEFAULT ); + if ( *rwm_rw == NULL ) { + return -1; + } + + /* this rewriteContext by default must be null; + * rules can be added if required */ + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "searchFilter"; + rargv[ 2 ] = NULL; + rewrite_parse( *rwm_rw, "<suffix massage>", 1, 2, rargv ); + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "default"; + rargv[ 2 ] = NULL; + rewrite_parse( *rwm_rw, "<suffix massage>", 2, 2, rargv ); + + return 0; +} + +static int +rwm_cf_gen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + BackendDB db; + char *argv0; + int idx0 = 0; + int rc = 0; + + db = *c->be; + db.bd_info = c->bi; + + if ( c->op == SLAP_CONFIG_EMIT ) { + struct berval bv = BER_BVNULL; + + switch ( c->type ) { + case RWM_CF_REWRITE: + if ( rwmap->rwm_bva_rewrite == NULL ) { + rc = 1; + + } else { + slap_bv_x_ordered_unparse( rwmap->rwm_bva_rewrite, &c->rvalue_vals ); + if ( !c->rvalue_vals ) { + rc = 1; + } + } + break; + + case RWM_CF_T_F_SUPPORT: + enum_to_verb( t_f_mode, (rwmap->rwm_flags & RWM_F_SUPPORT_T_F_MASK2), &bv ); + if ( BER_BVISNULL( &bv ) ) { + /* there's something wrong... */ + assert( 0 ); + rc = 1; + + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + break; + + case RWM_CF_MAP: + if ( rwmap->rwm_bva_map == NULL ) { + rc = 1; + + } else { + slap_bv_x_ordered_unparse( rwmap->rwm_bva_map, &c->rvalue_vals ); + if ( !c->rvalue_vals ) { + rc = 1; + } + } + break; + + case RWM_CF_NORMALIZE_MAPPED: + c->value_int = ( rwmap->rwm_flags & RWM_F_NORMALIZE_MAPPED_ATTRS ); + break; + + case RWM_CF_DROP_UNREQUESTED: + c->value_int = ( rwmap->rwm_flags & RWM_F_DROP_UNREQUESTED_ATTRS ); + break; + + default: + assert( 0 ); + rc = 1; + } + + return rc; + + } else if ( c->op == LDAP_MOD_DELETE ) { + switch ( c->type ) { + case RWM_CF_REWRITE: + if ( c->valx >= 0 ) { + int i; + + for ( i = 0; !BER_BVISNULL( &rwmap->rwm_bva_rewrite[ i ] ); i++ ) + /* count'em */ ; + + if ( c->valx >= i ) { + rc = 1; + break; + } + + ber_memfree( rwmap->rwm_bva_rewrite[ c->valx ].bv_val ); + for ( i = c->valx; !BER_BVISNULL( &rwmap->rwm_bva_rewrite[ i + 1 ] ); i++ ) + { + rwmap->rwm_bva_rewrite[ i ] = rwmap->rwm_bva_rewrite[ i + 1 ]; + } + BER_BVZERO( &rwmap->rwm_bva_rewrite[ i ] ); + + rewrite_info_delete( &rwmap->rwm_rw ); + assert( rwmap->rwm_rw == NULL ); + + rc = rwm_info_init( &rwmap->rwm_rw ); + + for ( i = 0; !BER_BVISNULL( &rwmap->rwm_bva_rewrite[ i ] ); i++ ) + { + ConfigArgs ca = { 0 }; + + ca.line = rwmap->rwm_bva_rewrite[ i ].bv_val; + ca.argc = 0; + init_config_argv( &ca ); + config_parse_ldif( &ca ); + + argv0 = ca.argv[ 0 ]; + ca.argv[ 0 ] += STRLENOF( "rwm-" ); + + if ( strcasecmp( ca.argv[ 0 ], "suffixmassage" ) == 0 ) { + rc = rwm_suffixmassage_config( &db, c->fname, c->lineno, + ca.argc, ca.argv ); + + } else { + rc = rwm_rw_config( &db, c->fname, c->lineno, + ca.argc, ca.argv ); + } + + ca.argv[ 0 ] = argv0; + + ch_free( ca.tline ); + ch_free( ca.argv ); + + assert( rc == 0 ); + } + + } else if ( rwmap->rwm_rw != NULL ) { + rewrite_info_delete( &rwmap->rwm_rw ); + assert( rwmap->rwm_rw == NULL ); + + ber_bvarray_free( rwmap->rwm_bva_rewrite ); + rwmap->rwm_bva_rewrite = NULL; + + rc = rwm_info_init( &rwmap->rwm_rw ); + } + break; + + case RWM_CF_T_F_SUPPORT: + rwmap->rwm_flags &= ~RWM_F_SUPPORT_T_F_MASK2; + break; + + case RWM_CF_MAP: + if ( c->valx >= 0 ) { + struct ldapmap rwm_oc = rwmap->rwm_oc; + struct ldapmap rwm_at = rwmap->rwm_at; + char *argv[5]; + int cnt = 0; + + if ( rwmap->rwm_bva_map ) { + for ( ; !BER_BVISNULL( &rwmap->rwm_bva_map[ cnt ] ); cnt++ ) + /* count */ ; + } + + if ( c->valx >= cnt ) { + rc = 1; + break; + } + + memset( &rwmap->rwm_oc, 0, sizeof( rwmap->rwm_oc ) ); + memset( &rwmap->rwm_at, 0, sizeof( rwmap->rwm_at ) ); + + /* re-parse all mappings except the one + * that needs to be eliminated */ + argv[0] = "map"; + for ( cnt = 0; !BER_BVISNULL( &rwmap->rwm_bva_map[ cnt ] ); cnt++ ) { + ConfigArgs ca = { 0 }; + + if ( cnt == c->valx ) { + continue; + } + + ca.line = rwmap->rwm_bva_map[ cnt ].bv_val; + ca.argc = 0; + init_config_argv( &ca ); + config_parse_ldif( &ca ); + + argv[1] = ca.argv[0]; + argv[2] = ca.argv[1]; + argv[3] = ca.argv[2]; + argv[4] = ca.argv[3]; + + rc = rwm_m_config( &db, c->fname, c->lineno, ca.argc + 1, argv ); + + ch_free( ca.tline ); + ch_free( ca.argv ); + + /* in case of failure, restore + * the existing mapping */ + if ( rc ) { + avl_free( rwmap->rwm_oc.remap, rwm_mapping_dst_free ); + avl_free( rwmap->rwm_oc.map, rwm_mapping_free ); + avl_free( rwmap->rwm_at.remap, rwm_mapping_dst_free ); + avl_free( rwmap->rwm_at.map, rwm_mapping_free ); + rwmap->rwm_oc = rwm_oc; + rwmap->rwm_at = rwm_at; + break; + } + } + + /* in case of success, destroy the old mapping + * and eliminate the deleted one */ + if ( rc == 0 ) { + avl_free( rwm_oc.remap, rwm_mapping_dst_free ); + avl_free( rwm_oc.map, rwm_mapping_free ); + avl_free( rwm_at.remap, rwm_mapping_dst_free ); + avl_free( rwm_at.map, rwm_mapping_free ); + + ber_memfree( rwmap->rwm_bva_map[ c->valx ].bv_val ); + for ( cnt = c->valx; !BER_BVISNULL( &rwmap->rwm_bva_map[ cnt ] ); cnt++ ) { + rwmap->rwm_bva_map[ cnt ] = rwmap->rwm_bva_map[ cnt + 1 ]; + } + } + + } else { + avl_free( rwmap->rwm_oc.remap, rwm_mapping_dst_free ); + avl_free( rwmap->rwm_oc.map, rwm_mapping_free ); + avl_free( rwmap->rwm_at.remap, rwm_mapping_dst_free ); + avl_free( rwmap->rwm_at.map, rwm_mapping_free ); + + rwmap->rwm_oc.remap = NULL; + rwmap->rwm_oc.map = NULL; + rwmap->rwm_at.remap = NULL; + rwmap->rwm_at.map = NULL; + + ber_bvarray_free( rwmap->rwm_bva_map ); + rwmap->rwm_bva_map = NULL; + } + break; + + case RWM_CF_NORMALIZE_MAPPED: + rwmap->rwm_flags &= ~RWM_F_NORMALIZE_MAPPED_ATTRS; + break; + + case RWM_CF_DROP_UNREQUESTED: + rwmap->rwm_flags &= ~RWM_F_DROP_UNREQUESTED_ATTRS; + break; + + default: + return 1; + } + return rc; + } + + if ( strncasecmp( c->argv[ 0 ], "olcRwm", STRLENOF( "olcRwm" ) ) == 0 ) { + idx0 = 1; + } + + switch ( c->type ) { + case RWM_CF_REWRITE: + if ( c->valx >= 0 ) { + struct rewrite_info *rwm_rw = rwmap->rwm_rw; + int i, last; + + for ( last = 0; rwmap->rwm_bva_rewrite && !BER_BVISNULL( &rwmap->rwm_bva_rewrite[ last ] ); last++ ) + /* count'em */ ; + + if ( c->valx > last ) { + c->valx = last; + } + + rwmap->rwm_rw = NULL; + rc = rwm_info_init( &rwmap->rwm_rw ); + + for ( i = 0; i < c->valx; i++ ) { + ConfigArgs ca = { 0 }; + + ca.line = rwmap->rwm_bva_rewrite[ i ].bv_val; + ca.argc = 0; + init_config_argv( &ca ); + config_parse_ldif( &ca ); + + argv0 = ca.argv[ 0 ]; + ca.argv[ 0 ] += STRLENOF( "rwm-" ); + + if ( strcasecmp( ca.argv[ 0 ], "suffixmassage" ) == 0 ) { + rc = rwm_suffixmassage_config( &db, c->fname, c->lineno, + ca.argc, ca.argv ); + + } else { + rc = rwm_rw_config( &db, c->fname, c->lineno, + ca.argc, ca.argv ); + } + + ca.argv[ 0 ] = argv0; + + ch_free( ca.tline ); + ch_free( ca.argv ); + + assert( rc == 0 ); + } + + argv0 = c->argv[ idx0 ]; + if ( strncasecmp( argv0, "rwm-", STRLENOF( "rwm-" ) ) != 0 ) { + return 1; + } + c->argv[ idx0 ] += STRLENOF( "rwm-" ); + if ( strcasecmp( c->argv[ idx0 ], "suffixmassage" ) == 0 ) { + rc = rwm_suffixmassage_config( &db, c->fname, c->lineno, + c->argc - idx0, &c->argv[ idx0 ] ); + + } else { + rc = rwm_rw_config( &db, c->fname, c->lineno, + c->argc - idx0, &c->argv[ idx0 ] ); + } + c->argv[ idx0 ] = argv0; + if ( rc != 0 ) { + rewrite_info_delete( &rwmap->rwm_rw ); + assert( rwmap->rwm_rw == NULL ); + + rwmap->rwm_rw = rwm_rw; + return 1; + } + + for ( i = c->valx; rwmap->rwm_bva_rewrite && !BER_BVISNULL( &rwmap->rwm_bva_rewrite[ i ] ); i++ ) + { + ConfigArgs ca = { 0 }; + + ca.line = rwmap->rwm_bva_rewrite[ i ].bv_val; + ca.argc = 0; + init_config_argv( &ca ); + config_parse_ldif( &ca ); + + argv0 = ca.argv[ 0 ]; + ca.argv[ 0 ] += STRLENOF( "rwm-" ); + + if ( strcasecmp( ca.argv[ 0 ], "suffixmassage" ) == 0 ) { + rc = rwm_suffixmassage_config( &db, c->fname, c->lineno, + ca.argc, ca.argv ); + + } else { + rc = rwm_rw_config( &db, c->fname, c->lineno, + ca.argc, ca.argv ); + } + + ca.argv[ 0 ] = argv0; + + ch_free( ca.tline ); + ch_free( ca.argv ); + + assert( rc == 0 ); + } + + rwmap->rwm_bva_rewrite = ch_realloc( rwmap->rwm_bva_rewrite, + ( last + 2 )*sizeof( struct berval ) ); + BER_BVZERO( &rwmap->rwm_bva_rewrite[last+1] ); + + for ( i = last - 1; i >= c->valx; i-- ) + { + rwmap->rwm_bva_rewrite[ i + 1 ] = rwmap->rwm_bva_rewrite[ i ]; + } + + rwm_bva_rewrite_add( rwmap, c->valx, &c->argv[ idx0 ] ); + + rewrite_info_delete( &rwm_rw ); + assert( rwm_rw == NULL ); + + break; + } + + argv0 = c->argv[ idx0 ]; + if ( strncasecmp( argv0, "rwm-", STRLENOF( "rwm-" ) ) != 0 ) { + return 1; + } + c->argv[ idx0 ] += STRLENOF( "rwm-" ); + if ( strcasecmp( c->argv[ idx0 ], "suffixmassage" ) == 0 ) { + rc = rwm_suffixmassage_config( &db, c->fname, c->lineno, + c->argc - idx0, &c->argv[ idx0 ] ); + + } else { + rc = rwm_rw_config( &db, c->fname, c->lineno, + c->argc - idx0, &c->argv[ idx0 ] ); + } + c->argv[ idx0 ] = argv0; + if ( rc ) { + return 1; + + } else { + rwm_bva_rewrite_add( rwmap, -1, &c->argv[ idx0 ] ); + } + break; + + case RWM_CF_T_F_SUPPORT: + rc = verb_to_mask( c->argv[ 1 ], t_f_mode ); + if ( BER_BVISNULL( &t_f_mode[ rc ].word ) ) { + return 1; + } + + rwmap->rwm_flags &= ~RWM_F_SUPPORT_T_F_MASK2; + rwmap->rwm_flags |= t_f_mode[ rc ].mask; + rc = 0; + break; + + case RWM_CF_MAP: + if ( c->valx >= 0 ) { + struct ldapmap rwm_oc = rwmap->rwm_oc; + struct ldapmap rwm_at = rwmap->rwm_at; + char *argv[5]; + int cnt = 0; + + if ( rwmap->rwm_bva_map ) { + for ( ; !BER_BVISNULL( &rwmap->rwm_bva_map[ cnt ] ); cnt++ ) + /* count */ ; + } + + if ( c->valx >= cnt ) { + c->valx = cnt; + } + + memset( &rwmap->rwm_oc, 0, sizeof( rwmap->rwm_oc ) ); + memset( &rwmap->rwm_at, 0, sizeof( rwmap->rwm_at ) ); + + /* re-parse all mappings, including the one + * that needs to be added */ + argv[0] = "map"; + for ( cnt = 0; cnt < c->valx; cnt++ ) { + ConfigArgs ca = { 0 }; + + ca.line = rwmap->rwm_bva_map[ cnt ].bv_val; + ca.argc = 0; + init_config_argv( &ca ); + config_parse_ldif( &ca ); + + argv[1] = ca.argv[0]; + argv[2] = ca.argv[1]; + argv[3] = ca.argv[2]; + argv[4] = ca.argv[3]; + + rc = rwm_m_config( &db, c->fname, c->lineno, ca.argc + 1, argv ); + + ch_free( ca.tline ); + ch_free( ca.argv ); + + /* in case of failure, restore + * the existing mapping */ + if ( rc ) { + goto rwmmap_fail; + } + } + + argv0 = c->argv[0]; + c->argv[0] = "map"; + rc = rwm_m_config( &db, c->fname, c->lineno, c->argc, c->argv ); + c->argv[0] = argv0; + if ( rc ) { + goto rwmmap_fail; + } + + if ( rwmap->rwm_bva_map ) { + for ( ; !BER_BVISNULL( &rwmap->rwm_bva_map[ cnt ] ); cnt++ ) { + ConfigArgs ca = { 0 }; + + ca.line = rwmap->rwm_bva_map[ cnt ].bv_val; + ca.argc = 0; + init_config_argv( &ca ); + config_parse_ldif( &ca ); + + argv[1] = ca.argv[0]; + argv[2] = ca.argv[1]; + argv[3] = ca.argv[2]; + argv[4] = ca.argv[3]; + + rc = rwm_m_config( &db, c->fname, c->lineno, ca.argc + 1, argv ); + + ch_free( ca.tline ); + ch_free( ca.argv ); + + /* in case of failure, restore + * the existing mapping */ + if ( rc ) { + goto rwmmap_fail; + } + } + } + + /* in case of success, destroy the old mapping + * and add the new one */ + if ( rc == 0 ) { + BerVarray tmp; + struct berval bv, *bvp = &bv; + + if ( rwm_bva_add( &bvp, 0, &c->argv[ idx0 ] ) ) { + rc = 1; + goto rwmmap_fail; + } + + tmp = ber_memrealloc( rwmap->rwm_bva_map, + sizeof( struct berval )*( cnt + 2 ) ); + if ( tmp == NULL ) { + ber_memfree( bv.bv_val ); + rc = 1; + goto rwmmap_fail; + } + rwmap->rwm_bva_map = tmp; + BER_BVZERO( &rwmap->rwm_bva_map[ cnt + 1 ] ); + + avl_free( rwm_oc.remap, rwm_mapping_dst_free ); + avl_free( rwm_oc.map, rwm_mapping_free ); + avl_free( rwm_at.remap, rwm_mapping_dst_free ); + avl_free( rwm_at.map, rwm_mapping_free ); + + for ( ; cnt-- > c->valx; ) { + rwmap->rwm_bva_map[ cnt + 1 ] = rwmap->rwm_bva_map[ cnt ]; + } + rwmap->rwm_bva_map[ c->valx ] = bv; + + } else { +rwmmap_fail:; + avl_free( rwmap->rwm_oc.remap, rwm_mapping_dst_free ); + avl_free( rwmap->rwm_oc.map, rwm_mapping_free ); + avl_free( rwmap->rwm_at.remap, rwm_mapping_dst_free ); + avl_free( rwmap->rwm_at.map, rwm_mapping_free ); + rwmap->rwm_oc = rwm_oc; + rwmap->rwm_at = rwm_at; + } + + break; + } + + argv0 = c->argv[ 0 ]; + c->argv[ 0 ] += STRLENOF( "rwm-" ); + rc = rwm_m_config( &db, c->fname, c->lineno, c->argc, c->argv ); + c->argv[ 0 ] = argv0; + if ( rc ) { + return 1; + + } else { + char *line; + struct berval bv; + + line = ldap_charray2str( &c->argv[ 1 ], " " ); + if ( line != NULL ) { + ber_str2bv( line, 0, 0, &bv ); + ber_bvarray_add( &rwmap->rwm_bva_map, &bv ); + } + } + break; + + case RWM_CF_NORMALIZE_MAPPED: + if ( c->value_int ) { + rwmap->rwm_flags |= RWM_F_NORMALIZE_MAPPED_ATTRS; + } else { + rwmap->rwm_flags &= ~RWM_F_NORMALIZE_MAPPED_ATTRS; + } + break; + + case RWM_CF_DROP_UNREQUESTED: + if ( c->value_int ) { + rwmap->rwm_flags |= RWM_F_DROP_UNREQUESTED_ATTRS; + } else { + rwmap->rwm_flags &= ~RWM_F_DROP_UNREQUESTED_ATTRS; + } + break; + + default: + assert( 0 ); + return 1; + } + + return rc; +} + +static int +rwm_db_init( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + struct ldaprwmap *rwmap; + int rc = 0; + + rwmap = (struct ldaprwmap *)ch_calloc( 1, sizeof( struct ldaprwmap ) ); + + /* default */ + rwmap->rwm_flags = RWM_F_DROP_UNREQUESTED_ATTRS; + + rc = rwm_info_init( &rwmap->rwm_rw ); + + on->on_bi.bi_private = (void *)rwmap; + + if ( rc ) { + (void)rwm_db_destroy( be, NULL ); + } + + return rc; +} + +static int +rwm_db_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + int rc = 0; + + if ( on->on_bi.bi_private ) { + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + if ( rwmap->rwm_rw ) { + rewrite_info_delete( &rwmap->rwm_rw ); + if ( rwmap->rwm_bva_rewrite ) + ber_bvarray_free( rwmap->rwm_bva_rewrite ); + } + + avl_free( rwmap->rwm_oc.remap, rwm_mapping_dst_free ); + avl_free( rwmap->rwm_oc.map, rwm_mapping_free ); + avl_free( rwmap->rwm_at.remap, rwm_mapping_dst_free ); + avl_free( rwmap->rwm_at.map, rwm_mapping_free ); + ber_bvarray_free( rwmap->rwm_bva_map ); + + ch_free( rwmap ); + } + + return rc; +} + +static slap_overinst rwm = { { NULL } }; + +#if SLAPD_OVER_RWM == SLAPD_MOD_DYNAMIC +static +#endif /* SLAPD_OVER_RWM == SLAPD_MOD_DYNAMIC */ +int +rwm_initialize( void ) +{ + int rc; + + /* Make sure we don't exceed the bits reserved for userland */ + config_check_userland( RWM_CF_LAST ); + + memset( &rwm, 0, sizeof( slap_overinst ) ); + + rwm.on_bi.bi_type = "rwm"; + rwm.on_bi.bi_flags = + SLAPO_BFLAG_SINGLE | + 0; + + rwm.on_bi.bi_db_init = rwm_db_init; + rwm.on_bi.bi_db_config = rwm_db_config; + rwm.on_bi.bi_db_destroy = rwm_db_destroy; + + rwm.on_bi.bi_op_bind = rwm_op_bind; + rwm.on_bi.bi_op_search = rwm_op_search; + rwm.on_bi.bi_op_compare = rwm_op_compare; + rwm.on_bi.bi_op_modify = rwm_op_modify; + rwm.on_bi.bi_op_modrdn = rwm_op_modrdn; + rwm.on_bi.bi_op_add = rwm_op_add; + rwm.on_bi.bi_op_delete = rwm_op_delete; + rwm.on_bi.bi_op_unbind = rwm_op_unbind; + rwm.on_bi.bi_extended = rwm_extended; +#if 1 /* TODO */ + rwm.on_bi.bi_entry_release_rw = rwm_entry_release_rw; + rwm.on_bi.bi_entry_get_rw = rwm_entry_get_rw; +#endif + + rwm.on_bi.bi_operational = rwm_operational; + rwm.on_bi.bi_chk_referrals = 0 /* rwm_chk_referrals */ ; + + rwm.on_bi.bi_connection_init = rwm_conn_init; + rwm.on_bi.bi_connection_destroy = rwm_conn_destroy; + + rwm.on_response = rwm_response; + + rwm.on_bi.bi_cf_ocs = rwmocs; + + rc = config_register_schema( rwmcfg, rwmocs ); + if ( rc ) { + return rc; + } + + return overlay_register( &rwm ); +} + +#if SLAPD_OVER_RWM == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return rwm_initialize(); +} +#endif /* SLAPD_OVER_RWM == SLAPD_MOD_DYNAMIC */ + +#endif /* SLAPD_OVER_RWM */ diff --git a/servers/slapd/overlays/rwm.h b/servers/slapd/overlays/rwm.h new file mode 100644 index 0000000..23e5400 --- /dev/null +++ b/servers/slapd/overlays/rwm.h @@ -0,0 +1,187 @@ +/* rwm.h - dn rewrite/attribute mapping header file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#ifndef RWM_H +#define RWM_H + +#ifndef ENABLE_REWRITE +#error "librewrite must be enabled!" +#endif /* ENABLE_REWRITE */ + +/* String rewrite library */ +#include "rewrite.h" + +LDAP_BEGIN_DECL + +/* define to enable referral DN massage by default */ +#undef RWM_REFERRAL_REWRITE + +struct ldapmap { + int drop_missing; + + Avlnode *map; + Avlnode *remap; +}; + +struct ldapmapping { + int m_flags; +#define RWMMAP_F_NONE 0x00 +#define RWMMAP_F_IS_OC 0x01 +#define RWMMAP_F_FREE_SRC 0x10 +#define RWMMAP_F_FREE_DST 0x20 + struct berval m_src; + union { + AttributeDescription *m_s_ad; + ObjectClass *m_s_oc; + } m_src_ref; +#define m_src_ad m_src_ref.m_s_ad +#define m_src_oc m_src_ref.m_s_oc + struct berval m_dst; + union { + AttributeDescription *m_d_ad; + ObjectClass *m_d_oc; + } m_dst_ref; +#define m_dst_ad m_dst_ref.m_d_ad +#define m_dst_oc m_dst_ref.m_d_oc +}; + +struct ldaprwmap { + /* + * DN rewriting + */ + struct rewrite_info *rwm_rw; + BerVarray rwm_bva_rewrite; + + /* + * Attribute/objectClass mapping + */ + struct ldapmap rwm_oc; + struct ldapmap rwm_at; + BerVarray rwm_bva_map; + +#define RWM_F_NONE (0x0000U) +#define RWM_F_NORMALIZE_MAPPED_ATTRS (0x0001U) +#define RWM_F_DROP_UNREQUESTED_ATTRS (0x0002U) +#define RWM_F_SUPPORT_T_F (0x4000U) +#define RWM_F_SUPPORT_T_F_DISCOVER (0x8000U) +#define RWM_F_SUPPORT_T_F_MASK (RWM_F_SUPPORT_T_F) +#define RWM_F_SUPPORT_T_F_MASK2 (RWM_F_SUPPORT_T_F|RWM_F_SUPPORT_T_F_DISCOVER) + unsigned rwm_flags; +}; + +/* Whatever context ldap_back_dn_massage needs... */ +typedef struct dncookie { + struct ldaprwmap *rwmap; + + Connection *conn; + char *ctx; + SlapReply *rs; +} dncookie; + +int rwm_dn_massage( dncookie *dc, struct berval *in, struct berval *dn ); +int rwm_dn_massage_pretty( dncookie *dc, struct berval *in, struct berval *pdn ); +int rwm_dn_massage_normalize( dncookie *dc, struct berval *in, struct berval *ndn ); +int rwm_dn_massage_pretty_normalize( dncookie *dc, struct berval *in, struct berval *pdn, struct berval *ndn ); + +/* attributeType/objectClass mapping */ +int rwm_mapping_cmp (const void *, const void *); +int rwm_mapping_dup (void *, void *); + +int rwm_map_init ( struct ldapmap *lm, struct ldapmapping ** ); +void rwm_map ( struct ldapmap *map, struct berval *s, struct berval *m, + int remap ); +int rwm_mapping ( struct ldapmap *map, struct berval *s, + struct ldapmapping **m, int remap ); +#define RWM_MAP 0 +#define RWM_REMAP 1 +char * +rwm_map_filter( + struct ldapmap *at_map, + struct ldapmap *oc_map, + struct berval *f ); + +#if 0 /* unused! */ +int +rwm_map_attrs( + struct ldapmap *at_map, + AttributeName *a, + int remap, + char ***mapped_attrs ); +#endif + +int +rwm_map_attrnames( + Operation *op, + struct ldapmap *at_map, + struct ldapmap *oc_map, + AttributeName *an, + AttributeName **anp, + int remap ); + +extern void rwm_mapping_dst_free ( void *mapping ); + +extern void rwm_mapping_free ( void *mapping ); + +extern int rwm_map_config( + struct ldapmap *oc_map, + struct ldapmap *at_map, + const char *fname, + int lineno, + int argc, + char **argv ); + +extern int +rwm_filter_map_rewrite( + Operation *op, + dncookie *dc, + Filter *f, + struct berval *fstr ); + +/* suffix massaging by means of librewrite */ +extern int +rwm_suffix_massage_config( + struct rewrite_info *info, + struct berval *pvnc, + struct berval *nvnc, + struct berval *prnc, + struct berval *nrnc); +extern int +rwm_dnattr_rewrite( + Operation *op, + SlapReply *rs, + void *cookie, + BerVarray a_vals, + BerVarray *pa_nvals ); +extern int +rwm_referral_rewrite( + Operation *op, + SlapReply *rs, + void *cookie, + BerVarray a_vals, + BerVarray *pa_nvals ); +extern int rwm_dnattr_result_rewrite( dncookie *dc, BerVarray a_vals, BerVarray a_nvals ); +extern int rwm_referral_result_rewrite( dncookie *dc, BerVarray a_vals ); + +LDAP_END_DECL + +#endif /* RWM_H */ diff --git a/servers/slapd/overlays/rwmconf.c b/servers/slapd/overlays/rwmconf.c new file mode 100644 index 0000000..bb019db --- /dev/null +++ b/servers/slapd/overlays/rwmconf.c @@ -0,0 +1,417 @@ +/* rwmconf.c - rewrite/map configuration file routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_RWM + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "rwm.h" +#include "lutil.h" + +int +rwm_map_config( + struct ldapmap *oc_map, + struct ldapmap *at_map, + const char *fname, + int lineno, + int argc, + char **argv ) +{ + struct ldapmap *map; + struct ldapmapping *mapping; + char *src, *dst; + int is_oc = 0; + int rc = 0; + + if ( argc < 3 || argc > 4 ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: syntax is \"map {objectclass | attribute} [<local> | *] {<foreign> | *}\"\n", + fname, lineno, 0 ); + return 1; + } + + if ( strcasecmp( argv[1], "objectclass" ) == 0 ) { + map = oc_map; + is_oc = 1; + + } else if ( strcasecmp( argv[1], "attribute" ) == 0 ) { + map = at_map; + + } else { + Debug( LDAP_DEBUG_ANY, "%s: line %d: syntax is " + "\"map {objectclass | attribute} [<local> | *] " + "{<foreign> | *}\"\n", + fname, lineno, 0 ); + return 1; + } + + if ( !is_oc && map->map == NULL ) { + /* only init if required */ + if ( rwm_map_init( map, &mapping ) != LDAP_SUCCESS ) { + return 1; + } + } + + if ( strcmp( argv[2], "*" ) == 0 ) { + if ( argc < 4 || strcmp( argv[3], "*" ) == 0 ) { + map->drop_missing = ( argc < 4 ); + goto success_return; + } + src = dst = argv[3]; + + } else if ( argc < 4 ) { + src = ""; + dst = argv[2]; + + } else { + src = argv[2]; + dst = ( strcmp( argv[3], "*" ) == 0 ? src : argv[3] ); + } + + if ( ( map == at_map ) + && ( strcasecmp( src, "objectclass" ) == 0 + || strcasecmp( dst, "objectclass" ) == 0 ) ) + { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: objectclass attribute cannot be mapped\n", + fname, lineno, 0 ); + return 1; + } + + mapping = (struct ldapmapping *)ch_calloc( 2, + sizeof(struct ldapmapping) ); + if ( mapping == NULL ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: out of memory\n", + fname, lineno, 0 ); + return 1; + } + ber_str2bv( src, 0, 1, &mapping[0].m_src ); + ber_str2bv( dst, 0, 1, &mapping[0].m_dst ); + mapping[1].m_src = mapping[0].m_dst; + mapping[1].m_dst = mapping[0].m_src; + + mapping[0].m_flags = RWMMAP_F_NONE; + mapping[1].m_flags = RWMMAP_F_NONE; + + /* + * schema check + */ + if ( is_oc ) { + if ( src[0] != '\0' ) { + mapping[0].m_src_oc = oc_bvfind( &mapping[0].m_src ); + if ( mapping[0].m_src_oc == NULL ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: warning, source objectClass '%s' " + "should be defined in schema\n", + fname, lineno, src ); + + /* + * FIXME: this should become an err + */ + mapping[0].m_src_oc = ch_malloc( sizeof( ObjectClass ) ); + memset( mapping[0].m_src_oc, 0, sizeof( ObjectClass ) ); + mapping[0].m_src_oc->soc_cname = mapping[0].m_src; + mapping[0].m_flags |= RWMMAP_F_FREE_SRC; + } + mapping[1].m_dst_oc = mapping[0].m_src_oc; + } + + mapping[0].m_dst_oc = oc_bvfind( &mapping[0].m_dst ); + if ( mapping[0].m_dst_oc == NULL ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: warning, destination objectClass '%s' " + "is not defined in schema\n", + fname, lineno, dst ); + + mapping[0].m_dst_oc = oc_bvfind_undef( &mapping[0].m_dst ); + if ( mapping[0].m_dst_oc == NULL ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: unable to mimic destination objectClass '%s'\n", + fname, lineno, dst ); + goto error_return; + } + } + mapping[1].m_src_oc = mapping[0].m_dst_oc; + + mapping[0].m_flags |= RWMMAP_F_IS_OC; + mapping[1].m_flags |= RWMMAP_F_IS_OC; + + } else { + int rc; + const char *text = NULL; + + if ( src[0] != '\0' ) { + rc = slap_bv2ad( &mapping[0].m_src, + &mapping[0].m_src_ad, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: warning, source attributeType '%s' " + "should be defined in schema\n", + fname, lineno, src ); + + /* + * we create a fake "proxied" ad + * and add it here. + */ + + rc = slap_bv2undef_ad( &mapping[0].m_src, + &mapping[0].m_src_ad, &text, + SLAP_AD_PROXIED ); + if ( rc != LDAP_SUCCESS ) { + char prefix[1024]; + snprintf( prefix, sizeof(prefix), + "%s: line %d: source attributeType '%s': %d", + fname, lineno, src, rc ); + Debug( LDAP_DEBUG_ANY, "%s (%s)\n", + prefix, text ? text : "null", 0 ); + goto error_return; + } + + } + mapping[1].m_dst_ad = mapping[0].m_src_ad; + } + + rc = slap_bv2ad( &mapping[0].m_dst, &mapping[0].m_dst_ad, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: warning, destination attributeType '%s' " + "is not defined in schema\n", + fname, lineno, dst ); + + rc = slap_bv2undef_ad( &mapping[0].m_dst, + &mapping[0].m_dst_ad, &text, + SLAP_AD_PROXIED ); + if ( rc != LDAP_SUCCESS ) { + char prefix[1024]; + snprintf( prefix, sizeof(prefix), + "%s: line %d: destination attributeType '%s': %d", + fname, lineno, dst, rc ); + Debug( LDAP_DEBUG_ANY, "%s (%s)\n", + prefix, text ? text : "null", 0 ); + goto error_return; + } + } + mapping[1].m_src_ad = mapping[0].m_dst_ad; + } + + if ( ( src[0] != '\0' && avl_find( map->map, (caddr_t)mapping, rwm_mapping_cmp ) != NULL) + || avl_find( map->remap, (caddr_t)&mapping[1], rwm_mapping_cmp ) != NULL) + { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: duplicate mapping found.\n", + fname, lineno, 0 ); + /* FIXME: free stuff */ + goto error_return; + } + + if ( src[0] != '\0' ) { + avl_insert( &map->map, (caddr_t)&mapping[0], + rwm_mapping_cmp, rwm_mapping_dup ); + } + avl_insert( &map->remap, (caddr_t)&mapping[1], + rwm_mapping_cmp, rwm_mapping_dup ); + +success_return:; + return rc; + +error_return:; + if ( mapping ) { + rwm_mapping_free( mapping ); + } + + return 1; +} + +static char * +rwm_suffix_massage_regexize( const char *s ) +{ + char *res, *ptr; + const char *p, *r; + int i; + + if ( s[0] == '\0' ) { + return ch_strdup( "^(.+)$" ); + } + + for ( i = 0, p = s; + ( r = strchr( p, ',' ) ) != NULL; + p = r + 1, i++ ) + ; + + res = ch_calloc( sizeof( char ), strlen( s ) + + STRLENOF( "((.+),)?" ) + + STRLENOF( "[ ]?" ) * i + + STRLENOF( "$" ) + 1 ); + + ptr = lutil_strcopy( res, "((.+),)?" ); + for ( i = 0, p = s; + ( r = strchr( p, ',' ) ) != NULL; + p = r + 1 , i++ ) { + ptr = lutil_strncopy( ptr, p, r - p + 1 ); + ptr = lutil_strcopy( ptr, "[ ]?" ); + + if ( r[ 1 ] == ' ' ) { + r++; + } + } + ptr = lutil_strcopy( ptr, p ); + ptr[0] = '$'; + ptr[1] = '\0'; + + return res; +} + +static char * +rwm_suffix_massage_patternize( const char *s, const char *p ) +{ + ber_len_t len; + char *res, *ptr; + + len = strlen( p ); + + if ( s[ 0 ] == '\0' ) { + len++; + } + + res = ch_calloc( sizeof( char ), len + STRLENOF( "%1" ) + 1 ); + if ( res == NULL ) { + return NULL; + } + + ptr = lutil_strcopy( res, ( p[0] == '\0' ? "%2" : "%1" ) ); + if ( s[ 0 ] == '\0' ) { + ptr[ 0 ] = ','; + ptr++; + } + lutil_strcopy( ptr, p ); + + return res; +} + +int +rwm_suffix_massage_config( + struct rewrite_info *info, + struct berval *pvnc, + struct berval *nvnc, + struct berval *prnc, + struct berval *nrnc +) +{ + char *rargv[ 5 ]; + int line = 0; + + rargv[ 0 ] = "rewriteEngine"; + rargv[ 1 ] = "on"; + rargv[ 2 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 2, rargv ); + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "default"; + rargv[ 2 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 2, rargv ); + + rargv[ 0 ] = "rewriteRule"; + rargv[ 1 ] = rwm_suffix_massage_regexize( pvnc->bv_val ); + rargv[ 2 ] = rwm_suffix_massage_patternize( pvnc->bv_val, prnc->bv_val ); + rargv[ 3 ] = ":"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + ch_free( rargv[ 1 ] ); + ch_free( rargv[ 2 ] ); + + if ( BER_BVISEMPTY( pvnc ) ) { + rargv[ 0 ] = "rewriteRule"; + rargv[ 1 ] = "^$"; + rargv[ 2 ] = prnc->bv_val; + rargv[ 3 ] = ":"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + } + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "searchEntryDN"; + rargv[ 2 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 2, rargv ); + + rargv[ 0 ] = "rewriteRule"; + rargv[ 1 ] = rwm_suffix_massage_regexize( prnc->bv_val ); + rargv[ 2 ] = rwm_suffix_massage_patternize( prnc->bv_val, pvnc->bv_val ); + rargv[ 3 ] = ":"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + ch_free( rargv[ 1 ] ); + ch_free( rargv[ 2 ] ); + + if ( BER_BVISEMPTY( prnc ) ) { + rargv[ 0 ] = "rewriteRule"; + rargv[ 1 ] = "^$"; + rargv[ 2 ] = pvnc->bv_val; + rargv[ 3 ] = ":"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + } + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "matchedDN"; + rargv[ 2 ] = "alias"; + rargv[ 3 ] = "searchEntryDN"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + +#ifdef RWM_REFERRAL_REWRITE + /* FIXME: we don't want this on by default, do we? */ + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "referralDN"; + rargv[ 2 ] = "alias"; + rargv[ 3 ] = "searchEntryDN"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); +#else /* ! RWM_REFERRAL_REWRITE */ + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "referralAttrDN"; + rargv[ 2 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 2, rargv ); + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "referralDN"; + rargv[ 2 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 2, rargv ); +#endif /* ! RWM_REFERRAL_REWRITE */ + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "searchAttrDN"; + rargv[ 2 ] = "alias"; + rargv[ 3 ] = "searchEntryDN"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + + return 0; +} + +#endif /* SLAPD_OVER_RWM */ diff --git a/servers/slapd/overlays/rwmdn.c b/servers/slapd/overlays/rwmdn.c new file mode 100644 index 0000000..7682ade --- /dev/null +++ b/servers/slapd/overlays/rwmdn.c @@ -0,0 +1,215 @@ +/* rwmdn.c - massages dns */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + + +#include "portable.h" + +#ifdef SLAPD_OVER_RWM + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "rwm.h" + +/* FIXME: after rewriting, we should also remap attributes ... */ + +/* + * massages "in" and normalizes it into "ndn" + * + * "ndn" may be untouched if no massaging occurred and its value was not null + */ +int +rwm_dn_massage_normalize( + dncookie *dc, + struct berval *in, + struct berval *ndn ) +{ + int rc; + struct berval mdn = BER_BVNULL; + + /* massage and normalize a DN */ + rc = rwm_dn_massage( dc, in, &mdn ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( mdn.bv_val == in->bv_val && !BER_BVISNULL( ndn ) ) { + return rc; + } + + rc = dnNormalize( 0, NULL, NULL, &mdn, ndn, NULL ); + + if ( mdn.bv_val != in->bv_val ) { + ch_free( mdn.bv_val ); + } + + return rc; +} + +/* + * massages "in" and prettifies it into "pdn" + * + * "pdn" may be untouched if no massaging occurred and its value was not null + */ +int +rwm_dn_massage_pretty( + dncookie *dc, + struct berval *in, + struct berval *pdn ) +{ + int rc; + struct berval mdn = BER_BVNULL; + + /* massage and pretty a DN */ + rc = rwm_dn_massage( dc, in, &mdn ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( mdn.bv_val == in->bv_val && !BER_BVISNULL( pdn ) ) { + return rc; + } + + rc = dnPretty( NULL, &mdn, pdn, NULL ); + + if ( mdn.bv_val != in->bv_val ) { + ch_free( mdn.bv_val ); + } + + return rc; +} + +/* + * massages "in" and prettifies and normalizes it into "pdn" and "ndn" + * + * "pdn" may be untouched if no massaging occurred and its value was not null; + * "ndn" may be untouched if no massaging occurred and its value was not null; + * if no massage occurred and "ndn" value was not null, it is filled + * with the normaized value of "pdn", much like ndn = dnNormalize( pdn ) + */ +int +rwm_dn_massage_pretty_normalize( + dncookie *dc, + struct berval *in, + struct berval *pdn, + struct berval *ndn ) +{ + int rc; + struct berval mdn = BER_BVNULL; + + /* massage, pretty and normalize a DN */ + rc = rwm_dn_massage( dc, in, &mdn ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( mdn.bv_val == in->bv_val && !BER_BVISNULL( pdn ) ) { + if ( BER_BVISNULL( ndn ) ) { + rc = dnNormalize( 0, NULL, NULL, &mdn, ndn, NULL ); + } + return rc; + } + + rc = dnPrettyNormal( NULL, &mdn, pdn, ndn, NULL ); + + if ( mdn.bv_val != in->bv_val ) { + ch_free( mdn.bv_val ); + } + + return rc; +} + +/* + * massages "in" into "dn" + * + * "dn" may contain the value of "in" if no massage occurred + */ +int +rwm_dn_massage( + dncookie *dc, + struct berval *in, + struct berval *dn +) +{ + int rc = 0; + struct berval mdn; + static char *dmy = ""; + char *in_val; + + assert( dc != NULL ); + assert( in != NULL ); + assert( dn != NULL ); + + /* protect from NULL berval */ + in_val = in->bv_val ? in->bv_val : dmy; + + rc = rewrite_session( dc->rwmap->rwm_rw, dc->ctx, + in_val, dc->conn, &mdn.bv_val ); + switch ( rc ) { + case REWRITE_REGEXEC_OK: + if ( !BER_BVISNULL( &mdn ) && mdn.bv_val != in_val ) { + mdn.bv_len = strlen( mdn.bv_val ); + *dn = mdn; + } else { + dn->bv_len = in->bv_len; + dn->bv_val = in_val; + } + rc = LDAP_SUCCESS; + + Debug( LDAP_DEBUG_ARGS, + "[rw] %s: \"%s\" -> \"%s\"\n", + dc->ctx, in_val, dn->bv_val ); + break; + + case REWRITE_REGEXEC_UNWILLING: + if ( dc->rs ) { + dc->rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + dc->rs->sr_text = "Operation not allowed"; + } + rc = LDAP_UNWILLING_TO_PERFORM; + break; + + case REWRITE_REGEXEC_ERR: + if ( dc->rs ) { + dc->rs->sr_err = LDAP_OTHER; + dc->rs->sr_text = "Rewrite error"; + } + rc = LDAP_OTHER; + break; + } + + if ( mdn.bv_val == dmy ) { + BER_BVZERO( &mdn ); + } + + if ( dn->bv_val == dmy ) { + BER_BVZERO( dn ); + } + + return rc; +} + +#endif /* SLAPD_OVER_RWM */ diff --git a/servers/slapd/overlays/rwmmap.c b/servers/slapd/overlays/rwmmap.c new file mode 100644 index 0000000..a730c9d --- /dev/null +++ b/servers/slapd/overlays/rwmmap.c @@ -0,0 +1,1347 @@ +/* rwmmap.c - rewrite/mapping routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_RWM + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "rwm.h" + +#undef ldap_debug /* silence a warning in ldap-int.h */ +#include "../../../libraries/libldap/ldap-int.h" + +int +rwm_mapping_cmp( const void *c1, const void *c2 ) +{ + struct ldapmapping *map1 = (struct ldapmapping *)c1; + struct ldapmapping *map2 = (struct ldapmapping *)c2; + int rc = map1->m_src.bv_len - map2->m_src.bv_len; + + if ( rc ) { + return rc; + } + + return strcasecmp( map1->m_src.bv_val, map2->m_src.bv_val ); +} + +int +rwm_mapping_dup( void *c1, void *c2 ) +{ + struct ldapmapping *map1 = (struct ldapmapping *)c1; + struct ldapmapping *map2 = (struct ldapmapping *)c2; + int rc = map1->m_src.bv_len - map2->m_src.bv_len; + + if ( rc ) { + return 0; + } + + return ( ( strcasecmp( map1->m_src.bv_val, map2->m_src.bv_val ) == 0 ) ? -1 : 0 ); +} + +int +rwm_map_init( struct ldapmap *lm, struct ldapmapping **m ) +{ + struct ldapmapping *mapping; + const char *text; + int rc; + + assert( m != NULL ); + + *m = NULL; + + mapping = (struct ldapmapping *)ch_calloc( 2, + sizeof( struct ldapmapping ) ); + if ( mapping == NULL ) { + return LDAP_NO_MEMORY; + } + + /* NOTE: this is needed to make sure that + * rwm-map attribute * + * does not filter out all attributes including objectClass */ + rc = slap_str2ad( "objectClass", &mapping[0].m_src_ad, &text ); + if ( rc != LDAP_SUCCESS ) { + ch_free( mapping ); + return rc; + } + + mapping[0].m_dst_ad = mapping[0].m_src_ad; + ber_dupbv( &mapping[0].m_src, &mapping[0].m_src_ad->ad_cname ); + ber_dupbv( &mapping[0].m_dst, &mapping[0].m_src ); + + mapping[1].m_src = mapping[0].m_src; + mapping[1].m_dst = mapping[0].m_dst; + mapping[1].m_src_ad = mapping[0].m_src_ad; + mapping[1].m_dst_ad = mapping[1].m_src_ad; + + avl_insert( &lm->map, (caddr_t)&mapping[0], + rwm_mapping_cmp, rwm_mapping_dup ); + avl_insert( &lm->remap, (caddr_t)&mapping[1], + rwm_mapping_cmp, rwm_mapping_dup ); + + *m = mapping; + + return rc; +} + +int +rwm_mapping( struct ldapmap *map, struct berval *s, struct ldapmapping **m, int remap ) +{ + Avlnode *tree; + struct ldapmapping fmapping; + + if ( map == NULL ) { + return 0; + } + + assert( m != NULL ); + + /* let special attrnames slip through (ITS#5760) */ + if ( bvmatch( s, slap_bv_no_attrs ) + || bvmatch( s, slap_bv_all_user_attrs ) + || bvmatch( s, slap_bv_all_operational_attrs ) ) + { + *m = NULL; + return 0; + } + + if ( remap == RWM_REMAP ) { + tree = map->remap; + + } else { + tree = map->map; + } + + fmapping.m_src = *s; + *m = (struct ldapmapping *)avl_find( tree, (caddr_t)&fmapping, + rwm_mapping_cmp ); + + if ( *m == NULL ) { + return map->drop_missing; + } + + return 0; +} + +void +rwm_map( struct ldapmap *map, struct berval *s, struct berval *bv, int remap ) +{ + struct ldapmapping *mapping; + + /* map->map may be NULL when mapping is configured, + * but map->remap can't */ + if ( map->remap == NULL ) { + *bv = *s; + return; + } + + BER_BVZERO( bv ); + ( void )rwm_mapping( map, s, &mapping, remap ); + if ( mapping != NULL ) { + if ( !BER_BVISNULL( &mapping->m_dst ) ) { + *bv = mapping->m_dst; + } + return; + } + + if ( !map->drop_missing ) { + *bv = *s; + } +} + +/* + * Map attribute names in place + */ +int +rwm_map_attrnames( + Operation *op, + struct ldapmap *at_map, + struct ldapmap *oc_map, + AttributeName *an, + AttributeName **anp, + int remap ) +{ + int i, j, x; + + assert( anp != NULL ); + + *anp = NULL; + + if ( an == NULL && op->o_bd->be_extra_anlist == NULL ) { + return LDAP_SUCCESS; + } + + i = 0; + if ( an != NULL ) { + for ( i = 0; !BER_BVISNULL( &an[i].an_name ); i++ ) + /* just count */ ; + } + + x = 0; + if ( op->o_bd->be_extra_anlist ) { + for ( ; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++ ) + /* just count */ ; + } + + assert( i > 0 || x > 0 ); + *anp = op->o_tmpcalloc( ( i + x + 1 ), sizeof( AttributeName ), + op->o_tmpmemctx ); + if ( *anp == NULL ) { + return LDAP_NO_MEMORY; + } + + j = 0; + if ( an != NULL ) { + for ( i = 0; !BER_BVISNULL( &an[i].an_name ); i++ ) { + struct ldapmapping *m; + int at_drop_missing = 0, + oc_drop_missing = 0; + + if ( an[i].an_desc ) { + if ( !at_map ) { + /* FIXME: better leave as is? */ + continue; + } + + at_drop_missing = rwm_mapping( at_map, &an[i].an_name, &m, remap ); + if ( at_drop_missing || ( m && BER_BVISNULL( &m->m_dst ) ) ) { + continue; + } + + if ( !m ) { + (*anp)[j] = an[i]; + j++; + continue; + } + + (*anp)[j] = an[i]; + if ( remap == RWM_MAP ) { + (*anp)[j].an_name = m->m_dst; + (*anp)[j].an_desc = m->m_dst_ad; + } else { + (*anp)[j].an_name = m->m_src; + (*anp)[j].an_desc = m->m_src_ad; + + } + + j++; + continue; + + } else if ( an[i].an_oc ) { + if ( !oc_map ) { + /* FIXME: better leave as is? */ + continue; + } + + oc_drop_missing = rwm_mapping( oc_map, &an[i].an_name, &m, remap ); + + if ( oc_drop_missing || ( m && BER_BVISNULL( &m->m_dst ) ) ) { + continue; + } + + if ( !m ) { + (*anp)[j] = an[i]; + j++; + continue; + } + + (*anp)[j] = an[i]; + if ( remap == RWM_MAP ) { + (*anp)[j].an_name = m->m_dst; + (*anp)[j].an_oc = m->m_dst_oc; + } else { + (*anp)[j].an_name = m->m_src; + (*anp)[j].an_oc = m->m_src_oc; + } + + } else { + at_drop_missing = rwm_mapping( at_map, &an[i].an_name, &m, remap ); + + if ( at_drop_missing || !m ) { + oc_drop_missing = rwm_mapping( oc_map, &an[i].an_name, &m, remap ); + + /* if both at_map and oc_map required to drop missing, + * then do it */ + if ( oc_drop_missing && at_drop_missing ) { + continue; + } + + /* if no oc_map mapping was found and at_map required + * to drop missing, then do it; otherwise, at_map wins + * and an is considered an attr and is left unchanged */ + if ( !m ) { + if ( at_drop_missing ) { + continue; + } + (*anp)[j] = an[i]; + j++; + continue; + } + + if ( BER_BVISNULL( &m->m_dst ) ) { + continue; + } + + (*anp)[j] = an[i]; + if ( remap == RWM_MAP ) { + (*anp)[j].an_name = m->m_dst; + (*anp)[j].an_oc = m->m_dst_oc; + } else { + (*anp)[j].an_name = m->m_src; + (*anp)[j].an_oc = m->m_src_oc; + } + j++; + continue; + } + + if ( !BER_BVISNULL( &m->m_dst ) ) { + (*anp)[j] = an[i]; + if ( remap == RWM_MAP ) { + (*anp)[j].an_name = m->m_dst; + (*anp)[j].an_desc = m->m_dst_ad; + } else { + (*anp)[j].an_name = m->m_src; + (*anp)[j].an_desc = m->m_src_ad; + } + j++; + continue; + } + } + } + } + + if ( op->o_bd->be_extra_anlist != NULL ) { + /* we assume be_extra_anlist are already mapped */ + for ( x = 0; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++ ) { + BER_BVZERO( &(*anp)[j].an_name ); + if ( op->o_bd->be_extra_anlist[x].an_desc && + ad_inlist( op->o_bd->be_extra_anlist[x].an_desc, *anp ) ) + { + continue; + } + + (*anp)[j] = op->o_bd->be_extra_anlist[x]; + j++; + } + } + + if ( j == 0 && ( i != 0 || x != 0 ) ) { + memset( &(*anp)[0], 0, sizeof( AttributeName ) ); + (*anp)[0].an_name = *slap_bv_no_attrs; + j = 1; + } + memset( &(*anp)[j], 0, sizeof( AttributeName ) ); + + return LDAP_SUCCESS; +} + +#if 0 /* unused! */ +int +rwm_map_attrs( + struct ldapmap *at_map, + AttributeName *an, + int remap, + char ***mapped_attrs ) +{ + int i, j; + char **na; + + if ( an == NULL ) { + *mapped_attrs = NULL; + return LDAP_SUCCESS; + } + + for ( i = 0; !BER_BVISNULL( &an[ i ].an_name ); i++ ) + /* count'em */ ; + + na = (char **)ch_calloc( i + 1, sizeof( char * ) ); + if ( na == NULL ) { + *mapped_attrs = NULL; + return LDAP_NO_MEMORY; + } + + for ( i = j = 0; !BER_BVISNULL( &an[i].an_name ); i++ ) { + struct ldapmapping *mapping; + + if ( rwm_mapping( at_map, &an[i].an_name, &mapping, remap ) ) { + continue; + } + + if ( !mapping ) { + na[ j++ ] = an[ i ].an_name.bv_val; + + } else if ( !BER_BVISNULL( &mapping->m_dst ) ) { + na[ j++ ] = mapping->m_dst.bv_val; + } + } + + if ( j == 0 && i != 0 ) { + na[ j++ ] = LDAP_NO_ATTRS; + } + + na[ j ] = NULL; + + *mapped_attrs = na; + + return LDAP_SUCCESS; +} +#endif + +static int +map_attr_value( + dncookie *dc, + AttributeDescription **adp, + struct berval *mapped_attr, + struct berval *value, + struct berval *mapped_value, + int remap, + void *memctx ) +{ + struct berval vtmp = BER_BVNULL; + int freeval = 0; + AttributeDescription *ad = *adp; + struct ldapmapping *mapping = NULL; + + rwm_mapping( &dc->rwmap->rwm_at, &ad->ad_cname, &mapping, remap ); + if ( mapping == NULL ) { + if ( dc->rwmap->rwm_at.drop_missing ) { + return -1; + } + + *mapped_attr = ad->ad_cname; + + } else { + *mapped_attr = mapping->m_dst; + } + + if ( value != NULL ) { + assert( mapped_value != NULL ); + + if ( ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName + || ( mapping != NULL && mapping->m_dst_ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) ) + { + dncookie fdc = *dc; + int rc; + + fdc.ctx = "searchFilterAttrDN"; + + vtmp = *value; + rc = rwm_dn_massage_normalize( &fdc, value, &vtmp ); + switch ( rc ) { + case LDAP_SUCCESS: + if ( vtmp.bv_val != value->bv_val ) { + freeval = 1; + } + break; + + case LDAP_UNWILLING_TO_PERFORM: + case LDAP_OTHER: + default: + return -1; + } + + } else if ( ad->ad_type->sat_equality && + ( ad->ad_type->sat_equality->smr_usage & SLAP_MR_MUTATION_NORMALIZER ) ) + { + if ( ad->ad_type->sat_equality->smr_normalize( + (SLAP_MR_DENORMALIZE|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX), + NULL, NULL, value, &vtmp, memctx ) ) + { + return -1; + } + freeval = 2; + + } else if ( ad == slap_schema.si_ad_objectClass + || ad == slap_schema.si_ad_structuralObjectClass ) + { + rwm_map( &dc->rwmap->rwm_oc, value, &vtmp, remap ); + if ( BER_BVISNULL( &vtmp ) || BER_BVISEMPTY( &vtmp ) ) { + vtmp = *value; + } + + } else { + vtmp = *value; + } + + filter_escape_value_x( &vtmp, mapped_value, memctx ); + + switch ( freeval ) { + case 1: + ch_free( vtmp.bv_val ); + break; + + case 2: + ber_memfree_x( vtmp.bv_val, memctx ); + break; + } + } + + if ( mapping != NULL ) { + assert( mapping->m_dst_ad != NULL ); + *adp = mapping->m_dst_ad; + } + + return 0; +} + +static int +rwm_int_filter_map_rewrite( + Operation *op, + dncookie *dc, + Filter *f, + struct berval *fstr ) +{ + int i; + Filter *p; + AttributeDescription *ad; + struct berval atmp, + vtmp, + *tmp; + static struct berval + /* better than nothing... */ + ber_bvfalse = BER_BVC( "(!(objectClass=*))" ), + ber_bvtf_false = BER_BVC( "(|)" ), + /* better than nothing... */ + ber_bvtrue = BER_BVC( "(objectClass=*)" ), + ber_bvtf_true = BER_BVC( "(&)" ), +#if 0 + /* no longer needed; preserved for completeness */ + ber_bvundefined = BER_BVC( "(?=undefined)" ), +#endif + ber_bverror = BER_BVC( "(?=error)" ), + ber_bvunknown = BER_BVC( "(?=unknown)" ), + ber_bvnone = BER_BVC( "(?=none)" ); + ber_len_t len; + + assert( fstr != NULL ); + BER_BVZERO( fstr ); + + if ( f == NULL ) { + ber_dupbv_x( fstr, &ber_bvnone, op->o_tmpmemctx ); + return LDAP_OTHER; + } + +#if 0 + /* ITS#6814: give the caller a chance to use undefined filters */ + if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) { + goto computed; + } +#endif + + switch ( f->f_choice & SLAPD_FILTER_MASK ) { + case LDAP_FILTER_EQUALITY: + ad = f->f_av_desc; + if ( map_attr_value( dc, &ad, &atmp, + &f->f_av_value, &vtmp, RWM_MAP, op->o_tmpmemctx ) ) + { + goto computed; + } + + fstr->bv_len = atmp.bv_len + vtmp.bv_len + STRLENOF( "(=)" ); + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=%s)", + atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" ); + + op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx ); + break; + + case LDAP_FILTER_GE: + ad = f->f_av_desc; + if ( map_attr_value( dc, &ad, &atmp, + &f->f_av_value, &vtmp, RWM_MAP, op->o_tmpmemctx ) ) + { + goto computed; + } + + fstr->bv_len = atmp.bv_len + vtmp.bv_len + STRLENOF( "(>=)" ); + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s>=%s)", + atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" ); + + op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx ); + break; + + case LDAP_FILTER_LE: + ad = f->f_av_desc; + if ( map_attr_value( dc, &ad, &atmp, + &f->f_av_value, &vtmp, RWM_MAP, op->o_tmpmemctx ) ) + { + goto computed; + } + + fstr->bv_len = atmp.bv_len + vtmp.bv_len + STRLENOF( "(<=)" ); + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s<=%s)", + atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" ); + + op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx ); + break; + + case LDAP_FILTER_APPROX: + ad = f->f_av_desc; + if ( map_attr_value( dc, &ad, &atmp, + &f->f_av_value, &vtmp, RWM_MAP, op->o_tmpmemctx ) ) + { + goto computed; + } + + fstr->bv_len = atmp.bv_len + vtmp.bv_len + STRLENOF( "(~=)" ); + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s~=%s)", + atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" ); + + op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx ); + break; + + case LDAP_FILTER_SUBSTRINGS: + ad = f->f_sub_desc; + if ( map_attr_value( dc, &ad, &atmp, + NULL, NULL, RWM_MAP, op->o_tmpmemctx ) ) + { + goto computed; + } + + /* cannot be a DN ... */ + + fstr->bv_len = atmp.bv_len + STRLENOF( "(=*)" ); + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 128, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)", + atmp.bv_val ); + + if ( !BER_BVISNULL( &f->f_sub_initial ) ) { + len = fstr->bv_len; + + filter_escape_value_x( &f->f_sub_initial, &vtmp, op->o_tmpmemctx ); + + fstr->bv_len += vtmp.bv_len; + fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1, + op->o_tmpmemctx ); + + snprintf( &fstr->bv_val[len - 2], vtmp.bv_len + 3, + /* "(attr=" */ "%s*)", + vtmp.bv_len ? vtmp.bv_val : "" ); + + op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx ); + } + + if ( f->f_sub_any != NULL ) { + for ( i = 0; !BER_BVISNULL( &f->f_sub_any[i] ); i++ ) { + len = fstr->bv_len; + filter_escape_value_x( &f->f_sub_any[i], &vtmp, + op->o_tmpmemctx ); + + fstr->bv_len += vtmp.bv_len + 1; + fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1, + op->o_tmpmemctx ); + + snprintf( &fstr->bv_val[len - 1], vtmp.bv_len + 3, + /* "(attr=[init]*[any*]" */ "%s*)", + vtmp.bv_len ? vtmp.bv_val : "" ); + op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx ); + } + } + + if ( !BER_BVISNULL( &f->f_sub_final ) ) { + len = fstr->bv_len; + + filter_escape_value_x( &f->f_sub_final, &vtmp, op->o_tmpmemctx ); + + fstr->bv_len += vtmp.bv_len; + fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1, + op->o_tmpmemctx ); + + snprintf( &fstr->bv_val[len - 1], vtmp.bv_len + 3, + /* "(attr=[init*][any*]" */ "%s)", + vtmp.bv_len ? vtmp.bv_val : "" ); + + op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx ); + } + + break; + + case LDAP_FILTER_PRESENT: + ad = f->f_desc; + if ( map_attr_value( dc, &ad, &atmp, + NULL, NULL, RWM_MAP, op->o_tmpmemctx ) ) + { + goto computed; + } + + fstr->bv_len = atmp.bv_len + STRLENOF( "(=*)" ); + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)", + atmp.bv_val ); + break; + + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: + fstr->bv_len = STRLENOF( "(%)" ); + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 128, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%c)", + f->f_choice == LDAP_FILTER_AND ? '&' : + f->f_choice == LDAP_FILTER_OR ? '|' : '!' ); + + for ( p = f->f_list; p != NULL; p = p->f_next ) { + int rc; + + len = fstr->bv_len; + + rc = rwm_int_filter_map_rewrite( op, dc, p, &vtmp ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + fstr->bv_len += vtmp.bv_len; + fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1, + op->o_tmpmemctx ); + + snprintf( &fstr->bv_val[len-1], vtmp.bv_len + 2, + /*"("*/ "%s)", vtmp.bv_len ? vtmp.bv_val : "" ); + + op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx ); + } + + break; + + case LDAP_FILTER_EXT: { + if ( f->f_mr_desc ) { + ad = f->f_mr_desc; + if ( map_attr_value( dc, &ad, &atmp, + &f->f_mr_value, &vtmp, RWM_MAP, op->o_tmpmemctx ) ) + { + goto computed; + } + + } else { + BER_BVSTR( &atmp, "" ); + filter_escape_value_x( &f->f_mr_value, &vtmp, op->o_tmpmemctx ); + } + + + fstr->bv_len = atmp.bv_len + + ( f->f_mr_dnattrs ? STRLENOF( ":dn" ) : 0 ) + + ( f->f_mr_rule_text.bv_len ? f->f_mr_rule_text.bv_len + 1 : 0 ) + + vtmp.bv_len + STRLENOF( "(:=)" ); + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s%s%s:=%s)", + atmp.bv_val, + f->f_mr_dnattrs ? ":dn" : "", + !BER_BVISEMPTY( &f->f_mr_rule_text ) ? ":" : "", + !BER_BVISEMPTY( &f->f_mr_rule_text ) ? f->f_mr_rule_text.bv_val : "", + vtmp.bv_len ? vtmp.bv_val : "" ); + op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx ); + break; + } + + case -1: +computed:; + filter_free_x( op, f, 0 ); + f->f_choice = SLAPD_FILTER_COMPUTED; + f->f_result = SLAPD_COMPARE_UNDEFINED; + /* fallthru */ + + case SLAPD_FILTER_COMPUTED: + switch ( f->f_result ) { + case LDAP_COMPARE_FALSE: + /* FIXME: treat UNDEFINED as FALSE */ + case SLAPD_COMPARE_UNDEFINED: + if ( dc->rwmap->rwm_flags & RWM_F_SUPPORT_T_F ) { + tmp = &ber_bvtf_false; + break; + } + tmp = &ber_bvfalse; + break; + + case LDAP_COMPARE_TRUE: + if ( dc->rwmap->rwm_flags & RWM_F_SUPPORT_T_F ) { + tmp = &ber_bvtf_true; + break; + } + tmp = &ber_bvtrue; + break; + + default: + tmp = &ber_bverror; + break; + } + + ber_dupbv_x( fstr, tmp, op->o_tmpmemctx ); + break; + + default: + ber_dupbv_x( fstr, &ber_bvunknown, op->o_tmpmemctx ); + break; + } + + return LDAP_SUCCESS; +} + +int +rwm_filter_map_rewrite( + Operation *op, + dncookie *dc, + Filter *f, + struct berval *fstr ) +{ + int rc; + dncookie fdc; + struct berval ftmp; + + rc = rwm_int_filter_map_rewrite( op, dc, f, fstr ); + + if ( rc != 0 ) { + return rc; + } + + fdc = *dc; + ftmp = *fstr; + + fdc.ctx = "searchFilter"; + + switch ( rewrite_session( fdc.rwmap->rwm_rw, fdc.ctx, + ( !BER_BVISEMPTY( &ftmp ) ? ftmp.bv_val : "" ), + fdc.conn, &fstr->bv_val ) ) + { + case REWRITE_REGEXEC_OK: + if ( !BER_BVISNULL( fstr ) ) { + fstr->bv_len = strlen( fstr->bv_val ); + + } else { + *fstr = ftmp; + } + + Debug( LDAP_DEBUG_ARGS, + "[rw] %s: \"%s\" -> \"%s\"\n", + fdc.ctx, ftmp.bv_val, fstr->bv_val ); + if ( fstr->bv_val != ftmp.bv_val ) { + ber_bvreplace_x( &ftmp, fstr, op->o_tmpmemctx ); + ch_free( fstr->bv_val ); + *fstr = ftmp; + } + rc = LDAP_SUCCESS; + break; + + case REWRITE_REGEXEC_UNWILLING: + if ( fdc.rs ) { + fdc.rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + fdc.rs->sr_text = "Operation not allowed"; + } + op->o_tmpfree( ftmp.bv_val, op->o_tmpmemctx ); + rc = LDAP_UNWILLING_TO_PERFORM; + break; + + case REWRITE_REGEXEC_ERR: + if ( fdc.rs ) { + fdc.rs->sr_err = LDAP_OTHER; + fdc.rs->sr_text = "Rewrite error"; + } + op->o_tmpfree( ftmp.bv_val, op->o_tmpmemctx ); + rc = LDAP_OTHER; + break; + } + + return rc; +} + +/* + * I don't like this much, but we need two different + * functions because different heap managers may be + * in use in back-ldap/meta to reduce the amount of + * calls to malloc routines, and some of the free() + * routines may be macros with args + */ +int +rwm_referral_rewrite( + Operation *op, + SlapReply *rs, + void *cookie, + BerVarray a_vals, + BerVarray *pa_nvals ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + int i, last; + + dncookie dc; + struct berval dn = BER_BVNULL, + ndn = BER_BVNULL; + + assert( a_vals != NULL ); + + /* + * Rewrite the dn if needed + */ + dc.rwmap = rwmap; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = (char *)cookie; + + for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ ) + ; + last--; + + if ( pa_nvals != NULL ) { + if ( *pa_nvals == NULL ) { + *pa_nvals = ch_malloc( ( last + 2 ) * sizeof(struct berval) ); + memset( *pa_nvals, 0, ( last + 2 ) * sizeof(struct berval) ); + } + } + + for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) { + struct berval olddn = BER_BVNULL, + oldval; + int rc; + LDAPURLDesc *ludp; + + oldval = a_vals[i]; + rc = ldap_url_parse( oldval.bv_val, &ludp ); + if ( rc != LDAP_URL_SUCCESS ) { + /* leave attr untouched if massage failed */ + if ( pa_nvals && BER_BVISNULL( &(*pa_nvals)[i] ) ) { + ber_dupbv( &(*pa_nvals)[i], &oldval ); + } + continue; + } + + /* FIXME: URLs like "ldap:///dc=suffix" if passed + * thru ldap_url_parse() and ldap_url_desc2str() + * get rewritten as "ldap:///dc=suffix??base"; + * we don't want this to occur... */ + if ( ludp->lud_scope == LDAP_SCOPE_BASE ) { + ludp->lud_scope = LDAP_SCOPE_DEFAULT; + } + + ber_str2bv( ludp->lud_dn, 0, 0, &olddn ); + + dn = olddn; + if ( pa_nvals ) { + ndn = olddn; + rc = rwm_dn_massage_pretty_normalize( &dc, &olddn, + &dn, &ndn ); + } else { + rc = rwm_dn_massage_pretty( &dc, &olddn, &dn ); + } + + switch ( rc ) { + case LDAP_UNWILLING_TO_PERFORM: + /* + * FIXME: need to check if it may be considered + * legal to trim values when adding/modifying; + * it should be when searching (e.g. ACLs). + */ + ch_free( a_vals[i].bv_val ); + if (last > i ) { + a_vals[i] = a_vals[last]; + if ( pa_nvals ) { + (*pa_nvals)[i] = (*pa_nvals)[last]; + } + } + BER_BVZERO( &a_vals[last] ); + if ( pa_nvals ) { + BER_BVZERO( &(*pa_nvals)[last] ); + } + last--; + break; + + case LDAP_SUCCESS: + if ( !BER_BVISNULL( &dn ) && dn.bv_val != olddn.bv_val ) { + char *newurl; + + ludp->lud_dn = dn.bv_val; + newurl = ldap_url_desc2str( ludp ); + ludp->lud_dn = olddn.bv_val; + ch_free( dn.bv_val ); + if ( newurl == NULL ) { + /* FIXME: leave attr untouched + * even if ldap_url_desc2str failed... + */ + break; + } + + ber_str2bv( newurl, 0, 1, &a_vals[i] ); + ber_memfree( newurl ); + + if ( pa_nvals ) { + ludp->lud_dn = ndn.bv_val; + newurl = ldap_url_desc2str( ludp ); + ludp->lud_dn = olddn.bv_val; + ch_free( ndn.bv_val ); + if ( newurl == NULL ) { + /* FIXME: leave attr untouched + * even if ldap_url_desc2str failed... + */ + ch_free( a_vals[i].bv_val ); + a_vals[i] = oldval; + break; + } + + if ( !BER_BVISNULL( &(*pa_nvals)[i] ) ) { + ch_free( (*pa_nvals)[i].bv_val ); + } + ber_str2bv( newurl, 0, 1, &(*pa_nvals)[i] ); + ber_memfree( newurl ); + } + + ch_free( oldval.bv_val ); + ludp->lud_dn = olddn.bv_val; + } + break; + + default: + /* leave attr untouched if massage failed */ + if ( pa_nvals && BER_BVISNULL( &(*pa_nvals)[i] ) ) { + ber_dupbv( &(*pa_nvals)[i], &a_vals[i] ); + } + break; + } + ldap_free_urldesc( ludp ); + } + + return 0; +} + +/* + * I don't like this much, but we need two different + * functions because different heap managers may be + * in use in back-ldap/meta to reduce the amount of + * calls to malloc routines, and some of the free() + * routines may be macros with args + */ +int +rwm_dnattr_rewrite( + Operation *op, + SlapReply *rs, + void *cookie, + BerVarray a_vals, + BerVarray *pa_nvals ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + int i, last; + + dncookie dc; + struct berval dn = BER_BVNULL, + ndn = BER_BVNULL; + BerVarray in; + + if ( a_vals ) { + in = a_vals; + + } else { + if ( pa_nvals == NULL || *pa_nvals == NULL ) { + return LDAP_OTHER; + } + in = *pa_nvals; + } + + /* + * Rewrite the dn if needed + */ + dc.rwmap = rwmap; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = (char *)cookie; + + for ( last = 0; !BER_BVISNULL( &in[last] ); last++ ); + last--; + if ( pa_nvals != NULL ) { + if ( *pa_nvals == NULL ) { + *pa_nvals = ch_malloc( ( last + 2 ) * sizeof(struct berval) ); + memset( *pa_nvals, 0, ( last + 2 ) * sizeof(struct berval) ); + } + } + + for ( i = 0; !BER_BVISNULL( &in[i] ); i++ ) { + int rc; + + if ( a_vals ) { + dn = in[i]; + if ( pa_nvals ) { + ndn = (*pa_nvals)[i]; + rc = rwm_dn_massage_pretty_normalize( &dc, &in[i], &dn, &ndn ); + } else { + rc = rwm_dn_massage_pretty( &dc, &in[i], &dn ); + } + } else { + ndn = in[i]; + rc = rwm_dn_massage_normalize( &dc, &in[i], &ndn ); + } + + switch ( rc ) { + case LDAP_UNWILLING_TO_PERFORM: + /* + * FIXME: need to check if it may be considered + * legal to trim values when adding/modifying; + * it should be when searching (e.g. ACLs). + */ + ch_free( in[i].bv_val ); + if (last > i ) { + in[i] = in[last]; + if ( a_vals && pa_nvals ) { + (*pa_nvals)[i] = (*pa_nvals)[last]; + } + } + BER_BVZERO( &in[last] ); + if ( a_vals && pa_nvals ) { + BER_BVZERO( &(*pa_nvals)[last] ); + } + last--; + break; + + case LDAP_SUCCESS: + if ( a_vals ) { + if ( !BER_BVISNULL( &dn ) && dn.bv_val != a_vals[i].bv_val ) { + ch_free( a_vals[i].bv_val ); + a_vals[i] = dn; + + if ( pa_nvals ) { + if ( !BER_BVISNULL( &(*pa_nvals)[i] ) ) { + ch_free( (*pa_nvals)[i].bv_val ); + } + (*pa_nvals)[i] = ndn; + } + } + + } else { + if ( !BER_BVISNULL( &ndn ) && ndn.bv_val != (*pa_nvals)[i].bv_val ) { + ch_free( (*pa_nvals)[i].bv_val ); + (*pa_nvals)[i] = ndn; + } + } + break; + + default: + /* leave attr untouched if massage failed */ + if ( a_vals && pa_nvals && BER_BVISNULL( &(*pa_nvals)[i] ) ) { + dnNormalize( 0, NULL, NULL, &a_vals[i], &(*pa_nvals)[i], NULL ); + } + break; + } + } + + return 0; +} + +int +rwm_referral_result_rewrite( + dncookie *dc, + BerVarray a_vals ) +{ + int i, last; + + for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ ); + last--; + + for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) { + struct berval dn, + olddn = BER_BVNULL; + int rc; + LDAPURLDesc *ludp; + + rc = ldap_url_parse( a_vals[i].bv_val, &ludp ); + if ( rc != LDAP_URL_SUCCESS ) { + /* leave attr untouched if massage failed */ + continue; + } + + /* FIXME: URLs like "ldap:///dc=suffix" if passed + * thru ldap_url_parse() and ldap_url_desc2str() + * get rewritten as "ldap:///dc=suffix??base"; + * we don't want this to occur... */ + if ( ludp->lud_scope == LDAP_SCOPE_BASE ) { + ludp->lud_scope = LDAP_SCOPE_DEFAULT; + } + + ber_str2bv( ludp->lud_dn, 0, 0, &olddn ); + + dn = olddn; + rc = rwm_dn_massage_pretty( dc, &olddn, &dn ); + switch ( rc ) { + case LDAP_UNWILLING_TO_PERFORM: + /* + * FIXME: need to check if it may be considered + * legal to trim values when adding/modifying; + * it should be when searching (e.g. ACLs). + */ + ch_free( a_vals[i].bv_val ); + if ( last > i ) { + a_vals[i] = a_vals[last]; + } + BER_BVZERO( &a_vals[last] ); + last--; + i--; + break; + + default: + /* leave attr untouched if massage failed */ + if ( !BER_BVISNULL( &dn ) && olddn.bv_val != dn.bv_val ) { + char *newurl; + + ludp->lud_dn = dn.bv_val; + newurl = ldap_url_desc2str( ludp ); + if ( newurl == NULL ) { + /* FIXME: leave attr untouched + * even if ldap_url_desc2str failed... + */ + break; + } + + ch_free( a_vals[i].bv_val ); + ber_str2bv( newurl, 0, 1, &a_vals[i] ); + ber_memfree( newurl ); + ludp->lud_dn = olddn.bv_val; + } + break; + } + + ldap_free_urldesc( ludp ); + } + + return 0; +} + +int +rwm_dnattr_result_rewrite( + dncookie *dc, + BerVarray a_vals, + BerVarray a_nvals ) +{ + int i, last; + + for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ ); + last--; + + for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) { + struct berval pdn, ndn = BER_BVNULL; + int rc; + + pdn = a_vals[i]; + rc = rwm_dn_massage_pretty_normalize( dc, &a_vals[i], &pdn, &ndn ); + switch ( rc ) { + case LDAP_UNWILLING_TO_PERFORM: + /* + * FIXME: need to check if it may be considered + * legal to trim values when adding/modifying; + * it should be when searching (e.g. ACLs). + */ + assert( a_vals[i].bv_val != a_nvals[i].bv_val ); + ch_free( a_vals[i].bv_val ); + ch_free( a_nvals[i].bv_val ); + if ( last > i ) { + a_vals[i] = a_vals[last]; + a_nvals[i] = a_nvals[last]; + } + BER_BVZERO( &a_vals[last] ); + BER_BVZERO( &a_nvals[last] ); + last--; + break; + + default: + /* leave attr untouched if massage failed */ + if ( !BER_BVISNULL( &pdn ) && a_vals[i].bv_val != pdn.bv_val ) { + ch_free( a_vals[i].bv_val ); + a_vals[i] = pdn; + } + if ( !BER_BVISNULL( &ndn ) && a_nvals[i].bv_val != ndn.bv_val ) { + ch_free( a_nvals[i].bv_val ); + a_nvals[i] = ndn; + } + break; + } + } + + return 0; +} + +void +rwm_mapping_dst_free( void *v_mapping ) +{ + struct ldapmapping *mapping = v_mapping; + + if ( BER_BVISEMPTY( &mapping[0].m_dst ) ) { + rwm_mapping_free( &mapping[ -1 ] ); + } +} + +void +rwm_mapping_free( void *v_mapping ) +{ + struct ldapmapping *mapping = v_mapping; + + if ( !BER_BVISNULL( &mapping[0].m_src ) ) { + ch_free( mapping[0].m_src.bv_val ); + } + + if ( mapping[0].m_flags & RWMMAP_F_FREE_SRC ) { + if ( mapping[0].m_flags & RWMMAP_F_IS_OC ) { + if ( mapping[0].m_src_oc ) { + ch_free( mapping[0].m_src_oc ); + } + + } else { + if ( mapping[0].m_src_ad ) { + ch_free( mapping[0].m_src_ad ); + } + } + } + + if ( !BER_BVISNULL( &mapping[0].m_dst ) ) { + ch_free( mapping[0].m_dst.bv_val ); + } + + if ( mapping[0].m_flags & RWMMAP_F_FREE_DST ) { + if ( mapping[0].m_flags & RWMMAP_F_IS_OC ) { + if ( mapping[0].m_dst_oc ) { + ch_free( mapping[0].m_dst_oc ); + } + + } else { + if ( mapping[0].m_dst_ad ) { + ch_free( mapping[0].m_dst_ad ); + } + } + } + + ch_free( mapping ); + +} + +#endif /* SLAPD_OVER_RWM */ diff --git a/servers/slapd/overlays/seqmod.c b/servers/slapd/overlays/seqmod.c new file mode 100644 index 0000000..e6ff9fb --- /dev/null +++ b/servers/slapd/overlays/seqmod.c @@ -0,0 +1,206 @@ +/* seqmod.c - sequenced modifies */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_SEQMOD + +#include "slap.h" +#include "config.h" + +/* This overlay serializes concurrent attempts to modify a single entry */ + +typedef struct modtarget { + struct modtarget *mt_next; + struct modtarget *mt_tail; + Operation *mt_op; +} modtarget; + +typedef struct seqmod_info { + Avlnode *sm_mods; /* entries being modified */ + ldap_pvt_thread_mutex_t sm_mutex; +} seqmod_info; + +static int +sm_avl_cmp( const void *c1, const void *c2 ) +{ + const modtarget *m1, *m2; + int rc; + + m1 = c1; m2 = c2; + rc = m1->mt_op->o_req_ndn.bv_len - m2->mt_op->o_req_ndn.bv_len; + + if ( rc ) return rc; + return ber_bvcmp( &m1->mt_op->o_req_ndn, &m2->mt_op->o_req_ndn ); +} + +static int +seqmod_op_cleanup( Operation *op, SlapReply *rs ) +{ + slap_callback *sc = op->o_callback; + seqmod_info *sm = sc->sc_private; + modtarget *mt, mtdummy; + Avlnode *av; + + mtdummy.mt_op = op; + /* This op is done, remove it */ + ldap_pvt_thread_mutex_lock( &sm->sm_mutex ); + av = avl_find2( sm->sm_mods, &mtdummy, sm_avl_cmp ); + assert(av != NULL); + + mt = av->avl_data; + + /* If there are more, promote the next one */ + if ( mt->mt_next ) { + av->avl_data = mt->mt_next; + mt->mt_next->mt_tail = mt->mt_tail; + } else { + avl_delete( &sm->sm_mods, mt, sm_avl_cmp ); + } + ldap_pvt_thread_mutex_unlock( &sm->sm_mutex ); + op->o_callback = sc->sc_next; + op->o_tmpfree( sc, op->o_tmpmemctx ); + + return 0; +} + +static int +seqmod_op_mod( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + seqmod_info *sm = on->on_bi.bi_private; + modtarget *mt; + Avlnode *av; + slap_callback *cb; + + cb = op->o_tmpcalloc( 1, sizeof(slap_callback) + sizeof(modtarget), + op->o_tmpmemctx ); + mt = (modtarget *)(cb+1); + mt->mt_next = NULL; + mt->mt_tail = mt; + mt->mt_op = op; + + /* See if we're already modifying this entry - don't allow + * near-simultaneous mods of the same entry + */ + ldap_pvt_thread_mutex_lock( &sm->sm_mutex ); + av = avl_find2( sm->sm_mods, mt, sm_avl_cmp ); + if ( av ) { + modtarget *mtp = av->avl_data; + mtp->mt_tail->mt_next = mt; + mtp->mt_tail = mt; + /* Wait for this op to get to head of list */ + while ( mtp != mt ) { + ldap_pvt_thread_mutex_unlock( &sm->sm_mutex ); + ldap_pvt_thread_yield(); + /* Let it finish - should use a condition + * variable here... */ + ldap_pvt_thread_mutex_lock( &sm->sm_mutex ); + mtp = av->avl_data; + } + } else { + /* Record that we're modifying this now */ + avl_insert( &sm->sm_mods, mt, sm_avl_cmp, avl_dup_error ); + } + ldap_pvt_thread_mutex_unlock( &sm->sm_mutex ); + + cb->sc_cleanup = seqmod_op_cleanup; + cb->sc_private = sm; + cb->sc_next = op->o_callback; + op->o_callback = cb; + + return SLAP_CB_CONTINUE; +} + +static int +seqmod_op_extended( + Operation *op, + SlapReply *rs +) +{ + if ( exop_is_write( op )) return seqmod_op_mod( op, rs ); + else return SLAP_CB_CONTINUE; +} + +static int +seqmod_db_open( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + seqmod_info *sm; + + sm = ch_calloc(1, sizeof(seqmod_info)); + on->on_bi.bi_private = sm; + + ldap_pvt_thread_mutex_init( &sm->sm_mutex ); + + return 0; +} + +static int +seqmod_db_close( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + seqmod_info *sm = (seqmod_info *)on->on_bi.bi_private; + + if ( sm ) { + ldap_pvt_thread_mutex_destroy( &sm->sm_mutex ); + + ch_free( sm ); + on->on_bi.bi_private = NULL; + } + + return 0; +} + +/* This overlay is set up for dynamic loading via moduleload. For static + * configuration, you'll need to arrange for the slap_overinst to be + * initialized and registered by some other function inside slapd. + */ + +static slap_overinst seqmod; + +int +seqmod_initialize() +{ + seqmod.on_bi.bi_type = "seqmod"; + seqmod.on_bi.bi_db_open = seqmod_db_open; + seqmod.on_bi.bi_db_close = seqmod_db_close; + + seqmod.on_bi.bi_op_modify = seqmod_op_mod; + seqmod.on_bi.bi_op_modrdn = seqmod_op_mod; + seqmod.on_bi.bi_extended = seqmod_op_extended; + + return overlay_register( &seqmod ); +} + +#if SLAPD_OVER_SEQMOD == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return seqmod_initialize(); +} +#endif /* SLAPD_OVER_SEQMOD == SLAPD_MOD_DYNAMIC */ + +#endif /* defined(SLAPD_OVER_SEQMOD) */ diff --git a/servers/slapd/overlays/slapover.txt b/servers/slapd/overlays/slapover.txt new file mode 100644 index 0000000..2015d8d --- /dev/null +++ b/servers/slapd/overlays/slapover.txt @@ -0,0 +1,158 @@ +slapd internal APIs + +Introduction + +Frontend, backend, database, callback, overlay - what does it all mean? + +The "frontend" refers to all the code that deals with the actual interaction +with an LDAP client. This includes the code to read requests from the network +and parse them into C data structures, all of the session management, and the +formatting of responses for transmission onto the network. It also includes the +access control engine and other features that are generic to LDAP processing, +features which are not dependent on a particular database implementation. +Because the frontend serves as the framework that ties everything together, +it should not change much over time. + +The terms "backend" and "database" have historically been used interchangeably +and/or in combination as if they are the same thing, but the code has a clear +distinction between the two. A "backend" is a type of module, and a "database" +is an instance of a backend type. Together they work with the frontend to +manage the actual data that are operated on by LDAP requests. Originally the +backend interface was relatively compact, with individual functions +corresponding to each LDAP operation type, plus functions for init, config, and +shutdown. The number of entry points has grown to allow greater flexibility, +but the concept is much the same as before. + +The language here can get a bit confusing. A backend in slapd is embodied in a +BackendInfo data structure, and a database is held in a BackendDB structure. +Originally it was all just a single Backend data structure, but things have +grown and the concept was split into these two parts. The idea behind the +distinct BackendInfo is to allow for any initialization and configuration that +may be needed by every instance of a type of database, as opposed to items that +are specific to just one instance. For example, you might have a database +library that requires an initialization routine to be called exactly once at +program startup. Then there may be a "open" function that must be called once +for each database instance. The BackendInfo.bi_open function provides the +one-time startup, while the BackendInfo.bi_db_open function provides the +per-database startup. The main feature of the BackendInfo structure is its +table of entry points for all of the database functions that it implements. +There's also a bi_private pointer that can be used to carry any configuration +state needed by the backend. (Note that this is state that applies to the +backend type, and thus to all database instances of the backend as well.) The +BackendDB structure carries all of the per-instance state for a backend +database. This includes the database suffix, ACLs, flags, various DNs, etc. It +also has a pointer to its BackendInfo, and a be_private pointer for use by the +particular backend instance. In practice, the per-type features are seldom +used, and all of the work is done in the per-instance data structures. + +Ordinarily an LDAP request is received by the slapd frontend, parsed into a +request structure, and then passed to the backend for processing. The backend +may call various utility functions in the frontend to assist in processing, and +then it eventually calls some send_ldap_result function in the frontend to send +results back to the client. The processing flow is pretty rigidly defined; even +though slapd is capable of dynamically loading new code modules, it was +difficult to add extensions that changed the basic protocol operations. If you +wanted to extend the server with special behaviors you would need to modify the +frontend or the backend or both, and generally you would need to write an +entire new backend to get some set of special features working. With OpenLDAP +2.1 we added the notion of a callback, which can intercept the results sent +from a backend before they are sent to a client. Using callbacks makes it +possible to modify the results if desired, or to simply discard the results +instead of sending them to any client. This callback feature is used +extensively in the SASL support to perform internal searches of slapd databases +when mapping authentication IDs into regular DNs. The callback mechanism is +also the basis of backglue, which allows separate databases to be searched as +if they were a single naming context. + +Very often, one needs to add just a tiny feature onto an otherwise "normal" +database. The usual way to achieve this was to use a programmable backend (like +back-perl) to preprocess various requests and then forward them back into slapd +to be handled by the real database. While this technique works, it is fairly +inefficient because it involves many transitions from network to slapd and back +again. The overlay concept introduced in OpenLDAP 2.2 allows code to be +inserted between the slapd frontend and any backend, so that incoming requests +can be intercepted before reaching the backend database. (There is also a SLAPI +plugin framework in OpenLDAP 2.2; it offers a lot of flexibility as well but is +not discussed here.) The overlay framework also uses the callback mechanism, so +outgoing results can also be intercepted by external code. All of this could +get unwieldy if a lot of overlays were being used, but there was also another +significant API change in OpenLDAP 2.2 to streamline internal processing. (See +the document "Refactoring the slapd ABI"...) + +OK, enough generalities... You should probably have a copy of slap.h in front +of you to continue here. + +What is an overlay? The structure defining it includes a BackendInfo structure +plus a few additional fields. It gets inserted into the usual frontend->backend +call chain by replacing the BackendDB's BackendInfo pointer with its own. The +framework to accomplish this is in backover.c. For a given backend, the +BackendInfo will point to a slap_overinfo structure. The slap_overinfo has a +BackendInfo that points to all of the overlay framework's entry points. It also +holds a copy of the original BackendInfo pointer, and a linked list of +slap_overinst structures. There is one slap_overinst per configured overlay, +and the set of overlays configured on a backend are treated like a stack; i.e., +the last one configured is at the top of the stack, and it executes first. + +Continuing with the stack notion - a request enters the frontend, is directed +to a backend by select_backend, and then intercepted by the top of the overlay +stack. This first overlay may do something with the request, and then return +SLAP_CB_CONTINUE, which will then cause processing to fall into the next +overlay, and so on down the stack until finally the request is handed to the +actual backend database. Likewise, when the database finishes processing and +sends a result, the overlay callback intercepts this and the topmost overlay +gets to process the result. If it returns SLAP_CB_CONTINUE then processing will +continue in the next overlay, and then any other callbacks, then finally the +result reaches the frontend for sending back to the client. At any step along +the way, a module may choose to fully process the request or result and not +allow it to propagate any further down the stack. Whenever a module returns +anything other than SLAP_CB_CONTINUE the processing stops. + +An overlay can call most frontend functions without any special consideration. +However, if a call is going to result in any backend code being invoked, then +the backend environment must be correct. During a normal backend invocation, +op->o_bd points to the BackendDB structure for the backend, and +op->o_bd->bd_info points to the BackendInfo for the backend. All of the +information a specific backend instance needs is in op->o_bd->be_private and +all of its entry points are in the BackendInfo structure. When overlays are in +use on a backend, op->o_bd->bd_info points to the BackendInfo (actually a +slap_overinfo) that contains the overlay framework. When a particular overlay +instance is executing, op->o_bd points to a copy of the original op->o_bd, and +op->o_bd->bd_info points to a slap_overinst which carries the information about +the current overlay. The slap_overinst contains an on_private pointer which can +be used to carry any configuration or state information the overlay needs. The +normal way to invoke a backend function is through the op->o_bd->bd_info table +of entry points, but obviously this must be set to the backend's original +BackendInfo in order to get to the right function. + +There are two approaches here. The slap_overinst also contains a on_info field +that points to the top slap_overinfo that wraps the current backend. The +simplest thing is for the overlay to set op->o_bd->bd_info to this on_info +value before invoking a backend function. This will cause processing of that +particular operation to begin at the top of the overlay stack, so all the other +overlays on the backend will also get a chance to handle this internal request. +The other possibility is to invoke the underlying backend directly, bypassing +the rest of the overlays, by calling through on_info->oi_orig. You should be +careful in choosing this approach, since it precludes other overlays from doing +their jobs. + +One of the more interesting uses for an overlay is to attach two (or more) +different database backends into a single execution stack. Assuming that the +basic frontend-managed information (suffix, rootdn, ACLs, etc.) will be the +same for all of the backends, the only thing the overlay needs to maintain is a +be_private and bd_info pointer for the added backends. The chain and proxycache +overlays are two complementary examples of this usage. The chain overlay +attaches a back-ldap backend to a local database backend, and allows referrals +to remote servers generated by the database to be processed by slapd instead of +being returned to the client. The proxycache overlay attaches a local database +to a back-ldap (or back-meta) backend and allows search results from remote +servers to be cached locally. In both cases the overlays must provide a bit of +glue to swap in the appropriate be_private and bd_info pointers before invoking +the attached backend, which can then be invoked as usual. + +Note on overlay initialization/destruction: you should allocate storage for +config info in the _db_init handler, and free this storage in the _db_destroy +handler. You must not free it in the _db_close handler because a module may +be opened/closed multiple times in a running slapd when using dynamic +configuration and the config info must remain intact. + +--- diff --git a/servers/slapd/overlays/sssvlv.c b/servers/slapd/overlays/sssvlv.c new file mode 100644 index 0000000..3f8c903 --- /dev/null +++ b/servers/slapd/overlays/sssvlv.c @@ -0,0 +1,1434 @@ +/* sssvlv.c - server side sort / virtual list view */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2009-2021 The OpenLDAP Foundation. + * Portions copyright 2009 Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. Support for multiple sorts per connection added + * by Raphael Ouazana. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_SSSVLV + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/ctype.h> + +#include <avl.h> + +#include "slap.h" +#include "lutil.h" +#include "config.h" + +#include "../../../libraries/liblber/lber-int.h" /* ber_rewind */ + +/* RFC2891: Server Side Sorting + * RFC2696: Paged Results + */ +#ifndef LDAP_MATCHRULE_IDENTIFIER +#define LDAP_MATCHRULE_IDENTIFIER 0x80L +#define LDAP_REVERSEORDER_IDENTIFIER 0x81L +#define LDAP_ATTRTYPES_IDENTIFIER 0x80L +#endif + +/* draft-ietf-ldapext-ldapv3-vlv-09.txt: Virtual List Views + */ +#ifndef LDAP_VLVBYINDEX_IDENTIFIER +#define LDAP_VLVBYINDEX_IDENTIFIER 0xa0L +#define LDAP_VLVBYVALUE_IDENTIFIER 0x81L +#define LDAP_VLVCONTEXT_IDENTIFIER 0x04L + +#define LDAP_VLV_SSS_MISSING 0x4C +#define LDAP_VLV_RANGE_ERROR 0x4D +#endif + +#define SAFESTR(macro_str, macro_def) ((macro_str) ? (macro_str) : (macro_def)) + +#define SSSVLV_DEFAULT_MAX_KEYS 5 +#define SSSVLV_DEFAULT_MAX_REQUEST_PER_CONN 5 + +#define NO_PS_COOKIE (PagedResultsCookie) -1 +#define NO_VC_CONTEXT (unsigned long) -1 + +typedef struct vlv_ctrl { + int vc_before; + int vc_after; + int vc_offset; + int vc_count; + struct berval vc_value; + unsigned long vc_context; +} vlv_ctrl; + +typedef struct sort_key +{ + AttributeDescription *sk_ad; + MatchingRule *sk_ordering; + int sk_direction; /* 1=normal, -1=reverse */ +} sort_key; + +typedef struct sort_ctrl { + int sc_nkeys; + sort_key sc_keys[1]; +} sort_ctrl; + + +typedef struct sort_node +{ + int sn_conn; + int sn_session; + struct berval sn_dn; + struct berval *sn_vals; +} sort_node; + +typedef struct sssvlv_info +{ + int svi_max; /* max concurrent sorts */ + int svi_num; /* current # sorts */ + int svi_max_keys; /* max sort keys per request */ + int svi_max_percon; /* max concurrent sorts per con */ +} sssvlv_info; + +typedef struct sort_op +{ + Avlnode *so_tree; + sort_ctrl *so_ctrl; + sssvlv_info *so_info; + int so_paged; + int so_page_size; + int so_nentries; + int so_vlv; + int so_vlv_rc; + int so_vlv_target; + int so_session; + unsigned long so_vcontext; + int so_running; +} sort_op; + +/* There is only one conn table for all overlay instances */ +/* Each conn can handle one session by context */ +static sort_op ***sort_conns; +static ldap_pvt_thread_mutex_t sort_conns_mutex; +static int ov_count; +static const char *debug_header = "sssvlv"; + +static int sss_cid; +static int vlv_cid; + +/* RFC 2981 Section 2.2 + * If a sort key is a multi-valued attribute, and an entry happens to + * have multiple values for that attribute and no other controls are + * present that affect the sorting order, then the server SHOULD use the + * least value (according to the ORDERING rule for that attribute). + */ +static struct berval* select_value( + Attribute *attr, + sort_key *key ) +{ + struct berval* ber1, *ber2; + MatchingRule *mr = key->sk_ordering; + unsigned i; + int cmp; + + ber1 = &(attr->a_nvals[0]); + ber2 = ber1+1; + for ( i = 1; i < attr->a_numvals; i++,ber2++ ) { + mr->smr_match( &cmp, 0, mr->smr_syntax, mr, ber1, ber2 ); + if ( cmp > 0 ) { + ber1 = ber2; + } + } + + Debug(LDAP_DEBUG_TRACE, "%s: value selected for compare: %s\n", + debug_header, + SAFESTR(ber1->bv_val, "<Empty>"), + 0); + + return ber1; +} + +static int node_cmp( const void* val1, const void* val2 ) +{ + sort_node *sn1 = (sort_node *)val1; + sort_node *sn2 = (sort_node *)val2; + sort_ctrl *sc; + MatchingRule *mr; + int i, cmp = 0; + assert( sort_conns[sn1->sn_conn] + && sort_conns[sn1->sn_conn][sn1->sn_session] + && sort_conns[sn1->sn_conn][sn1->sn_session]->so_ctrl ); + sc = sort_conns[sn1->sn_conn][sn1->sn_session]->so_ctrl; + + for ( i=0; cmp == 0 && i<sc->sc_nkeys; i++ ) { + if ( BER_BVISNULL( &sn1->sn_vals[i] )) { + if ( BER_BVISNULL( &sn2->sn_vals[i] )) + cmp = 0; + else + cmp = sc->sc_keys[i].sk_direction; + } else if ( BER_BVISNULL( &sn2->sn_vals[i] )) { + cmp = sc->sc_keys[i].sk_direction * -1; + } else { + mr = sc->sc_keys[i].sk_ordering; + mr->smr_match( &cmp, 0, mr->smr_syntax, mr, + &sn1->sn_vals[i], &sn2->sn_vals[i] ); + if ( cmp ) + cmp *= sc->sc_keys[i].sk_direction; + } + } + return cmp; +} + +static int node_insert( const void *val1, const void *val2 ) +{ + /* Never return equal so that new entries are always inserted */ + return node_cmp( val1, val2 ) < 0 ? -1 : 1; +} + +static int pack_vlv_response_control( + Operation *op, + SlapReply *rs, + sort_op *so, + LDAPControl **ctrlsp ) +{ + LDAPControl *ctrl; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + struct berval cookie, bv; + int rc; + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + rc = ber_printf( ber, "{iie", so->so_vlv_target, so->so_nentries, + so->so_vlv_rc ); + + if ( rc != -1 && so->so_vcontext ) { + cookie.bv_val = (char *)&so->so_vcontext; + cookie.bv_len = sizeof(so->so_vcontext); + rc = ber_printf( ber, "tO", LDAP_VLVCONTEXT_IDENTIFIER, &cookie ); + } + + if ( rc != -1 ) { + rc = ber_printf( ber, "}" ); + } + + if ( rc != -1 ) { + rc = ber_flatten2( ber, &bv, 0 ); + } + + if ( rc != -1 ) { + ctrl = (LDAPControl *)op->o_tmpalloc( sizeof(LDAPControl)+ + bv.bv_len, op->o_tmpmemctx ); + ctrl->ldctl_oid = LDAP_CONTROL_VLVRESPONSE; + ctrl->ldctl_iscritical = 0; + ctrl->ldctl_value.bv_val = (char *)(ctrl+1); + ctrl->ldctl_value.bv_len = bv.bv_len; + AC_MEMCPY( ctrl->ldctl_value.bv_val, bv.bv_val, bv.bv_len ); + ctrlsp[0] = ctrl; + } else { + ctrlsp[0] = NULL; + rs->sr_err = LDAP_OTHER; + } + + ber_free_buf( ber ); + + return rs->sr_err; +} + +static int pack_pagedresult_response_control( + Operation *op, + SlapReply *rs, + sort_op *so, + LDAPControl **ctrlsp ) +{ + LDAPControl *ctrl; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + PagedResultsCookie resp_cookie; + struct berval cookie, bv; + int rc; + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + if ( so->so_nentries > 0 ) { + resp_cookie = ( PagedResultsCookie )so->so_tree; + cookie.bv_len = sizeof( PagedResultsCookie ); + cookie.bv_val = (char *)&resp_cookie; + } else { + resp_cookie = ( PagedResultsCookie )0; + BER_BVZERO( &cookie ); + } + + op->o_conn->c_pagedresults_state.ps_cookie = resp_cookie; + op->o_conn->c_pagedresults_state.ps_count + = ((PagedResultsState *)op->o_pagedresults_state)->ps_count + + rs->sr_nentries; + + rc = ber_printf( ber, "{iO}", so->so_nentries, &cookie ); + if ( rc != -1 ) { + rc = ber_flatten2( ber, &bv, 0 ); + } + + if ( rc != -1 ) { + ctrl = (LDAPControl *)op->o_tmpalloc( sizeof(LDAPControl)+ + bv.bv_len, op->o_tmpmemctx ); + ctrl->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS; + ctrl->ldctl_iscritical = 0; + ctrl->ldctl_value.bv_val = (char *)(ctrl+1); + ctrl->ldctl_value.bv_len = bv.bv_len; + AC_MEMCPY( ctrl->ldctl_value.bv_val, bv.bv_val, bv.bv_len ); + ctrlsp[0] = ctrl; + } else { + ctrlsp[0] = NULL; + rs->sr_err = LDAP_OTHER; + } + + ber_free_buf( ber ); + + return rs->sr_err; +} + +static int pack_sss_response_control( + Operation *op, + SlapReply *rs, + LDAPControl **ctrlsp ) +{ + LDAPControl *ctrl; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + struct berval bv; + int rc; + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + /* Pack error code */ + rc = ber_printf(ber, "{e}", rs->sr_err); + + if ( rc != -1) + rc = ber_flatten2( ber, &bv, 0 ); + + if ( rc != -1 ) { + ctrl = (LDAPControl *)op->o_tmpalloc( sizeof(LDAPControl)+ + bv.bv_len, op->o_tmpmemctx ); + ctrl->ldctl_oid = LDAP_CONTROL_SORTRESPONSE; + ctrl->ldctl_iscritical = 0; + ctrl->ldctl_value.bv_val = (char *)(ctrl+1); + ctrl->ldctl_value.bv_len = bv.bv_len; + AC_MEMCPY( ctrl->ldctl_value.bv_val, bv.bv_val, bv.bv_len ); + ctrlsp[0] = ctrl; + } else { + ctrlsp[0] = NULL; + rs->sr_err = LDAP_OTHER; + } + + ber_free_buf( ber ); + + return rs->sr_err; +} + +/* Return the session id or -1 if unknown */ +static int find_session_by_so( + int svi_max_percon, + int conn_id, + sort_op *so ) +{ + int sess_id; + if (so == NULL) { + return -1; + } + for (sess_id = 0; sess_id < svi_max_percon; sess_id++) { + if ( sort_conns[conn_id] && sort_conns[conn_id][sess_id] == so ) + return sess_id; + } + return -1; +} + +/* Return the session id or -1 if unknown */ +static int find_session_by_context( + int svi_max_percon, + int conn_id, + unsigned long vc_context, + PagedResultsCookie ps_cookie ) +{ + int sess_id; + for(sess_id = 0; sess_id < svi_max_percon; sess_id++) { + if( sort_conns[conn_id] && sort_conns[conn_id][sess_id] && + ( sort_conns[conn_id][sess_id]->so_vcontext == vc_context || + (PagedResultsCookie) sort_conns[conn_id][sess_id]->so_tree == ps_cookie ) ) + return sess_id; + } + return -1; +} + +static int find_next_session( + int svi_max_percon, + int conn_id ) +{ + int sess_id; + assert(sort_conns[conn_id] != NULL); + for(sess_id = 0; sess_id < svi_max_percon; sess_id++) { + if(!sort_conns[conn_id][sess_id]) { + return sess_id; + } + } + if (sess_id >= svi_max_percon) { + return -1; + } else { + return sess_id; + } +} + +static void free_sort_op( Connection *conn, sort_op *so ) +{ + int sess_id; + + ldap_pvt_thread_mutex_lock( &sort_conns_mutex ); + sess_id = find_session_by_so( so->so_info->svi_max_percon, conn->c_conn_idx, so ); + if ( sess_id > -1 ) { + sort_conns[conn->c_conn_idx][sess_id] = NULL; + so->so_info->svi_num--; + } + ldap_pvt_thread_mutex_unlock( &sort_conns_mutex ); + + if ( sess_id > -1 ){ + if ( so->so_tree ) { + if ( so->so_paged > SLAP_CONTROL_IGNORED ) { + Avlnode *cur_node, *next_node; + cur_node = so->so_tree; + while ( cur_node ) { + next_node = tavl_next( cur_node, TAVL_DIR_RIGHT ); + ch_free( cur_node->avl_data ); + ber_memfree( cur_node ); + + cur_node = next_node; + } + } else { + tavl_free( so->so_tree, ch_free ); + } + so->so_tree = NULL; + } + + ch_free( so ); + } +} + +static void free_sort_ops( Connection *conn, sort_op **sos, int svi_max_percon ) +{ + int sess_id; + sort_op *so; + + for( sess_id = 0; sess_id < svi_max_percon ; sess_id++ ) { + so = sort_conns[conn->c_conn_idx][sess_id]; + if ( so ) { + free_sort_op( conn, so ); + sort_conns[conn->c_conn_idx][sess_id] = NULL; + } + } +} + +static void send_list( + Operation *op, + SlapReply *rs, + sort_op *so) +{ + Avlnode *cur_node, *tmp_node; + vlv_ctrl *vc = op->o_controls[vlv_cid]; + int i, j, dir, rc; + BackendDB *be; + Entry *e; + LDAPControl *ctrls[2]; + + rs->sr_attrs = op->ors_attrs; + + /* FIXME: it may be better to just flatten the tree into + * an array before doing all of this... + */ + + /* Are we just counting an offset? */ + if ( BER_BVISNULL( &vc->vc_value )) { + if ( vc->vc_offset == vc->vc_count ) { + /* wants the last entry in the list */ + cur_node = tavl_end(so->so_tree, TAVL_DIR_RIGHT); + so->so_vlv_target = so->so_nentries; + } else if ( vc->vc_offset == 1 ) { + /* wants the first entry in the list */ + cur_node = tavl_end(so->so_tree, TAVL_DIR_LEFT); + so->so_vlv_target = 1; + } else { + int target; + /* Just iterate to the right spot */ + if ( vc->vc_count && vc->vc_count != so->so_nentries ) { + if ( vc->vc_offset > vc->vc_count ) + goto range_err; + target = so->so_nentries * vc->vc_offset / vc->vc_count; + } else { + if ( vc->vc_offset > so->so_nentries ) { +range_err: + so->so_vlv_rc = LDAP_VLV_RANGE_ERROR; + pack_vlv_response_control( op, rs, so, ctrls ); + ctrls[1] = NULL; + slap_add_ctrls( op, rs, ctrls ); + rs->sr_err = LDAP_VLV_ERROR; + return; + } + target = vc->vc_offset; + } + so->so_vlv_target = target; + /* Start at left and go right, or start at right and go left? */ + if ( target < so->so_nentries / 2 ) { + cur_node = tavl_end(so->so_tree, TAVL_DIR_LEFT); + dir = TAVL_DIR_RIGHT; + } else { + cur_node = tavl_end(so->so_tree, TAVL_DIR_RIGHT); + dir = TAVL_DIR_LEFT; + target = so->so_nentries - target + 1; + } + for ( i=1; i<target; i++ ) + cur_node = tavl_next( cur_node, dir ); + } + } else { + /* we're looking for a specific value */ + sort_ctrl *sc = so->so_ctrl; + MatchingRule *mr = sc->sc_keys[0].sk_ordering; + sort_node *sn; + struct berval bv; + + if ( mr->smr_normalize ) { + rc = mr->smr_normalize( SLAP_MR_VALUE_OF_SYNTAX, + mr->smr_syntax, mr, &vc->vc_value, &bv, op->o_tmpmemctx ); + if ( rc ) { + so->so_vlv_rc = LDAP_INAPPROPRIATE_MATCHING; + pack_vlv_response_control( op, rs, so, ctrls ); + ctrls[1] = NULL; + slap_add_ctrls( op, rs, ctrls ); + rs->sr_err = LDAP_VLV_ERROR; + return; + } + } else { + bv = vc->vc_value; + } + + sn = op->o_tmpalloc( sizeof(sort_node) + + sc->sc_nkeys * sizeof(struct berval), op->o_tmpmemctx ); + sn->sn_vals = (struct berval *)(sn+1); + sn->sn_conn = op->o_conn->c_conn_idx; + sn->sn_session = find_session_by_so( so->so_info->svi_max_percon, op->o_conn->c_conn_idx, so ); + sn->sn_vals[0] = bv; + for (i=1; i<sc->sc_nkeys; i++) { + BER_BVZERO( &sn->sn_vals[i] ); + } + cur_node = tavl_find3( so->so_tree, sn, node_cmp, &j ); + /* didn't find >= match */ + if ( j > 0 ) { + if ( cur_node ) + cur_node = tavl_next( cur_node, TAVL_DIR_RIGHT ); + } + op->o_tmpfree( sn, op->o_tmpmemctx ); + + if ( !cur_node ) { + so->so_vlv_target = so->so_nentries + 1; + } else { + sort_node *sn = so->so_tree->avl_data; + /* start from the left or the right side? */ + mr->smr_match( &i, 0, mr->smr_syntax, mr, &bv, &sn->sn_vals[0] ); + if ( i > 0 ) { + tmp_node = tavl_end(so->so_tree, TAVL_DIR_RIGHT); + dir = TAVL_DIR_LEFT; + } else { + tmp_node = tavl_end(so->so_tree, TAVL_DIR_LEFT); + dir = TAVL_DIR_RIGHT; + } + for (i=0; tmp_node != cur_node; + tmp_node = tavl_next( tmp_node, dir ), i++); + so->so_vlv_target = (dir == TAVL_DIR_RIGHT) ? i+1 : so->so_nentries - i; + } + if ( bv.bv_val != vc->vc_value.bv_val ) + op->o_tmpfree( bv.bv_val, op->o_tmpmemctx ); + } + if ( !cur_node ) { + i = 1; + cur_node = tavl_end(so->so_tree, TAVL_DIR_RIGHT); + } else { + i = 0; + } + for ( ; i<vc->vc_before; i++ ) { + tmp_node = tavl_next( cur_node, TAVL_DIR_LEFT ); + if ( !tmp_node ) break; + cur_node = tmp_node; + } + j = i + vc->vc_after + 1; + be = op->o_bd; + for ( i=0; i<j; i++ ) { + sort_node *sn = cur_node->avl_data; + + if ( slapd_shutdown ) break; + + op->o_bd = select_backend( &sn->sn_dn, 0 ); + e = NULL; + rc = be_entry_get_rw( op, &sn->sn_dn, NULL, NULL, 0, &e ); + + if ( e && rc == LDAP_SUCCESS ) { + rs->sr_entry = e; + rs->sr_flags = REP_ENTRY_MUSTRELEASE; + rs->sr_err = send_search_entry( op, rs ); + if ( rs->sr_err == LDAP_UNAVAILABLE ) + break; + } + cur_node = tavl_next( cur_node, TAVL_DIR_RIGHT ); + if ( !cur_node ) break; + } + so->so_vlv_rc = LDAP_SUCCESS; + + op->o_bd = be; +} + +static void send_page( Operation *op, SlapReply *rs, sort_op *so ) +{ + Avlnode *cur_node = so->so_tree; + Avlnode *next_node = NULL; + BackendDB *be = op->o_bd; + Entry *e; + int rc; + + rs->sr_attrs = op->ors_attrs; + + while ( cur_node && rs->sr_nentries < so->so_page_size ) { + sort_node *sn = cur_node->avl_data; + + if ( slapd_shutdown ) break; + + next_node = tavl_next( cur_node, TAVL_DIR_RIGHT ); + + op->o_bd = select_backend( &sn->sn_dn, 0 ); + e = NULL; + rc = be_entry_get_rw( op, &sn->sn_dn, NULL, NULL, 0, &e ); + + ch_free( cur_node->avl_data ); + ber_memfree( cur_node ); + + cur_node = next_node; + so->so_nentries--; + + if ( e && rc == LDAP_SUCCESS ) { + rs->sr_entry = e; + rs->sr_flags = REP_ENTRY_MUSTRELEASE; + rs->sr_err = send_search_entry( op, rs ); + if ( rs->sr_err == LDAP_UNAVAILABLE ) + break; + } + } + + /* Set the first entry to send for the next page */ + so->so_tree = next_node; + if ( next_node ) + next_node->avl_left = NULL; + + op->o_bd = be; +} + +static void send_entry( + Operation *op, + SlapReply *rs, + sort_op *so) +{ + Debug(LDAP_DEBUG_TRACE, + "%s: response control: status=%d, text=%s\n", + debug_header, rs->sr_err, SAFESTR(rs->sr_text, "<None>")); + + if ( !so->so_tree ) + return; + + /* RFC 2891: If critical then send the entries iff they were + * succesfully sorted. If non-critical send all entries + * whether they were sorted or not. + */ + if ( (op->o_ctrlflag[sss_cid] != SLAP_CONTROL_CRITICAL) || + (rs->sr_err == LDAP_SUCCESS) ) + { + if ( so->so_vlv > SLAP_CONTROL_IGNORED ) { + send_list( op, rs, so ); + } else { + /* Get the first node to send */ + Avlnode *start_node = tavl_end(so->so_tree, TAVL_DIR_LEFT); + so->so_tree = start_node; + + if ( so->so_paged <= SLAP_CONTROL_IGNORED ) { + /* Not paged result search. Send all entries. + * Set the page size to the number of entries + * so that send_page() will send all entries. + */ + so->so_page_size = so->so_nentries; + } + + send_page( op, rs, so ); + } + } +} + +static void send_result( + Operation *op, + SlapReply *rs, + sort_op *so) +{ + LDAPControl *ctrls[3]; + int rc, i = 0; + + rc = pack_sss_response_control( op, rs, ctrls ); + if ( rc == LDAP_SUCCESS ) { + i++; + rc = -1; + if ( so->so_paged > SLAP_CONTROL_IGNORED ) { + rc = pack_pagedresult_response_control( op, rs, so, ctrls+1 ); + } else if ( so->so_vlv > SLAP_CONTROL_IGNORED ) { + rc = pack_vlv_response_control( op, rs, so, ctrls+1 ); + } + if ( rc == LDAP_SUCCESS ) + i++; + } + ctrls[i] = NULL; + + if ( ctrls[0] != NULL ) + slap_add_ctrls( op, rs, ctrls ); + send_ldap_result( op, rs ); + + if ( so->so_tree == NULL ) { + /* Search finished, so clean up */ + free_sort_op( op->o_conn, so ); + } else { + so->so_running = 0; + } +} + +static int sssvlv_op_response( + Operation *op, + SlapReply *rs ) +{ + sort_ctrl *sc = op->o_controls[sss_cid]; + sort_op *so = op->o_callback->sc_private; + + if ( rs->sr_type == REP_SEARCH ) { + int i; + size_t len; + sort_node *sn, *sn2; + struct berval *bv; + char *ptr; + + len = sizeof(sort_node) + sc->sc_nkeys * sizeof(struct berval) + + rs->sr_entry->e_nname.bv_len + 1; + sn = op->o_tmpalloc( len, op->o_tmpmemctx ); + sn->sn_vals = (struct berval *)(sn+1); + + /* Build tmp list of key values */ + for ( i=0; i<sc->sc_nkeys; i++ ) { + Attribute *a = attr_find( rs->sr_entry->e_attrs, + sc->sc_keys[i].sk_ad ); + if ( a ) { + if ( a->a_numvals > 1 ) { + bv = select_value( a, &sc->sc_keys[i] ); + } else { + bv = a->a_nvals; + } + sn->sn_vals[i] = *bv; + len += bv->bv_len + 1; + } else { + BER_BVZERO( &sn->sn_vals[i] ); + } + } + + /* Now dup into regular memory */ + sn2 = ch_malloc( len ); + sn2->sn_vals = (struct berval *)(sn2+1); + AC_MEMCPY( sn2->sn_vals, sn->sn_vals, + sc->sc_nkeys * sizeof(struct berval)); + + ptr = (char *)(sn2->sn_vals + sc->sc_nkeys); + sn2->sn_dn.bv_val = ptr; + sn2->sn_dn.bv_len = rs->sr_entry->e_nname.bv_len; + AC_MEMCPY( ptr, rs->sr_entry->e_nname.bv_val, + rs->sr_entry->e_nname.bv_len ); + ptr += rs->sr_entry->e_nname.bv_len; + *ptr++ = '\0'; + for ( i=0; i<sc->sc_nkeys; i++ ) { + if ( !BER_BVISNULL( &sn2->sn_vals[i] )) { + AC_MEMCPY(ptr, sn2->sn_vals[i].bv_val, sn2->sn_vals[i].bv_len); + sn2->sn_vals[i].bv_val = ptr; + ptr += sn2->sn_vals[i].bv_len; + *ptr++ = '\0'; + } + } + op->o_tmpfree( sn, op->o_tmpmemctx ); + sn = sn2; + sn->sn_conn = op->o_conn->c_conn_idx; + sn->sn_session = find_session_by_so( so->so_info->svi_max_percon, op->o_conn->c_conn_idx, so ); + + /* Insert into the AVL tree */ + tavl_insert(&(so->so_tree), sn, node_insert, avl_dup_error); + + so->so_nentries++; + + /* Collected the keys so that they can be sorted. Thus, stop + * the entry from propagating. + */ + rs->sr_err = LDAP_SUCCESS; + } + else if ( rs->sr_type == REP_RESULT ) { + /* Remove serversort response callback. + * We don't want the entries that we are about to send to be + * processed by serversort response again. + */ + if ( op->o_callback->sc_response == sssvlv_op_response ) { + op->o_callback = op->o_callback->sc_next; + } + + send_entry( op, rs, so ); + send_result( op, rs, so ); + } + + return rs->sr_err; +} + +static int sssvlv_op_search( + Operation *op, + SlapReply *rs) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + sssvlv_info *si = on->on_bi.bi_private; + int rc = SLAP_CB_CONTINUE; + int ok; + sort_op *so = NULL, so2; + sort_ctrl *sc; + PagedResultsState *ps; + vlv_ctrl *vc; + int sess_id; + + if ( op->o_ctrlflag[sss_cid] <= SLAP_CONTROL_IGNORED ) { + if ( op->o_ctrlflag[vlv_cid] > SLAP_CONTROL_IGNORED ) { + LDAPControl *ctrls[2]; + so2.so_vcontext = 0; + so2.so_vlv_target = 0; + so2.so_nentries = 0; + so2.so_vlv_rc = LDAP_VLV_SSS_MISSING; + so2.so_vlv = op->o_ctrlflag[vlv_cid]; + rc = pack_vlv_response_control( op, rs, &so2, ctrls ); + if ( rc == LDAP_SUCCESS ) { + ctrls[1] = NULL; + slap_add_ctrls( op, rs, ctrls ); + } + rs->sr_err = LDAP_VLV_ERROR; + rs->sr_text = "Sort control is required with VLV"; + goto leave; + } + /* Not server side sort so just continue */ + return SLAP_CB_CONTINUE; + } + + Debug(LDAP_DEBUG_TRACE, + "==> sssvlv_search: <%s> %s, control flag: %d\n", + op->o_req_dn.bv_val, op->ors_filterstr.bv_val, + op->o_ctrlflag[sss_cid]); + + sc = op->o_controls[sss_cid]; + if ( sc->sc_nkeys > si->svi_max_keys ) { + rs->sr_text = "Too many sort keys"; + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + goto leave; + } + + ps = ( op->o_pagedresults > SLAP_CONTROL_IGNORED ) ? + (PagedResultsState*)(op->o_pagedresults_state) : NULL; + vc = op->o_ctrlflag[vlv_cid] > SLAP_CONTROL_IGNORED ? + op->o_controls[vlv_cid] : NULL; + + if ( ps && vc ) { + rs->sr_text = "VLV incompatible with PagedResults"; + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + goto leave; + } + + ok = 1; + ldap_pvt_thread_mutex_lock( &sort_conns_mutex ); + /* Is there already a sort running on this conn? */ + sess_id = find_session_by_context( si->svi_max_percon, op->o_conn->c_conn_idx, vc ? vc->vc_context : NO_VC_CONTEXT, ps ? ps->ps_cookie : NO_PS_COOKIE ); + if ( sess_id >= 0 ) { + so = sort_conns[op->o_conn->c_conn_idx][sess_id]; + + if( so->so_running > 0 ){ + /* another thread is handling, response busy to client */ + so = NULL; + ok = 0; + } else { + + /* Is it a continuation of a VLV search? */ + if ( !vc || so->so_vlv <= SLAP_CONTROL_IGNORED || + vc->vc_context != so->so_vcontext ) { + /* Is it a continuation of a paged search? */ + if ( !ps || so->so_paged <= SLAP_CONTROL_IGNORED || + op->o_conn->c_pagedresults_state.ps_cookie != ps->ps_cookie ) { + ok = 0; + } else if ( !ps->ps_size ) { + /* Abandoning current request */ + ok = 0; + so->so_nentries = 0; + rs->sr_err = LDAP_SUCCESS; + } + } + if (( vc && so->so_paged > SLAP_CONTROL_IGNORED ) || + ( ps && so->so_vlv > SLAP_CONTROL_IGNORED )) { + /* changed from paged to vlv or vice versa, abandon */ + ok = 0; + so->so_nentries = 0; + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + } + + if ( ok ) { + /* occupy before mutex unlock */ + so->so_running = 1; + } + + } + /* Are there too many running overall? */ + } else if ( si->svi_num >= si->svi_max ) { + ok = 0; + } else if ( ( sess_id = find_next_session(si->svi_max_percon, op->o_conn->c_conn_idx ) ) < 0 ) { + ok = 0; + } else { + /* OK, this connection now has a sort running */ + si->svi_num++; + sort_conns[op->o_conn->c_conn_idx][sess_id] = &so2; + sort_conns[op->o_conn->c_conn_idx][sess_id]->so_session = sess_id; + } + ldap_pvt_thread_mutex_unlock( &sort_conns_mutex ); + if ( ok ) { + /* If we're a global overlay, this check got bypassed */ + if ( !op->ors_limit && limits_check( op, rs )) + return rs->sr_err; + /* are we continuing a VLV search? */ + if ( so && vc && vc->vc_context ) { + so->so_ctrl = sc; + send_list( op, rs, so ); + send_result( op, rs, so ); + rc = LDAP_SUCCESS; + /* are we continuing a paged search? */ + } else if ( so && ps && ps->ps_cookie ) { + so->so_ctrl = sc; + send_page( op, rs, so ); + send_result( op, rs, so ); + rc = LDAP_SUCCESS; + } else { + slap_callback *cb = op->o_tmpalloc( sizeof(slap_callback), + op->o_tmpmemctx ); + /* Install serversort response callback to handle a new search */ + if ( ps || vc ) { + so = ch_calloc( 1, sizeof(sort_op)); + } else { + so = op->o_tmpcalloc( 1, sizeof(sort_op), op->o_tmpmemctx ); + } + sort_conns[op->o_conn->c_conn_idx][sess_id] = so; + + cb->sc_cleanup = NULL; + cb->sc_response = sssvlv_op_response; + cb->sc_next = op->o_callback; + cb->sc_private = so; + cb->sc_writewait = NULL; + + so->so_tree = NULL; + so->so_ctrl = sc; + so->so_info = si; + if ( ps ) { + so->so_paged = op->o_pagedresults; + so->so_page_size = ps->ps_size; + op->o_pagedresults = SLAP_CONTROL_IGNORED; + } else { + so->so_paged = 0; + so->so_page_size = 0; + if ( vc ) { + so->so_vlv = op->o_ctrlflag[vlv_cid]; + so->so_vlv_target = 0; + so->so_vlv_rc = 0; + } else { + so->so_vlv = SLAP_CONTROL_NONE; + } + } + so->so_session = sess_id; + so->so_vlv = op->o_ctrlflag[vlv_cid]; + so->so_vcontext = (unsigned long)so; + so->so_nentries = 0; + so->so_running = 1; + + op->o_callback = cb; + } + } else { + if ( so && !so->so_nentries ) { + free_sort_op( op->o_conn, so ); + } else { + rs->sr_text = "Other sort requests already in progress"; + rs->sr_err = LDAP_BUSY; + } +leave: + rc = rs->sr_err; + send_ldap_result( op, rs ); + } + + return rc; +} + +static int get_ordering_rule( + AttributeDescription *ad, + struct berval *matchrule, + SlapReply *rs, + MatchingRule **ordering ) +{ + MatchingRule* mr; + + if ( matchrule && matchrule->bv_val ) { + mr = mr_find( matchrule->bv_val ); + if ( mr == NULL ) { + rs->sr_err = LDAP_INAPPROPRIATE_MATCHING; + rs->sr_text = "serverSort control: No ordering rule"; + Debug(LDAP_DEBUG_TRACE, "%s: no ordering rule function for %s\n", + debug_header, matchrule->bv_val, 0); + } + } + else { + mr = ad->ad_type->sat_ordering; + if ( mr == NULL ) { + rs->sr_err = LDAP_INAPPROPRIATE_MATCHING; + rs->sr_text = "serverSort control: No ordering rule"; + Debug(LDAP_DEBUG_TRACE, + "%s: no ordering rule specified and no default ordering rule for attribute %s\n", + debug_header, ad->ad_cname.bv_val, 0); + } + } + + *ordering = mr; + return rs->sr_err; +} + +static int count_key(BerElement *ber) +{ + char *end; + ber_len_t len; + ber_tag_t tag; + int count = 0; + + /* Server Side Sort Control is a SEQUENCE of SEQUENCE */ + for ( tag = ber_first_element( ber, &len, &end ); + tag == LBER_SEQUENCE; + tag = ber_next_element( ber, &len, end )) + { + tag = ber_skip_tag( ber, &len ); + ber_skip_data( ber, len ); + ++count; + } + ber_rewind( ber ); + + return count; +} + +static int build_key( + BerElement *ber, + SlapReply *rs, + sort_key *key ) +{ + struct berval attr; + struct berval matchrule = BER_BVNULL; + ber_int_t reverse = 0; + ber_tag_t tag; + ber_len_t len; + MatchingRule *ordering = NULL; + AttributeDescription *ad = NULL; + const char *text; + + if (( tag = ber_scanf( ber, "{" )) == LBER_ERROR ) { + rs->sr_text = "serverSort control: decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + return rs->sr_err; + } + + if (( tag = ber_scanf( ber, "m", &attr )) == LBER_ERROR ) { + rs->sr_text = "serverSort control: attribute decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + return rs->sr_err; + } + + tag = ber_peek_tag( ber, &len ); + if ( tag == LDAP_MATCHRULE_IDENTIFIER ) { + if (( tag = ber_scanf( ber, "m", &matchrule )) == LBER_ERROR ) { + rs->sr_text = "serverSort control: matchrule decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + return rs->sr_err; + } + tag = ber_peek_tag( ber, &len ); + } + + if ( tag == LDAP_REVERSEORDER_IDENTIFIER ) { + if (( tag = ber_scanf( ber, "b", &reverse )) == LBER_ERROR ) { + rs->sr_text = "serverSort control: reverse decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + return rs->sr_err; + } + } + + if (( tag = ber_scanf( ber, "}" )) == LBER_ERROR ) { + rs->sr_text = "serverSort control: decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + return rs->sr_err; + } + + if ( slap_bv2ad( &attr, &ad, &text ) != LDAP_SUCCESS ) { + rs->sr_text = + "serverSort control: Unrecognized attribute type in sort key"; + Debug(LDAP_DEBUG_TRACE, + "%s: Unrecognized attribute type in sort key: %s\n", + debug_header, SAFESTR(attr.bv_val, "<None>"), 0); + rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE; + return rs->sr_err; + } + + /* get_ordering_rule will set sr_err and sr_text */ + get_ordering_rule( ad, &matchrule, rs, &ordering ); + if ( rs->sr_err != LDAP_SUCCESS ) { + return rs->sr_err; + } + + key->sk_ad = ad; + key->sk_ordering = ordering; + key->sk_direction = reverse ? -1 : 1; + + return rs->sr_err; +} + +/* Conforms to RFC4510 re: Criticality, original RFC2891 spec is broken + * Also see ITS#7253 for discussion + */ +static int sss_parseCtrl( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + BerElementBuffer berbuf; + BerElement *ber; + ber_tag_t tag; + ber_len_t len; + int i; + sort_ctrl *sc; + + rs->sr_err = LDAP_PROTOCOL_ERROR; + + if ( op->o_ctrlflag[sss_cid] > SLAP_CONTROL_IGNORED ) { + rs->sr_text = "sorted results control specified multiple times"; + } else if ( BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = "sorted results control value is absent"; + } else if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) { + rs->sr_text = "sorted results control value is empty"; + } else { + rs->sr_err = LDAP_SUCCESS; + } + if ( rs->sr_err != LDAP_SUCCESS ) + return rs->sr_err; + + op->o_ctrlflag[sss_cid] = ctrl->ldctl_iscritical ? + SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL; + + ber = (BerElement *)&berbuf; + ber_init2( ber, &ctrl->ldctl_value, 0 ); + i = count_key( ber ); + + sc = op->o_tmpalloc( sizeof(sort_ctrl) + + (i-1) * sizeof(sort_key), op->o_tmpmemctx ); + sc->sc_nkeys = i; + op->o_controls[sss_cid] = sc; + + /* peel off initial sequence */ + ber_scanf( ber, "{" ); + + i = 0; + do { + if ( build_key( ber, rs, &sc->sc_keys[i] ) != LDAP_SUCCESS ) + break; + i++; + tag = ber_peek_tag( ber, &len ); + } while ( tag != LBER_DEFAULT ); + + return rs->sr_err; +} + +static int vlv_parseCtrl( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + BerElementBuffer berbuf; + BerElement *ber; + ber_tag_t tag; + ber_len_t len; + vlv_ctrl *vc, vc2; + + rs->sr_err = LDAP_PROTOCOL_ERROR; + rs->sr_text = NULL; + + if ( op->o_ctrlflag[vlv_cid] > SLAP_CONTROL_IGNORED ) { + rs->sr_text = "vlv control specified multiple times"; + } else if ( BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = "vlv control value is absent"; + } else if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) { + rs->sr_text = "vlv control value is empty"; + } + if ( rs->sr_text != NULL ) + return rs->sr_err; + + op->o_ctrlflag[vlv_cid] = ctrl->ldctl_iscritical ? + SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL; + + ber = (BerElement *)&berbuf; + ber_init2( ber, &ctrl->ldctl_value, 0 ); + + rs->sr_err = LDAP_PROTOCOL_ERROR; + + tag = ber_scanf( ber, "{ii", &vc2.vc_before, &vc2.vc_after ); + if ( tag == LBER_ERROR ) { + return rs->sr_err; + } + + tag = ber_peek_tag( ber, &len ); + if ( tag == LDAP_VLVBYINDEX_IDENTIFIER ) { + tag = ber_scanf( ber, "{ii}", &vc2.vc_offset, &vc2.vc_count ); + if ( tag == LBER_ERROR ) + return rs->sr_err; + BER_BVZERO( &vc2.vc_value ); + } else if ( tag == LDAP_VLVBYVALUE_IDENTIFIER ) { + tag = ber_scanf( ber, "m", &vc2.vc_value ); + if ( tag == LBER_ERROR || BER_BVISNULL( &vc2.vc_value )) + return rs->sr_err; + } else { + return rs->sr_err; + } + tag = ber_peek_tag( ber, &len ); + if ( tag == LDAP_VLVCONTEXT_IDENTIFIER ) { + struct berval bv; + tag = ber_scanf( ber, "m", &bv ); + if ( tag == LBER_ERROR || bv.bv_len != sizeof(vc2.vc_context)) + return rs->sr_err; + AC_MEMCPY( &vc2.vc_context, bv.bv_val, bv.bv_len ); + } else { + vc2.vc_context = 0; + } + + vc = op->o_tmpalloc( sizeof(vlv_ctrl), op->o_tmpmemctx ); + *vc = vc2; + op->o_controls[vlv_cid] = vc; + rs->sr_err = LDAP_SUCCESS; + + return rs->sr_err; +} + +static int sssvlv_connection_destroy( BackendDB *be, Connection *conn ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + sssvlv_info *si = on->on_bi.bi_private; + + if ( sort_conns[conn->c_conn_idx] ) { + free_sort_ops( conn, sort_conns[conn->c_conn_idx], si->svi_max_percon ); + } + + return LDAP_SUCCESS; +} + +static int sssvlv_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + sssvlv_info *si = on->on_bi.bi_private; + int rc; + int conn_index; + + /* If not set, default to 1/2 of available threads */ + if ( !si->svi_max ) + si->svi_max = connection_pool_max / 2; + + if ( dtblsize && !sort_conns ) { + ldap_pvt_thread_mutex_init( &sort_conns_mutex ); + /* accommodate for c_conn_idx == -1 */ + sort_conns = ch_calloc( dtblsize + 1, sizeof(sort_op **) ); + for ( conn_index = 0 ; conn_index < dtblsize + 1 ; conn_index++ ) { + sort_conns[conn_index] = ch_calloc( si->svi_max_percon, sizeof(sort_op *) ); + } + sort_conns++; + } + + rc = overlay_register_control( be, LDAP_CONTROL_SORTREQUEST ); + if ( rc == LDAP_SUCCESS ) + rc = overlay_register_control( be, LDAP_CONTROL_VLVREQUEST ); + return rc; +} + +static ConfigTable sssvlv_cfg[] = { + { "sssvlv-max", "num", + 2, 2, 0, ARG_INT|ARG_OFFSET, + (void *)offsetof(sssvlv_info, svi_max), + "( OLcfgOvAt:21.1 NAME 'olcSssVlvMax' " + "DESC 'Maximum number of concurrent Sort requests' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "sssvlv-maxkeys", "num", + 2, 2, 0, ARG_INT|ARG_OFFSET, + (void *)offsetof(sssvlv_info, svi_max_keys), + "( OLcfgOvAt:21.2 NAME 'olcSssVlvMaxKeys' " + "DESC 'Maximum number of Keys in a Sort request' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "sssvlv-maxperconn", "num", + 2, 2, 0, ARG_INT|ARG_OFFSET, + (void *)offsetof(sssvlv_info, svi_max_percon), + "( OLcfgOvAt:21.3 NAME 'olcSssVlvMaxPerConn' " + "DESC 'Maximum number of concurrent paged search requests per connection' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs sssvlv_ocs[] = { + { "( OLcfgOvOc:21.1 " + "NAME 'olcSssVlvConfig' " + "DESC 'SSS VLV configuration' " + "SUP olcOverlayConfig " + "MAY ( olcSssVlvMax $ olcSssVlvMaxKeys $ olcSssVlvMaxPerConn ) )", + Cft_Overlay, sssvlv_cfg, NULL, NULL }, + { NULL, 0, NULL } +}; + +static int sssvlv_db_init( + BackendDB *be, + ConfigReply *cr) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + sssvlv_info *si; + + if ( ov_count == 0 ) { + int rc; + + rc = register_supported_control2( LDAP_CONTROL_SORTREQUEST, + SLAP_CTRL_SEARCH, + NULL, + sss_parseCtrl, + 1 /* replace */, + &sss_cid ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "Failed to register Sort Request control '%s' (%d)\n", + LDAP_CONTROL_SORTREQUEST, rc, 0 ); + return rc; + } + + rc = register_supported_control2( LDAP_CONTROL_VLVREQUEST, + SLAP_CTRL_SEARCH, + NULL, + vlv_parseCtrl, + 1 /* replace */, + &vlv_cid ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "Failed to register VLV Request control '%s' (%d)\n", + LDAP_CONTROL_VLVREQUEST, rc, 0 ); +#ifdef SLAP_CONFIG_DELETE + overlay_unregister_control( be, LDAP_CONTROL_SORTREQUEST ); + unregister_supported_control( LDAP_CONTROL_SORTREQUEST ); +#endif /* SLAP_CONFIG_DELETE */ + return rc; + } + } + + si = (sssvlv_info *)ch_malloc(sizeof(sssvlv_info)); + on->on_bi.bi_private = si; + + si->svi_max = 0; + si->svi_num = 0; + si->svi_max_keys = SSSVLV_DEFAULT_MAX_KEYS; + si->svi_max_percon = SSSVLV_DEFAULT_MAX_REQUEST_PER_CONN; + + ov_count++; + + return LDAP_SUCCESS; +} + +static int sssvlv_db_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + sssvlv_info *si = (sssvlv_info *)on->on_bi.bi_private; + int conn_index; + + ov_count--; + if ( !ov_count && sort_conns) { + sort_conns--; + for ( conn_index = 0 ; conn_index < dtblsize + 1 ; conn_index++ ) { + ch_free(sort_conns[conn_index]); + } + ch_free(sort_conns); + ldap_pvt_thread_mutex_destroy( &sort_conns_mutex ); + } + +#ifdef SLAP_CONFIG_DELETE + overlay_unregister_control( be, LDAP_CONTROL_SORTREQUEST ); + overlay_unregister_control( be, LDAP_CONTROL_VLVREQUEST ); + if ( ov_count == 0 ) { + unregister_supported_control( LDAP_CONTROL_SORTREQUEST ); + unregister_supported_control( LDAP_CONTROL_VLVREQUEST ); + } +#endif /* SLAP_CONFIG_DELETE */ + + if ( si ) { + ch_free( si ); + on->on_bi.bi_private = NULL; + } + return LDAP_SUCCESS; +} + +static slap_overinst sssvlv; + +int sssvlv_initialize() +{ + int rc; + + sssvlv.on_bi.bi_type = "sssvlv"; + sssvlv.on_bi.bi_db_init = sssvlv_db_init; + sssvlv.on_bi.bi_db_destroy = sssvlv_db_destroy; + sssvlv.on_bi.bi_db_open = sssvlv_db_open; + sssvlv.on_bi.bi_connection_destroy = sssvlv_connection_destroy; + sssvlv.on_bi.bi_op_search = sssvlv_op_search; + + sssvlv.on_bi.bi_cf_ocs = sssvlv_ocs; + + rc = config_register_schema( sssvlv_cfg, sssvlv_ocs ); + if ( rc ) + return rc; + + rc = overlay_register( &sssvlv ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "Failed to register server side sort overlay\n", 0, 0, 0 ); + } + + return rc; +} + +#if SLAPD_OVER_SSSVLV == SLAPD_MOD_DYNAMIC +int init_module( int argc, char *argv[]) +{ + return sssvlv_initialize(); +} +#endif + +#endif /* SLAPD_OVER_SSSVLV */ diff --git a/servers/slapd/overlays/syncprov.c b/servers/slapd/overlays/syncprov.c new file mode 100644 index 0000000..60d196e --- /dev/null +++ b/servers/slapd/overlays/syncprov.c @@ -0,0 +1,3717 @@ +/* $OpenLDAP$ */ +/* syncprov.c - syncrepl provider */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_SYNCPROV + +#include <ac/string.h> +#include "lutil.h" +#include "slap.h" +#include "config.h" +#include "ldap_rq.h" + +#ifdef LDAP_DEVEL +#define CHECK_CSN 1 +#endif + +/* A modify request on a particular entry */ +typedef struct modinst { + struct modinst *mi_next; + Operation *mi_op; +} modinst; + +typedef struct modtarget { + struct modinst *mt_mods; + struct modinst *mt_tail; + struct berval mt_dn; + ldap_pvt_thread_mutex_t mt_mutex; +} modtarget; + +/* All the info of a psearch result that's shared between + * multiple queues + */ +typedef struct resinfo { + struct syncres *ri_list; + Entry *ri_e; + struct berval ri_dn; + struct berval ri_ndn; + struct berval ri_uuid; + struct berval ri_csn; + struct berval ri_cookie; + char ri_isref; + ldap_pvt_thread_mutex_t ri_mutex; +} resinfo; + +/* A queued result of a persistent search */ +typedef struct syncres { + struct syncres *s_next; /* list of results on this psearch queue */ + struct syncres *s_rilist; /* list of psearches using this result */ + resinfo *s_info; + char s_mode; +} syncres; + +/* Record of a persistent search */ +typedef struct syncops { + struct syncops *s_next; + struct syncprov_info_t *s_si; + struct berval s_base; /* ndn of search base */ + ID s_eid; /* entryID of search base */ + Operation *s_op; /* search op */ + int s_rid; + int s_sid; + struct berval s_filterstr; + int s_flags; /* search status */ +#define PS_IS_REFRESHING 0x01 +#define PS_IS_DETACHED 0x02 +#define PS_WROTE_BASE 0x04 +#define PS_FIND_BASE 0x08 +#define PS_FIX_FILTER 0x10 +#define PS_TASK_QUEUED 0x20 + + int s_inuse; /* reference count */ + struct syncres *s_res; + struct syncres *s_restail; + ldap_pvt_thread_mutex_t s_mutex; +} syncops; + +/* A received sync control */ +typedef struct sync_control { + struct sync_cookie sr_state; + int sr_rhint; +} sync_control; + +#if 0 /* moved back to slap.h */ +#define o_sync o_ctrlflag[slap_cids.sc_LDAPsync] +#endif +/* o_sync_mode uses data bits of o_sync */ +#define o_sync_mode o_ctrlflag[slap_cids.sc_LDAPsync] + +#define SLAP_SYNC_NONE (LDAP_SYNC_NONE<<SLAP_CONTROL_SHIFT) +#define SLAP_SYNC_REFRESH (LDAP_SYNC_REFRESH_ONLY<<SLAP_CONTROL_SHIFT) +#define SLAP_SYNC_PERSIST (LDAP_SYNC_RESERVED<<SLAP_CONTROL_SHIFT) +#define SLAP_SYNC_REFRESH_AND_PERSIST (LDAP_SYNC_REFRESH_AND_PERSIST<<SLAP_CONTROL_SHIFT) + +/* Record of which searches matched at premodify step */ +typedef struct syncmatches { + struct syncmatches *sm_next; + syncops *sm_op; +} syncmatches; + +/* Session log data */ +typedef struct slog_entry { + struct berval se_uuid; + struct berval se_csn; + int se_sid; + ber_tag_t se_tag; +} slog_entry; + +typedef struct sessionlog { + BerVarray sl_mincsn; + int *sl_sids; + int sl_numcsns; + int sl_num; + int sl_size; + int sl_playing; + Avlnode *sl_entries; + ldap_pvt_thread_rdwr_t sl_mutex; +} sessionlog; + +/* The main state for this overlay */ +typedef struct syncprov_info_t { + syncops *si_ops; + struct berval si_contextdn; + BerVarray si_ctxcsn; /* ldapsync context */ + int *si_sids; + int si_numcsns; + int si_chkops; /* checkpointing info */ + int si_chktime; + int si_numops; /* number of ops since last checkpoint */ + int si_nopres; /* Skip present phase */ + int si_usehint; /* use reload hint */ + int si_active; /* True if there are active mods */ + int si_dirty; /* True if the context is dirty, i.e changes + * have been made without updating the csn. */ + time_t si_chklast; /* time of last checkpoint */ + Avlnode *si_mods; /* entries being modified */ + sessionlog *si_logs; + ldap_pvt_thread_rdwr_t si_csn_rwlock; + ldap_pvt_thread_mutex_t si_ops_mutex; + ldap_pvt_thread_mutex_t si_mods_mutex; + ldap_pvt_thread_mutex_t si_resp_mutex; +} syncprov_info_t; + +typedef struct opcookie { + slap_overinst *son; + syncmatches *smatches; + modtarget *smt; + Entry *se; + struct berval sdn; /* DN of entry, for deletes */ + struct berval sndn; + struct berval suuid; /* UUID of entry */ + struct berval sctxcsn; + short osid; /* sid of op csn */ + short rsid; /* sid of relay */ + short sreference; /* Is the entry a reference? */ + syncres ssres; +} opcookie; + +typedef struct fbase_cookie { + struct berval *fdn; /* DN of a modified entry, for scope testing */ + syncops *fss; /* persistent search we're testing against */ + int fbase; /* if TRUE we found the search base and it's still valid */ + int fscope; /* if TRUE then fdn is within the psearch scope */ +} fbase_cookie; + +static AttributeName csn_anlist[3]; +static AttributeName uuid_anlist[2]; + +/* Build a LDAPsync intermediate state control */ +static int +syncprov_state_ctrl( + Operation *op, + SlapReply *rs, + Entry *e, + int entry_sync_state, + LDAPControl **ctrls, + int num_ctrls, + int send_cookie, + struct berval *cookie ) +{ + Attribute* a; + int ret; + + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + LDAPControl *cp; + struct berval bv; + struct berval entryuuid_bv = BER_BVNULL; + + ber_init2( ber, 0, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + for ( a = e->e_attrs; a != NULL; a = a->a_next ) { + AttributeDescription *desc = a->a_desc; + if ( desc == slap_schema.si_ad_entryUUID ) { + entryuuid_bv = a->a_nvals[0]; + break; + } + } + + /* FIXME: what if entryuuid is NULL or empty ? */ + + if ( send_cookie && cookie ) { + ber_printf( ber, "{eOON}", + entry_sync_state, &entryuuid_bv, cookie ); + } else { + ber_printf( ber, "{eON}", + entry_sync_state, &entryuuid_bv ); + } + + ret = ber_flatten2( ber, &bv, 0 ); + if ( ret == 0 ) { + cp = op->o_tmpalloc( sizeof( LDAPControl ) + bv.bv_len, op->o_tmpmemctx ); + cp->ldctl_oid = LDAP_CONTROL_SYNC_STATE; + cp->ldctl_iscritical = (op->o_sync == SLAP_CONTROL_CRITICAL); + cp->ldctl_value.bv_val = (char *)&cp[1]; + cp->ldctl_value.bv_len = bv.bv_len; + AC_MEMCPY( cp->ldctl_value.bv_val, bv.bv_val, bv.bv_len ); + ctrls[num_ctrls] = cp; + } + ber_free_buf( ber ); + + if ( ret < 0 ) { + Debug( LDAP_DEBUG_TRACE, + "slap_build_sync_ctrl: ber_flatten2 failed (%d)\n", + ret, 0, 0 ); + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return LDAP_OTHER; + } + + return LDAP_SUCCESS; +} + +/* Build a LDAPsync final state control */ +static int +syncprov_done_ctrl( + Operation *op, + SlapReply *rs, + LDAPControl **ctrls, + int num_ctrls, + int send_cookie, + struct berval *cookie, + int refreshDeletes ) +{ + int ret; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + LDAPControl *cp; + struct berval bv; + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + ber_printf( ber, "{" ); + if ( send_cookie && cookie ) { + ber_printf( ber, "O", cookie ); + } + if ( refreshDeletes == LDAP_SYNC_REFRESH_DELETES ) { + ber_printf( ber, "b", refreshDeletes ); + } + ber_printf( ber, "N}" ); + + ret = ber_flatten2( ber, &bv, 0 ); + if ( ret == 0 ) { + cp = op->o_tmpalloc( sizeof( LDAPControl ) + bv.bv_len, op->o_tmpmemctx ); + cp->ldctl_oid = LDAP_CONTROL_SYNC_DONE; + cp->ldctl_iscritical = (op->o_sync == SLAP_CONTROL_CRITICAL); + cp->ldctl_value.bv_val = (char *)&cp[1]; + cp->ldctl_value.bv_len = bv.bv_len; + AC_MEMCPY( cp->ldctl_value.bv_val, bv.bv_val, bv.bv_len ); + ctrls[num_ctrls] = cp; + } + + ber_free_buf( ber ); + + if ( ret < 0 ) { + Debug( LDAP_DEBUG_TRACE, + "syncprov_done_ctrl: ber_flatten2 failed (%d)\n", + ret, 0, 0 ); + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return LDAP_OTHER; + } + + return LDAP_SUCCESS; +} + +static int +syncprov_sendinfo( + Operation *op, + SlapReply *rs, + int type, + struct berval *cookie, + int refreshDone, + BerVarray syncUUIDs, + int refreshDeletes ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + struct berval rspdata; + + int ret; + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + if ( type ) { + switch ( type ) { + case LDAP_TAG_SYNC_NEW_COOKIE: + Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendinfo: " + "sending a new cookie=%s\n", + op->o_log_prefix, cookie->bv_val, 0 ); + ber_printf( ber, "tO", type, cookie ); + break; + case LDAP_TAG_SYNC_REFRESH_DELETE: + case LDAP_TAG_SYNC_REFRESH_PRESENT: + Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendinfo: " + "%s cookie=%s\n", + op->o_log_prefix, + type == LDAP_TAG_SYNC_REFRESH_DELETE ? "refreshDelete" : "refreshPresent", + cookie ? cookie->bv_val : "" ); + ber_printf( ber, "t{", type ); + if ( cookie ) { + ber_printf( ber, "O", cookie ); + } + if ( refreshDone == 0 ) { + ber_printf( ber, "b", refreshDone ); + } + ber_printf( ber, "N}" ); + break; + case LDAP_TAG_SYNC_ID_SET: + Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendinfo: " + "%s syncIdSet cookie=%s\n", + op->o_log_prefix, refreshDeletes ? "delete" : "present", + cookie ? cookie->bv_val : "" ); + ber_printf( ber, "t{", type ); + if ( cookie ) { + ber_printf( ber, "O", cookie ); + } + if ( refreshDeletes == 1 ) { + ber_printf( ber, "b", refreshDeletes ); + } + ber_printf( ber, "[W]", syncUUIDs ); + ber_printf( ber, "N}" ); + break; + default: + Debug( LDAP_DEBUG_TRACE, + "%s syncprov_sendinfo: invalid syncinfo type (%d)\n", + op->o_log_prefix, type, 0 ); + return LDAP_OTHER; + } + } + + ret = ber_flatten2( ber, &rspdata, 0 ); + + if ( ret < 0 ) { + Debug( LDAP_DEBUG_TRACE, + "syncprov_sendinfo: ber_flatten2 failed (%d)\n", + ret, 0, 0 ); + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return LDAP_OTHER; + } + + rs->sr_rspoid = LDAP_SYNC_INFO; + rs->sr_rspdata = &rspdata; + send_ldap_intermediate( op, rs ); + rs->sr_rspdata = NULL; + ber_free_buf( ber ); + + return LDAP_SUCCESS; +} + +/* Find a modtarget in an AVL tree */ +static int +sp_avl_cmp( const void *c1, const void *c2 ) +{ + const modtarget *m1, *m2; + int rc; + + m1 = c1; m2 = c2; + rc = m1->mt_dn.bv_len - m2->mt_dn.bv_len; + + if ( rc ) return rc; + return ber_bvcmp( &m1->mt_dn, &m2->mt_dn ); +} + +static int +syncprov_sessionlog_cmp( const void *l, const void *r ) +{ + const slog_entry *left = l, *right = r; + int ret = ber_bvcmp( &left->se_csn, &right->se_csn ); + if ( !ret ) + ret = ber_bvcmp( &left->se_uuid, &right->se_uuid ); + /* Only time we have two modifications with same CSN is when we detect a + * rename during replication. + * We invert the test here because LDAP_REQ_MODDN is + * numerically greater than LDAP_REQ_MODIFY but we + * want it to occur first. + */ + if ( !ret ) + ret = right->se_tag - left->se_tag; + + return ret; +} + +/* syncprov_findbase: + * finds the true DN of the base of a search (with alias dereferencing) and + * checks to make sure the base entry doesn't get replaced with a different + * entry (e.g., swapping trees via ModDN, or retargeting an alias). If a + * change is detected, any persistent search on this base must be terminated / + * reloaded. + * On the first call, we just save the DN and entryID. On subsequent calls + * we compare the DN and entryID with the saved values. + */ +static int +findbase_cb( Operation *op, SlapReply *rs ) +{ + slap_callback *sc = op->o_callback; + + if ( rs->sr_type == REP_SEARCH && rs->sr_err == LDAP_SUCCESS ) { + fbase_cookie *fc = sc->sc_private; + + /* If no entryID, we're looking for the first time. + * Just store whatever we got. + */ + if ( fc->fss->s_eid == NOID ) { + fc->fbase = 2; + fc->fss->s_eid = rs->sr_entry->e_id; + ber_dupbv( &fc->fss->s_base, &rs->sr_entry->e_nname ); + + } else if ( rs->sr_entry->e_id == fc->fss->s_eid && + dn_match( &rs->sr_entry->e_nname, &fc->fss->s_base )) { + + /* OK, the DN is the same and the entryID is the same. */ + fc->fbase = 1; + } + } + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "findbase failed! %d\n", rs->sr_err,0,0 ); + } + return LDAP_SUCCESS; +} + +static Filter generic_filter = { LDAP_FILTER_PRESENT, { 0 }, NULL }; +static struct berval generic_filterstr = BER_BVC("(objectclass=*)"); + +static int +syncprov_findbase( Operation *op, fbase_cookie *fc ) +{ + /* Use basic parameters from syncrepl search, but use + * current op's threadctx / tmpmemctx + */ + ldap_pvt_thread_mutex_lock( &fc->fss->s_mutex ); + if ( fc->fss->s_flags & PS_FIND_BASE ) { + slap_callback cb = {0}; + Operation fop; + SlapReply frs = { REP_RESULT }; + int rc; + + fc->fss->s_flags ^= PS_FIND_BASE; + ldap_pvt_thread_mutex_unlock( &fc->fss->s_mutex ); + + fop = *fc->fss->s_op; + + fop.o_bd = fop.o_bd->bd_self; + fop.o_hdr = op->o_hdr; + fop.o_time = op->o_time; + fop.o_tincr = op->o_tincr; + fop.o_extra = op->o_extra; + + cb.sc_response = findbase_cb; + cb.sc_private = fc; + + fop.o_sync_mode = 0; /* turn off sync mode */ + fop.o_managedsait = SLAP_CONTROL_CRITICAL; + fop.o_callback = &cb; + fop.o_tag = LDAP_REQ_SEARCH; + fop.ors_scope = LDAP_SCOPE_BASE; + fop.ors_limit = NULL; + fop.ors_slimit = 1; + fop.ors_tlimit = SLAP_NO_LIMIT; + fop.ors_attrs = slap_anlist_no_attrs; + fop.ors_attrsonly = 1; + fop.ors_filter = &generic_filter; + fop.ors_filterstr = generic_filterstr; + + Debug( LDAP_DEBUG_SYNC, "%s syncprov_findbase: searching\n", op->o_log_prefix, 0, 0 ); + rc = fop.o_bd->be_search( &fop, &frs ); + } else { + ldap_pvt_thread_mutex_unlock( &fc->fss->s_mutex ); + fc->fbase = 1; + } + + /* After the first call, see if the fdn resides in the scope */ + if ( fc->fbase == 1 ) { + switch ( fc->fss->s_op->ors_scope ) { + case LDAP_SCOPE_BASE: + fc->fscope = dn_match( fc->fdn, &fc->fss->s_base ); + break; + case LDAP_SCOPE_ONELEVEL: { + struct berval pdn; + dnParent( fc->fdn, &pdn ); + fc->fscope = dn_match( &pdn, &fc->fss->s_base ); + break; } + case LDAP_SCOPE_SUBTREE: + fc->fscope = dnIsSuffix( fc->fdn, &fc->fss->s_base ); + break; + case LDAP_SCOPE_SUBORDINATE: + fc->fscope = dnIsSuffix( fc->fdn, &fc->fss->s_base ) && + !dn_match( fc->fdn, &fc->fss->s_base ); + break; + } + } + + if ( fc->fbase ) + return LDAP_SUCCESS; + + /* If entryID has changed, then the base of this search has + * changed. Invalidate the psearch. + */ + return LDAP_NO_SUCH_OBJECT; +} + +/* syncprov_findcsn: + * This function has three different purposes, but they all use a search + * that filters on entryCSN so they're combined here. + * 1: at startup time, after a contextCSN has been read from the database, + * we search for all entries with CSN >= contextCSN in case the contextCSN + * was not checkpointed at the previous shutdown. + * + * 2: when the current contextCSN is known and we have a sync cookie, we search + * for one entry with CSN = the cookie CSN. If not found, try <= cookie CSN. + * If an entry is found, the cookie CSN is valid, otherwise it is stale. + * + * 3: during a refresh phase, we search for all entries with CSN <= the cookie + * CSN, and generate Present records for them. We always collect this result + * in SyncID sets, even if there's only one match. + */ +typedef enum find_csn_t { + FIND_MAXCSN = 1, + FIND_CSN = 2, + FIND_PRESENT = 3 +} find_csn_t; + +static int +findmax_cb( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH && rs->sr_err == LDAP_SUCCESS ) { + struct berval *maxcsn = op->o_callback->sc_private; + Attribute *a = attr_find( rs->sr_entry->e_attrs, + slap_schema.si_ad_entryCSN ); + + if ( a && ber_bvcmp( &a->a_vals[0], maxcsn ) > 0 && + slap_parse_csn_sid( &a->a_vals[0] ) == slap_serverID ) { + maxcsn->bv_len = a->a_vals[0].bv_len; + strcpy( maxcsn->bv_val, a->a_vals[0].bv_val ); + } + } + return LDAP_SUCCESS; +} + +static int +findcsn_cb( Operation *op, SlapReply *rs ) +{ + slap_callback *sc = op->o_callback; + + /* We just want to know that at least one exists, so it's OK if + * we exceed the unchecked limit. + */ + if ( rs->sr_err == LDAP_ADMINLIMIT_EXCEEDED || + (rs->sr_type == REP_SEARCH && rs->sr_err == LDAP_SUCCESS )) { + sc->sc_private = (void *)1; + } + return LDAP_SUCCESS; +} + +/* Build a list of entryUUIDs for sending in a SyncID set */ + +#define UUID_LEN 16 + +typedef struct fpres_cookie { + int num; + BerVarray uuids; + char *last; +} fpres_cookie; + +static int +findpres_cb( Operation *op, SlapReply *rs ) +{ + slap_callback *sc = op->o_callback; + fpres_cookie *pc = sc->sc_private; + Attribute *a; + int ret = SLAP_CB_CONTINUE; + + switch ( rs->sr_type ) { + case REP_SEARCH: + a = attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryUUID ); + if ( a ) { + pc->uuids[pc->num].bv_val = pc->last; + AC_MEMCPY( pc->uuids[pc->num].bv_val, a->a_nvals[0].bv_val, + pc->uuids[pc->num].bv_len ); + pc->num++; + pc->last = pc->uuids[pc->num].bv_val; + pc->uuids[pc->num].bv_val = NULL; + } + ret = LDAP_SUCCESS; + if ( pc->num != SLAP_SYNCUUID_SET_SIZE ) + break; + /* FALLTHRU */ + case REP_RESULT: + ret = rs->sr_err; + if ( pc->num ) { + ret = syncprov_sendinfo( op, rs, LDAP_TAG_SYNC_ID_SET, NULL, + 0, pc->uuids, 0 ); + pc->uuids[pc->num].bv_val = pc->last; + pc->num = 0; + pc->last = pc->uuids[0].bv_val; + } + break; + default: + break; + } + return ret; +} + +static int +syncprov_findcsn( Operation *op, find_csn_t mode, struct berval *csn ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + syncprov_info_t *si = on->on_bi.bi_private; + + slap_callback cb = {0}; + Operation fop; + SlapReply frs = { REP_RESULT }; + char buf[LDAP_PVT_CSNSTR_BUFSIZE + STRLENOF("(entryCSN<=)")]; + char cbuf[LDAP_PVT_CSNSTR_BUFSIZE]; + struct berval maxcsn; + Filter cf; + AttributeAssertion eq = ATTRIBUTEASSERTION_INIT; + fpres_cookie pcookie; + sync_control *srs = NULL; + struct slap_limits_set fc_limits; + int i, rc = LDAP_SUCCESS, findcsn_retry = 1; + int maxid; + + if ( mode != FIND_MAXCSN ) { + srs = op->o_controls[slap_cids.sc_LDAPsync]; + } + + Debug( LDAP_DEBUG_SYNC, "%s syncprov_findcsn: mode=%s csn=%s\n", + op->o_log_prefix, + mode == FIND_MAXCSN ? + "FIND_MAXCSN" : + mode == FIND_CSN ? + "FIND_CSN" : + "FIND_PRESENT", + csn ? csn->bv_val : "" ); + + fop = *op; + fop.o_sync_mode &= SLAP_CONTROL_MASK; /* turn off sync_mode */ + /* We want pure entries, not referrals */ + fop.o_managedsait = SLAP_CONTROL_CRITICAL; + + cf.f_ava = &eq; + cf.f_av_desc = slap_schema.si_ad_entryCSN; + BER_BVZERO( &cf.f_av_value ); + cf.f_next = NULL; + + fop.o_callback = &cb; + fop.ors_limit = NULL; + fop.ors_tlimit = SLAP_NO_LIMIT; + fop.ors_filter = &cf; + fop.ors_filterstr.bv_val = buf; + +again: + switch( mode ) { + case FIND_MAXCSN: + cf.f_choice = LDAP_FILTER_GE; + /* If there are multiple CSNs, use the one with our serverID */ + for ( i=0; i<si->si_numcsns; i++) { + if ( slap_serverID == si->si_sids[i] ) { + maxid = i; + break; + } + } + if ( i == si->si_numcsns ) { + /* No match: this is multimaster, and none of the content in the DB + * originated locally. Treat like no CSN. + */ + return LDAP_NO_SUCH_OBJECT; + } + cf.f_av_value = si->si_ctxcsn[maxid]; + fop.ors_filterstr.bv_len = snprintf( buf, sizeof( buf ), + "(entryCSN>=%s)", cf.f_av_value.bv_val ); + if ( fop.ors_filterstr.bv_len >= sizeof( buf ) ) { + return LDAP_OTHER; + } + fop.ors_attrsonly = 0; + fop.ors_attrs = csn_anlist; + fop.ors_slimit = SLAP_NO_LIMIT; + cb.sc_private = &maxcsn; + cb.sc_response = findmax_cb; + strcpy( cbuf, cf.f_av_value.bv_val ); + maxcsn.bv_val = cbuf; + maxcsn.bv_len = cf.f_av_value.bv_len; + break; + case FIND_CSN: + if ( BER_BVISEMPTY( &cf.f_av_value )) { + cf.f_av_value = *csn; + } + fop.o_dn = op->o_bd->be_rootdn; + fop.o_ndn = op->o_bd->be_rootndn; + fop.o_req_dn = op->o_bd->be_suffix[0]; + fop.o_req_ndn = op->o_bd->be_nsuffix[0]; + /* Look for exact match the first time */ + if ( findcsn_retry ) { + cf.f_choice = LDAP_FILTER_EQUALITY; + fop.ors_filterstr.bv_len = snprintf( buf, sizeof( buf ), + "(entryCSN=%s)", cf.f_av_value.bv_val ); + /* On retry, look for <= */ + } else { + cf.f_choice = LDAP_FILTER_LE; + fop.ors_limit = &fc_limits; + memset( &fc_limits, 0, sizeof( fc_limits )); + fc_limits.lms_s_unchecked = 1; + fop.ors_filterstr.bv_len = snprintf( buf, sizeof( buf ), + "(entryCSN<=%s)", cf.f_av_value.bv_val ); + } + if ( fop.ors_filterstr.bv_len >= sizeof( buf ) ) { + return LDAP_OTHER; + } + fop.ors_attrsonly = 1; + fop.ors_attrs = slap_anlist_no_attrs; + fop.ors_slimit = 1; + cb.sc_private = NULL; + cb.sc_response = findcsn_cb; + break; + case FIND_PRESENT: + fop.ors_filter = op->ors_filter; + fop.ors_filterstr = op->ors_filterstr; + fop.ors_attrsonly = 0; + fop.ors_attrs = uuid_anlist; + fop.ors_slimit = SLAP_NO_LIMIT; + cb.sc_private = &pcookie; + cb.sc_response = findpres_cb; + pcookie.num = 0; + + /* preallocate storage for a full set */ + pcookie.uuids = op->o_tmpalloc( (SLAP_SYNCUUID_SET_SIZE+1) * + sizeof(struct berval) + SLAP_SYNCUUID_SET_SIZE * UUID_LEN, + op->o_tmpmemctx ); + pcookie.last = (char *)(pcookie.uuids + SLAP_SYNCUUID_SET_SIZE+1); + pcookie.uuids[0].bv_val = pcookie.last; + pcookie.uuids[0].bv_len = UUID_LEN; + for (i=1; i<SLAP_SYNCUUID_SET_SIZE; i++) { + pcookie.uuids[i].bv_val = pcookie.uuids[i-1].bv_val + UUID_LEN; + pcookie.uuids[i].bv_len = UUID_LEN; + } + break; + } + + fop.o_bd->bd_info = (BackendInfo *)on->on_info; + fop.o_bd->be_search( &fop, &frs ); + fop.o_bd->bd_info = (BackendInfo *)on; + + switch( mode ) { + case FIND_MAXCSN: + if ( ber_bvcmp( &si->si_ctxcsn[maxid], &maxcsn )) { +#ifdef CHECK_CSN + Syntax *syn = slap_schema.si_ad_contextCSN->ad_type->sat_syntax; + assert( !syn->ssyn_validate( syn, &maxcsn )); +#endif + ber_bvreplace( &si->si_ctxcsn[maxid], &maxcsn ); + si->si_numops++; /* ensure a checkpoint */ + } + break; + case FIND_CSN: + /* If matching CSN was not found, invalidate the context. */ + Log4( LDAP_DEBUG_SYNC, ldap_syslog_level, "%s syncprov_findcsn: csn%s=%s %sfound\n", + op->o_log_prefix, + cf.f_choice == LDAP_FILTER_EQUALITY ? "=" : "<", + cf.f_av_value.bv_val, cb.sc_private ? "" : "not " ); + if ( !cb.sc_private ) { + /* If we didn't find an exact match, then try for <= */ + if ( findcsn_retry ) { + findcsn_retry = 0; + rs_reinit( &frs, REP_RESULT ); + goto again; + } + rc = LDAP_NO_SUCH_OBJECT; + } + break; + case FIND_PRESENT: + op->o_tmpfree( pcookie.uuids, op->o_tmpmemctx ); + break; + } + + return rc; +} + +static void free_resinfo( syncres *sr ) +{ + syncres **st; + int freeit = 0; + ldap_pvt_thread_mutex_lock( &sr->s_info->ri_mutex ); + for (st = &sr->s_info->ri_list; *st; st = &(*st)->s_rilist) { + if (*st == sr) { + *st = sr->s_rilist; + break; + } + } + if ( !sr->s_info->ri_list ) + freeit = 1; + ldap_pvt_thread_mutex_unlock( &sr->s_info->ri_mutex ); + if ( freeit ) { + ldap_pvt_thread_mutex_destroy( &sr->s_info->ri_mutex ); + if ( sr->s_info->ri_e ) + entry_free( sr->s_info->ri_e ); + if ( !BER_BVISNULL( &sr->s_info->ri_cookie )) + ch_free( sr->s_info->ri_cookie.bv_val ); + ch_free( sr->s_info ); + } +} + +static int +syncprov_free_syncop( syncops *so, int unlink ) +{ + syncres *sr, *srnext; + GroupAssertion *ga, *gnext; + + ldap_pvt_thread_mutex_lock( &so->s_mutex ); + /* already being freed, or still in use */ + if ( !so->s_inuse || --so->s_inuse > 0 ) { + ldap_pvt_thread_mutex_unlock( &so->s_mutex ); + return 0; + } + ldap_pvt_thread_mutex_unlock( &so->s_mutex ); + if ( unlink ) { + syncops **sop; + ldap_pvt_thread_mutex_lock( &so->s_si->si_ops_mutex ); + for ( sop = &so->s_si->si_ops; *sop; sop = &(*sop)->s_next ) { + if ( *sop == so ) { + *sop = so->s_next; + break; + } + } + ldap_pvt_thread_mutex_unlock( &so->s_si->si_ops_mutex ); + } + if ( so->s_flags & PS_IS_DETACHED ) { + filter_free( so->s_op->ors_filter ); + for ( ga = so->s_op->o_groups; ga; ga=gnext ) { + gnext = ga->ga_next; + ch_free( ga ); + } + ch_free( so->s_op ); + } + ch_free( so->s_base.bv_val ); + for ( sr=so->s_res; sr; sr=srnext ) { + srnext = sr->s_next; + free_resinfo( sr ); + ch_free( sr ); + } + ldap_pvt_thread_mutex_destroy( &so->s_mutex ); + ch_free( so ); + return 1; +} + +/* Send a persistent search response */ +static int +syncprov_sendresp( Operation *op, resinfo *ri, syncops *so, int mode ) +{ + SlapReply rs = { REP_SEARCH }; + struct berval cookie, csns[2]; + Entry e_uuid = {0}; + Attribute a_uuid = {0}; + + if ( so->s_op->o_abandon ) + return SLAPD_ABANDON; + + rs.sr_ctrls = op->o_tmpalloc( sizeof(LDAPControl *)*2, op->o_tmpmemctx ); + rs.sr_ctrls[1] = NULL; + rs.sr_flags = REP_CTRLS_MUSTBEFREED; + csns[0] = ri->ri_csn; + BER_BVZERO( &csns[1] ); + slap_compose_sync_cookie( op, &cookie, csns, so->s_rid, slap_serverID ? slap_serverID : -1 ); + +#ifdef LDAP_DEBUG + if ( so->s_sid > 0 ) { + Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendresp: to=%03x, cookie=%s\n", + op->o_log_prefix, so->s_sid, cookie.bv_val ); + } else { + Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendresp: cookie=%s\n", + op->o_log_prefix, cookie.bv_val, 0 ); + } +#endif + + e_uuid.e_attrs = &a_uuid; + a_uuid.a_desc = slap_schema.si_ad_entryUUID; + a_uuid.a_nvals = &ri->ri_uuid; + rs.sr_err = syncprov_state_ctrl( op, &rs, &e_uuid, + mode, rs.sr_ctrls, 0, 1, &cookie ); + op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx ); + + rs.sr_entry = &e_uuid; + if ( mode == LDAP_SYNC_ADD || mode == LDAP_SYNC_MODIFY ) { + e_uuid = *ri->ri_e; + e_uuid.e_private = NULL; + } + + switch( mode ) { + case LDAP_SYNC_ADD: + if ( ri->ri_isref && so->s_op->o_managedsait <= SLAP_CONTROL_IGNORED ) { + rs.sr_ref = get_entry_referrals( op, rs.sr_entry ); + rs.sr_err = send_search_reference( op, &rs ); + ber_bvarray_free( rs.sr_ref ); + break; + } + /* fallthru */ + case LDAP_SYNC_MODIFY: + Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendresp: sending %s, dn=%s\n", + op->o_log_prefix, + mode == LDAP_SYNC_ADD ? "LDAP_SYNC_ADD" : "LDAP_SYNC_MODIFY", + e_uuid.e_nname.bv_val ); + rs.sr_attrs = op->ors_attrs; + rs.sr_err = send_search_entry( op, &rs ); + break; + case LDAP_SYNC_DELETE: + Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendresp: " + "sending LDAP_SYNC_DELETE, dn=%s\n", + op->o_log_prefix, ri->ri_dn.bv_val, 0 ); + e_uuid.e_attrs = NULL; + e_uuid.e_name = ri->ri_dn; + e_uuid.e_nname = ri->ri_ndn; + if ( ri->ri_isref && so->s_op->o_managedsait <= SLAP_CONTROL_IGNORED ) { + struct berval bv = BER_BVNULL; + rs.sr_ref = &bv; + rs.sr_err = send_search_reference( op, &rs ); + } else { + rs.sr_err = send_search_entry( op, &rs ); + } + break; + default: + assert(0); + } + return rs.sr_err; +} + +static void +syncprov_qstart( syncops *so ); + +/* Play back queued responses */ +static int +syncprov_qplay( Operation *op, syncops *so ) +{ + syncres *sr; + int rc = 0; + + do { + ldap_pvt_thread_mutex_lock( &so->s_mutex ); + sr = so->s_res; + /* Exit loop with mutex held */ + if ( !sr ) + break; + so->s_res = sr->s_next; + if ( !so->s_res ) + so->s_restail = NULL; + ldap_pvt_thread_mutex_unlock( &so->s_mutex ); + + if ( !so->s_op->o_abandon ) { + + if ( sr->s_mode == LDAP_SYNC_NEW_COOKIE ) { + SlapReply rs = { REP_INTERMEDIATE }; + + rc = syncprov_sendinfo( op, &rs, LDAP_TAG_SYNC_NEW_COOKIE, + &sr->s_info->ri_cookie, 0, NULL, 0 ); + } else { + rc = syncprov_sendresp( op, sr->s_info, so, sr->s_mode ); + } + } + + free_resinfo( sr ); + ch_free( sr ); + + if ( so->s_op->o_abandon ) + continue; + + /* Exit loop with mutex held */ + ldap_pvt_thread_mutex_lock( &so->s_mutex ); + break; + + } while (1); + + /* We now only send one change at a time, to prevent one + * psearch from hogging all the CPU. Resubmit this task if + * there are more responses queued and no errors occurred. + */ + + if ( rc == 0 && so->s_res ) { + syncprov_qstart( so ); + } else { + so->s_flags ^= PS_TASK_QUEUED; + } + + ldap_pvt_thread_mutex_unlock( &so->s_mutex ); + return rc; +} + +/* task for playing back queued responses */ +static void * +syncprov_qtask( void *ctx, void *arg ) +{ + syncops *so = arg; + OperationBuffer opbuf; + Operation *op; + BackendDB be; + int rc; + + op = &opbuf.ob_op; + *op = *so->s_op; + op->o_hdr = &opbuf.ob_hdr; + op->o_controls = opbuf.ob_controls; + memset( op->o_controls, 0, sizeof(opbuf.ob_controls) ); + op->o_sync = SLAP_CONTROL_IGNORED; + + *op->o_hdr = *so->s_op->o_hdr; + + op->o_tmpmemctx = slap_sl_mem_create(SLAP_SLAB_SIZE, SLAP_SLAB_STACK, ctx, 1); + op->o_tmpmfuncs = &slap_sl_mfuncs; + op->o_threadctx = ctx; + + /* syncprov_qplay expects a fake db */ + be = *so->s_op->o_bd; + be.be_flags |= SLAP_DBFLAG_OVERLAY; + op->o_bd = &be; + LDAP_SLIST_FIRST(&op->o_extra) = NULL; + op->o_callback = NULL; + + rc = syncprov_qplay( op, so ); + + /* decrement use count... */ + syncprov_free_syncop( so, 1 ); + + return NULL; +} + +/* Start the task to play back queued psearch responses */ +static void +syncprov_qstart( syncops *so ) +{ + so->s_flags |= PS_TASK_QUEUED; + so->s_inuse++; + ldap_pvt_thread_pool_submit( &connection_pool, + syncprov_qtask, so ); +} + +/* Queue a persistent search response */ +static int +syncprov_qresp( opcookie *opc, syncops *so, int mode ) +{ + syncres *sr; + resinfo *ri; + int srsize; + struct berval csn = opc->sctxcsn; + + sr = ch_malloc( sizeof( syncres )); + sr->s_next = NULL; + sr->s_mode = mode; + if ( !opc->ssres.s_info ) { + + srsize = sizeof( resinfo ); + if ( csn.bv_len ) + srsize += csn.bv_len + 1; + + if ( opc->se ) { + Attribute *a; + ri = ch_malloc( srsize ); + ri->ri_dn = opc->se->e_name; + ri->ri_ndn = opc->se->e_nname; + a = attr_find( opc->se->e_attrs, slap_schema.si_ad_entryUUID ); + if ( a ) + ri->ri_uuid = a->a_nvals[0]; + else + ri->ri_uuid.bv_len = 0; + if ( csn.bv_len ) { + ri->ri_csn.bv_val = (char *)(ri + 1); + ri->ri_csn.bv_len = csn.bv_len; + memcpy( ri->ri_csn.bv_val, csn.bv_val, csn.bv_len ); + ri->ri_csn.bv_val[csn.bv_len] = '\0'; + } else { + ri->ri_csn.bv_val = NULL; + } + } else { + srsize += opc->suuid.bv_len + + opc->sdn.bv_len + 1 + opc->sndn.bv_len + 1; + ri = ch_malloc( srsize ); + ri->ri_dn.bv_val = (char *)(ri + 1); + ri->ri_dn.bv_len = opc->sdn.bv_len; + ri->ri_ndn.bv_val = lutil_strcopy( ri->ri_dn.bv_val, + opc->sdn.bv_val ) + 1; + ri->ri_ndn.bv_len = opc->sndn.bv_len; + ri->ri_uuid.bv_val = lutil_strcopy( ri->ri_ndn.bv_val, + opc->sndn.bv_val ) + 1; + ri->ri_uuid.bv_len = opc->suuid.bv_len; + AC_MEMCPY( ri->ri_uuid.bv_val, opc->suuid.bv_val, opc->suuid.bv_len ); + if ( csn.bv_len ) { + ri->ri_csn.bv_val = ri->ri_uuid.bv_val + ri->ri_uuid.bv_len; + memcpy( ri->ri_csn.bv_val, csn.bv_val, csn.bv_len ); + ri->ri_csn.bv_val[csn.bv_len] = '\0'; + } else { + ri->ri_csn.bv_val = NULL; + } + } + ri->ri_list = &opc->ssres; + ri->ri_e = opc->se; + ri->ri_csn.bv_len = csn.bv_len; + ri->ri_isref = opc->sreference; + BER_BVZERO( &ri->ri_cookie ); + ldap_pvt_thread_mutex_init( &ri->ri_mutex ); + opc->se = NULL; + opc->ssres.s_info = ri; + } + ri = opc->ssres.s_info; + sr->s_info = ri; + ldap_pvt_thread_mutex_lock( &ri->ri_mutex ); + sr->s_rilist = ri->ri_list; + ri->ri_list = sr; + if ( mode == LDAP_SYNC_NEW_COOKIE && BER_BVISNULL( &ri->ri_cookie )) { + syncprov_info_t *si = opc->son->on_bi.bi_private; + + slap_compose_sync_cookie( NULL, &ri->ri_cookie, si->si_ctxcsn, + so->s_rid, slap_serverID ? slap_serverID : -1); + } + Debug( LDAP_DEBUG_SYNC, "%s syncprov_qresp: " + "set up a new syncres mode=%d csn=%s\n", + so->s_op->o_log_prefix, mode, csn.bv_val ); + ldap_pvt_thread_mutex_unlock( &ri->ri_mutex ); + + ldap_pvt_thread_mutex_lock( &so->s_mutex ); + if ( !so->s_res ) { + so->s_res = sr; + } else { + so->s_restail->s_next = sr; + } + so->s_restail = sr; + + /* If the base of the psearch was modified, check it next time round */ + if ( so->s_flags & PS_WROTE_BASE ) { + so->s_flags ^= PS_WROTE_BASE; + so->s_flags |= PS_FIND_BASE; + } + if (( so->s_flags & (PS_IS_DETACHED|PS_TASK_QUEUED)) == PS_IS_DETACHED ) { + syncprov_qstart( so ); + } + ldap_pvt_thread_mutex_unlock( &so->s_mutex ); + return LDAP_SUCCESS; +} + +static int +syncprov_drop_psearch( syncops *so, int lock ) +{ + if ( so->s_flags & PS_IS_DETACHED ) { + if ( lock ) + ldap_pvt_thread_mutex_lock( &so->s_op->o_conn->c_mutex ); + so->s_op->o_conn->c_n_ops_executing--; + so->s_op->o_conn->c_n_ops_completed++; + LDAP_STAILQ_REMOVE( &so->s_op->o_conn->c_ops, so->s_op, Operation, + o_next ); + if ( lock ) + ldap_pvt_thread_mutex_unlock( &so->s_op->o_conn->c_mutex ); + } + return syncprov_free_syncop( so, 0 ); +} + +static int +syncprov_ab_cleanup( Operation *op, SlapReply *rs ) +{ + slap_callback *sc = op->o_callback; + op->o_callback = sc->sc_next; + syncprov_drop_psearch( sc->sc_private, 0 ); + op->o_tmpfree( sc, op->o_tmpmemctx ); + return 0; +} + +static int +syncprov_op_abandon( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + syncprov_info_t *si = on->on_bi.bi_private; + syncops *so, **sop; + + ldap_pvt_thread_mutex_lock( &si->si_ops_mutex ); + for ( sop=&si->si_ops; (so = *sop); sop = &(*sop)->s_next ) { + if ( so->s_op->o_connid == op->o_connid && + so->s_op->o_msgid == op->orn_msgid ) { + so->s_op->o_abandon = 1; + *sop = so->s_next; + break; + } + } + ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex ); + if ( so ) { + /* Is this really a Cancel exop? */ + if ( op->o_tag != LDAP_REQ_ABANDON ) { + so->s_op->o_cancel = SLAP_CANCEL_ACK; + rs->sr_err = LDAP_CANCELLED; + send_ldap_result( so->s_op, rs ); + if ( so->s_flags & PS_IS_DETACHED ) { + slap_callback *cb; + cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx ); + cb->sc_cleanup = syncprov_ab_cleanup; + cb->sc_next = op->o_callback; + cb->sc_private = so; + op->o_callback = cb; + return SLAP_CB_CONTINUE; + } + } + syncprov_drop_psearch( so, 0 ); + } + return SLAP_CB_CONTINUE; +} + +/* Find which persistent searches are affected by this operation */ +static void +syncprov_matchops( Operation *op, opcookie *opc, int saveit ) +{ + slap_overinst *on = opc->son; + syncprov_info_t *si = on->on_bi.bi_private; + + fbase_cookie fc; + syncops **pss; + Entry *e = NULL; + Attribute *a; + int rc, gonext; + struct berval newdn; + int freefdn = 0; + BackendDB *b0 = op->o_bd, db; + + fc.fdn = &op->o_req_ndn; + /* compute new DN */ + if ( op->o_tag == LDAP_REQ_MODRDN && !saveit ) { + struct berval pdn; + if ( op->orr_nnewSup ) pdn = *op->orr_nnewSup; + else dnParent( fc.fdn, &pdn ); + build_new_dn( &newdn, &pdn, &op->orr_nnewrdn, op->o_tmpmemctx ); + fc.fdn = &newdn; + freefdn = 1; + } + if ( op->o_tag != LDAP_REQ_ADD ) { + if ( !SLAP_ISOVERLAY( op->o_bd )) { + db = *op->o_bd; + op->o_bd = &db; + } + rc = overlay_entry_get_ov( op, fc.fdn, NULL, NULL, 0, &e, on ); + /* If we're sending responses now, make a copy and unlock the DB */ + if ( e && !saveit ) { + if ( !opc->se ) + opc->se = entry_dup( e ); + overlay_entry_release_ov( op, e, 0, on ); + e = opc->se; + } + if ( rc ) { + Debug( LDAP_DEBUG_SYNC, "%s syncprov_matchops: " + "%s check, error finding entry dn=%s in database\n", + op->o_log_prefix, saveit ? "initial" : "final", fc.fdn->bv_val ); + op->o_bd = b0; + return; + } + } else { + e = op->ora_e; + if ( !saveit ) { + if ( !opc->se ) + opc->se = entry_dup( e ); + e = opc->se; + } + } + + if ( saveit || op->o_tag == LDAP_REQ_ADD ) { + ber_dupbv_x( &opc->sdn, &e->e_name, op->o_tmpmemctx ); + ber_dupbv_x( &opc->sndn, &e->e_nname, op->o_tmpmemctx ); + opc->sreference = is_entry_referral( e ); + a = attr_find( e->e_attrs, slap_schema.si_ad_entryUUID ); + if ( a ) + ber_dupbv_x( &opc->suuid, &a->a_nvals[0], op->o_tmpmemctx ); + Log4( LDAP_DEBUG_SYNC, ldap_syslog_level, "%s syncprov_matchops: " + "%srecording uuid for dn=%s on opc=%p\n", + op->o_log_prefix, a ? "" : "not ", opc->sdn.bv_val, opc ); + } else if ( op->o_tag == LDAP_REQ_MODRDN && !saveit ) { + op->o_tmpfree( opc->sndn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( opc->sdn.bv_val, op->o_tmpmemctx ); + ber_dupbv_x( &opc->sdn, &e->e_name, op->o_tmpmemctx ); + ber_dupbv_x( &opc->sndn, &e->e_nname, op->o_tmpmemctx ); + } + + ldap_pvt_thread_mutex_lock( &si->si_ops_mutex ); + for (pss = &si->si_ops; *pss; pss = gonext ? &(*pss)->s_next : pss) + { + Operation op2; + Opheader oh; + syncmatches *sm; + int found = 0; + syncops *snext, *ss = *pss; + + gonext = 1; + if ( ss->s_op->o_abandon ) + continue; + + /* Don't send ops back to the originator */ + if ( opc->osid > 0 && opc->osid == ss->s_sid ) { + Debug( LDAP_DEBUG_SYNC, "syncprov_matchops: skipping original sid %03x\n", + opc->osid, 0, 0 ); + continue; + } + + /* Don't send ops back to the messenger */ + if ( opc->rsid > 0 && opc->rsid == ss->s_sid ) { + Debug( LDAP_DEBUG_SYNC, "syncprov_matchops: skipping relayed sid %03x\n", + opc->rsid, 0, 0 ); + continue; + } + + /* validate base */ + fc.fss = ss; + fc.fbase = 0; + fc.fscope = 0; + + /* If the base of the search is missing, signal a refresh */ + rc = syncprov_findbase( op, &fc ); + if ( rc != LDAP_SUCCESS ) { + SlapReply rs = {REP_RESULT}; + send_ldap_error( ss->s_op, &rs, LDAP_SYNC_REFRESH_REQUIRED, + "search base has changed" ); + snext = ss->s_next; + if ( syncprov_drop_psearch( ss, 1 ) ) + *pss = snext; + gonext = 0; + continue; + } + + /* If we're sending results now, look for this op in old matches */ + if ( !saveit ) { + syncmatches *old; + + /* Did we modify the search base? */ + if ( dn_match( &op->o_req_ndn, &ss->s_base )) { + ldap_pvt_thread_mutex_lock( &ss->s_mutex ); + ss->s_flags |= PS_WROTE_BASE; + ldap_pvt_thread_mutex_unlock( &ss->s_mutex ); + } + + for ( sm=opc->smatches, old=(syncmatches *)&opc->smatches; sm; + old=sm, sm=sm->sm_next ) { + if ( sm->sm_op == ss ) { + found = 1; + old->sm_next = sm->sm_next; + op->o_tmpfree( sm, op->o_tmpmemctx ); + break; + } + } + } + + if ( fc.fscope ) { + ldap_pvt_thread_mutex_lock( &ss->s_mutex ); + op2 = *ss->s_op; + oh = *op->o_hdr; + oh.oh_conn = ss->s_op->o_conn; + oh.oh_connid = ss->s_op->o_connid; + op2.o_bd = op->o_bd->bd_self; + op2.o_hdr = &oh; + op2.o_extra = op->o_extra; + op2.o_callback = NULL; + if (ss->s_flags & PS_FIX_FILTER) { + /* Skip the AND/GE clause that we stuck on in front. We + would lose deletes/mods that happen during the refresh + phase otherwise (ITS#6555) */ + op2.ors_filter = ss->s_op->ors_filter->f_and->f_next; + } + rc = test_filter( &op2, e, op2.ors_filter ); + ldap_pvt_thread_mutex_unlock( &ss->s_mutex ); + } + + Debug( LDAP_DEBUG_TRACE, "syncprov_matchops: sid %03x fscope %d rc %d\n", + ss->s_sid, fc.fscope, rc ); + + /* check if current o_req_dn is in scope and matches filter */ + if ( fc.fscope && rc == LDAP_COMPARE_TRUE ) { + if ( saveit ) { + sm = op->o_tmpalloc( sizeof(syncmatches), op->o_tmpmemctx ); + sm->sm_next = opc->smatches; + sm->sm_op = ss; + ldap_pvt_thread_mutex_lock( &ss->s_mutex ); + ++ss->s_inuse; + ldap_pvt_thread_mutex_unlock( &ss->s_mutex ); + opc->smatches = sm; + } else { + /* if found send UPDATE else send ADD */ + syncprov_qresp( opc, ss, + found ? LDAP_SYNC_MODIFY : LDAP_SYNC_ADD ); + } + } else if ( !saveit && found ) { + /* send DELETE */ + syncprov_qresp( opc, ss, LDAP_SYNC_DELETE ); + } else if ( !saveit ) { + syncprov_qresp( opc, ss, LDAP_SYNC_NEW_COOKIE ); + } + if ( !saveit && found ) { + /* Decrement s_inuse, was incremented when called + * with saveit == TRUE + */ + snext = ss->s_next; + if ( syncprov_free_syncop( ss, 0 ) ) { + *pss = snext; + gonext = 0; + } + } + } + ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex ); + + if ( op->o_tag != LDAP_REQ_ADD && e ) { + if ( !SLAP_ISOVERLAY( op->o_bd )) { + op->o_bd = &db; + } + if ( saveit ) + overlay_entry_release_ov( op, e, 0, on ); + op->o_bd = b0; + } + if ( !saveit ) { + if ( opc->ssres.s_info ) + free_resinfo( &opc->ssres ); + else if ( opc->se ) + entry_free( opc->se ); + } + if ( freefdn ) { + op->o_tmpfree( fc.fdn->bv_val, op->o_tmpmemctx ); + } + op->o_bd = b0; +} + +static int +syncprov_op_cleanup( Operation *op, SlapReply *rs ) +{ + slap_callback *cb = op->o_callback; + opcookie *opc = cb->sc_private; + slap_overinst *on = opc->son; + syncprov_info_t *si = on->on_bi.bi_private; + syncmatches *sm, *snext; + modtarget *mt; + + ldap_pvt_thread_mutex_lock( &si->si_ops_mutex ); + if ( si->si_active ) + si->si_active--; + ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex ); + + for (sm = opc->smatches; sm; sm=snext) { + snext = sm->sm_next; + syncprov_free_syncop( sm->sm_op, 1 ); + op->o_tmpfree( sm, op->o_tmpmemctx ); + } + + /* Remove op from lock table */ + mt = opc->smt; + if ( mt ) { + modinst *mi = (modinst *)(opc+1), **m2; + ldap_pvt_thread_mutex_lock( &mt->mt_mutex ); + for (m2 = &mt->mt_mods; ; m2 = &(*m2)->mi_next) { + if ( *m2 == mi ) { + *m2 = mi->mi_next; + if ( mt->mt_tail == mi ) + mt->mt_tail = ( m2 == &mt->mt_mods ) ? NULL : (modinst *)m2; + break; + } + } + /* If there are more, promote the next one */ + if ( mt->mt_mods ) { + ldap_pvt_thread_mutex_unlock( &mt->mt_mutex ); + } else { + ldap_pvt_thread_mutex_unlock( &mt->mt_mutex ); + ldap_pvt_thread_mutex_lock( &si->si_mods_mutex ); + avl_delete( &si->si_mods, mt, sp_avl_cmp ); + ldap_pvt_thread_mutex_unlock( &si->si_mods_mutex ); + ldap_pvt_thread_mutex_destroy( &mt->mt_mutex ); + ch_free( mt->mt_dn.bv_val ); + ch_free( mt ); + } + } + if ( !BER_BVISNULL( &opc->suuid )) + op->o_tmpfree( opc->suuid.bv_val, op->o_tmpmemctx ); + if ( !BER_BVISNULL( &opc->sndn )) + op->o_tmpfree( opc->sndn.bv_val, op->o_tmpmemctx ); + if ( !BER_BVISNULL( &opc->sdn )) + op->o_tmpfree( opc->sdn.bv_val, op->o_tmpmemctx ); + op->o_callback = cb->sc_next; + op->o_tmpfree(cb, op->o_tmpmemctx); + + return 0; +} + +static void +syncprov_checkpoint( Operation *op, slap_overinst *on ) +{ + syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private; + Modifications mod; + Operation opm; + SlapReply rsm = {REP_RESULT}; + slap_callback cb = {0}; + BackendDB be; + BackendInfo *bi; + +#ifdef CHECK_CSN + Syntax *syn = slap_schema.si_ad_contextCSN->ad_type->sat_syntax; + + int i; + for ( i=0; i<si->si_numcsns; i++ ) { + assert( !syn->ssyn_validate( syn, si->si_ctxcsn+i )); + } +#endif + + Debug( LDAP_DEBUG_SYNC, "%s syncprov_checkpoint: running checkpoint\n", + op->o_log_prefix, 0, 0 ); + + mod.sml_numvals = si->si_numcsns; + mod.sml_values = si->si_ctxcsn; + mod.sml_nvalues = NULL; + mod.sml_desc = slap_schema.si_ad_contextCSN; + mod.sml_op = LDAP_MOD_REPLACE; + mod.sml_flags = SLAP_MOD_INTERNAL; + mod.sml_next = NULL; + + cb.sc_response = slap_null_cb; + opm = *op; + opm.o_tag = LDAP_REQ_MODIFY; + opm.o_callback = &cb; + opm.orm_modlist = &mod; + opm.orm_no_opattrs = 1; + if ( SLAP_GLUE_SUBORDINATE( op->o_bd )) { + be = *on->on_info->oi_origdb; + opm.o_bd = &be; + } + opm.o_req_dn = si->si_contextdn; + opm.o_req_ndn = si->si_contextdn; + bi = opm.o_bd->bd_info; + opm.o_bd->bd_info = on->on_info->oi_orig; + opm.o_managedsait = SLAP_CONTROL_NONCRITICAL; + opm.o_no_schema_check = 1; + opm.o_dont_replicate = 1; + opm.o_opid = -1; + opm.o_bd->be_modify( &opm, &rsm ); + + if ( rsm.sr_err == LDAP_NO_SUCH_OBJECT && + SLAP_SYNC_SUBENTRY( opm.o_bd )) { + const char *text; + char txtbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof txtbuf; + Entry *e = slap_create_context_csn_entry( opm.o_bd, NULL ); + rs_reinit( &rsm, REP_RESULT ); + slap_mods2entry( &mod, &e, 0, 1, &text, txtbuf, textlen); + opm.ora_e = e; + opm.o_bd->be_add( &opm, &rsm ); + if ( e == opm.ora_e ) + be_entry_release_w( &opm, opm.ora_e ); + } + opm.o_bd->bd_info = bi; + + if ( mod.sml_next != NULL ) { + slap_mods_free( mod.sml_next, 1 ); + } +#ifdef CHECK_CSN + for ( i=0; i<si->si_numcsns; i++ ) { + assert( !syn->ssyn_validate( syn, si->si_ctxcsn+i )); + } +#endif +} + +static void +syncprov_add_slog( Operation *op ) +{ + opcookie *opc = op->o_callback->sc_private; + slap_overinst *on = opc->son; + syncprov_info_t *si = on->on_bi.bi_private; + sessionlog *sl; + slog_entry *se; + char uuidstr[40]; + int rc; + + sl = si->si_logs; + { + if ( BER_BVISEMPTY( &op->o_csn ) ) { + /* During the syncrepl refresh phase we can receive operations + * without a csn. We cannot reliably determine the consumers + * state with respect to such operations, so we ignore them and + * wipe out anything in the log if we see them. + */ + ldap_pvt_thread_rdwr_wlock( &sl->sl_mutex ); + /* can only do this if no one else is reading the log at the moment */ + if ( !sl->sl_playing ) { + tavl_free( sl->sl_entries, (AVL_FREE)ch_free ); + sl->sl_num = 0; + sl->sl_entries = NULL; + } + ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex ); + return; + } + + /* Allocate a record. UUIDs are not NUL-terminated. */ + se = ch_malloc( sizeof( slog_entry ) + opc->suuid.bv_len + + op->o_csn.bv_len + 1 ); + se->se_tag = op->o_tag; + + se->se_uuid.bv_val = (char *)(&se[1]); + AC_MEMCPY( se->se_uuid.bv_val, opc->suuid.bv_val, opc->suuid.bv_len ); + se->se_uuid.bv_len = opc->suuid.bv_len; + + se->se_csn.bv_val = se->se_uuid.bv_val + opc->suuid.bv_len; + AC_MEMCPY( se->se_csn.bv_val, op->o_csn.bv_val, op->o_csn.bv_len ); + se->se_csn.bv_val[op->o_csn.bv_len] = '\0'; + se->se_csn.bv_len = op->o_csn.bv_len; + se->se_sid = slap_parse_csn_sid( &se->se_csn ); + + ldap_pvt_thread_rdwr_wlock( &sl->sl_mutex ); + if ( LogTest( LDAP_DEBUG_SYNC ) ) { + uuidstr[0] = 0; + if ( !BER_BVISEMPTY( &opc->suuid ) ) { + lutil_uuidstr_from_normalized( opc->suuid.bv_val, opc->suuid.bv_len, + uuidstr, 40 ); + } + Debug( LDAP_DEBUG_SYNC, "%s syncprov_add_slog: " + "adding csn=%s to sessionlog, uuid=%s\n", + op->o_log_prefix, se->se_csn.bv_val, uuidstr ); + } + if ( !sl->sl_entries ) { + if ( !sl->sl_mincsn ) { + sl->sl_numcsns = 1; + sl->sl_mincsn = ch_malloc( 2*sizeof( struct berval )); + sl->sl_sids = ch_malloc( sizeof( int )); + sl->sl_sids[0] = se->se_sid; + ber_dupbv( sl->sl_mincsn, &se->se_csn ); + BER_BVZERO( &sl->sl_mincsn[1] ); + } + } + rc = tavl_insert( &sl->sl_entries, se, syncprov_sessionlog_cmp, avl_dup_error ); + if ( rc ) { + Debug( LDAP_DEBUG_SYNC, "%s syncprov_add_slog: " + "duplicate sessionlog entry ignored: csn=%s, uuid=%s\n", + op->o_log_prefix, se->se_csn.bv_val, uuidstr ); + ch_free( se ); + goto leave; + } + sl->sl_num++; + if ( !sl->sl_playing && sl->sl_num > sl->sl_size ) { + Avlnode *edge = tavl_end( sl->sl_entries, TAVL_DIR_LEFT ); + while ( sl->sl_num > sl->sl_size ) { + int i; + Avlnode *next = tavl_next( edge, TAVL_DIR_RIGHT ); + se = edge->avl_data; + Debug( LDAP_DEBUG_SYNC, "%s syncprov_add_slog: " + "expiring csn=%s from sessionlog (sessionlog size=%d)\n", + op->o_log_prefix, se->se_csn.bv_val, sl->sl_num ); + for ( i=0; i<sl->sl_numcsns; i++ ) + if ( sl->sl_sids[i] >= se->se_sid ) + break; + if ( i == sl->sl_numcsns || sl->sl_sids[i] != se->se_sid ) { + Debug( LDAP_DEBUG_SYNC, "%s syncprov_add_slog: " + "adding csn=%s to mincsn\n", + op->o_log_prefix, se->se_csn.bv_val, 0 ); + slap_insert_csn_sids( (struct sync_cookie *)sl, + i, se->se_sid, &se->se_csn ); + } else { + Log4( LDAP_DEBUG_SYNC, ldap_syslog_level, "%s syncprov_add_slog: " + "updating mincsn for sid=%d csn=%s to %s\n", + op->o_log_prefix, se->se_sid, sl->sl_mincsn[i].bv_val, se->se_csn.bv_val ); + ber_bvreplace( &sl->sl_mincsn[i], &se->se_csn ); + } + tavl_delete( &sl->sl_entries, se, syncprov_sessionlog_cmp ); + ch_free( se ); + edge = next; + sl->sl_num--; + } + } +leave: + ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex ); + } +} + +/* Just set a flag if we found the matching entry */ +static int +playlog_cb( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH ) { + op->o_callback->sc_private = (void *)1; + } + return rs->sr_err; +} + +/* enter with sl->sl_mutex locked, release before returning */ +static void +syncprov_playlog( Operation *op, SlapReply *rs, sessionlog *sl, + sync_control *srs, BerVarray ctxcsn, int numcsns, int *sids, + struct berval *mincsn ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + int i, j, ndel, num, nmods, mmods; + Avlnode *entry; + char cbuf[LDAP_PVT_CSNSTR_BUFSIZE]; + BerVarray uuids; + struct berval delcsn[2]; + + if ( !sl->sl_num ) { + ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex ); + return; + } + + num = sl->sl_num; + i = 0; + nmods = 0; + sl->sl_playing++; + ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex ); + + uuids = op->o_tmpalloc( (num+1) * sizeof( struct berval ) + + num * UUID_LEN, op->o_tmpmemctx ); + uuids[0].bv_val = (char *)(uuids + num + 1); + + delcsn[0].bv_len = 0; + delcsn[0].bv_val = cbuf; + BER_BVZERO(&delcsn[1]); + + ldap_pvt_thread_rdwr_rlock( &sl->sl_mutex ); + /* Make a copy of the relevant UUIDs. Put the Deletes up front + * and everything else at the end. Do this first so we can + * let the write side manage the sessionlog again. + */ + assert( sl->sl_entries ); + + /* Find first relevant log entry. If greater than mincsn, backtrack one entry */ + { + slog_entry te = {0}; + te.se_csn = *mincsn; + entry = tavl_find3( sl->sl_entries, &te, syncprov_sessionlog_cmp, &ndel ); + } + if ( ndel > 0 && entry ) + entry = tavl_next( entry, TAVL_DIR_LEFT ); + /* if none, just start at beginning */ + if ( !entry ) + entry = tavl_end( sl->sl_entries, TAVL_DIR_LEFT ); + + do { + slog_entry *se = entry->avl_data; + int k; + + /* Make sure writes can still make progress */ + ldap_pvt_thread_rdwr_runlock( &sl->sl_mutex ); + ndel = 1; + for ( k=0; k<srs->sr_state.numcsns; k++ ) { + if ( se->se_sid == srs->sr_state.sids[k] ) { + ndel = ber_bvcmp( &se->se_csn, &srs->sr_state.ctxcsn[k] ); + break; + } + } + if ( ndel <= 0 ) { + ldap_pvt_thread_rdwr_rlock( &sl->sl_mutex ); + continue; + } + ndel = 0; + for ( k=0; k<numcsns; k++ ) { + if ( se->se_sid == sids[k] ) { + ndel = ber_bvcmp( &se->se_csn, &ctxcsn[k] ); + break; + } + } + if ( ndel > 0 ) { + Debug( LDAP_DEBUG_SYNC, "%s syncprov_playlog: " + "cmp %d, too new\n", op->o_log_prefix, ndel, 0 ); + ldap_pvt_thread_rdwr_rlock( &sl->sl_mutex ); + break; + } + if ( se->se_tag == LDAP_REQ_DELETE ) { + j = i; + i++; + AC_MEMCPY( cbuf, se->se_csn.bv_val, se->se_csn.bv_len ); + delcsn[0].bv_len = se->se_csn.bv_len; + delcsn[0].bv_val[delcsn[0].bv_len] = '\0'; + } else { + if ( se->se_tag == LDAP_REQ_ADD ) { + ldap_pvt_thread_rdwr_rlock( &sl->sl_mutex ); + continue; + } + nmods++; + j = num - nmods; + } + uuids[j].bv_val = uuids[0].bv_val + (j * UUID_LEN); + AC_MEMCPY(uuids[j].bv_val, se->se_uuid.bv_val, UUID_LEN); + uuids[j].bv_len = UUID_LEN; + + if ( LogTest( LDAP_DEBUG_SYNC ) ) { + char uuidstr[40] = {}; + lutil_uuidstr_from_normalized( uuids[j].bv_val, uuids[j].bv_len, + uuidstr, 40 ); + Log4( LDAP_DEBUG_SYNC, ldap_syslog_level, "%s syncprov_playlog: " + "picking a %s entry uuid=%s cookie=%s\n", + op->o_log_prefix, se->se_tag == LDAP_REQ_DELETE ? "deleted" : "modified", + uuidstr, delcsn[0].bv_len ? delcsn[0].bv_val : "(null)" ); + } + ldap_pvt_thread_rdwr_rlock( &sl->sl_mutex ); + } while ( (entry = tavl_next( entry, TAVL_DIR_RIGHT )) != NULL ); + ldap_pvt_thread_rdwr_runlock( &sl->sl_mutex ); + ldap_pvt_thread_rdwr_wlock( &sl->sl_mutex ); + sl->sl_playing--; + ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex ); + + ndel = i; + + /* Zero out unused slots */ + for ( i=ndel; i < num - nmods; i++ ) + uuids[i].bv_len = 0; + + /* Mods must be validated to see if they belong in this delete set. + */ + + mmods = nmods; + /* Strip any duplicates */ + for ( i=0; i<nmods; i++ ) { + for ( j=0; j<ndel; j++ ) { + if ( bvmatch( &uuids[j], &uuids[num - 1 - i] )) { + uuids[num - 1 - i].bv_len = 0; + mmods --; + break; + } + } + if ( uuids[num - 1 - i].bv_len == 0 ) continue; + for ( j=0; j<i; j++ ) { + if ( bvmatch( &uuids[num - 1 - j], &uuids[num - 1 - i] )) { + uuids[num - 1 - i].bv_len = 0; + mmods --; + break; + } + } + } + + if ( mmods ) { + Operation fop; + int rc; + Filter mf, af; + AttributeAssertion eq = ATTRIBUTEASSERTION_INIT; + slap_callback cb = {0}; + + fop = *op; + + fop.o_sync_mode = 0; + fop.o_callback = &cb; + fop.ors_limit = NULL; + fop.ors_tlimit = SLAP_NO_LIMIT; + fop.ors_attrs = slap_anlist_all_attributes; + fop.ors_attrsonly = 0; + fop.o_managedsait = SLAP_CONTROL_CRITICAL; + + af.f_choice = LDAP_FILTER_AND; + af.f_next = NULL; + af.f_and = &mf; + mf.f_choice = LDAP_FILTER_EQUALITY; + mf.f_ava = &eq; + mf.f_av_desc = slap_schema.si_ad_entryUUID; + mf.f_next = fop.ors_filter; + + fop.ors_filter = ⁡ + + cb.sc_response = playlog_cb; + fop.o_bd->bd_info = (BackendInfo *)on->on_info; + + for ( i=ndel; i<num; i++ ) { + if ( uuids[i].bv_len != 0 ) { + SlapReply frs = { REP_RESULT }; + + mf.f_av_value = uuids[i]; + cb.sc_private = NULL; + fop.ors_slimit = 1; + rc = fop.o_bd->be_search( &fop, &frs ); + + /* If entry was not found, add to delete list */ + if ( !cb.sc_private ) { + uuids[ndel++] = uuids[i]; + } + } + } + fop.o_bd->bd_info = (BackendInfo *)on; + } + if ( ndel ) { + struct berval cookie; + + if ( delcsn[0].bv_len ) { + slap_compose_sync_cookie( op, &cookie, delcsn, srs->sr_state.rid, + slap_serverID ? slap_serverID : -1 ); + + Debug( LDAP_DEBUG_SYNC, "%s syncprov_playlog: cookie=%s\n", + op->o_log_prefix, cookie.bv_val, 0 ); + } + + uuids[ndel].bv_val = NULL; + syncprov_sendinfo( op, rs, LDAP_TAG_SYNC_ID_SET, + delcsn[0].bv_len ? &cookie : NULL, 0, uuids, 1 ); + if ( delcsn[0].bv_len ) { + op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx ); + } + } + op->o_tmpfree( uuids, op->o_tmpmemctx ); +} + +static int +syncprov_new_ctxcsn( opcookie *opc, syncprov_info_t *si, int csn_changed, int numvals, BerVarray vals ) +{ + unsigned i; + int j, sid; + + for ( i=0; i<numvals; i++ ) { + sid = slap_parse_csn_sid( &vals[i] ); + for ( j=0; j<si->si_numcsns; j++ ) { + if ( sid < si->si_sids[j] ) + break; + if ( sid == si->si_sids[j] ) { + if ( ber_bvcmp( &vals[i], &si->si_ctxcsn[j] ) > 0 ) { + ber_bvreplace( &si->si_ctxcsn[j], &vals[i] ); + csn_changed = 1; + } + break; + } + } + + if ( j == si->si_numcsns || sid != si->si_sids[j] ) { + slap_insert_csn_sids( (struct sync_cookie *)&si->si_ctxcsn, + j, sid, &vals[i] ); + csn_changed = 1; + } + } + if ( csn_changed ) + si->si_dirty = 0; + ldap_pvt_thread_rdwr_wunlock( &si->si_csn_rwlock ); + + if ( csn_changed ) { + syncops *ss; + ldap_pvt_thread_mutex_lock( &si->si_ops_mutex ); + for ( ss = si->si_ops; ss; ss = ss->s_next ) { + if ( ss->s_op->o_abandon ) + continue; + /* Send the updated csn to all syncrepl consumers, + * including the server from which it originated. + * The syncrepl consumer and syncprov provider on + * the originating server may be configured to store + * their csn values in different entries. + */ + syncprov_qresp( opc, ss, LDAP_SYNC_NEW_COOKIE ); + } + ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex ); + } + return csn_changed; +} + +static int +syncprov_op_response( Operation *op, SlapReply *rs ) +{ + opcookie *opc = op->o_callback->sc_private; + slap_overinst *on = opc->son; + syncprov_info_t *si = on->on_bi.bi_private; + syncmatches *sm; + + if ( rs->sr_err == LDAP_SUCCESS ) + { + struct berval maxcsn; + char cbuf[LDAP_PVT_CSNSTR_BUFSIZE]; + int do_check = 0, have_psearches, foundit, csn_changed = 0; + + ldap_pvt_thread_mutex_lock( &si->si_resp_mutex ); + + /* Update our context CSN */ + cbuf[0] = '\0'; + maxcsn.bv_val = cbuf; + maxcsn.bv_len = sizeof(cbuf); + ldap_pvt_thread_rdwr_wlock( &si->si_csn_rwlock ); + + slap_get_commit_csn( op, &maxcsn, &foundit ); + if ( BER_BVISEMPTY( &maxcsn ) && SLAP_GLUE_SUBORDINATE( op->o_bd )) { + /* syncrepl queues the CSN values in the db where + * it is configured , not where the changes are made. + * So look for a value in the glue db if we didn't + * find any in this db. + */ + BackendDB *be = op->o_bd; + op->o_bd = select_backend( &be->be_nsuffix[0], 1); + maxcsn.bv_val = cbuf; + maxcsn.bv_len = sizeof(cbuf); + slap_get_commit_csn( op, &maxcsn, &foundit ); + op->o_bd = be; + } + if ( !BER_BVISEMPTY( &maxcsn ) ) { + int i, sid; +#ifdef CHECK_CSN + Syntax *syn = slap_schema.si_ad_contextCSN->ad_type->sat_syntax; + assert( !syn->ssyn_validate( syn, &maxcsn )); +#endif + sid = slap_parse_csn_sid( &maxcsn ); + for ( i=0; i<si->si_numcsns; i++ ) { + if ( sid < si->si_sids[i] ) + break; + if ( sid == si->si_sids[i] ) { + if ( ber_bvcmp( &maxcsn, &si->si_ctxcsn[i] ) > 0 ) { + ber_bvreplace( &si->si_ctxcsn[i], &maxcsn ); + csn_changed = 1; + } + break; + } + } + /* It's a new SID for us */ + if ( i == si->si_numcsns || sid != si->si_sids[i] ) { + slap_insert_csn_sids((struct sync_cookie *)&(si->si_ctxcsn), + i, sid, &maxcsn ); + csn_changed = 1; + } + } + + /* Don't do any processing for consumer contextCSN updates */ + if ( SLAPD_SYNC_IS_SYNCCONN( op->o_connid ) && + op->o_tag == LDAP_REQ_MODIFY && + op->orm_modlist && + op->orm_modlist->sml_op == LDAP_MOD_REPLACE && + op->orm_modlist->sml_desc == slap_schema.si_ad_contextCSN ) { + /* Catch contextCSN updates from syncrepl. We have to look at + * all the attribute values, as there may be more than one csn + * that changed, and only one can be passed in the csn queue. + */ + csn_changed = syncprov_new_ctxcsn( opc, si, csn_changed, + op->orm_modlist->sml_numvals, op->orm_modlist->sml_values ); + if ( csn_changed ) + si->si_numops++; + goto leave; + } + if ( op->o_dont_replicate ) { + if ( csn_changed ) + si->si_numops++; + ldap_pvt_thread_rdwr_wunlock( &si->si_csn_rwlock ); + goto leave; + } + + /* If we're adding the context entry, parse all of its contextCSNs */ + if ( op->o_tag == LDAP_REQ_ADD && + dn_match( &op->o_req_ndn, &si->si_contextdn )) { + Attribute *a = attr_find( op->ora_e->e_attrs, slap_schema.si_ad_contextCSN ); + if ( a ) { + csn_changed = syncprov_new_ctxcsn( opc, si, csn_changed, a->a_numvals, a->a_vals ); + if ( csn_changed ) + si->si_numops++; + goto added; + } + } + + if ( csn_changed ) + si->si_numops++; + if ( si->si_chkops || si->si_chktime ) { + /* Never checkpoint adding the context entry, + * it will deadlock + */ + if ( op->o_tag != LDAP_REQ_ADD || + !dn_match( &op->o_req_ndn, &si->si_contextdn )) { + if ( si->si_chkops && si->si_numops >= si->si_chkops ) { + do_check = 1; + si->si_numops = 0; + } + if ( si->si_chktime && + (op->o_time - si->si_chklast >= si->si_chktime )) { + if ( si->si_chklast ) { + do_check = 1; + si->si_chklast = op->o_time; + } else { + si->si_chklast = 1; + } + } + } + } + si->si_dirty = !csn_changed; + ldap_pvt_thread_rdwr_wunlock( &si->si_csn_rwlock ); + +added: + if ( do_check ) { + ldap_pvt_thread_rdwr_rlock( &si->si_csn_rwlock ); + syncprov_checkpoint( op, on ); + ldap_pvt_thread_rdwr_runlock( &si->si_csn_rwlock ); + } + + /* only update consumer ctx if this is a newer csn */ + if ( csn_changed ) { + opc->sctxcsn = maxcsn; + } + + /* Handle any persistent searches */ + ldap_pvt_thread_mutex_lock( &si->si_ops_mutex ); + have_psearches = ( si->si_ops != NULL ); + ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex ); + if ( have_psearches ) { + switch(op->o_tag) { + case LDAP_REQ_ADD: + case LDAP_REQ_MODIFY: + case LDAP_REQ_MODRDN: + case LDAP_REQ_EXTENDED: + syncprov_matchops( op, opc, 0 ); + break; + case LDAP_REQ_DELETE: + /* for each match in opc->smatches: + * send DELETE msg + */ + for ( sm = opc->smatches; sm; sm=sm->sm_next ) { + if ( sm->sm_op->s_op->o_abandon ) + continue; + syncprov_qresp( opc, sm->sm_op, LDAP_SYNC_DELETE ); + } + if ( opc->ssres.s_info ) + free_resinfo( &opc->ssres ); + break; + } + } + + /* Add any log records */ + if ( si->si_logs ) { + syncprov_add_slog( op ); + } +leave: ldap_pvt_thread_mutex_unlock( &si->si_resp_mutex ); + } + return SLAP_CB_CONTINUE; +} + +/* We don't use a subentry to store the context CSN any more. + * We expose the current context CSN as an operational attribute + * of the suffix entry. + */ +static int +syncprov_op_compare( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + syncprov_info_t *si = on->on_bi.bi_private; + int rc = SLAP_CB_CONTINUE; + + if ( dn_match( &op->o_req_ndn, &si->si_contextdn ) && + op->oq_compare.rs_ava->aa_desc == slap_schema.si_ad_contextCSN ) + { + Entry e = {0}; + Attribute a = {0}; + + e.e_name = si->si_contextdn; + e.e_nname = si->si_contextdn; + e.e_attrs = &a; + + a.a_desc = slap_schema.si_ad_contextCSN; + + ldap_pvt_thread_rdwr_rlock( &si->si_csn_rwlock ); + + a.a_vals = si->si_ctxcsn; + a.a_nvals = a.a_vals; + a.a_numvals = si->si_numcsns; + + rs->sr_err = access_allowed( op, &e, op->oq_compare.rs_ava->aa_desc, + &op->oq_compare.rs_ava->aa_value, ACL_COMPARE, NULL ); + if ( ! rs->sr_err ) { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto return_results; + } + + if ( get_assert( op ) && + ( test_filter( op, &e, get_assertion( op ) ) != LDAP_COMPARE_TRUE ) ) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto return_results; + } + + + rs->sr_err = LDAP_COMPARE_FALSE; + + if ( attr_valfind( &a, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + &op->oq_compare.rs_ava->aa_value, NULL, op->o_tmpmemctx ) == 0 ) + { + rs->sr_err = LDAP_COMPARE_TRUE; + } + +return_results:; + + ldap_pvt_thread_rdwr_runlock( &si->si_csn_rwlock ); + + send_ldap_result( op, rs ); + + if( rs->sr_err == LDAP_COMPARE_FALSE || rs->sr_err == LDAP_COMPARE_TRUE ) { + rs->sr_err = LDAP_SUCCESS; + } + rc = rs->sr_err; + } + + return rc; +} + +static int +syncprov_op_mod( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + syncprov_info_t *si = on->on_bi.bi_private; + slap_callback *cb; + opcookie *opc; + int have_psearches, cbsize; + + ldap_pvt_thread_mutex_lock( &si->si_ops_mutex ); + have_psearches = ( si->si_ops != NULL ); + si->si_active++; + ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex ); + + cbsize = sizeof(slap_callback) + sizeof(opcookie) + + (have_psearches ? sizeof(modinst) : 0 ); + + cb = op->o_tmpcalloc(1, cbsize, op->o_tmpmemctx); + opc = (opcookie *)(cb+1); + opc->son = on; + cb->sc_response = syncprov_op_response; + cb->sc_cleanup = syncprov_op_cleanup; + cb->sc_private = opc; + cb->sc_next = op->o_callback; + op->o_callback = cb; + + opc->osid = -1; + opc->rsid = -1; + if ( op->o_csn.bv_val ) { + opc->osid = slap_parse_csn_sid( &op->o_csn ); + } + if ( op->o_controls ) { + struct sync_cookie *scook = + op->o_controls[slap_cids.sc_LDAPsync]; + if ( scook ) + opc->rsid = scook->sid; + } + + if ( op->o_dont_replicate ) + return SLAP_CB_CONTINUE; + + /* If there are active persistent searches, lock this operation. + * See seqmod.c for the locking logic on its own. + */ + if ( have_psearches ) { + modtarget *mt, mtdummy; + modinst *mi; + + mi = (modinst *)(opc+1); + mi->mi_op = op; + + /* See if we're already modifying this entry... */ + mtdummy.mt_dn = op->o_req_ndn; +retry: + ldap_pvt_thread_mutex_lock( &si->si_mods_mutex ); + mt = avl_find( si->si_mods, &mtdummy, sp_avl_cmp ); + if ( mt ) { + ldap_pvt_thread_mutex_lock( &mt->mt_mutex ); + if ( mt->mt_mods == NULL ) { + /* Cannot reuse this mt, as another thread is about + * to release it in syncprov_op_cleanup. Wait for them + * to finish; our own insert is required to succeed. + */ + ldap_pvt_thread_mutex_unlock( &mt->mt_mutex ); + ldap_pvt_thread_mutex_unlock( &si->si_mods_mutex ); + ldap_pvt_thread_yield(); + goto retry; + } + } + if ( mt ) { + mt->mt_tail->mi_next = mi; + mt->mt_tail = mi; + ldap_pvt_thread_mutex_unlock( &si->si_mods_mutex ); + /* wait for this op to get to head of list */ + while ( mt->mt_mods != mi ) { + modinst *m2; + /* don't wait on other mods from the same thread */ + for ( m2 = mt->mt_mods; m2; m2 = m2->mi_next ) { + if ( m2->mi_op->o_threadctx == op->o_threadctx ) { + break; + } + } + if ( m2 ) + break; + + ldap_pvt_thread_mutex_unlock( &mt->mt_mutex ); + /* FIXME: if dynamic config can delete overlays or + * databases we'll have to check for cleanup here. + * Currently it's not an issue because there are + * no dynamic config deletes... + */ + if ( slapd_shutdown ) + return SLAPD_ABANDON; + + if ( !ldap_pvt_thread_pool_pausecheck( &connection_pool )) + ldap_pvt_thread_yield(); + ldap_pvt_thread_mutex_lock( &mt->mt_mutex ); + + /* clean up if the caller is giving up */ + if ( op->o_abandon ) { + modinst **m2; + slap_callback **sc; + for (m2 = &mt->mt_mods; ; m2 = &(*m2)->mi_next) { + if ( *m2 == mi ) { + *m2 = mi->mi_next; + if ( mt->mt_tail == mi ) + mt->mt_tail = ( m2 == &mt->mt_mods ) ? NULL : (modinst *)m2; + break; + } + } + for (sc = &op->o_callback; ; sc = &(*sc)->sc_next) { + if ( *sc == cb ) { + *sc = cb->sc_next; + break; + } + } + op->o_tmpfree( cb, op->o_tmpmemctx ); + ldap_pvt_thread_mutex_unlock( &mt->mt_mutex ); + return SLAPD_ABANDON; + } + } + ldap_pvt_thread_mutex_unlock( &mt->mt_mutex ); + } else { + /* Record that we're modifying this entry now */ + mt = ch_malloc( sizeof(modtarget) ); + mt->mt_mods = mi; + mt->mt_tail = mi; + ber_dupbv( &mt->mt_dn, &mi->mi_op->o_req_ndn ); + ldap_pvt_thread_mutex_init( &mt->mt_mutex ); + avl_insert( &si->si_mods, mt, sp_avl_cmp, avl_dup_error ); + ldap_pvt_thread_mutex_unlock( &si->si_mods_mutex ); + } + opc->smt = mt; + } + + if (( have_psearches || si->si_logs ) && op->o_tag != LDAP_REQ_ADD ) + syncprov_matchops( op, opc, 1 ); + + return SLAP_CB_CONTINUE; +} + +static int +syncprov_op_extended( Operation *op, SlapReply *rs ) +{ + if ( exop_is_write( op )) + return syncprov_op_mod( op, rs ); + + return SLAP_CB_CONTINUE; +} + +typedef struct searchstate { + slap_overinst *ss_on; + syncops *ss_so; + BerVarray ss_ctxcsn; + int *ss_sids; + int ss_numcsns; +#define SS_PRESENT 0x01 +#define SS_CHANGED 0x02 + int ss_flags; +} searchstate; + +typedef struct SyncOperationBuffer { + Operation sob_op; + Opheader sob_hdr; + OpExtra sob_oe; + AttributeName sob_extra; /* not always present */ + /* Further data allocated here */ +} SyncOperationBuffer; + +static void +syncprov_detach_op( Operation *op, syncops *so, slap_overinst *on ) +{ + SyncOperationBuffer *sopbuf2; + Operation *op2; + int i, alen = 0; + size_t size; + char *ptr; + GroupAssertion *g1, *g2; + + /* count the search attrs */ + for (i=0; op->ors_attrs && !BER_BVISNULL( &op->ors_attrs[i].an_name ); i++) { + alen += op->ors_attrs[i].an_name.bv_len + 1; + } + /* Make a new copy of the operation */ + size = offsetof( SyncOperationBuffer, sob_extra ) + + (i ? ( (i+1) * sizeof(AttributeName) + alen) : 0) + + op->o_req_dn.bv_len + 1 + + op->o_req_ndn.bv_len + 1 + + op->o_ndn.bv_len + 1 + + so->s_filterstr.bv_len + 1; + sopbuf2 = ch_calloc( 1, size ); + op2 = &sopbuf2->sob_op; + op2->o_hdr = &sopbuf2->sob_hdr; + LDAP_SLIST_FIRST(&op2->o_extra) = &sopbuf2->sob_oe; + + /* Copy the fields we care about explicitly, leave the rest alone */ + *op2->o_hdr = *op->o_hdr; + op2->o_tag = op->o_tag; + op2->o_time = op->o_time; + op2->o_bd = on->on_info->oi_origdb; + op2->o_request = op->o_request; + op2->o_managedsait = op->o_managedsait; + LDAP_SLIST_FIRST(&op2->o_extra)->oe_key = on; + LDAP_SLIST_NEXT(LDAP_SLIST_FIRST(&op2->o_extra), oe_next) = NULL; + + ptr = (char *) sopbuf2 + offsetof( SyncOperationBuffer, sob_extra ); + if ( i ) { + op2->ors_attrs = (AttributeName *) ptr; + ptr = (char *) &op2->ors_attrs[i+1]; + for (i=0; !BER_BVISNULL( &op->ors_attrs[i].an_name ); i++) { + op2->ors_attrs[i] = op->ors_attrs[i]; + op2->ors_attrs[i].an_name.bv_val = ptr; + ptr = lutil_strcopy( ptr, op->ors_attrs[i].an_name.bv_val ) + 1; + } + BER_BVZERO( &op2->ors_attrs[i].an_name ); + } + + op2->o_authz = op->o_authz; + op2->o_ndn.bv_val = ptr; + ptr = lutil_strcopy(ptr, op->o_ndn.bv_val) + 1; + op2->o_dn = op2->o_ndn; + op2->o_req_dn.bv_len = op->o_req_dn.bv_len; + op2->o_req_dn.bv_val = ptr; + ptr = lutil_strcopy(ptr, op->o_req_dn.bv_val) + 1; + op2->o_req_ndn.bv_len = op->o_req_ndn.bv_len; + op2->o_req_ndn.bv_val = ptr; + ptr = lutil_strcopy(ptr, op->o_req_ndn.bv_val) + 1; + op2->ors_filterstr.bv_val = ptr; + strcpy( ptr, so->s_filterstr.bv_val ); + op2->ors_filterstr.bv_len = so->s_filterstr.bv_len; + + /* Skip the AND/GE clause that we stuck on in front */ + if ( so->s_flags & PS_FIX_FILTER ) { + op2->ors_filter = op->ors_filter->f_and->f_next; + so->s_flags ^= PS_FIX_FILTER; + } else { + op2->ors_filter = op->ors_filter; + } + op2->ors_filter = filter_dup( op2->ors_filter, NULL ); + so->s_op = op2; + + /* Copy any cached group ACLs individually */ + op2->o_groups = NULL; + for ( g1=op->o_groups; g1; g1=g1->ga_next ) { + g2 = ch_malloc( sizeof(GroupAssertion) + g1->ga_len ); + *g2 = *g1; + strcpy( g2->ga_ndn, g1->ga_ndn ); + g2->ga_next = op2->o_groups; + op2->o_groups = g2; + } + /* Don't allow any further group caching */ + op2->o_do_not_cache = 1; + + /* Add op2 to conn so abandon will find us */ + op->o_conn->c_n_ops_executing++; + op->o_conn->c_n_ops_completed--; + LDAP_STAILQ_INSERT_TAIL( &op->o_conn->c_ops, op2, o_next ); + so->s_flags |= PS_IS_DETACHED; + + /* Prevent anyone else from trying to send a result for this op */ + op->o_abandon = 1; +} + +static int +syncprov_search_response( Operation *op, SlapReply *rs ) +{ + searchstate *ss = op->o_callback->sc_private; + slap_overinst *on = ss->ss_on; + syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private; + sync_control *srs = op->o_controls[slap_cids.sc_LDAPsync]; + + if ( rs->sr_type == REP_SEARCH || rs->sr_type == REP_SEARCHREF ) { + Attribute *a; + /* If we got a referral without a referral object, there's + * something missing that we cannot replicate. Just ignore it. + * The consumer will abort because we didn't send the expected + * control. + */ + if ( !rs->sr_entry ) { + assert( rs->sr_entry != NULL ); + Debug( LDAP_DEBUG_ANY, "%s syncprov_search_response: " + "bogus referral in context\n", op->o_log_prefix, 0, 0 ); + return SLAP_CB_CONTINUE; + } + a = attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryCSN ); + if ( a == NULL && rs->sr_operational_attrs != NULL ) { + a = attr_find( rs->sr_operational_attrs, slap_schema.si_ad_entryCSN ); + } + if ( a ) { + int i, sid; + sid = slap_parse_csn_sid( &a->a_nvals[0] ); + + /* If not a persistent search */ + if ( !ss->ss_so ) { + /* Make sure entry is less than the snapshot'd contextCSN */ + for ( i=0; i<ss->ss_numcsns; i++ ) { + if ( sid == ss->ss_sids[i] && ber_bvcmp( &a->a_nvals[0], + &ss->ss_ctxcsn[i] ) > 0 ) { + Log4( LDAP_DEBUG_SYNC, ldap_syslog_level, "%s syncprov_search_response: " + "Entry %s CSN %s greater than snapshot %s\n", + op->o_log_prefix, + rs->sr_entry->e_name.bv_val, + a->a_nvals[0].bv_val, + ss->ss_ctxcsn[i].bv_val ); + return LDAP_SUCCESS; + } + } + } + + /* Don't send old entries twice */ + if ( srs->sr_state.ctxcsn ) { + for ( i=0; i<srs->sr_state.numcsns; i++ ) { + if ( sid == srs->sr_state.sids[i] && + ber_bvcmp( &a->a_nvals[0], + &srs->sr_state.ctxcsn[i] )<= 0 ) { + Log4( LDAP_DEBUG_SYNC, ldap_syslog_level, "%s syncprov_search_response: " + "Entry %s CSN %s older or equal to ctx %s\n", + op->o_log_prefix, + rs->sr_entry->e_name.bv_val, + a->a_nvals[0].bv_val, + srs->sr_state.ctxcsn[i].bv_val ); + return LDAP_SUCCESS; + } + } + } + } + rs->sr_ctrls = op->o_tmpalloc( sizeof(LDAPControl *)*2, + op->o_tmpmemctx ); + rs->sr_ctrls[1] = NULL; + rs->sr_flags |= REP_CTRLS_MUSTBEFREED; + /* If we're in delta-sync mode, always send a cookie */ + if ( si->si_nopres && si->si_usehint && a ) { + struct berval cookie; + slap_compose_sync_cookie( op, &cookie, a->a_nvals, srs->sr_state.rid, slap_serverID ? slap_serverID : -1 ); + rs->sr_err = syncprov_state_ctrl( op, rs, rs->sr_entry, + LDAP_SYNC_ADD, rs->sr_ctrls, 0, 1, &cookie ); + op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx ); + } else { + rs->sr_err = syncprov_state_ctrl( op, rs, rs->sr_entry, + LDAP_SYNC_ADD, rs->sr_ctrls, 0, 0, NULL ); + } + } else if ( rs->sr_type == REP_RESULT && rs->sr_err == LDAP_SUCCESS ) { + struct berval cookie = BER_BVNULL; + + if ( ( ss->ss_flags & SS_CHANGED ) && + ss->ss_ctxcsn && !BER_BVISNULL( &ss->ss_ctxcsn[0] )) { + slap_compose_sync_cookie( op, &cookie, ss->ss_ctxcsn, + srs->sr_state.rid, slap_serverID ? slap_serverID : -1 ); + + Debug( LDAP_DEBUG_SYNC, "%s syncprov_search_response: cookie=%s\n", + op->o_log_prefix, cookie.bv_val, 0 ); + } + + /* Is this a regular refresh? + * Note: refresh never gets here if there were no changes + */ + if ( !ss->ss_so ) { + rs->sr_ctrls = op->o_tmpalloc( sizeof(LDAPControl *)*2, + op->o_tmpmemctx ); + rs->sr_ctrls[1] = NULL; + rs->sr_flags |= REP_CTRLS_MUSTBEFREED; + rs->sr_err = syncprov_done_ctrl( op, rs, rs->sr_ctrls, + 0, 1, &cookie, ( ss->ss_flags & SS_PRESENT ) ? LDAP_SYNC_REFRESH_PRESENTS : + LDAP_SYNC_REFRESH_DELETES ); + op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx ); + } else { + /* It's RefreshAndPersist, transition to Persist phase */ + syncprov_sendinfo( op, rs, ( ss->ss_flags & SS_PRESENT ) ? + LDAP_TAG_SYNC_REFRESH_PRESENT : LDAP_TAG_SYNC_REFRESH_DELETE, + ( ss->ss_flags & SS_CHANGED ) ? &cookie : NULL, + 1, NULL, 0 ); + if ( !BER_BVISNULL( &cookie )) + op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx ); + + /* Detach this Op from frontend control */ + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + + /* But not if this connection was closed along the way */ + if ( op->o_abandon ) { + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + /* syncprov_ab_cleanup will free this syncop */ + return SLAPD_ABANDON; + + } else { + ldap_pvt_thread_mutex_lock( &ss->ss_so->s_mutex ); + /* Turn off the refreshing flag */ + ss->ss_so->s_flags ^= PS_IS_REFRESHING; + + Debug( LDAP_DEBUG_SYNC, "%s syncprov_search_response: " + "detaching op\n", op->o_log_prefix, 0, 0 ); + syncprov_detach_op( op, ss->ss_so, on ); + + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + + /* If there are queued responses, fire them off */ + if ( ss->ss_so->s_res ) + syncprov_qstart( ss->ss_so ); + ldap_pvt_thread_mutex_unlock( &ss->ss_so->s_mutex ); + } + + return LDAP_SUCCESS; + } + } + + return SLAP_CB_CONTINUE; +} + +static int +syncprov_op_search( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private; + slap_callback *cb; + int gotstate = 0, changed = 0, do_present = 0; + syncops *sop = NULL; + searchstate *ss; + sync_control *srs; + BerVarray ctxcsn; + int i, *sids, numcsns; + struct berval mincsn, maxcsn; + int minsid, maxsid; + int dirty = 0; + + if ( !(op->o_sync_mode & SLAP_SYNC_REFRESH) ) return SLAP_CB_CONTINUE; + + if ( op->ors_deref & LDAP_DEREF_SEARCHING ) { + send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR, "illegal value for derefAliases" ); + return rs->sr_err; + } + + srs = op->o_controls[slap_cids.sc_LDAPsync]; + Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: " + "got a %ssearch with a cookie=%s\n", + op->o_log_prefix, + op->o_sync_mode & SLAP_SYNC_PERSIST ? "persistent ": "", + srs->sr_state.octet_str.bv_val ); + + /* If this is a persistent search, set it up right away */ + if ( op->o_sync_mode & SLAP_SYNC_PERSIST ) { + syncops so = {0}; + fbase_cookie fc; + opcookie opc; + slap_callback sc = {0}; + + fc.fss = &so; + fc.fbase = 0; + so.s_eid = NOID; + so.s_op = op; + so.s_flags = PS_IS_REFRESHING | PS_FIND_BASE; + /* syncprov_findbase expects to be called as a callback... */ + sc.sc_private = &opc; + opc.son = on; + ldap_pvt_thread_mutex_init( &so.s_mutex ); + cb = op->o_callback; + op->o_callback = ≻ + rs->sr_err = syncprov_findbase( op, &fc ); + op->o_callback = cb; + ldap_pvt_thread_mutex_destroy( &so.s_mutex ); + + if ( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + return rs->sr_err; + } + sop = ch_malloc( sizeof( syncops )); + *sop = so; + sop->s_rid = srs->sr_state.rid; + sop->s_sid = srs->sr_state.sid; + /* set refcount=2 to prevent being freed out from under us + * by abandons that occur while we're running here + */ + sop->s_inuse = 2; + + ldap_pvt_thread_mutex_lock( &si->si_ops_mutex ); + while ( si->si_active ) { + /* Wait for active mods to finish before proceeding, as they + * may already have inspected the si_ops list looking for + * consumers to replicate the change to. Using the log + * doesn't help, as we may finish playing it before the + * active mods gets added to it. + */ + ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex ); + if ( slapd_shutdown ) { + ch_free( sop ); + return SLAPD_ABANDON; + } + if ( !ldap_pvt_thread_pool_pausecheck( &connection_pool )) + ldap_pvt_thread_yield(); + ldap_pvt_thread_mutex_lock( &si->si_ops_mutex ); + } + if ( op->o_abandon ) { + ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex ); + ch_free( sop ); + return SLAPD_ABANDON; + } + ldap_pvt_thread_mutex_init( &sop->s_mutex ); + sop->s_next = si->si_ops; + sop->s_si = si; + si->si_ops = sop; + ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex ); + Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: " + "registered persistent search\n", op->o_log_prefix, 0, 0 ); + } + + /* snapshot the ctxcsn + * Note: this must not be done before the psearch setup. (ITS#8365) + */ + ldap_pvt_thread_rdwr_rlock( &si->si_csn_rwlock ); + numcsns = si->si_numcsns; + if ( numcsns ) { + ber_bvarray_dup_x( &ctxcsn, si->si_ctxcsn, op->o_tmpmemctx ); + sids = op->o_tmpalloc( numcsns * sizeof(int), op->o_tmpmemctx ); + for ( i=0; i<numcsns; i++ ) + sids[i] = si->si_sids[i]; + } else { + ctxcsn = NULL; + sids = NULL; + } + dirty = si->si_dirty; + ldap_pvt_thread_rdwr_runlock( &si->si_csn_rwlock ); + + /* If we have a cookie, handle the PRESENT lookups */ + if ( srs->sr_state.ctxcsn ) { + sessionlog *sl; + int i, j; + + /* If we don't have any CSN of our own yet, bail out. + */ + if ( !numcsns ) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "consumer has state info but provider doesn't!"; + goto bailout; + } + + if ( !si->si_nopres ) + do_present = SS_PRESENT; + + /* If there are SIDs we don't recognize in the cookie, drop them */ + for (i=0; i<srs->sr_state.numcsns; ) { + for (j=i; j<numcsns; j++) { + if ( srs->sr_state.sids[i] <= sids[j] ) { + break; + } + } + /* not found */ + if ( j == numcsns || srs->sr_state.sids[i] != sids[j] ) { + char *tmp = srs->sr_state.ctxcsn[i].bv_val; + srs->sr_state.numcsns--; + for ( j=i; j<srs->sr_state.numcsns; j++ ) { + srs->sr_state.ctxcsn[j] = srs->sr_state.ctxcsn[j+1]; + srs->sr_state.sids[j] = srs->sr_state.sids[j+1]; + } + srs->sr_state.ctxcsn[j].bv_val = tmp; + srs->sr_state.ctxcsn[j].bv_len = 0; + continue; + } + i++; + } + + if (srs->sr_state.numcsns != numcsns) { + /* consumer doesn't have the right number of CSNs */ + Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: " + "consumer cookie is missing a csn we track\n", + op->o_log_prefix, 0, 0 ); + changed = SS_CHANGED; + if ( srs->sr_state.ctxcsn ) { + ber_bvarray_free_x( srs->sr_state.ctxcsn, op->o_tmpmemctx ); + srs->sr_state.ctxcsn = NULL; + } + if ( srs->sr_state.sids ) { + slap_sl_free( srs->sr_state.sids, op->o_tmpmemctx ); + srs->sr_state.sids = NULL; + } + srs->sr_state.numcsns = 0; + goto shortcut; + } + + /* Find the smallest CSN which differs from contextCSN */ + mincsn.bv_len = 0; + maxcsn.bv_len = 0; + for ( i=0,j=0; i<srs->sr_state.numcsns; i++ ) { + int newer; + while ( srs->sr_state.sids[i] != sids[j] ) j++; + if ( BER_BVISEMPTY( &maxcsn ) || ber_bvcmp( &maxcsn, + &srs->sr_state.ctxcsn[i] ) < 0 ) { + maxcsn = srs->sr_state.ctxcsn[i]; + maxsid = sids[j]; + } + newer = ber_bvcmp( &srs->sr_state.ctxcsn[i], &ctxcsn[j] ); + /* If our state is newer, tell consumer about changes */ + if ( newer < 0) { + changed = SS_CHANGED; + if ( BER_BVISEMPTY( &mincsn ) || ber_bvcmp( &mincsn, + &srs->sr_state.ctxcsn[i] ) > 0 ) { + mincsn = srs->sr_state.ctxcsn[i]; + minsid = sids[j]; + } + } else if ( newer > 0 && sids[j] == slap_serverID ) { + /* our state is older, complain to consumer */ + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "consumer state is newer than provider!"; + Log5( LDAP_DEBUG_SYNC, ldap_syslog_level, "%s syncprov_op_search: " + "consumer %d state %s is newer than provider %d state %s\n", + op->o_log_prefix, sids[i], srs->sr_state.ctxcsn[i].bv_val, + sids[j], /* == slap_serverID */ + ctxcsn[j].bv_val); +bailout: + if ( sop ) { + syncops **sp = &si->si_ops; + + ldap_pvt_thread_mutex_lock( &si->si_ops_mutex ); + while ( *sp != sop ) + sp = &(*sp)->s_next; + *sp = sop->s_next; + ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex ); + ch_free( sop ); + } + rs->sr_ctrls = NULL; + send_ldap_result( op, rs ); + return rs->sr_err; + } + } + if ( BER_BVISEMPTY( &mincsn )) { + mincsn = maxcsn; + minsid = maxsid; + } + + /* If nothing has changed, shortcut it */ + if ( !changed && !dirty ) { + do_present = 0; +no_change: if ( !(op->o_sync_mode & SLAP_SYNC_PERSIST) ) { + LDAPControl *ctrls[2]; + + ctrls[0] = NULL; + ctrls[1] = NULL; + syncprov_done_ctrl( op, rs, ctrls, 0, 0, + NULL, LDAP_SYNC_REFRESH_DELETES ); + rs->sr_ctrls = ctrls; + rs->sr_err = LDAP_SUCCESS; + send_ldap_result( op, rs ); + rs->sr_ctrls = NULL; + return rs->sr_err; + } + Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: " + "no change, skipping log replay\n", + op->o_log_prefix, 0, 0 ); + goto shortcut; + } + + /* Do we have a sessionlog for this search? */ + sl=si->si_logs; + if ( sl ) { + int do_play = 0; + ldap_pvt_thread_rdwr_wlock( &sl->sl_mutex ); + /* Are there any log entries, and is the consumer state + * present in the session log? + */ + if ( sl->sl_num > 0 ) { + int i; + for ( i=0; i<sl->sl_numcsns; i++ ) { + /* SID not present == new enough */ + if ( minsid < sl->sl_sids[i] ) { + do_play = 1; + break; + } + /* SID present */ + if ( minsid == sl->sl_sids[i] ) { + /* new enough? */ + if ( ber_bvcmp( &mincsn, &sl->sl_mincsn[i] ) >= 0 ) + do_play = 1; + break; + } + } + /* SID not present == new enough */ + if ( i == sl->sl_numcsns ) + do_play = 1; + } + if ( do_play ) { + do_present = 0; + /* mutex is unlocked in playlog */ + syncprov_playlog( op, rs, sl, srs, ctxcsn, numcsns, sids, &mincsn ); + } else { + ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex ); + } + } + /* Is the CSN still present in the database? */ + if ( syncprov_findcsn( op, FIND_CSN, &mincsn ) != LDAP_SUCCESS ) { + /* No, so a reload is required */ + /* the 2.2 consumer doesn't send this hint */ + if ( si->si_usehint && srs->sr_rhint == 0 ) { + if ( ctxcsn ) + ber_bvarray_free_x( ctxcsn, op->o_tmpmemctx ); + if ( sids ) + op->o_tmpfree( sids, op->o_tmpmemctx ); + rs->sr_err = LDAP_SYNC_REFRESH_REQUIRED; + rs->sr_text = "sync cookie is stale"; + goto bailout; + } + Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: " + "failed to find entry with csn=%s, ignoring cookie\n", + op->o_log_prefix, mincsn.bv_val, 0 ); + if ( srs->sr_state.ctxcsn ) { + ber_bvarray_free_x( srs->sr_state.ctxcsn, op->o_tmpmemctx ); + srs->sr_state.ctxcsn = NULL; + } + if ( srs->sr_state.sids ) { + slap_sl_free( srs->sr_state.sids, op->o_tmpmemctx ); + srs->sr_state.sids = NULL; + } + srs->sr_state.numcsns = 0; + } else { + gotstate = 1; + /* If changed and doing Present lookup, send Present UUIDs */ + if ( do_present && syncprov_findcsn( op, FIND_PRESENT, 0 ) != + LDAP_SUCCESS ) { + if ( ctxcsn ) + ber_bvarray_free_x( ctxcsn, op->o_tmpmemctx ); + if ( sids ) + op->o_tmpfree( sids, op->o_tmpmemctx ); + goto bailout; + } + } + } else { + /* The consumer knows nothing, we know nothing. OK. */ + if (!numcsns) + goto no_change; + /* No consumer state, assume something has changed */ + changed = SS_CHANGED; + } + +shortcut: + /* Append CSN range to search filter, save original filter + * for persistent search evaluation + */ + if ( sop ) { + ldap_pvt_thread_mutex_lock( &sop->s_mutex ); + sop->s_filterstr = op->ors_filterstr; + /* correct the refcount that was set to 2 before */ + sop->s_inuse--; + } + + /* If something changed, find the changes */ + if ( gotstate && ( changed || dirty ) ) { + Filter *fand, *fava; + + fand = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + fand->f_choice = LDAP_FILTER_AND; + fand->f_next = NULL; + fava = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + fand->f_and = fava; + fava->f_choice = LDAP_FILTER_GE; + fava->f_ava = op->o_tmpalloc( sizeof(AttributeAssertion), op->o_tmpmemctx ); + fava->f_ava->aa_desc = slap_schema.si_ad_entryCSN; +#ifdef LDAP_COMP_MATCH + fava->f_ava->aa_cf = NULL; +#endif + ber_dupbv_x( &fava->f_ava->aa_value, &mincsn, op->o_tmpmemctx ); + fava->f_next = op->ors_filter; + op->ors_filter = fand; + filter2bv_x( op, op->ors_filter, &op->ors_filterstr ); + if ( sop ) { + sop->s_flags |= PS_FIX_FILTER; + } + } + if ( sop ) { + ldap_pvt_thread_mutex_unlock( &sop->s_mutex ); + } + + /* Let our callback add needed info to returned entries */ + cb = op->o_tmpcalloc(1, sizeof(slap_callback)+sizeof(searchstate), op->o_tmpmemctx); + ss = (searchstate *)(cb+1); + ss->ss_on = on; + ss->ss_so = sop; + ss->ss_flags = do_present | changed; + ss->ss_ctxcsn = ctxcsn; + ss->ss_numcsns = numcsns; + ss->ss_sids = sids; + cb->sc_response = syncprov_search_response; + cb->sc_private = ss; + cb->sc_next = op->o_callback; + op->o_callback = cb; + + /* If this is a persistent search and no changes were reported during + * the refresh phase, just invoke the response callback to transition + * us into persist phase + */ + if ( !changed && !dirty ) { + Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: " + "nothing changed, finishing up initial search early\n", + op->o_log_prefix, 0, 0 ); + rs->sr_err = LDAP_SUCCESS; + rs->sr_nentries = 0; + send_ldap_result( op, rs ); + return rs->sr_err; + } + return SLAP_CB_CONTINUE; +} + +static int +syncprov_operational( + Operation *op, + SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private; + + /* This prevents generating unnecessarily; frontend will strip + * any statically stored copy. + */ + if ( op->o_sync != SLAP_CONTROL_NONE ) + return SLAP_CB_CONTINUE; + + if ( rs->sr_entry && + dn_match( &rs->sr_entry->e_nname, &si->si_contextdn )) { + + if ( SLAP_OPATTRS( rs->sr_attr_flags ) || + ad_inlist( slap_schema.si_ad_contextCSN, rs->sr_attrs )) { + Attribute *a, **ap = NULL; + + for ( a=rs->sr_entry->e_attrs; a; a=a->a_next ) { + if ( a->a_desc == slap_schema.si_ad_contextCSN ) + break; + } + + ldap_pvt_thread_rdwr_rlock( &si->si_csn_rwlock ); + if ( si->si_ctxcsn ) { + if ( !a ) { + for ( ap = &rs->sr_operational_attrs; *ap; + ap=&(*ap)->a_next ); + + a = attr_alloc( slap_schema.si_ad_contextCSN ); + *ap = a; + } + + if ( !ap ) { + if ( rs_entry2modifiable( op, rs, on )) { + a = attr_find( rs->sr_entry->e_attrs, + slap_schema.si_ad_contextCSN ); + } + if ( a->a_nvals != a->a_vals ) { + ber_bvarray_free( a->a_nvals ); + } + a->a_nvals = NULL; + ber_bvarray_free( a->a_vals ); + a->a_vals = NULL; + a->a_numvals = 0; + } + attr_valadd( a, si->si_ctxcsn, si->si_ctxcsn, si->si_numcsns ); + } + ldap_pvt_thread_rdwr_runlock( &si->si_csn_rwlock ); + } + } + return SLAP_CB_CONTINUE; +} + +enum { + SP_CHKPT = 1, + SP_SESSL, + SP_NOPRES, + SP_USEHINT +}; + +static ConfigDriver sp_cf_gen; + +static ConfigTable spcfg[] = { + { "syncprov-checkpoint", "ops> <minutes", 3, 3, 0, ARG_MAGIC|SP_CHKPT, + sp_cf_gen, "( OLcfgOvAt:1.1 NAME 'olcSpCheckpoint' " + "DESC 'ContextCSN checkpoint interval in ops and minutes' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "syncprov-sessionlog", "ops", 2, 2, 0, ARG_INT|ARG_MAGIC|SP_SESSL, + sp_cf_gen, "( OLcfgOvAt:1.2 NAME 'olcSpSessionlog' " + "DESC 'Session log size in ops' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "syncprov-nopresent", NULL, 2, 2, 0, ARG_ON_OFF|ARG_MAGIC|SP_NOPRES, + sp_cf_gen, "( OLcfgOvAt:1.3 NAME 'olcSpNoPresent' " + "DESC 'Omit Present phase processing' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "syncprov-reloadhint", NULL, 2, 2, 0, ARG_ON_OFF|ARG_MAGIC|SP_USEHINT, + sp_cf_gen, "( OLcfgOvAt:1.4 NAME 'olcSpReloadHint' " + "DESC 'Observe Reload Hint in Request control' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs spocs[] = { + { "( OLcfgOvOc:1.1 " + "NAME 'olcSyncProvConfig' " + "DESC 'SyncRepl Provider configuration' " + "SUP olcOverlayConfig " + "MAY ( olcSpCheckpoint " + "$ olcSpSessionlog " + "$ olcSpNoPresent " + "$ olcSpReloadHint " + ") )", + Cft_Overlay, spcfg }, + { NULL, 0, NULL } +}; + +static int +sp_cf_gen(ConfigArgs *c) +{ + slap_overinst *on = (slap_overinst *)c->bi; + syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private; + int rc = 0; + + if ( c->op == SLAP_CONFIG_EMIT ) { + switch ( c->type ) { + case SP_CHKPT: + if ( si->si_chkops || si->si_chktime ) { + struct berval bv; + /* we assume si_chktime is a multiple of 60 + * because the parsed value was originally + * multiplied by 60 */ + bv.bv_len = snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%d %d", si->si_chkops, si->si_chktime/60 ); + if ( bv.bv_len >= sizeof( c->cr_msg ) ) { + rc = 1; + } else { + bv.bv_val = c->cr_msg; + value_add_one( &c->rvalue_vals, &bv ); + } + } else { + rc = 1; + } + break; + case SP_SESSL: + if ( si->si_logs ) { + c->value_int = si->si_logs->sl_size; + } else { + rc = 1; + } + break; + case SP_NOPRES: + if ( si->si_nopres ) { + c->value_int = 1; + } else { + rc = 1; + } + break; + case SP_USEHINT: + if ( si->si_usehint ) { + c->value_int = 1; + } else { + rc = 1; + } + break; + } + return rc; + } else if ( c->op == LDAP_MOD_DELETE ) { + switch ( c->type ) { + case SP_CHKPT: + si->si_chkops = 0; + si->si_chktime = 0; + break; + case SP_SESSL: + si->si_logs->sl_size = 0; + break; + case SP_NOPRES: + si->si_nopres = 0; + break; + case SP_USEHINT: + si->si_usehint = 0; + break; + } + return rc; + } + switch ( c->type ) { + case SP_CHKPT: + if ( lutil_atoi( &si->si_chkops, c->argv[1] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s unable to parse checkpoint ops # \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + if ( si->si_chkops <= 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid checkpoint ops # \"%d\"", + c->argv[0], si->si_chkops ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + if ( lutil_atoi( &si->si_chktime, c->argv[2] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s unable to parse checkpoint time \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + if ( si->si_chktime <= 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid checkpoint time \"%d\"", + c->argv[0], si->si_chkops ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + si->si_chktime *= 60; + break; + case SP_SESSL: { + sessionlog *sl; + int size = c->value_int; + + if ( size < 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s size %d is negative", + c->argv[0], size ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + sl = si->si_logs; + if ( !sl ) { + sl = ch_calloc( 1, sizeof( sessionlog )); + ldap_pvt_thread_rdwr_init( &sl->sl_mutex ); + si->si_logs = sl; + } + sl->sl_size = size; + } + break; + case SP_NOPRES: + si->si_nopres = c->value_int; + break; + case SP_USEHINT: + si->si_usehint = c->value_int; + break; + } + return rc; +} + +/* ITS#3456 we cannot run this search on the main thread, must use a + * child thread in order to insure we have a big enough stack. + */ +static void * +syncprov_db_otask( + void *ptr +) +{ + syncprov_findcsn( ptr, FIND_MAXCSN, 0 ); + return NULL; +} + +static int +syncprov_db_ocallback( + Operation *op, + SlapReply *rs +) +{ + if ( rs->sr_type == REP_SEARCH && rs->sr_err == LDAP_SUCCESS ) { + if ( rs->sr_entry->e_name.bv_len ) + op->o_callback->sc_private = (void *)1; + } + return LDAP_SUCCESS; +} + +/* ITS#9015 see if the DB is really empty */ +static void * +syncprov_db_otask2( + void *ptr +) +{ + Operation *op = ptr; + SlapReply rs = {REP_RESULT}; + slap_callback cb = {0}; + int rc; + + cb.sc_response = syncprov_db_ocallback; + + op->o_managedsait = SLAP_CONTROL_CRITICAL; + op->o_callback = &cb; + op->o_tag = LDAP_REQ_SEARCH; + op->ors_scope = LDAP_SCOPE_SUBTREE; + op->ors_limit = NULL; + op->ors_slimit = 1; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_attrs = slap_anlist_no_attrs; + op->ors_attrsonly = 1; + op->ors_deref = LDAP_DEREF_NEVER; + op->ors_filter = &generic_filter; + op->ors_filterstr = generic_filterstr; + rc = op->o_bd->be_search( op, &rs ); + if ( rc == LDAP_SIZELIMIT_EXCEEDED || cb.sc_private ) + op->ors_slimit = 2; + return NULL; +} + +/* Read any existing contextCSN from the underlying db. + * Then search for any entries newer than that. If no value exists, + * just generate it. Cache whatever result. + */ +static int +syncprov_db_open( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private; + + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation *op; + Entry *e = NULL; + Attribute *a; + int rc; + void *thrctx = NULL; + + if ( !SLAP_LASTMOD( be )) { + Debug( LDAP_DEBUG_ANY, + "syncprov_db_open: invalid config, lastmod must be enabled\n", 0, 0, 0 ); + return -1; + } + + if ( slapMode & SLAP_TOOL_MODE ) { + return 0; + } + + rc = overlay_register_control( be, LDAP_CONTROL_SYNC ); + if ( rc ) { + return rc; + } + + Debug( LDAP_DEBUG_SYNC, "syncprov_db_open: " + "starting syncprov for suffix %s\n", + be->be_suffix[0].bv_val, 0, 0 ); + + thrctx = ldap_pvt_thread_pool_context(); + connection_fake_init2( &conn, &opbuf, thrctx, 0 ); + op = &opbuf.ob_op; + op->o_bd = be; + op->o_dn = be->be_rootdn; + op->o_ndn = be->be_rootndn; + + if ( SLAP_SYNC_SUBENTRY( be )) { + build_new_dn( &si->si_contextdn, be->be_nsuffix, + (struct berval *)&slap_ldapsync_cn_bv, NULL ); + } else { + si->si_contextdn = be->be_nsuffix[0]; + } + rc = overlay_entry_get_ov( op, &si->si_contextdn, NULL, + slap_schema.si_ad_contextCSN, 0, &e, on ); + + if ( e ) { + ldap_pvt_thread_t tid; + + a = attr_find( e->e_attrs, slap_schema.si_ad_contextCSN ); + if ( a ) { + ber_bvarray_dup_x( &si->si_ctxcsn, a->a_vals, NULL ); + si->si_numcsns = a->a_numvals; + si->si_sids = slap_parse_csn_sids( si->si_ctxcsn, a->a_numvals, NULL ); + slap_sort_csn_sids( si->si_ctxcsn, si->si_sids, si->si_numcsns, NULL ); + } + overlay_entry_release_ov( op, e, 0, on ); + if ( si->si_ctxcsn && !SLAP_DBCLEAN( be )) { + op->o_tag = LDAP_REQ_SEARCH; + op->o_req_dn = be->be_suffix[0]; + op->o_req_ndn = be->be_nsuffix[0]; + op->ors_scope = LDAP_SCOPE_SUBTREE; + ldap_pvt_thread_create( &tid, 0, syncprov_db_otask, op ); + ldap_pvt_thread_join( tid, NULL ); + } + } + + /* Didn't find a contextCSN, should we generate one? */ + if ( !si->si_ctxcsn ) { + char csnbuf[ LDAP_PVT_CSNSTR_BUFSIZE ]; + struct berval csn; + + if ( SLAP_SINGLE_SHADOW( op->o_bd ) ) { + /* Not in charge of this serverID, don't generate anything. */ + goto out; + } + if ( !SLAP_SYNC_SUBENTRY( be ) && rc != LDAP_SUCCESS + && rc != LDAP_NO_SUCH_ATTRIBUTE ) { + /* If the DB is genuinely empty, don't generate one either. */ + goto out; + } + if ( !si->si_contextdn.bv_len ) { + ldap_pvt_thread_t tid; + /* a glue entry here with no contextCSN might mean an empty DB. + * we need to search for children, to be sure. + */ + op->o_req_dn = be->be_suffix[0]; + op->o_req_ndn = be->be_nsuffix[0]; + op->o_bd->bd_info = (BackendInfo *)on->on_info; + ldap_pvt_thread_create( &tid, 0, syncprov_db_otask2, op ); + ldap_pvt_thread_join( tid, NULL ); + if ( op->ors_slimit == 1 ) + goto out; + } + + csn.bv_val = csnbuf; + csn.bv_len = sizeof( csnbuf ); + slap_get_csn( op, &csn, 0 ); + value_add_one( &si->si_ctxcsn, &csn ); + si->si_numcsns = 1; + si->si_sids = ch_malloc( sizeof(int) ); + si->si_sids[0] = slap_serverID; + Debug( LDAP_DEBUG_SYNC, "syncprov_db_open: " + "generated a new ctxcsn=%s for suffix %s\n", + csn.bv_val, be->be_suffix[0].bv_val, 0 ); + + /* make sure we do a checkpoint on close */ + si->si_numops++; + } + + /* Initialize the sessionlog mincsn */ + if ( si->si_logs && si->si_numcsns ) { + sessionlog *sl = si->si_logs; + int i; + ber_bvarray_dup_x( &sl->sl_mincsn, si->si_ctxcsn, NULL ); + sl->sl_numcsns = si->si_numcsns; + sl->sl_sids = ch_malloc( si->si_numcsns * sizeof(int) ); + for ( i=0; i < si->si_numcsns; i++ ) + sl->sl_sids[i] = si->si_sids[i]; + } + +out: + op->o_bd->bd_info = (BackendInfo *)on; + return 0; +} + +/* Write the current contextCSN into the underlying db. + */ +static int +syncprov_db_close( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private; +#ifdef SLAP_CONFIG_DELETE + syncops *so, *sonext; +#endif /* SLAP_CONFIG_DELETE */ + + if ( slapMode & SLAP_TOOL_MODE ) { + return 0; + } + if ( si->si_numops ) { + Connection conn = {0}; + OperationBuffer opbuf; + Operation *op; + void *thrctx; + + thrctx = ldap_pvt_thread_pool_context(); + connection_fake_init2( &conn, &opbuf, thrctx, 0 ); + op = &opbuf.ob_op; + op->o_bd = be; + op->o_dn = be->be_rootdn; + op->o_ndn = be->be_rootndn; + syncprov_checkpoint( op, on ); + } + +#ifdef SLAP_CONFIG_DELETE + if ( !slapd_shutdown ) { + ldap_pvt_thread_mutex_lock( &si->si_ops_mutex ); + for ( so=si->si_ops, sonext=so; so; so=sonext ) { + SlapReply rs = {REP_RESULT}; + rs.sr_err = LDAP_UNAVAILABLE; + send_ldap_result( so->s_op, &rs ); + sonext=so->s_next; + syncprov_drop_psearch( so, 0); + } + si->si_ops=NULL; + ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex ); + } + overlay_unregister_control( be, LDAP_CONTROL_SYNC ); +#endif /* SLAP_CONFIG_DELETE */ + + return 0; +} + +static int +syncprov_db_init( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + syncprov_info_t *si; + + if ( SLAP_ISGLOBALOVERLAY( be ) ) { + Debug( LDAP_DEBUG_ANY, + "syncprov must be instantiated within a database.\n", + 0, 0, 0 ); + return 1; + } + + si = ch_calloc(1, sizeof(syncprov_info_t)); + on->on_bi.bi_private = si; + ldap_pvt_thread_rdwr_init( &si->si_csn_rwlock ); + ldap_pvt_thread_mutex_init( &si->si_ops_mutex ); + ldap_pvt_thread_mutex_init( &si->si_mods_mutex ); + ldap_pvt_thread_mutex_init( &si->si_resp_mutex ); + + csn_anlist[0].an_desc = slap_schema.si_ad_entryCSN; + csn_anlist[0].an_name = slap_schema.si_ad_entryCSN->ad_cname; + csn_anlist[1].an_desc = slap_schema.si_ad_entryUUID; + csn_anlist[1].an_name = slap_schema.si_ad_entryUUID->ad_cname; + + uuid_anlist[0].an_desc = slap_schema.si_ad_entryUUID; + uuid_anlist[0].an_name = slap_schema.si_ad_entryUUID->ad_cname; + + return 0; +} + +static int +syncprov_db_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private; + + if ( si ) { + if ( si->si_logs ) { + sessionlog *sl = si->si_logs; + + tavl_free( sl->sl_entries, (AVL_FREE)ch_free ); + if ( sl->sl_mincsn ) + ber_bvarray_free( sl->sl_mincsn ); + if ( sl->sl_sids ) + ch_free( sl->sl_sids ); + + ldap_pvt_thread_rdwr_destroy(&si->si_logs->sl_mutex); + ch_free( si->si_logs ); + } + if ( si->si_ctxcsn ) + ber_bvarray_free( si->si_ctxcsn ); + if ( si->si_sids ) + ch_free( si->si_sids ); + ldap_pvt_thread_mutex_destroy( &si->si_resp_mutex ); + ldap_pvt_thread_mutex_destroy( &si->si_mods_mutex ); + ldap_pvt_thread_mutex_destroy( &si->si_ops_mutex ); + ldap_pvt_thread_rdwr_destroy( &si->si_csn_rwlock ); + ch_free( si ); + } + + return 0; +} + +static int syncprov_parseCtrl ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + ber_tag_t tag; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_int_t mode; + ber_len_t len; + struct berval cookie = BER_BVNULL; + sync_control *sr; + int rhint = 0; + + if ( op->o_sync != SLAP_CONTROL_NONE ) { + rs->sr_text = "Sync control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( op->o_pagedresults != SLAP_CONTROL_NONE ) { + rs->sr_text = "Sync control specified with pagedResults control"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = "Sync control value is absent"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) { + rs->sr_text = "Sync control value is empty"; + return LDAP_PROTOCOL_ERROR; + } + + /* Parse the control value + * syncRequestValue ::= SEQUENCE { + * mode ENUMERATED { + * -- 0 unused + * refreshOnly (1), + * -- 2 reserved + * refreshAndPersist (3) + * }, + * cookie syncCookie OPTIONAL + * } + */ + + ber_init2( ber, &ctrl->ldctl_value, 0 ); + + if ( (tag = ber_scanf( ber, "{i" /*}*/, &mode )) == LBER_ERROR ) { + rs->sr_text = "Sync control : mode decoding error"; + return LDAP_PROTOCOL_ERROR; + } + + switch( mode ) { + case LDAP_SYNC_REFRESH_ONLY: + mode = SLAP_SYNC_REFRESH; + break; + case LDAP_SYNC_REFRESH_AND_PERSIST: + mode = SLAP_SYNC_REFRESH_AND_PERSIST; + break; + default: + rs->sr_text = "Sync control : unknown update mode"; + return LDAP_PROTOCOL_ERROR; + } + + tag = ber_peek_tag( ber, &len ); + + if ( tag == LDAP_TAG_SYNC_COOKIE ) { + if (( ber_scanf( ber, /*{*/ "m", &cookie )) == LBER_ERROR ) { + rs->sr_text = "Sync control : cookie decoding error"; + return LDAP_PROTOCOL_ERROR; + } + tag = ber_peek_tag( ber, &len ); + } + if ( tag == LDAP_TAG_RELOAD_HINT ) { + if (( ber_scanf( ber, /*{*/ "b", &rhint )) == LBER_ERROR ) { + rs->sr_text = "Sync control : rhint decoding error"; + return LDAP_PROTOCOL_ERROR; + } + } + if (( ber_scanf( ber, /*{*/ "}")) == LBER_ERROR ) { + rs->sr_text = "Sync control : decoding error"; + return LDAP_PROTOCOL_ERROR; + } + sr = op->o_tmpcalloc( 1, sizeof(struct sync_control), op->o_tmpmemctx ); + sr->sr_rhint = rhint; + if (!BER_BVISNULL(&cookie)) { + ber_dupbv_x( &sr->sr_state.octet_str, &cookie, op->o_tmpmemctx ); + /* If parse fails, pretend no cookie was sent */ + if ( slap_parse_sync_cookie( &sr->sr_state, op->o_tmpmemctx ) || + sr->sr_state.rid == -1 ) { + if ( sr->sr_state.ctxcsn ) { + ber_bvarray_free_x( sr->sr_state.ctxcsn, op->o_tmpmemctx ); + sr->sr_state.ctxcsn = NULL; + } + sr->sr_state.numcsns = 0; + } + } + + op->o_controls[slap_cids.sc_LDAPsync] = sr; + + op->o_sync = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + op->o_sync_mode |= mode; /* o_sync_mode shares o_sync */ + + return LDAP_SUCCESS; +} + +/* This overlay is set up for dynamic loading via moduleload. For static + * configuration, you'll need to arrange for the slap_overinst to be + * initialized and registered by some other function inside slapd. + */ + +static slap_overinst syncprov; + +int +syncprov_initialize() +{ + int rc; + + rc = register_supported_control( LDAP_CONTROL_SYNC, + SLAP_CTRL_SEARCH, NULL, + syncprov_parseCtrl, &slap_cids.sc_LDAPsync ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "syncprov_init: Failed to register control %d\n", rc, 0, 0 ); + return rc; + } + + syncprov.on_bi.bi_type = "syncprov"; + syncprov.on_bi.bi_db_init = syncprov_db_init; + syncprov.on_bi.bi_db_destroy = syncprov_db_destroy; + syncprov.on_bi.bi_db_open = syncprov_db_open; + syncprov.on_bi.bi_db_close = syncprov_db_close; + + syncprov.on_bi.bi_op_abandon = syncprov_op_abandon; + syncprov.on_bi.bi_op_cancel = syncprov_op_abandon; + + syncprov.on_bi.bi_op_add = syncprov_op_mod; + syncprov.on_bi.bi_op_compare = syncprov_op_compare; + syncprov.on_bi.bi_op_delete = syncprov_op_mod; + syncprov.on_bi.bi_op_modify = syncprov_op_mod; + syncprov.on_bi.bi_op_modrdn = syncprov_op_mod; + syncprov.on_bi.bi_op_search = syncprov_op_search; + syncprov.on_bi.bi_extended = syncprov_op_extended; + syncprov.on_bi.bi_operational = syncprov_operational; + + syncprov.on_bi.bi_cf_ocs = spocs; + + generic_filter.f_desc = slap_schema.si_ad_objectClass; + + rc = config_register_schema( spcfg, spocs ); + if ( rc ) return rc; + + return overlay_register( &syncprov ); +} + +#if SLAPD_OVER_SYNCPROV == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return syncprov_initialize(); +} +#endif /* SLAPD_OVER_SYNCPROV == SLAPD_MOD_DYNAMIC */ + +#endif /* defined(SLAPD_OVER_SYNCPROV) */ diff --git a/servers/slapd/overlays/translucent.c b/servers/slapd/overlays/translucent.c new file mode 100644 index 0000000..f25118b --- /dev/null +++ b/servers/slapd/overlays/translucent.c @@ -0,0 +1,1423 @@ +/* translucent.c - translucent proxy module */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2021 The OpenLDAP Foundation. + * Portions Copyright 2005 Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Symas Corp. for inclusion in + * OpenLDAP Software. This work was sponsored by Hewlett-Packard. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_TRANSLUCENT + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "lutil.h" + +#include "config.h" + +/* config block */ +typedef struct translucent_info { + BackendDB db; /* captive backend */ + AttributeName *local; /* valid attrs for local filters */ + AttributeName *remote; /* valid attrs for remote filters */ + int strict; + int no_glue; + int defer_db_open; + int bind_local; + int pwmod_local; +} translucent_info; + +static ConfigLDAPadd translucent_ldadd; +static ConfigCfAdd translucent_cfadd; + +static ConfigDriver translucent_cf_gen; + +enum { + TRANS_LOCAL = 1, + TRANS_REMOTE +}; + +static ConfigTable translucentcfg[] = { + { "translucent_strict", "on|off", 1, 2, 0, + ARG_ON_OFF|ARG_OFFSET, + (void *)offsetof(translucent_info, strict), + "( OLcfgOvAt:14.1 NAME 'olcTranslucentStrict' " + "DESC 'Reveal attribute deletion constraint violations' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "translucent_no_glue", "on|off", 1, 2, 0, + ARG_ON_OFF|ARG_OFFSET, + (void *)offsetof(translucent_info, no_glue), + "( OLcfgOvAt:14.2 NAME 'olcTranslucentNoGlue' " + "DESC 'Disable automatic glue records for ADD and MODRDN' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "translucent_local", "attr[,attr...]", 1, 2, 0, + ARG_MAGIC|TRANS_LOCAL, + translucent_cf_gen, + "( OLcfgOvAt:14.3 NAME 'olcTranslucentLocal' " + "DESC 'Attributes to use in local search filter' " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "translucent_remote", "attr[,attr...]", 1, 2, 0, + ARG_MAGIC|TRANS_REMOTE, + translucent_cf_gen, + "( OLcfgOvAt:14.4 NAME 'olcTranslucentRemote' " + "DESC 'Attributes to use in remote search filter' " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "translucent_bind_local", "on|off", 1, 2, 0, + ARG_ON_OFF|ARG_OFFSET, + (void *)offsetof(translucent_info, bind_local), + "( OLcfgOvAt:14.5 NAME 'olcTranslucentBindLocal' " + "DESC 'Enable local bind' " + "SYNTAX OMsBoolean SINGLE-VALUE)", NULL, NULL }, + { "translucent_pwmod_local", "on|off", 1, 2, 0, + ARG_ON_OFF|ARG_OFFSET, + (void *)offsetof(translucent_info, pwmod_local), + "( OLcfgOvAt:14.6 NAME 'olcTranslucentPwModLocal' " + "DESC 'Enable local RFC 3062 Password Modify extended operation' " + "SYNTAX OMsBoolean SINGLE-VALUE)", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs translucentocs[] = { + { "( OLcfgOvOc:14.1 " + "NAME 'olcTranslucentConfig' " + "DESC 'Translucent configuration' " + "SUP olcOverlayConfig " + "MAY ( olcTranslucentStrict $ olcTranslucentNoGlue $" + " olcTranslucentLocal $ olcTranslucentRemote $" + " olcTranslucentBindLocal $ olcTranslucentPwModLocal ) )", + Cft_Overlay, translucentcfg, NULL, translucent_cfadd }, + { "( OLcfgOvOc:14.2 " + "NAME 'olcTranslucentDatabase' " + "DESC 'Translucent target database configuration' " + /* co_table is initialized in translucent_initialize() */ + "AUXILIARY )", Cft_Misc, NULL, translucent_ldadd }, + { NULL, 0, NULL } +}; +/* for translucent_init() */ + +static int +translucent_ldadd_cleanup( ConfigArgs *ca ) +{ + slap_overinst *on = ca->ca_private; + translucent_info *ov = on->on_bi.bi_private; + + ov->defer_db_open = 0; + return backend_startup_one( ca->be, &ca->reply ); +} + +static int +translucent_ldadd( CfEntryInfo *cei, Entry *e, ConfigArgs *ca ) +{ + slap_overinst *on; + translucent_info *ov; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_ldadd\n", 0, 0, 0); + + if ( cei->ce_type != Cft_Overlay || !cei->ce_bi || + cei->ce_bi->bi_cf_ocs != translucentocs ) + return LDAP_CONSTRAINT_VIOLATION; + + on = (slap_overinst *)cei->ce_bi; + ov = on->on_bi.bi_private; + ca->be = &ov->db; + ca->ca_private = on; + if ( CONFIG_ONLINE_ADD( ca )) + ca->cleanup = translucent_ldadd_cleanup; + else + ov->defer_db_open = 0; + + return LDAP_SUCCESS; +} + +static int +translucent_cfadd( Operation *op, SlapReply *rs, Entry *e, ConfigArgs *ca ) +{ + CfEntryInfo *cei = e->e_private; + slap_overinst *on = (slap_overinst *)cei->ce_bi; + translucent_info *ov = on->on_bi.bi_private; + struct berval bv; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_cfadd\n", 0, 0, 0); + + /* FIXME: should not hardcode "olcDatabase" here */ + bv.bv_len = snprintf( ca->cr_msg, sizeof( ca->cr_msg ), + "olcDatabase=" SLAP_X_ORDERED_FMT "%s", + 0, ov->db.bd_info->bi_type ); + if ( bv.bv_len >= sizeof( ca->cr_msg ) ) { + return -1; + } + bv.bv_val = ca->cr_msg; + ca->be = &ov->db; + ov->defer_db_open = 0; + + /* We can only create this entry if the database is table-driven + */ + if ( ov->db.bd_info->bi_cf_ocs ) + config_build_entry( op, rs, cei, ca, &bv, + ov->db.bd_info->bi_cf_ocs, + &translucentocs[1] ); + + return 0; +} + +static int +translucent_cf_gen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + translucent_info *ov = on->on_bi.bi_private; + AttributeName **an, *a2; + int i; + + if ( c->type == TRANS_LOCAL ) + an = &ov->local; + else + an = &ov->remote; + + if ( c->op == SLAP_CONFIG_EMIT ) { + if ( !*an ) + return 1; + for ( i = 0; !BER_BVISNULL(&(*an)[i].an_name); i++ ) { + value_add_one( &c->rvalue_vals, &(*an)[i].an_name ); + } + return ( i < 1 ); + } else if ( c->op == LDAP_MOD_DELETE ) { + if ( c->valx < 0 ) { + anlist_free( *an, 1, NULL ); + *an = NULL; + } else { + i = c->valx; + ch_free( (*an)[i].an_name.bv_val ); + do { + (*an)[i] = (*an)[i+1]; + i++; + } while ( !BER_BVISNULL( &(*an)[i].an_name )); + } + return 0; + } + a2 = str2anlist( *an, c->argv[1], "," ); + if ( !a2 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s unable to parse attribute %s", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg, 0 ); + return ARG_BAD_CONF; + } + *an = a2; + return 0; +} + +static slap_overinst translucent; + +/* +** glue_parent() +** call syncrepl_add_glue() with the parent suffix; +** +*/ + +static struct berval glue[] = { BER_BVC("top"), BER_BVC("glue"), BER_BVNULL }; + +void glue_parent(Operation *op) { + Operation nop = *op; + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct berval ndn = BER_BVNULL; + Attribute *a; + Entry *e; + struct berval pdn; + + dnParent( &op->o_req_ndn, &pdn ); + ber_dupbv_x( &ndn, &pdn, op->o_tmpmemctx ); + + Debug(LDAP_DEBUG_TRACE, "=> glue_parent: fabricating glue for <%s>\n", ndn.bv_val, 0, 0); + + e = entry_alloc(); + e->e_id = NOID; + ber_dupbv(&e->e_name, &ndn); + ber_dupbv(&e->e_nname, &ndn); + + a = attr_alloc( slap_schema.si_ad_objectClass ); + a->a_numvals = 2; + a->a_vals = ch_malloc(sizeof(struct berval) * 3); + ber_dupbv(&a->a_vals[0], &glue[0]); + ber_dupbv(&a->a_vals[1], &glue[1]); + ber_dupbv(&a->a_vals[2], &glue[2]); + a->a_nvals = a->a_vals; + a->a_next = e->e_attrs; + e->e_attrs = a; + + a = attr_alloc( slap_schema.si_ad_structuralObjectClass ); + a->a_numvals = 1; + a->a_vals = ch_malloc(sizeof(struct berval) * 2); + ber_dupbv(&a->a_vals[0], &glue[1]); + ber_dupbv(&a->a_vals[1], &glue[2]); + a->a_nvals = a->a_vals; + a->a_next = e->e_attrs; + e->e_attrs = a; + + nop.o_req_dn = ndn; + nop.o_req_ndn = ndn; + nop.ora_e = e; + + nop.o_bd->bd_info = (BackendInfo *) on->on_info->oi_orig; + syncrepl_add_glue(&nop, e); + nop.o_bd->bd_info = (BackendInfo *) on; + + op->o_tmpfree( ndn.bv_val, op->o_tmpmemctx ); + + return; +} + +/* +** free_attr_chain() +** free only the Attribute*, not the contents; +** +*/ +void free_attr_chain(Attribute *b) { + Attribute *a; + for(a=b; a; a=a->a_next) { + a->a_vals = NULL; + a->a_nvals = NULL; + } + attrs_free( b ); + return; +} + +/* +** translucent_add() +** if not bound as root, send ACCESS error; +** if glue, glue_parent(); +** return CONTINUE; +** +*/ + +static int translucent_add(Operation *op, SlapReply *rs) { + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + translucent_info *ov = on->on_bi.bi_private; + Debug(LDAP_DEBUG_TRACE, "==> translucent_add: %s\n", + op->o_req_dn.bv_val, 0, 0); + if(!be_isroot(op)) { + op->o_bd->bd_info = (BackendInfo *) on->on_info; + send_ldap_error(op, rs, LDAP_INSUFFICIENT_ACCESS, + "user modification of overlay database not permitted"); + op->o_bd->bd_info = (BackendInfo *) on; + return(rs->sr_err); + } + if(!ov->no_glue) glue_parent(op); + return(SLAP_CB_CONTINUE); +} + +/* +** translucent_modrdn() +** if not bound as root, send ACCESS error; +** if !glue, glue_parent(); +** else return CONTINUE; +** +*/ + +static int translucent_modrdn(Operation *op, SlapReply *rs) { + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + translucent_info *ov = on->on_bi.bi_private; + Debug(LDAP_DEBUG_TRACE, "==> translucent_modrdn: %s -> %s\n", + op->o_req_dn.bv_val, op->orr_newrdn.bv_val, 0); + if(!be_isroot(op)) { + op->o_bd->bd_info = (BackendInfo *) on->on_info; + send_ldap_error(op, rs, LDAP_INSUFFICIENT_ACCESS, + "user modification of overlay database not permitted"); + op->o_bd->bd_info = (BackendInfo *) on; + return(rs->sr_err); + } + if(!ov->no_glue) { + op->o_tag = LDAP_REQ_ADD; + glue_parent(op); + op->o_tag = LDAP_REQ_MODRDN; + } + return(SLAP_CB_CONTINUE); +} + +/* +** translucent_delete() +** if not bound as root, send ACCESS error; +** else return CONTINUE; +** +*/ + +static int translucent_delete(Operation *op, SlapReply *rs) { + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + Debug(LDAP_DEBUG_TRACE, "==> translucent_delete: %s\n", + op->o_req_dn.bv_val, 0, 0); + if(!be_isroot(op)) { + op->o_bd->bd_info = (BackendInfo *) on->on_info; + send_ldap_error(op, rs, LDAP_INSUFFICIENT_ACCESS, + "user modification of overlay database not permitted"); + op->o_bd->bd_info = (BackendInfo *) on; + return(rs->sr_err); + } + return(SLAP_CB_CONTINUE); +} + +static int +translucent_tag_cb( Operation *op, SlapReply *rs ) +{ + op->o_tag = LDAP_REQ_MODIFY; + op->orm_modlist = op->o_callback->sc_private; + rs->sr_tag = slap_req2res( op->o_tag ); + + return SLAP_CB_CONTINUE; +} + +/* +** translucent_modify() +** modify in local backend if exists in both; +** otherwise, add to local backend; +** fail if not defined in captive backend; +** +*/ + +static int translucent_modify(Operation *op, SlapReply *rs) { + SlapReply nrs = { REP_RESULT }; + + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + translucent_info *ov = on->on_bi.bi_private; + Entry *e = NULL, *re = NULL; + Attribute *a, *ax; + Modifications *m, **mm; + BackendDB *db; + int del, rc, erc = 0; + slap_callback cb = { 0 }; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_modify: %s\n", + op->o_req_dn.bv_val, 0, 0); + + if(ov->defer_db_open) { + send_ldap_error(op, rs, LDAP_UNAVAILABLE, + "remote DB not available"); + return(rs->sr_err); + } +/* +** fetch entry from the captive backend; +** if it did not exist, fail; +** release it, if captive backend supports this; +** +*/ + + db = op->o_bd; + op->o_bd = &ov->db; + ov->db.be_acl = op->o_bd->be_acl; + rc = ov->db.bd_info->bi_entry_get_rw(op, &op->o_req_ndn, NULL, NULL, 0, &re); + op->o_bd = db; + if(rc != LDAP_SUCCESS || re == NULL ) { + send_ldap_error((op), rs, LDAP_NO_SUCH_OBJECT, + "attempt to modify nonexistent local record"); + return(rs->sr_err); + } +/* +** fetch entry from local backend; +** if it exists: +** foreach Modification: +** if attr not present in local: +** if Mod == LDAP_MOD_DELETE: +** if remote attr not present, return NO_SUCH; +** if remote attr present, drop this Mod; +** else force this Mod to LDAP_MOD_ADD; +** return CONTINUE; +** +*/ + + op->o_bd->bd_info = (BackendInfo *) on->on_info->oi_orig; + rc = be_entry_get_rw(op, &op->o_req_ndn, NULL, NULL, 0, &e); + op->o_bd->bd_info = (BackendInfo *) on; + + if(e && rc == LDAP_SUCCESS) { + Debug(LDAP_DEBUG_TRACE, "=> translucent_modify: found local entry\n", 0, 0, 0); + for(mm = &op->orm_modlist; *mm; ) { + m = *mm; + for(a = e->e_attrs; a; a = a->a_next) + if(a->a_desc == m->sml_desc) break; + if(a) { + mm = &m->sml_next; + continue; /* found local attr */ + } + if(m->sml_op == LDAP_MOD_DELETE) { + for(a = re->e_attrs; a; a = a->a_next) + if(a->a_desc == m->sml_desc) break; + /* not found remote attr */ + if(!a) { + erc = LDAP_NO_SUCH_ATTRIBUTE; + goto release; + } + if(ov->strict) { + erc = LDAP_CONSTRAINT_VIOLATION; + goto release; + } + Debug(LDAP_DEBUG_TRACE, + "=> translucent_modify: silently dropping delete: %s\n", + m->sml_desc->ad_cname.bv_val, 0, 0); + *mm = m->sml_next; + m->sml_next = NULL; + slap_mods_free(m, 1); + continue; + } + m->sml_op = LDAP_MOD_ADD; + mm = &m->sml_next; + } + erc = SLAP_CB_CONTINUE; +release: + if(re) { + if(ov->db.bd_info->bi_entry_release_rw) { + op->o_bd = &ov->db; + ov->db.bd_info->bi_entry_release_rw(op, re, 0); + op->o_bd = db; + } else + entry_free(re); + } + op->o_bd->bd_info = (BackendInfo *) on->on_info->oi_orig; + be_entry_release_r(op, e); + op->o_bd->bd_info = (BackendInfo *) on; + if(erc == SLAP_CB_CONTINUE) { + return(erc); + } else if(erc) { + send_ldap_error(op, rs, erc, + "attempt to delete nonexistent attribute"); + return(erc); + } + } + + /* don't leak remote entry copy */ + if(re) { + if(ov->db.bd_info->bi_entry_release_rw) { + op->o_bd = &ov->db; + ov->db.bd_info->bi_entry_release_rw(op, re, 0); + op->o_bd = db; + } else + entry_free(re); + } +/* +** foreach Modification: +** if MOD_ADD or MOD_REPLACE, add Attribute; +** if no Modifications were suitable: +** if strict, throw CONSTRAINT_VIOLATION; +** else, return early SUCCESS; +** fabricate Entry with new Attribute chain; +** glue_parent() for this Entry; +** call bi_op_add() in local backend; +** +*/ + + Debug(LDAP_DEBUG_TRACE, "=> translucent_modify: fabricating local add\n", 0, 0, 0); + a = NULL; + for(del = 0, ax = NULL, m = op->orm_modlist; m; m = m->sml_next) { + Attribute atmp; + if(((m->sml_op & LDAP_MOD_OP) != LDAP_MOD_ADD) && + ((m->sml_op & LDAP_MOD_OP) != LDAP_MOD_REPLACE)) { + Debug(LDAP_DEBUG_ANY, + "=> translucent_modify: silently dropped modification(%d): %s\n", + m->sml_op, m->sml_desc->ad_cname.bv_val, 0); + if((m->sml_op & LDAP_MOD_OP) == LDAP_MOD_DELETE) del++; + continue; + } + atmp.a_desc = m->sml_desc; + atmp.a_vals = m->sml_values; + atmp.a_nvals = m->sml_nvalues ? m->sml_nvalues : atmp.a_vals; + atmp.a_numvals = m->sml_numvals; + atmp.a_flags = 0; + a = attr_dup( &atmp ); + a->a_next = ax; + ax = a; + } + + if(del && ov->strict) { + attrs_free( a ); + send_ldap_error(op, rs, LDAP_CONSTRAINT_VIOLATION, + "attempt to delete attributes from local database"); + return(rs->sr_err); + } + + if(!ax) { + if(ov->strict) { + send_ldap_error(op, rs, LDAP_CONSTRAINT_VIOLATION, + "modification contained other than ADD or REPLACE"); + return(rs->sr_err); + } + /* rs->sr_text = "no valid modification found"; */ + rs->sr_err = LDAP_SUCCESS; + send_ldap_result(op, rs); + return(rs->sr_err); + } + + e = entry_alloc(); + ber_dupbv( &e->e_name, &op->o_req_dn ); + ber_dupbv( &e->e_nname, &op->o_req_ndn ); + e->e_attrs = a; + + op->o_tag = LDAP_REQ_ADD; + cb.sc_response = translucent_tag_cb; + cb.sc_private = op->orm_modlist; + op->oq_add.rs_e = e; + + glue_parent(op); + + cb.sc_next = op->o_callback; + op->o_callback = &cb; + rc = on->on_info->oi_orig->bi_op_add(op, &nrs); + if ( op->ora_e == e ) + entry_free( e ); + op->o_callback = cb.sc_next; + + return(rc); +} + +static int translucent_compare(Operation *op, SlapReply *rs) { + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + translucent_info *ov = on->on_bi.bi_private; + AttributeAssertion *ava = op->orc_ava; + Entry *e = NULL; + BackendDB *db; + int rc; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_compare: <%s> %s:%s\n", + op->o_req_dn.bv_val, ava->aa_desc->ad_cname.bv_val, ava->aa_value.bv_val); + +/* +** if the local backend has an entry for this attribute: +** CONTINUE and let it do the compare; +** +*/ + rc = overlay_entry_get_ov(op, &op->o_req_ndn, NULL, ava->aa_desc, 0, &e, on); + if(rc == LDAP_SUCCESS && e) { + overlay_entry_release_ov(op, e, 0, on); + return(SLAP_CB_CONTINUE); + } + + if(ov->defer_db_open) { + send_ldap_error(op, rs, LDAP_UNAVAILABLE, + "remote DB not available"); + return(rs->sr_err); + } +/* +** call compare() in the captive backend; +** return the result; +** +*/ + db = op->o_bd; + op->o_bd = &ov->db; + ov->db.be_acl = op->o_bd->be_acl; + rc = ov->db.bd_info->bi_op_compare(op, rs); + op->o_bd = db; + + return(rc); +} + +static int translucent_pwmod(Operation *op, SlapReply *rs) { + SlapReply nrs = { REP_RESULT }; + Operation nop; + + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + translucent_info *ov = on->on_bi.bi_private; + Entry *e = NULL, *re = NULL; + BackendDB *db; + int rc = 0; + slap_callback cb = { 0 }; + + if (!ov->pwmod_local) { + rs->sr_err = LDAP_CONSTRAINT_VIOLATION, + rs->sr_text = "attempt to modify password in local database"; + return rs->sr_err; + } + +/* +** fetch entry from the captive backend; +** if it did not exist, fail; +** release it, if captive backend supports this; +** +*/ + db = op->o_bd; + op->o_bd = &ov->db; + ov->db.be_acl = op->o_bd->be_acl; + rc = ov->db.bd_info->bi_entry_get_rw(op, &op->o_req_ndn, NULL, NULL, 0, &re); + if(rc != LDAP_SUCCESS || re == NULL ) { + send_ldap_error((op), rs, LDAP_NO_SUCH_OBJECT, + "attempt to modify nonexistent local record"); + return(rs->sr_err); + } + op->o_bd = db; +/* +** fetch entry from local backend; +** if it exists: +** return CONTINUE; +*/ + + op->o_bd->bd_info = (BackendInfo *) on->on_info->oi_orig; + rc = be_entry_get_rw(op, &op->o_req_ndn, NULL, NULL, 0, &e); + op->o_bd->bd_info = (BackendInfo *) on; + + if(e && rc == LDAP_SUCCESS) { + if(re) { + if(ov->db.bd_info->bi_entry_release_rw) { + op->o_bd = &ov->db; + ov->db.bd_info->bi_entry_release_rw(op, re, 0); + op->o_bd = db; + } else { + entry_free(re); + } + } + op->o_bd->bd_info = (BackendInfo *) on->on_info->oi_orig; + be_entry_release_r(op, e); + op->o_bd->bd_info = (BackendInfo *) on; + return SLAP_CB_CONTINUE; + } + + /* don't leak remote entry copy */ + if(re) { + if(ov->db.bd_info->bi_entry_release_rw) { + op->o_bd = &ov->db; + ov->db.bd_info->bi_entry_release_rw(op, re, 0); + op->o_bd = db; + } else { + entry_free(re); + } + } +/* +** glue_parent() for this Entry; +** call bi_op_add() in local backend; +** +*/ + e = entry_alloc(); + ber_dupbv( &e->e_name, &op->o_req_dn ); + ber_dupbv( &e->e_nname, &op->o_req_ndn ); + e->e_attrs = NULL; + + nop = *op; + nop.o_tag = LDAP_REQ_ADD; + cb.sc_response = slap_null_cb; + nop.oq_add.rs_e = e; + + glue_parent(&nop); + + nop.o_callback = &cb; + rc = on->on_info->oi_orig->bi_op_add(&nop, &nrs); + if ( nop.ora_e == e ) { + entry_free( e ); + } + + if ( rc == LDAP_SUCCESS ) { + return SLAP_CB_CONTINUE; + } + + return rc; +} + +static int translucent_exop(Operation *op, SlapReply *rs) { + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + translucent_info *ov = on->on_bi.bi_private; + const struct berval bv_exop_pwmod = BER_BVC(LDAP_EXOP_MODIFY_PASSWD); + + Debug(LDAP_DEBUG_TRACE, "==> translucent_exop: %s\n", + op->o_req_dn.bv_val, 0, 0); + + if(ov->defer_db_open) { + send_ldap_error(op, rs, LDAP_UNAVAILABLE, + "remote DB not available"); + return(rs->sr_err); + } + + if ( bvmatch( &bv_exop_pwmod, &op->ore_reqoid ) ) { + return translucent_pwmod( op, rs ); + } + + return SLAP_CB_CONTINUE; +} + +/* +** translucent_search_cb() +** merge local data with remote data +** +** Four cases: +** 1: remote search, no local filter +** merge data and send immediately +** 2: remote search, with local filter +** merge data and save +** 3: local search, no remote filter +** merge data and send immediately +** 4: local search, with remote filter +** check list, merge, send, delete +*/ + +#define RMT_SIDE 0 +#define LCL_SIDE 1 +#define USE_LIST 2 + +typedef struct trans_ctx { + BackendDB *db; + slap_overinst *on; + Filter *orig; + Avlnode *list; + int step; + int slimit; + AttributeName *attrs; +} trans_ctx; + +static int translucent_search_cb(Operation *op, SlapReply *rs) { + trans_ctx *tc; + BackendDB *db; + slap_overinst *on; + translucent_info *ov; + Entry *le, *re; + Attribute *a, *ax, *an, *as = NULL; + int rc; + int test_f = 0; + + tc = op->o_callback->sc_private; + + /* Don't let the op complete while we're gathering data */ + if ( rs->sr_type == REP_RESULT && ( tc->step & USE_LIST )) + return 0; + + if(rs->sr_type != REP_SEARCH || !rs->sr_entry) + return(SLAP_CB_CONTINUE); + + Debug(LDAP_DEBUG_TRACE, "==> translucent_search_cb: %s\n", + rs->sr_entry->e_name.bv_val, 0, 0); + + op->ors_slimit = tc->slimit + ( tc->slimit > 0 ? 1 : 0 ); + if ( op->ors_attrs == slap_anlist_all_attributes ) { + op->ors_attrs = tc->attrs; + rs->sr_attrs = tc->attrs; + rs->sr_attr_flags = slap_attr_flags( rs->sr_attrs ); + } + + on = tc->on; + ov = on->on_bi.bi_private; + + db = op->o_bd; + re = NULL; + + /* If we have local, get remote */ + if ( tc->step & LCL_SIDE ) { + le = rs->sr_entry; + /* If entry is already on list, use it */ + if ( tc->step & USE_LIST ) { + re = tavl_delete( &tc->list, le, entry_dn_cmp ); + if ( re ) { + rs_flush_entry( op, rs, on ); + rc = test_filter( op, re, tc->orig ); + if ( rc == LDAP_COMPARE_TRUE ) { + rs->sr_flags |= REP_ENTRY_MUSTBEFREED; + rs->sr_entry = re; + + if ( tc->slimit >= 0 && rs->sr_nentries >= tc->slimit ) { + return LDAP_SIZELIMIT_EXCEEDED; + } + + return SLAP_CB_CONTINUE; + } else { + entry_free( re ); + rs->sr_entry = NULL; + return 0; + } + } + } + op->o_bd = &ov->db; + rc = be_entry_get_rw( op, &rs->sr_entry->e_nname, NULL, NULL, 0, &re ); + if ( rc == LDAP_SUCCESS && re ) { + Entry *tmp = entry_dup( re ); + be_entry_release_r( op, re ); + re = tmp; + test_f = 1; + } + } else { + /* Else we have remote, get local */ + op->o_bd = tc->db; + le = NULL; + rc = overlay_entry_get_ov(op, &rs->sr_entry->e_nname, NULL, NULL, 0, &le, on); + if ( rc == LDAP_SUCCESS && le ) { + re = entry_dup( rs->sr_entry ); + rs_flush_entry( op, rs, on ); + } else { + le = NULL; + } + } + +/* +** if we got remote and local entry: +** foreach local attr: +** foreach remote attr: +** if match, remote attr with local attr; +** if new local, add to list; +** append new local attrs to remote; +** +*/ + + if ( re && le ) { + for(ax = le->e_attrs; ax; ax = ax->a_next) { + for(a = re->e_attrs; a; a = a->a_next) { + if(a->a_desc == ax->a_desc) { + test_f = 1; + if(a->a_vals != a->a_nvals) + ber_bvarray_free(a->a_nvals); + ber_bvarray_free(a->a_vals); + ber_bvarray_dup_x( &a->a_vals, ax->a_vals, NULL ); + if ( ax->a_vals == ax->a_nvals ) { + a->a_nvals = a->a_vals; + } else { + ber_bvarray_dup_x( &a->a_nvals, ax->a_nvals, NULL ); + } + break; + } + } + if(a) continue; + an = attr_dup(ax); + an->a_next = as; + as = an; + } + /* Dispose of local entry */ + if ( tc->step & LCL_SIDE ) { + rs_flush_entry(op, rs, on); + } else { + overlay_entry_release_ov(op, le, 0, on); + } + + /* literally append, so locals are always last */ + if(as) { + if(re->e_attrs) { + for(ax = re->e_attrs; ax->a_next; ax = ax->a_next); + ax->a_next = as; + } else { + re->e_attrs = as; + } + } + /* If both filters, save entry for later */ + if ( tc->step == (USE_LIST|RMT_SIDE) ) { + tavl_insert( &tc->list, re, entry_dn_cmp, avl_dup_error ); + rs->sr_entry = NULL; + rc = 0; + } else { + /* send it now */ + rs->sr_entry = re; + rs->sr_flags |= REP_ENTRY_MUSTBEFREED; + if ( test_f ) { + rc = test_filter( op, rs->sr_entry, tc->orig ); + if ( rc == LDAP_COMPARE_TRUE ) { + rc = SLAP_CB_CONTINUE; + } else { + rc = 0; + } + } else { + rc = SLAP_CB_CONTINUE; + } + } + } else if ( le ) { + /* Only a local entry: remote was deleted + * Ought to delete the local too... + */ + rc = 0; + } else if ( tc->step & USE_LIST ) { + /* Only a remote entry, but both filters: + * Test the complete filter + */ + rc = test_filter( op, rs->sr_entry, tc->orig ); + if ( rc == LDAP_COMPARE_TRUE ) { + rc = SLAP_CB_CONTINUE; + } else { + rc = 0; + } + } else { + /* Only a remote entry, only remote filter: + * just pass thru + */ + rc = SLAP_CB_CONTINUE; + } + + op->o_bd = db; + + if ( rc == SLAP_CB_CONTINUE && tc->slimit >= 0 && rs->sr_nentries >= tc->slimit ) { + return LDAP_SIZELIMIT_EXCEEDED; + } + + return rc; +} + +/* Dup the filter, excluding invalid elements */ +static Filter * +trans_filter_dup(Operation *op, Filter *f, AttributeName *an) +{ + Filter *n = NULL; + + if ( !f ) + return NULL; + + switch( f->f_choice & SLAPD_FILTER_MASK ) { + case SLAPD_FILTER_COMPUTED: + n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + n->f_choice = f->f_choice; + n->f_result = f->f_result; + n->f_next = NULL; + break; + + case LDAP_FILTER_PRESENT: + if ( ad_inlist( f->f_desc, an )) { + n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + n->f_choice = f->f_choice; + n->f_desc = f->f_desc; + n->f_next = NULL; + } + break; + + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + case LDAP_FILTER_APPROX: + case LDAP_FILTER_SUBSTRINGS: + case LDAP_FILTER_EXT: + if ( !f->f_av_desc || ad_inlist( f->f_av_desc, an )) { + n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + n->f_choice = f->f_choice; + n->f_ava = f->f_ava; + n->f_next = NULL; + } + break; + + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: { + Filter **p; + + n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + n->f_choice = f->f_choice; + n->f_next = NULL; + + for ( p = &n->f_list, f = f->f_list; f; f = f->f_next ) { + *p = trans_filter_dup( op, f, an ); + if ( !*p ) + continue; + p = &(*p)->f_next; + } + /* nothing valid in this list */ + if ( !n->f_list ) { + op->o_tmpfree( n, op->o_tmpmemctx ); + return NULL; + } + /* Only 1 element in this list */ + if ((n->f_choice & SLAPD_FILTER_MASK) != LDAP_FILTER_NOT && + !n->f_list->f_next ) { + f = n->f_list; + *n = *f; + op->o_tmpfree( f, op->o_tmpmemctx ); + } + break; + } + } + return n; +} + +static void +trans_filter_free( Operation *op, Filter *f ) +{ + Filter *n, *p, *next; + + f->f_choice &= SLAPD_FILTER_MASK; + + switch( f->f_choice ) { + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: + /* Free in reverse order */ + n = NULL; + for ( p = f->f_list; p; p = next ) { + next = p->f_next; + p->f_next = n; + n = p; + } + for ( p = n; p; p = next ) { + next = p->f_next; + trans_filter_free( op, p ); + } + break; + default: + break; + } + op->o_tmpfree( f, op->o_tmpmemctx ); +} + +/* +** translucent_search() +** search via captive backend; +** override results with any local data; +** +*/ + +static int translucent_search(Operation *op, SlapReply *rs) { + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + translucent_info *ov = on->on_bi.bi_private; + slap_callback cb = { NULL, NULL, NULL, NULL }; + trans_ctx tc; + Filter *fl, *fr; + struct berval fbv; + int rc = 0; + + if ( op->o_managedsait > SLAP_CONTROL_IGNORED ) + return SLAP_CB_CONTINUE; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_search: <%s> %s\n", + op->o_req_dn.bv_val, op->ors_filterstr.bv_val, 0); + + if(ov->defer_db_open) { + send_ldap_error(op, rs, LDAP_UNAVAILABLE, + "remote DB not available"); + return(rs->sr_err); + } + + fr = ov->remote ? trans_filter_dup( op, op->ors_filter, ov->remote ) : NULL; + fl = ov->local ? trans_filter_dup( op, op->ors_filter, ov->local ) : NULL; + cb.sc_response = (slap_response *) translucent_search_cb; + cb.sc_private = &tc; + cb.sc_next = op->o_callback; + + ov->db.be_acl = op->o_bd->be_acl; + tc.db = op->o_bd; + tc.on = on; + tc.orig = op->ors_filter; + tc.list = NULL; + tc.step = 0; + tc.slimit = op->ors_slimit; + tc.attrs = NULL; + fbv = op->ors_filterstr; + + op->o_callback = &cb; + + if ( fr || !fl ) { + tc.attrs = op->ors_attrs; + op->ors_slimit = SLAP_NO_LIMIT; + op->ors_attrs = slap_anlist_all_attributes; + op->o_bd = &ov->db; + tc.step |= RMT_SIDE; + if ( fl ) { + tc.step |= USE_LIST; + op->ors_filter = fr; + filter2bv_x( op, fr, &op->ors_filterstr ); + } + rc = ov->db.bd_info->bi_op_search(op, rs); + if ( op->ors_attrs == slap_anlist_all_attributes ) + op->ors_attrs = tc.attrs; + op->o_bd = tc.db; + if ( fl ) { + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + } + } + if ( fl && !rc ) { + tc.step |= LCL_SIDE; + op->ors_filter = fl; + filter2bv_x( op, fl, &op->ors_filterstr ); + rc = overlay_op_walk( op, rs, op_search, on->on_info, on->on_next ); + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + } + op->ors_filterstr = fbv; + op->ors_filter = tc.orig; + op->o_callback = cb.sc_next; + rs->sr_attrs = op->ors_attrs; + rs->sr_attr_flags = slap_attr_flags( rs->sr_attrs ); + + /* Send out anything remaining on the list and finish */ + if ( tc.step & USE_LIST ) { + if ( tc.list ) { + Avlnode *av; + + av = tavl_end( tc.list, TAVL_DIR_LEFT ); + while ( av ) { + rs->sr_entry = av->avl_data; + if ( rc == LDAP_SUCCESS && LDAP_COMPARE_TRUE == + test_filter( op, rs->sr_entry, op->ors_filter )) + { + rs->sr_flags = REP_ENTRY_MUSTBEFREED; + rc = send_search_entry( op, rs ); + } else { + entry_free( rs->sr_entry ); + } + av = tavl_next( av, TAVL_DIR_RIGHT ); + } + tavl_free( tc.list, NULL ); + rs->sr_flags = 0; + rs->sr_entry = NULL; + } + send_ldap_result( op, rs ); + } + + op->ors_slimit = tc.slimit; + + /* Free in reverse order */ + if ( fl ) + trans_filter_free( op, fl ); + if ( fr ) + trans_filter_free( op, fr ); + + return rc; +} + + +/* +** translucent_bind() +** pass bind request to captive backend; +** +*/ + +static int translucent_bind(Operation *op, SlapReply *rs) { + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + translucent_info *ov = on->on_bi.bi_private; + BackendDB *db; + slap_callback sc = { 0 }, *save_cb; + int rc; + + Debug(LDAP_DEBUG_TRACE, "translucent_bind: <%s> method %d\n", + op->o_req_dn.bv_val, op->orb_method, 0); + + if(ov->defer_db_open) { + send_ldap_error(op, rs, LDAP_UNAVAILABLE, + "remote DB not available"); + return(rs->sr_err); + } + + if (ov->bind_local) { + sc.sc_response = slap_null_cb; + save_cb = op->o_callback; + op->o_callback = ≻ + } + + db = op->o_bd; + op->o_bd = &ov->db; + ov->db.be_acl = op->o_bd->be_acl; + rc = ov->db.bd_info->bi_op_bind(op, rs); + op->o_bd = db; + + if (ov->bind_local) { + op->o_callback = save_cb; + if (rc != LDAP_SUCCESS) { + rc = SLAP_CB_CONTINUE; + } + } + + return rc; +} + +/* +** translucent_connection_destroy() +** pass disconnect notification to captive backend; +** +*/ + +static int translucent_connection_destroy(BackendDB *be, Connection *conn) { + slap_overinst *on = (slap_overinst *) be->bd_info; + translucent_info *ov = on->on_bi.bi_private; + int rc = 0; + + Debug(LDAP_DEBUG_TRACE, "translucent_connection_destroy\n", 0, 0, 0); + + rc = ov->db.bd_info->bi_connection_destroy(&ov->db, conn); + + return(rc); +} + +/* +** translucent_db_config() +** pass config directives to captive backend; +** parse unrecognized directives ourselves; +** +*/ + +static int translucent_db_config( + BackendDB *be, + const char *fname, + int lineno, + int argc, + char **argv +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + translucent_info *ov = on->on_bi.bi_private; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_db_config: %s\n", + argc ? argv[0] : "", 0, 0); + + /* Something for the captive database? */ + if ( ov->db.bd_info && ov->db.bd_info->bi_db_config ) + return ov->db.bd_info->bi_db_config( &ov->db, fname, lineno, + argc, argv ); + return SLAP_CONF_UNKNOWN; +} + +/* +** translucent_db_init() +** initialize the captive backend; +** +*/ + +static int translucent_db_init(BackendDB *be, ConfigReply *cr) { + slap_overinst *on = (slap_overinst *) be->bd_info; + translucent_info *ov; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_db_init\n", 0, 0, 0); + + ov = ch_calloc(1, sizeof(translucent_info)); + on->on_bi.bi_private = ov; + ov->db = *be; + ov->db.be_private = NULL; + ov->defer_db_open = 1; + + if ( !backend_db_init( "ldap", &ov->db, -1, NULL )) { + Debug( LDAP_DEBUG_CONFIG, "translucent: unable to open captive back-ldap\n", 0, 0, 0); + return 1; + } + SLAP_DBFLAGS(be) |= SLAP_DBFLAG_NO_SCHEMA_CHECK; + SLAP_DBFLAGS(be) |= SLAP_DBFLAG_NOLASTMOD; + + return 0; +} + +/* +** translucent_db_open() +** if the captive backend has an open() method, call it; +** +*/ + +static int translucent_db_open(BackendDB *be, ConfigReply *cr) { + slap_overinst *on = (slap_overinst *) be->bd_info; + translucent_info *ov = on->on_bi.bi_private; + int rc; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_db_open\n", 0, 0, 0); + + /* need to inherit something from the original database... */ + ov->db.be_def_limit = be->be_def_limit; + ov->db.be_limits = be->be_limits; + ov->db.be_acl = be->be_acl; + ov->db.be_dfltaccess = be->be_dfltaccess; + + if ( ov->defer_db_open ) + return 0; + + rc = backend_startup_one( &ov->db, cr ); + + if(rc) Debug(LDAP_DEBUG_TRACE, + "translucent: bi_db_open() returned error %d\n", rc, 0, 0); + + return(rc); +} + +/* +** translucent_db_close() +** if the captive backend has a close() method, call it +** +*/ + +static int +translucent_db_close( BackendDB *be, ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + translucent_info *ov = on->on_bi.bi_private; + int rc = 0; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_db_close\n", 0, 0, 0); + + if ( ov && ov->db.bd_info && ov->db.bd_info->bi_db_close ) { + rc = ov->db.bd_info->bi_db_close(&ov->db, NULL); + } + + return(rc); +} + +/* +** translucent_db_destroy() +** if the captive backend has a db_destroy() method, call it; +** free any config data +** +*/ + +static int +translucent_db_destroy( BackendDB *be, ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + translucent_info *ov = on->on_bi.bi_private; + int rc = 0; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_db_destroy\n", 0, 0, 0); + + if ( ov ) { + if ( ov->remote ) + anlist_free( ov->remote, 1, NULL ); + if ( ov->local ) + anlist_free( ov->local, 1, NULL ); + if ( ov->db.be_private != NULL ) { + backend_stopdown_one( &ov->db ); + } + + ldap_pvt_thread_mutex_destroy( &ov->db.be_pcl_mutex ); + ch_free(ov); + on->on_bi.bi_private = NULL; + } + + return(rc); +} + +/* +** translucent_initialize() +** initialize the slap_overinst with our entry points; +** +*/ + +int translucent_initialize() { + + int rc; + + /* olcDatabaseDummy is defined in slapd, and Windows + will not let us initialize a struct element with a data pointer + from another library, so we have to initialize this element + "by hand". */ + translucentocs[1].co_table = olcDatabaseDummy; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_initialize\n", 0, 0, 0); + + translucent.on_bi.bi_type = "translucent"; + translucent.on_bi.bi_db_init = translucent_db_init; + translucent.on_bi.bi_db_config = translucent_db_config; + translucent.on_bi.bi_db_open = translucent_db_open; + translucent.on_bi.bi_db_close = translucent_db_close; + translucent.on_bi.bi_db_destroy = translucent_db_destroy; + translucent.on_bi.bi_op_bind = translucent_bind; + translucent.on_bi.bi_op_add = translucent_add; + translucent.on_bi.bi_op_modify = translucent_modify; + translucent.on_bi.bi_op_modrdn = translucent_modrdn; + translucent.on_bi.bi_op_delete = translucent_delete; + translucent.on_bi.bi_op_search = translucent_search; + translucent.on_bi.bi_op_compare = translucent_compare; + translucent.on_bi.bi_connection_destroy = translucent_connection_destroy; + translucent.on_bi.bi_extended = translucent_exop; + + translucent.on_bi.bi_cf_ocs = translucentocs; + rc = config_register_schema ( translucentcfg, translucentocs ); + if ( rc ) return rc; + + return(overlay_register(&translucent)); +} + +#if SLAPD_OVER_TRANSLUCENT == SLAPD_MOD_DYNAMIC && defined(PIC) +int init_module(int argc, char *argv[]) { + return translucent_initialize(); +} +#endif + +#endif /* SLAPD_OVER_TRANSLUCENT */ diff --git a/servers/slapd/overlays/unique.c b/servers/slapd/overlays/unique.c new file mode 100644 index 0000000..2d87f3b --- /dev/null +++ b/servers/slapd/overlays/unique.c @@ -0,0 +1,1464 @@ +/* unique.c - attribute uniqueness module */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2021 The OpenLDAP Foundation. + * Portions Copyright 2004,2006-2007 Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Symas Corporation for + * inclusion in OpenLDAP Software, with subsequent enhancements by + * Emily Backes at Symas Corporation. This work was sponsored by + * Hewlett-Packard. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_UNIQUE + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "config.h" + +#define UNIQUE_DEFAULT_URI ("ldap:///??sub") + +static slap_overinst unique; + +typedef struct unique_attrs_s { + struct unique_attrs_s *next; /* list of attrs */ + AttributeDescription *attr; +} unique_attrs; + +typedef struct unique_domain_uri_s { + struct unique_domain_uri_s *next; + struct berval dn; + struct berval ndn; + struct berval filter; + Filter *f; + struct unique_attrs_s *attrs; + int scope; +} unique_domain_uri; + +typedef struct unique_domain_s { + struct unique_domain_s *next; + struct berval domain_spec; + struct unique_domain_uri_s *uri; + char ignore; /* polarity of attributes */ + char strict; /* null considered unique too */ +} unique_domain; + +typedef struct unique_data_s { + struct unique_domain_s *domains; + struct unique_domain_s *legacy; + char legacy_strict_set; +} unique_data; + +typedef struct unique_counter_s { + struct berval *ndn; + int count; +} unique_counter; + +enum { + UNIQUE_BASE = 1, + UNIQUE_IGNORE, + UNIQUE_ATTR, + UNIQUE_STRICT, + UNIQUE_URI +}; + +static ConfigDriver unique_cf_base; +static ConfigDriver unique_cf_attrs; +static ConfigDriver unique_cf_strict; +static ConfigDriver unique_cf_uri; + +static ConfigTable uniquecfg[] = { + { "unique_base", "basedn", 2, 2, 0, ARG_DN|ARG_MAGIC|UNIQUE_BASE, + unique_cf_base, "( OLcfgOvAt:10.1 NAME 'olcUniqueBase' " + "DESC 'Subtree for uniqueness searches' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL }, + { "unique_ignore", "attribute...", 2, 0, 0, ARG_MAGIC|UNIQUE_IGNORE, + unique_cf_attrs, "( OLcfgOvAt:10.2 NAME 'olcUniqueIgnore' " + "DESC 'Attributes for which uniqueness shall not be enforced' " + "EQUALITY caseIgnoreMatch " + "ORDERING caseIgnoreOrderingMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "unique_attributes", "attribute...", 2, 0, 0, ARG_MAGIC|UNIQUE_ATTR, + unique_cf_attrs, "( OLcfgOvAt:10.3 NAME 'olcUniqueAttribute' " + "DESC 'Attributes for which uniqueness shall be enforced' " + "EQUALITY caseIgnoreMatch " + "ORDERING caseIgnoreOrderingMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "unique_strict", "on|off", 1, 2, 0, ARG_MAGIC|UNIQUE_STRICT, + unique_cf_strict, "( OLcfgOvAt:10.4 NAME 'olcUniqueStrict' " + "DESC 'Enforce uniqueness of null values' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "unique_uri", "ldapuri", 2, 3, 0, ARG_MAGIC|UNIQUE_URI, + unique_cf_uri, "( OLcfgOvAt:10.5 NAME 'olcUniqueURI' " + "DESC 'List of keywords and LDAP URIs for a uniqueness domain' " + "EQUALITY caseExactMatch " + "ORDERING caseExactOrderingMatch " + "SUBSTR caseExactSubstringsMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs uniqueocs[] = { + { "( OLcfgOvOc:10.1 " + "NAME 'olcUniqueConfig' " + "DESC 'Attribute value uniqueness configuration' " + "SUP olcOverlayConfig " + "MAY ( olcUniqueBase $ olcUniqueIgnore $ " + "olcUniqueAttribute $ olcUniqueStrict $ " + "olcUniqueURI ) )", + Cft_Overlay, uniquecfg }, + { NULL, 0, NULL } +}; + +static void +unique_free_domain_uri ( unique_domain_uri *uri ) +{ + unique_domain_uri *next_uri = NULL; + unique_attrs *attr, *next_attr = NULL; + + while ( uri ) { + next_uri = uri->next; + ch_free ( uri->dn.bv_val ); + ch_free ( uri->ndn.bv_val ); + ch_free ( uri->filter.bv_val ); + filter_free( uri->f ); + attr = uri->attrs; + while ( attr ) { + next_attr = attr->next; + ch_free (attr); + attr = next_attr; + } + ch_free ( uri ); + uri = next_uri; + } +} + +/* free an entire stack of domains */ +static void +unique_free_domain ( unique_domain *domain ) +{ + unique_domain *next_domain = NULL; + + while ( domain ) { + next_domain = domain->next; + ch_free ( domain->domain_spec.bv_val ); + unique_free_domain_uri ( domain->uri ); + ch_free ( domain ); + domain = next_domain; + } +} + +static int +unique_new_domain_uri ( unique_domain_uri **urip, + const LDAPURLDesc *url_desc, + ConfigArgs *c ) +{ + int i, rc = LDAP_SUCCESS; + unique_domain_uri *uri; + struct berval bv = {0, NULL}; + BackendDB *be = (BackendDB *)c->be; + char ** attr_str; + AttributeDescription * ad; + const char * text; + + uri = ch_calloc ( 1, sizeof ( unique_domain_uri ) ); + + if ( url_desc->lud_host && url_desc->lud_host[0] ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "host <%s> not allowed in URI", + url_desc->lud_host ); + rc = ARG_BAD_CONF; + goto exit; + } + + if ( url_desc->lud_dn && url_desc->lud_dn[0] ) { + ber_str2bv( url_desc->lud_dn, 0, 0, &bv ); + rc = dnPrettyNormal( NULL, + &bv, + &uri->dn, + &uri->ndn, + NULL ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> invalid DN %d (%s)", + url_desc->lud_dn, rc, ldap_err2string( rc )); + rc = ARG_BAD_CONF; + goto exit; + } + + if ( be->be_nsuffix == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "suffix must be set" ); + Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n", + c->cr_msg, NULL, NULL ); + rc = ARG_BAD_CONF; + goto exit; + } + + if ( !dnIsSuffix ( &uri->ndn, &be->be_nsuffix[0] ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "dn <%s> is not a suffix of backend base dn <%s>", + uri->dn.bv_val, + be->be_nsuffix[0].bv_val ); + rc = ARG_BAD_CONF; + goto exit; + } + + if ( BER_BVISNULL( &be->be_rootndn ) || BER_BVISEMPTY( &be->be_rootndn ) ) { + Debug( LDAP_DEBUG_ANY, + "slapo-unique needs a rootdn; " + "backend <%s> has none, YMMV.\n", + be->be_nsuffix[0].bv_val, 0, 0 ); + } + } + + attr_str = url_desc->lud_attrs; + if ( attr_str ) { + for ( i=0; attr_str[i]; ++i ) { + unique_attrs * attr; + ad = NULL; + if ( slap_str2ad ( attr_str[i], &ad, &text ) + == LDAP_SUCCESS) { + attr = ch_calloc ( 1, + sizeof ( unique_attrs ) ); + attr->attr = ad; + attr->next = uri->attrs; + uri->attrs = attr; + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unique: attribute: %s: %s", + attr_str[i], text ); + rc = ARG_BAD_CONF; + goto exit; + } + } + } + + uri->scope = url_desc->lud_scope; + if ( !uri->scope ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unique: uri with base scope will always be unique"); + rc = ARG_BAD_CONF; + goto exit; + } + + if (url_desc->lud_filter) { + char *ptr; + uri->f = str2filter( url_desc->lud_filter ); + if ( !uri->f ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unique: bad filter"); + rc = ARG_BAD_CONF; + goto exit; + } + /* make sure the strfilter is in normal form (ITS#5581) */ + filter2bv( uri->f, &uri->filter ); + ptr = strstr( uri->filter.bv_val, "(?=" /*)*/ ); + if ( ptr != NULL && ptr <= ( uri->filter.bv_val - STRLENOF( "(?=" /*)*/ ) + uri->filter.bv_len ) ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unique: bad filter"); + rc = ARG_BAD_CONF; + goto exit; + } + } +exit: + uri->next = *urip; + *urip = uri; + if ( rc ) { + Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg, 0 ); + unique_free_domain_uri ( uri ); + *urip = NULL; + } + return rc; +} + +static int +unique_new_domain_uri_basic ( unique_domain_uri **urip, + ConfigArgs *c ) +{ + LDAPURLDesc *url_desc = NULL; + int rc; + + rc = ldap_url_parse ( UNIQUE_DEFAULT_URI, &url_desc ); + if ( rc ) return rc; + rc = unique_new_domain_uri ( urip, url_desc, c ); + ldap_free_urldesc ( url_desc ); + return rc; +} + +/* if *domain is non-null, it's pushed down the stack. + * note that the entire stack is freed if there is an error, + * so build added domains in a separate stack before adding them + * + * domain_specs look like + * + * [strict ][ignore ]uri[[ uri]...] + * e.g. "ldap:///ou=foo,o=bar?uid?sub ldap:///ou=baz,o=bar?uid?sub" + * "strict ldap:///ou=accounts,o=bar?uid,uidNumber?one" + * etc + * + * so finally strictness is per-domain + * but so is ignore-state, and that would be better as a per-url thing + */ +static int +unique_new_domain ( unique_domain **domainp, + char *domain_spec, + ConfigArgs *c ) +{ + char *uri_start; + int rc = LDAP_SUCCESS; + int uri_err = 0; + unique_domain * domain; + LDAPURLDesc *url_desc, *url_descs = NULL; + + Debug(LDAP_DEBUG_TRACE, "==> unique_new_domain <%s>\n", + domain_spec, 0, 0); + + domain = ch_calloc ( 1, sizeof (unique_domain) ); + ber_str2bv( domain_spec, 0, 1, &domain->domain_spec ); + + uri_start = domain_spec; + if ( strncasecmp ( uri_start, "ignore ", + STRLENOF( "ignore " ) ) == 0 ) { + domain->ignore = 1; + uri_start += STRLENOF( "ignore " ); + } + if ( strncasecmp ( uri_start, "strict ", + STRLENOF( "strict " ) ) == 0 ) { + domain->strict = 1; + uri_start += STRLENOF( "strict " ); + if ( !domain->ignore + && strncasecmp ( uri_start, "ignore ", + STRLENOF( "ignore " ) ) == 0 ) { + domain->ignore = 1; + uri_start += STRLENOF( "ignore " ); + } + } + rc = ldap_url_parselist_ext ( &url_descs, uri_start, " ", 0 ); + if ( rc ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> invalid ldap urilist", + uri_start ); + rc = ARG_BAD_CONF; + goto exit; + } + + for ( url_desc = url_descs; + url_desc; + url_desc = url_desc->lud_next ) { + rc = unique_new_domain_uri ( &domain->uri, + url_desc, + c ); + if ( rc ) { + rc = ARG_BAD_CONF; + uri_err = 1; + goto exit; + } + } + +exit: + if ( url_descs ) ldap_free_urldesc ( url_descs ); + domain->next = *domainp; + *domainp = domain; + if ( rc ) { + Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg, 0 ); + unique_free_domain ( domain ); + *domainp = NULL; + } + return rc; +} + +static int +unique_cf_base( ConfigArgs *c ) +{ + BackendDB *be = (BackendDB *)c->be; + slap_overinst *on = (slap_overinst *)c->bi; + unique_data *private = (unique_data *) on->on_bi.bi_private; + unique_domain *domains = private->domains; + unique_domain *legacy = private->legacy; + int rc = ARG_BAD_CONF; + + switch ( c->op ) { + case SLAP_CONFIG_EMIT: + rc = 0; + if ( legacy && legacy->uri && legacy->uri->dn.bv_val ) { + rc = value_add_one ( &c->rvalue_vals, + &legacy->uri->dn ); + if ( rc ) return rc; + rc = value_add_one ( &c->rvalue_nvals, + &legacy->uri->ndn ); + if ( rc ) return rc; + } + break; + case LDAP_MOD_DELETE: + assert ( legacy && legacy->uri && legacy->uri->dn.bv_val ); + rc = 0; + ch_free ( legacy->uri->dn.bv_val ); + ch_free ( legacy->uri->ndn.bv_val ); + BER_BVZERO( &legacy->uri->dn ); + BER_BVZERO( &legacy->uri->ndn ); + if ( !legacy->uri->attrs ) { + unique_free_domain_uri ( legacy->uri ); + legacy->uri = NULL; + } + if ( !legacy->uri && !private->legacy_strict_set ) { + unique_free_domain ( legacy ); + private->legacy = legacy = NULL; + } + break; + case LDAP_MOD_ADD: + case SLAP_CONFIG_ADD: + if ( domains ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "cannot set legacy attrs when URIs are present" ); + Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n", + c->cr_msg, NULL, NULL ); + rc = ARG_BAD_CONF; + break; + } + if ( be->be_nsuffix == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "suffix must be set" ); + Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n", + c->cr_msg, NULL, NULL ); + rc = ARG_BAD_CONF; + break; + } + if ( !dnIsSuffix ( &c->value_ndn, + &be->be_nsuffix[0] ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "dn is not a suffix of backend base" ); + Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n", + c->cr_msg, NULL, NULL ); + rc = ARG_BAD_CONF; + break; + } + if ( !legacy ) { + unique_new_domain ( &private->legacy, + UNIQUE_DEFAULT_URI, + c ); + legacy = private->legacy; + } + if ( !legacy->uri ) + unique_new_domain_uri_basic ( &legacy->uri, c ); + ch_free ( legacy->uri->dn.bv_val ); + ch_free ( legacy->uri->ndn.bv_val ); + legacy->uri->dn = c->value_dn; + legacy->uri->ndn = c->value_ndn; + rc = 0; + break; + default: + abort(); + } + + if ( rc ) { + ch_free( c->value_dn.bv_val ); + BER_BVZERO( &c->value_dn ); + ch_free( c->value_ndn.bv_val ); + BER_BVZERO( &c->value_ndn ); + } + + return rc; +} + +static int +unique_cf_attrs( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + unique_data *private = (unique_data *) on->on_bi.bi_private; + unique_domain *domains = private->domains; + unique_domain *legacy = private->legacy; + unique_attrs *new_attrs = NULL; + unique_attrs *attr, *next_attr, *reverse_attrs; + unique_attrs **attrp; + int rc = ARG_BAD_CONF; + int i; + + switch ( c->op ) { + case SLAP_CONFIG_EMIT: + if ( legacy + && (c->type == UNIQUE_IGNORE) == legacy->ignore + && legacy->uri ) + for ( attr = legacy->uri->attrs; + attr; + attr = attr->next ) + value_add_one( &c->rvalue_vals, + &attr->attr->ad_cname ); + rc = 0; + break; + case LDAP_MOD_DELETE: + if ( legacy + && (c->type == UNIQUE_IGNORE) == legacy->ignore + && legacy->uri + && legacy->uri->attrs) { + if ( c->valx < 0 ) { /* delete all */ + for ( attr = legacy->uri->attrs; + attr; + attr = next_attr ) { + next_attr = attr->next; + ch_free ( attr ); + } + legacy->uri->attrs = NULL; + } else { /* delete by index */ + attrp = &legacy->uri->attrs; + for ( i=0; i < c->valx; ++i ) + attrp = &(*attrp)->next; + attr = *attrp; + *attrp = attr->next; + ch_free (attr); + } + if ( !legacy->uri->attrs + && !legacy->uri->dn.bv_val ) { + unique_free_domain_uri ( legacy->uri ); + legacy->uri = NULL; + } + if ( !legacy->uri && !private->legacy_strict_set ) { + unique_free_domain ( legacy ); + private->legacy = legacy = NULL; + } + } + rc = 0; + break; + case LDAP_MOD_ADD: + case SLAP_CONFIG_ADD: + if ( domains ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "cannot set legacy attrs when URIs are present" ); + Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n", + c->cr_msg, NULL, NULL ); + rc = ARG_BAD_CONF; + break; + } + if ( legacy + && legacy->uri + && legacy->uri->attrs + && (c->type == UNIQUE_IGNORE) != legacy->ignore ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "cannot set both attrs and ignore-attrs" ); + Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n", + c->cr_msg, NULL, NULL ); + rc = ARG_BAD_CONF; + break; + } + if ( !legacy ) { + unique_new_domain ( &private->legacy, + UNIQUE_DEFAULT_URI, + c ); + legacy = private->legacy; + } + if ( !legacy->uri ) + unique_new_domain_uri_basic ( &legacy->uri, c ); + rc = 0; + for ( i=1; c->argv[i]; ++i ) { + AttributeDescription * ad = NULL; + const char * text; + if ( slap_str2ad ( c->argv[i], &ad, &text ) + == LDAP_SUCCESS) { + + attr = ch_calloc ( 1, + sizeof ( unique_attrs ) ); + attr->attr = ad; + attr->next = new_attrs; + new_attrs = attr; + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unique: attribute: %s: %s", + c->argv[i], text ); + for ( attr = new_attrs; + attr; + attr=next_attr ) { + next_attr = attr->next; + ch_free ( attr ); + } + rc = ARG_BAD_CONF; + break; + } + } + if ( rc ) break; + + /* (nconc legacy->uri->attrs (nreverse new_attrs)) */ + reverse_attrs = NULL; + for ( attr = new_attrs; + attr; + attr = next_attr ) { + next_attr = attr->next; + attr->next = reverse_attrs; + reverse_attrs = attr; + } + for ( attrp = &legacy->uri->attrs; + *attrp; + attrp = &(*attrp)->next ) ; + *attrp = reverse_attrs; + + legacy->ignore = ( c->type == UNIQUE_IGNORE ); + break; + default: + abort(); + } + + if ( rc ) { + Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg, 0 ); + } + return rc; +} + +static int +unique_cf_strict( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + unique_data *private = (unique_data *) on->on_bi.bi_private; + unique_domain *domains = private->domains; + unique_domain *legacy = private->legacy; + int rc = ARG_BAD_CONF; + + switch ( c->op ) { + case SLAP_CONFIG_EMIT: + /* We process the boolean manually instead of using + * ARG_ON_OFF so that we can three-state it; + * olcUniqueStrict is either TRUE, FALSE, or missing, + * and missing is necessary to add olcUniqueURIs... + */ + if ( private->legacy_strict_set ) { + struct berval bv; + bv.bv_val = legacy->strict ? "TRUE" : "FALSE"; + bv.bv_len = legacy->strict ? + STRLENOF("TRUE") : + STRLENOF("FALSE"); + value_add_one ( &c->rvalue_vals, &bv ); + } + rc = 0; + break; + case LDAP_MOD_DELETE: + if ( legacy ) { + legacy->strict = 0; + if ( ! legacy->uri ) { + unique_free_domain ( legacy ); + private->legacy = NULL; + } + } + private->legacy_strict_set = 0; + rc = 0; + break; + case LDAP_MOD_ADD: + case SLAP_CONFIG_ADD: + if ( domains ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "cannot set legacy attrs when URIs are present" ); + Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n", + c->cr_msg, NULL, NULL ); + rc = ARG_BAD_CONF; + break; + } + if ( ! legacy ) { + unique_new_domain ( &private->legacy, + UNIQUE_DEFAULT_URI, + c ); + legacy = private->legacy; + } + /* ... not using ARG_ON_OFF makes this necessary too */ + assert ( c->argc == 2 ); + legacy->strict = (strcasecmp ( c->argv[1], "TRUE" ) == 0); + private->legacy_strict_set = 1; + rc = 0; + break; + default: + abort(); + } + + return rc; +} + +static int +unique_cf_uri( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + unique_data *private = (unique_data *) on->on_bi.bi_private; + unique_domain *domains = private->domains; + unique_domain *legacy = private->legacy; + unique_domain *domain = NULL, **domainp = NULL; + int rc = ARG_BAD_CONF; + int i; + + switch ( c->op ) { + case SLAP_CONFIG_EMIT: + for ( domain = domains; + domain; + domain = domain->next ) { + rc = value_add_one ( &c->rvalue_vals, + &domain->domain_spec ); + if ( rc ) break; + } + break; + case LDAP_MOD_DELETE: + if ( c->valx < 0 ) { /* delete them all! */ + unique_free_domain ( domains ); + private->domains = NULL; + } else { /* delete just one */ + domainp = &private->domains; + for ( i=0; i < c->valx && *domainp; ++i ) + domainp = &(*domainp)->next; + + /* If *domainp is null, we walked off the end + * of the list. This happens when back-config + * and the overlay are out-of-sync, like when + * rejecting changes before ITS#4752 gets + * fixed. + * + * This should never happen, but will appear + * if you backport this version of + * slapo-unique without the config-undo fixes + * + * test024 Will hit this case in such a + * situation. + */ + assert (*domainp != NULL); + + domain = *domainp; + *domainp = domain->next; + domain->next = NULL; + unique_free_domain ( domain ); + } + rc = 0; + break; + + case SLAP_CONFIG_ADD: /* fallthrough */ + case LDAP_MOD_ADD: + if ( legacy ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "cannot set Uri when legacy attrs are present" ); + Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n", + c->cr_msg, NULL, NULL ); + rc = ARG_BAD_CONF; + break; + } + rc = 0; + if ( c->line ) rc = unique_new_domain ( &domain, c->line, c ); + else rc = unique_new_domain ( &domain, c->argv[1], c ); + if ( rc ) break; + assert ( domain->next == NULL ); + for ( domainp = &private->domains; + *domainp; + domainp = &(*domainp)->next ) ; + *domainp = domain; + + break; + + default: + abort (); + } + + return rc; +} + +/* +** allocate new unique_data; +** initialize, copy basedn; +** store in on_bi.bi_private; +** +*/ + +static int +unique_db_init( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + unique_data **privatep = (unique_data **) &on->on_bi.bi_private; + + Debug(LDAP_DEBUG_TRACE, "==> unique_db_init\n", 0, 0, 0); + + *privatep = ch_calloc ( 1, sizeof ( unique_data ) ); + + return 0; +} + +static int +unique_db_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + unique_data **privatep = (unique_data **) &on->on_bi.bi_private; + unique_data *private = *privatep; + + Debug(LDAP_DEBUG_TRACE, "==> unique_db_destroy\n", 0, 0, 0); + + if ( private ) { + unique_domain *domains = private->domains; + unique_domain *legacy = private->legacy; + + unique_free_domain ( domains ); + unique_free_domain ( legacy ); + ch_free ( private ); + *privatep = NULL; + } + + return 0; +} + + +/* +** search callback +** if this is a REP_SEARCH, count++; +** +*/ + +static int count_attr_cb( + Operation *op, + SlapReply *rs +) +{ + unique_counter *uc; + + /* because you never know */ + if(!op || !rs) return(0); + + /* Only search entries are interesting */ + if(rs->sr_type != REP_SEARCH) return(0); + + uc = op->o_callback->sc_private; + + /* Ignore the current entry */ + if ( dn_match( uc->ndn, &rs->sr_entry->e_nname )) return(0); + + Debug(LDAP_DEBUG_TRACE, "==> count_attr_cb <%s>\n", + rs->sr_entry ? rs->sr_entry->e_name.bv_val : "UNKNOWN_DN", 0, 0); + + uc->count++; + + return(0); +} + +/* count the length of one attribute ad + * (and all of its values b) + * in the proposed filter + */ +static int +count_filter_len( + unique_domain *domain, + unique_domain_uri *uri, + AttributeDescription *ad, + BerVarray b +) +{ + unique_attrs *attr; + int i; + int ks = 0; + + while ( !is_at_operational( ad->ad_type ) ) { + if ( uri->attrs ) { + for ( attr = uri->attrs; attr; attr = attr->next ) { + if ( ad == attr->attr ) { + break; + } + } + if ( ( domain->ignore && attr ) + || (!domain->ignore && !attr )) { + break; + } + } + if ( b && b[0].bv_val ) { + for (i = 0; b[i].bv_val; i++ ) { + /* note: make room for filter escaping... */ + ks += ( 3 * b[i].bv_len ) + ad->ad_cname.bv_len + STRLENOF( "(=)" ); + } + } else if ( domain->strict ) { + ks += ad->ad_cname.bv_len + STRLENOF( "(=*)" ); /* (attr=*) */ + } + break; + } + + return ks; +} + +static char * +build_filter( + unique_domain *domain, + unique_domain_uri *uri, + AttributeDescription *ad, + BerVarray b, + char *kp, + int ks, + void *ctx +) +{ + unique_attrs *attr; + int i; + + while ( !is_at_operational( ad->ad_type ) ) { + if ( uri->attrs ) { + for ( attr = uri->attrs; attr; attr = attr->next ) { + if ( ad == attr->attr ) { + break; + } + } + if ( ( domain->ignore && attr ) + || (!domain->ignore && !attr )) { + break; + } + } + if ( b && b[0].bv_val ) { + for ( i = 0; b[i].bv_val; i++ ) { + struct berval bv; + int len; + + ldap_bv2escaped_filter_value_x( &b[i], &bv, 1, ctx ); + if (!b[i].bv_len) + bv.bv_val = b[i].bv_val; + len = snprintf( kp, ks, "(%s=%s)", ad->ad_cname.bv_val, bv.bv_val ); + assert( len >= 0 && len < ks ); + kp += len; + if ( bv.bv_val != b[i].bv_val ) { + ber_memfree_x( bv.bv_val, ctx ); + } + } + } else if ( domain->strict ) { + int len; + len = snprintf( kp, ks, "(%s=*)", ad->ad_cname.bv_val ); + assert( len >= 0 && len < ks ); + kp += len; + } + break; + } + return kp; +} + +static int +unique_search( + Operation *op, + Operation *nop, + struct berval * dn, + int scope, + SlapReply *rs, + struct berval *key +) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + SlapReply nrs = { REP_RESULT }; + slap_callback cb = { NULL, NULL, NULL, NULL }; /* XXX */ + unique_counter uq = { NULL, 0 }; + int rc; + + Debug(LDAP_DEBUG_TRACE, "==> unique_search %s\n", key->bv_val, 0, 0); + + nop->ors_filter = str2filter_x(nop, key->bv_val); + if(nop->ors_filter == NULL) { + op->o_bd->bd_info = (BackendInfo *) on->on_info; + send_ldap_error(op, rs, LDAP_OTHER, + "unique_search invalid filter"); + return(rs->sr_err); + } + + nop->ors_filterstr = *key; + + cb.sc_response = (slap_response*)count_attr_cb; + cb.sc_private = &uq; + nop->o_callback = &cb; + nop->o_tag = LDAP_REQ_SEARCH; + nop->ors_scope = scope; + nop->ors_deref = LDAP_DEREF_NEVER; + nop->ors_limit = NULL; + nop->ors_slimit = SLAP_NO_LIMIT; + nop->ors_tlimit = SLAP_NO_LIMIT; + nop->ors_attrs = slap_anlist_no_attrs; + nop->ors_attrsonly = 1; + + uq.ndn = &op->o_req_ndn; + + nop->o_req_ndn = *dn; + nop->o_ndn = op->o_bd->be_rootndn; + + nop->o_bd = on->on_info->oi_origdb; + rc = nop->o_bd->be_search(nop, &nrs); + filter_free_x(nop, nop->ors_filter, 1); + op->o_tmpfree( key->bv_val, op->o_tmpmemctx ); + + if(rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_OBJECT) { + op->o_bd->bd_info = (BackendInfo *) on->on_info; + send_ldap_error(op, rs, rc, "unique_search failed"); + return(rs->sr_err); + } + + Debug(LDAP_DEBUG_TRACE, "=> unique_search found %d records\n", uq.count, 0, 0); + + if(uq.count) { + op->o_bd->bd_info = (BackendInfo *) on->on_info; + send_ldap_error(op, rs, LDAP_CONSTRAINT_VIOLATION, + "some attributes not unique"); + return(rs->sr_err); + } + + return(SLAP_CB_CONTINUE); +} + +static int +unique_add( + Operation *op, + SlapReply *rs +) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + unique_data *private = (unique_data *) on->on_bi.bi_private; + unique_domain *domains = private->domains; + unique_domain *legacy = private->legacy; + unique_domain *domain; + Operation nop = *op; + Attribute *a; + char *key, *kp; + struct berval bvkey; + int rc = SLAP_CB_CONTINUE; + + Debug(LDAP_DEBUG_TRACE, "==> unique_add <%s>\n", + op->o_req_dn.bv_val, 0, 0); + + /* skip the checks if the operation has manageDsaIt control in it + * (for replication) */ + if ( op->o_managedsait > SLAP_CONTROL_IGNORED + && access_allowed ( op, op->ora_e, + slap_schema.si_ad_entry, NULL, + ACL_MANAGE, NULL ) ) { + Debug(LDAP_DEBUG_TRACE, "unique_add: administrative bypass, skipping\n", 0, 0, 0); + return rc; + } + + for ( domain = legacy ? legacy : domains; + domain; + domain = domain->next ) + { + unique_domain_uri *uri; + + for ( uri = domain->uri; + uri; + uri = uri->next ) + { + int len; + int ks = 0; + + if ( uri->ndn.bv_val + && !dnIsSuffix( &op->o_req_ndn, &uri->ndn )) + continue; + + if ( uri->f ) { + if ( test_filter( NULL, op->ora_e, uri->f ) + == LDAP_COMPARE_FALSE ) + { + Debug( LDAP_DEBUG_TRACE, + "==> unique_add_skip<%s>\n", + op->o_req_dn.bv_val, 0, 0 ); + continue; + } + } + + if(!(a = op->ora_e->e_attrs)) { + op->o_bd->bd_info = (BackendInfo *) on->on_info; + send_ldap_error(op, rs, LDAP_INVALID_SYNTAX, + "unique_add() got null op.ora_e.e_attrs"); + rc = rs->sr_err; + break; + + } else { + for(; a; a = a->a_next) { + ks += count_filter_len ( domain, + uri, + a->a_desc, + a->a_vals); + } + } + + /* skip this domain-uri if it isn't involved */ + if ( !ks ) continue; + + /* terminating NUL */ + ks += sizeof("(|)"); + + if ( uri->filter.bv_val && uri->filter.bv_len ) + ks += uri->filter.bv_len + STRLENOF ("(&)"); + kp = key = op->o_tmpalloc(ks, op->o_tmpmemctx); + + if ( uri->filter.bv_val && uri->filter.bv_len ) { + len = snprintf (kp, ks, "(&%s", uri->filter.bv_val); + assert( len >= 0 && len < ks ); + kp += len; + } + len = snprintf(kp, ks - (kp - key), "(|"); + assert( len >= 0 && len < ks - (kp - key) ); + kp += len; + + for(a = op->ora_e->e_attrs; a; a = a->a_next) + kp = build_filter(domain, + uri, + a->a_desc, + a->a_vals, + kp, + ks - ( kp - key ), + op->o_tmpmemctx); + + len = snprintf(kp, ks - (kp - key), ")"); + assert( len >= 0 && len < ks - (kp - key) ); + kp += len; + if ( uri->filter.bv_val && uri->filter.bv_len ) { + len = snprintf(kp, ks - (kp - key), ")"); + assert( len >= 0 && len < ks - (kp - key) ); + kp += len; + } + bvkey.bv_val = key; + bvkey.bv_len = kp - key; + + rc = unique_search ( op, + &nop, + uri->ndn.bv_val ? + &uri->ndn : + &op->o_bd->be_nsuffix[0], + uri->scope, + rs, + &bvkey); + + if ( rc != SLAP_CB_CONTINUE ) break; + } + if ( rc != SLAP_CB_CONTINUE ) break; + } + + return rc; +} + + +static int +unique_modify( + Operation *op, + SlapReply *rs +) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + unique_data *private = (unique_data *) on->on_bi.bi_private; + unique_domain *domains = private->domains; + unique_domain *legacy = private->legacy; + unique_domain *domain; + Operation nop = *op; + Modifications *m; + Entry *e = NULL; + char *key, *kp; + struct berval bvkey; + int rc = SLAP_CB_CONTINUE; + + Debug(LDAP_DEBUG_TRACE, "==> unique_modify <%s>\n", + op->o_req_dn.bv_val, 0, 0); + + if ( !op->orm_modlist ) { + Debug(LDAP_DEBUG_TRACE, "unique_modify: got empty modify op\n", 0, 0, 0); + return rc; + } + + /* skip the checks if the operation has manageDsaIt control in it + * (for replication) */ + if ( op->o_managedsait > SLAP_CONTROL_IGNORED + && overlay_entry_get_ov(op, &op->o_req_ndn, NULL, NULL, 0, &e, on) == LDAP_SUCCESS + && e + && access_allowed ( op, e, + slap_schema.si_ad_entry, NULL, + ACL_MANAGE, NULL ) ) { + Debug(LDAP_DEBUG_TRACE, "unique_modify: administrative bypass, skipping\n", 0, 0, 0); + overlay_entry_release_ov( op, e, 0, on ); + return rc; + } + if ( e ) { + overlay_entry_release_ov( op, e, 0, on ); + } + + for ( domain = legacy ? legacy : domains; + domain; + domain = domain->next ) + { + unique_domain_uri *uri; + + for ( uri = domain->uri; + uri; + uri = uri->next ) + { + int len; + int ks = 0; + + if ( uri->ndn.bv_val + && !dnIsSuffix( &op->o_req_ndn, &uri->ndn )) + continue; + + for ( m = op->orm_modlist; m; m = m->sml_next) + if ( (m->sml_op & LDAP_MOD_OP) + != LDAP_MOD_DELETE ) + ks += count_filter_len + ( domain, + uri, + m->sml_desc, + m->sml_values); + + /* skip this domain-uri if it isn't involved */ + if ( !ks ) continue; + + /* terminating NUL */ + ks += sizeof("(|)"); + + if ( uri->filter.bv_val && uri->filter.bv_len ) + ks += uri->filter.bv_len + STRLENOF ("(&)"); + kp = key = op->o_tmpalloc(ks, op->o_tmpmemctx); + + if ( uri->filter.bv_val && uri->filter.bv_len ) { + len = snprintf(kp, ks, "(&%s", uri->filter.bv_val); + assert( len >= 0 && len < ks ); + kp += len; + } + len = snprintf(kp, ks - (kp - key), "(|"); + assert( len >= 0 && len < ks - (kp - key) ); + kp += len; + + for(m = op->orm_modlist; m; m = m->sml_next) + if ( (m->sml_op & LDAP_MOD_OP) + != LDAP_MOD_DELETE ) + kp = build_filter ( domain, + uri, + m->sml_desc, + m->sml_values, + kp, + ks - (kp - key), + op->o_tmpmemctx ); + + len = snprintf(kp, ks - (kp - key), ")"); + assert( len >= 0 && len < ks - (kp - key) ); + kp += len; + if ( uri->filter.bv_val && uri->filter.bv_len ) { + len = snprintf (kp, ks - (kp - key), ")"); + assert( len >= 0 && len < ks - (kp - key) ); + kp += len; + } + bvkey.bv_val = key; + bvkey.bv_len = kp - key; + + rc = unique_search ( op, + &nop, + uri->ndn.bv_val ? + &uri->ndn : + &op->o_bd->be_nsuffix[0], + uri->scope, + rs, + &bvkey); + + if ( rc != SLAP_CB_CONTINUE ) break; + } + if ( rc != SLAP_CB_CONTINUE ) break; + } + + return rc; +} + + +static int +unique_modrdn( + Operation *op, + SlapReply *rs +) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + unique_data *private = (unique_data *) on->on_bi.bi_private; + unique_domain *domains = private->domains; + unique_domain *legacy = private->legacy; + unique_domain *domain; + Operation nop = *op; + Entry *e = NULL; + char *key, *kp; + struct berval bvkey; + LDAPRDN newrdn; + struct berval bv[2]; + int rc = SLAP_CB_CONTINUE; + + Debug(LDAP_DEBUG_TRACE, "==> unique_modrdn <%s> <%s>\n", + op->o_req_dn.bv_val, op->orr_newrdn.bv_val, 0); + + /* skip the checks if the operation has manageDsaIt control in it + * (for replication) */ + if ( op->o_managedsait > SLAP_CONTROL_IGNORED + && overlay_entry_get_ov(op, &op->o_req_ndn, NULL, NULL, 0, &e, on) == LDAP_SUCCESS + && e + && access_allowed ( op, e, + slap_schema.si_ad_entry, NULL, + ACL_MANAGE, NULL ) ) { + Debug(LDAP_DEBUG_TRACE, "unique_modrdn: administrative bypass, skipping\n", 0, 0, 0); + overlay_entry_release_ov( op, e, 0, on ); + return rc; + } + if ( e ) { + overlay_entry_release_ov( op, e, 0, on ); + } + + for ( domain = legacy ? legacy : domains; + domain; + domain = domain->next ) + { + unique_domain_uri *uri; + + for ( uri = domain->uri; + uri; + uri = uri->next ) + { + int i, len; + int ks = 0; + + if ( uri->ndn.bv_val + && !dnIsSuffix( &op->o_req_ndn, &uri->ndn ) + && (!op->orr_nnewSup + || !dnIsSuffix( op->orr_nnewSup, &uri->ndn ))) + continue; + + if ( ldap_bv2rdn_x ( &op->oq_modrdn.rs_newrdn, + &newrdn, + (char **)&rs->sr_text, + LDAP_DN_FORMAT_LDAP, + op->o_tmpmemctx ) ) { + op->o_bd->bd_info = (BackendInfo *) on->on_info; + send_ldap_error(op, rs, LDAP_INVALID_SYNTAX, + "unknown type(s) used in RDN"); + rc = rs->sr_err; + break; + } + + rc = SLAP_CB_CONTINUE; + for ( i=0; newrdn[i]; i++) { + AttributeDescription *ad = NULL; + if ( slap_bv2ad( &newrdn[i]->la_attr, &ad, &rs->sr_text )) { + ldap_rdnfree_x( newrdn, op->o_tmpmemctx ); + rs->sr_err = LDAP_INVALID_SYNTAX; + send_ldap_result( op, rs ); + rc = rs->sr_err; + break; + } + newrdn[i]->la_private = ad; + } + if ( rc != SLAP_CB_CONTINUE ) break; + + bv[1].bv_val = NULL; + bv[1].bv_len = 0; + + for ( i=0; newrdn[i]; i++ ) { + bv[0] = newrdn[i]->la_value; + ks += count_filter_len ( domain, + uri, + newrdn[i]->la_private, + bv); + } + + /* skip this domain if it isn't involved */ + if ( !ks ) continue; + + /* terminating NUL */ + ks += sizeof("(|)"); + + if ( uri->filter.bv_val && uri->filter.bv_len ) + ks += uri->filter.bv_len + STRLENOF ("(&)"); + kp = key = op->o_tmpalloc(ks, op->o_tmpmemctx); + + if ( uri->filter.bv_val && uri->filter.bv_len ) { + len = snprintf(kp, ks, "(&%s", uri->filter.bv_val); + assert( len >= 0 && len < ks ); + kp += len; + } + len = snprintf(kp, ks - (kp - key), "(|"); + assert( len >= 0 && len < ks - (kp - key) ); + kp += len; + + for ( i=0; newrdn[i]; i++) { + bv[0] = newrdn[i]->la_value; + kp = build_filter ( domain, + uri, + newrdn[i]->la_private, + bv, + kp, + ks - (kp - key ), + op->o_tmpmemctx); + } + + len = snprintf(kp, ks - (kp - key), ")"); + assert( len >= 0 && len < ks - (kp - key) ); + kp += len; + if ( uri->filter.bv_val && uri->filter.bv_len ) { + len = snprintf (kp, ks - (kp - key), ")"); + assert( len >= 0 && len < ks - (kp - key) ); + kp += len; + } + bvkey.bv_val = key; + bvkey.bv_len = kp - key; + + rc = unique_search ( op, + &nop, + uri->ndn.bv_val ? + &uri->ndn : + &op->o_bd->be_nsuffix[0], + uri->scope, + rs, + &bvkey); + + if ( rc != SLAP_CB_CONTINUE ) break; + } + if ( rc != SLAP_CB_CONTINUE ) break; + } + + return rc; +} + +/* +** init_module is last so the symbols resolve "for free" -- +** it expects to be called automagically during dynamic module initialization +*/ + +int +unique_initialize() +{ + int rc; + + /* statically declared just after the #includes at top */ + memset (&unique, 0, sizeof(unique)); + + unique.on_bi.bi_type = "unique"; + unique.on_bi.bi_db_init = unique_db_init; + unique.on_bi.bi_db_destroy = unique_db_destroy; + unique.on_bi.bi_op_add = unique_add; + unique.on_bi.bi_op_modify = unique_modify; + unique.on_bi.bi_op_modrdn = unique_modrdn; + + unique.on_bi.bi_cf_ocs = uniqueocs; + rc = config_register_schema( uniquecfg, uniqueocs ); + if ( rc ) return rc; + + return(overlay_register(&unique)); +} + +#if SLAPD_OVER_UNIQUE == SLAPD_MOD_DYNAMIC && defined(PIC) +int init_module(int argc, char *argv[]) { + return unique_initialize(); +} +#endif + +#endif /* SLAPD_OVER_UNIQUE */ diff --git a/servers/slapd/overlays/valsort.c b/servers/slapd/overlays/valsort.c new file mode 100644 index 0000000..eb28791 --- /dev/null +++ b/servers/slapd/overlays/valsort.c @@ -0,0 +1,580 @@ +/* valsort.c - sort attribute values */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2021 The OpenLDAP Foundation. + * Portions copyright 2005 Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. This work was sponsored by Stanford University. + */ + +/* + * This overlay sorts the values of multi-valued attributes when returning + * them in a search response. + */ +#include "portable.h" + +#ifdef SLAPD_OVER_VALSORT + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/ctype.h> + +#include "slap.h" +#include "config.h" +#include "lutil.h" + +#define VALSORT_ASCEND 0 +#define VALSORT_DESCEND 1 + +#define VALSORT_ALPHA 2 +#define VALSORT_NUMERIC 4 + +#define VALSORT_WEIGHTED 8 + +typedef struct valsort_info { + struct valsort_info *vi_next; + struct berval vi_dn; + AttributeDescription *vi_ad; + slap_mask_t vi_sort; +} valsort_info; + +static int valsort_cid; + +static ConfigDriver valsort_cf_func; + +static ConfigTable valsort_cfats[] = { + { "valsort-attr", "attribute> <dn> <sort-type", 4, 5, 0, ARG_MAGIC, + valsort_cf_func, "( OLcfgOvAt:5.1 NAME 'olcValSortAttr' " + "DESC 'Sorting rule for attribute under given DN' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { NULL } +}; + +static ConfigOCs valsort_cfocs[] = { + { "( OLcfgOvOc:5.1 " + "NAME 'olcValSortConfig' " + "DESC 'Value Sorting configuration' " + "SUP olcOverlayConfig " + "MUST olcValSortAttr )", + Cft_Overlay, valsort_cfats }, + { NULL } +}; + +static slap_verbmasks sorts[] = { + { BER_BVC("alpha-ascend"), VALSORT_ASCEND|VALSORT_ALPHA }, + { BER_BVC("alpha-descend"), VALSORT_DESCEND|VALSORT_ALPHA }, + { BER_BVC("numeric-ascend"), VALSORT_ASCEND|VALSORT_NUMERIC }, + { BER_BVC("numeric-descend"), VALSORT_DESCEND|VALSORT_NUMERIC }, + { BER_BVC("weighted"), VALSORT_WEIGHTED }, + { BER_BVNULL, 0 } +}; + +static Syntax *syn_numericString; + +static int +valsort_cf_func(ConfigArgs *c) { + slap_overinst *on = (slap_overinst *)c->bi; + valsort_info vitmp, *vi; + const char *text = NULL; + int i, is_numeric; + struct berval bv = BER_BVNULL; + + if ( c->op == SLAP_CONFIG_EMIT ) { + for ( vi = on->on_bi.bi_private; vi; vi = vi->vi_next ) { + struct berval bv2 = BER_BVNULL, bvret; + char *ptr; + int len; + + len = vi->vi_ad->ad_cname.bv_len + 1 + vi->vi_dn.bv_len + 2; + i = vi->vi_sort; + if ( i & VALSORT_WEIGHTED ) { + enum_to_verb( sorts, VALSORT_WEIGHTED, &bv2 ); + len += bv2.bv_len + 1; + i ^= VALSORT_WEIGHTED; + } + if ( i ) { + enum_to_verb( sorts, i, &bv ); + len += bv.bv_len + 1; + } + bvret.bv_val = ch_malloc( len+1 ); + bvret.bv_len = len; + + ptr = lutil_strcopy( bvret.bv_val, vi->vi_ad->ad_cname.bv_val ); + *ptr++ = ' '; + *ptr++ = '"'; + ptr = lutil_strcopy( ptr, vi->vi_dn.bv_val ); + *ptr++ = '"'; + if ( vi->vi_sort & VALSORT_WEIGHTED ) { + *ptr++ = ' '; + ptr = lutil_strcopy( ptr, bv2.bv_val ); + } + if ( i ) { + *ptr++ = ' '; + strcpy( ptr, bv.bv_val ); + } + ber_bvarray_add( &c->rvalue_vals, &bvret ); + } + i = ( c->rvalue_vals != NULL ) ? 0 : 1; + return i; + } else if ( c->op == LDAP_MOD_DELETE ) { + if ( c->valx < 0 ) { + for ( vi = on->on_bi.bi_private; vi; vi = on->on_bi.bi_private ) { + on->on_bi.bi_private = vi->vi_next; + ch_free( vi->vi_dn.bv_val ); + ch_free( vi ); + } + } else { + valsort_info **prev; + + for (i=0, prev = (valsort_info **)&on->on_bi.bi_private, + vi = *prev; vi && i<c->valx; + prev = &vi->vi_next, vi = vi->vi_next, i++ ); + (*prev)->vi_next = vi->vi_next; + ch_free( vi->vi_dn.bv_val ); + ch_free( vi ); + } + return 0; + } + vitmp.vi_ad = NULL; + i = slap_str2ad( c->argv[1], &vitmp.vi_ad, &text ); + if ( i ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), "<%s> %s", c->argv[0], text ); + Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n", + c->log, c->cr_msg, c->argv[1] ); + return(1); + } + if ( is_at_single_value( vitmp.vi_ad->ad_type )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> %s is single-valued, ignoring", c->argv[0], + vitmp.vi_ad->ad_cname.bv_val ); + Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n", + c->log, c->cr_msg, c->argv[1] ); + return(0); + } + is_numeric = ( vitmp.vi_ad->ad_type->sat_syntax == syn_numericString || + vitmp.vi_ad->ad_type->sat_syntax == slap_schema.si_syn_integer ) ? 1 + : 0; + ber_str2bv( c->argv[2], 0, 0, &bv ); + i = dnNormalize( 0, NULL, NULL, &bv, &vitmp.vi_dn, NULL ); + if ( i ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unable to normalize DN", c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n", + c->log, c->cr_msg, c->argv[2] ); + return(1); + } + i = verb_to_mask( c->argv[3], sorts ); + if ( BER_BVISNULL( &sorts[i].word )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unrecognized sort type", c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n", + c->log, c->cr_msg, c->argv[3] ); + return(1); + } + vitmp.vi_sort = sorts[i].mask; + if ( sorts[i].mask == VALSORT_WEIGHTED && c->argc == 5 ) { + i = verb_to_mask( c->argv[4], sorts ); + if ( BER_BVISNULL( &sorts[i].word )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unrecognized sort type", c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n", + c->log, c->cr_msg, c->argv[4] ); + return(1); + } + vitmp.vi_sort |= sorts[i].mask; + } + if (( vitmp.vi_sort & VALSORT_NUMERIC ) && !is_numeric ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> numeric sort specified for non-numeric syntax", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n", + c->log, c->cr_msg, c->argv[1] ); + return(1); + } + vi = ch_malloc( sizeof(valsort_info) ); + *vi = vitmp; + vi->vi_next = on->on_bi.bi_private; + on->on_bi.bi_private = vi; + return 0; +} + +/* Use Insertion Sort algorithm on selected values */ +static void +do_sort( Operation *op, Attribute *a, int beg, int num, slap_mask_t sort ) +{ + int i, j, gotnvals; + struct berval tmp, ntmp, *vals = NULL, *nvals; + + gotnvals = (a->a_vals != a->a_nvals ); + + nvals = a->a_nvals + beg; + if ( gotnvals ) + vals = a->a_vals + beg; + + if ( sort & VALSORT_NUMERIC ) { + long *numbers = op->o_tmpalloc( num * sizeof(long), op->o_tmpmemctx ), + idx; + for (i=0; i<num; i++) + numbers[i] = strtol( nvals[i].bv_val, NULL, 0 ); + + for (i=1; i<num; i++) { + idx = numbers[i]; + ntmp = nvals[i]; + if ( gotnvals ) tmp = vals[i]; + j = i; + while ( j>0 ) { + int cmp = (sort & VALSORT_DESCEND) ? numbers[j-1] < idx : + numbers[j-1] > idx; + if ( !cmp ) break; + numbers[j] = numbers[j-1]; + nvals[j] = nvals[j-1]; + if ( gotnvals ) vals[j] = vals[j-1]; + j--; + } + numbers[j] = idx; + nvals[j] = ntmp; + if ( gotnvals ) vals[j] = tmp; + } + op->o_tmpfree( numbers, op->o_tmpmemctx ); + } else { + for (i=1; i<num; i++) { + ntmp = nvals[i]; + if ( gotnvals ) tmp = vals[i]; + j = i; + while ( j>0 ) { + int cmp = strcmp( nvals[j-1].bv_val, ntmp.bv_val ); + cmp = (sort & VALSORT_DESCEND) ? (cmp < 0) : (cmp > 0); + if ( !cmp ) break; + + nvals[j] = nvals[j-1]; + if ( gotnvals ) vals[j] = vals[j-1]; + j--; + } + nvals[j] = ntmp; + if ( gotnvals ) vals[j] = tmp; + } + } +} + +static int +valsort_response( Operation *op, SlapReply *rs ) +{ + slap_overinst *on; + valsort_info *vi; + Attribute *a; + + /* If this is not a search response, or it is a syncrepl response, + * or the valsort control wants raw results, pass thru unmodified. + */ + if ( rs->sr_type != REP_SEARCH || + ( _SCM(op->o_sync) > SLAP_CONTROL_IGNORED ) || + ( op->o_ctrlflag[valsort_cid] & SLAP_CONTROL_DATA0)) + return SLAP_CB_CONTINUE; + + on = (slap_overinst *) op->o_bd->bd_info; + vi = on->on_bi.bi_private; + + /* And we must have something configured */ + if ( !vi ) return SLAP_CB_CONTINUE; + + /* Find a rule whose baseDN matches this entry */ + for (; vi; vi = vi->vi_next ) { + int i, n; + + if ( !dnIsSuffix( &rs->sr_entry->e_nname, &vi->vi_dn )) + continue; + + /* Find attr that this rule affects */ + a = attr_find( rs->sr_entry->e_attrs, vi->vi_ad ); + if ( !a ) continue; + + if ( rs_entry2modifiable( op, rs, on )) { + a = attr_find( rs->sr_entry->e_attrs, vi->vi_ad ); + } + + n = a->a_numvals; + if ( vi->vi_sort & VALSORT_WEIGHTED ) { + int j, gotnvals; + long *index = op->o_tmpalloc( n * sizeof(long), op->o_tmpmemctx ); + + gotnvals = (a->a_vals != a->a_nvals ); + + for (i=0; i<n; i++) { + char *ptr = ber_bvchr( &a->a_nvals[i], '{' ); + char *end = NULL; + if ( !ptr ) { + Debug(LDAP_DEBUG_TRACE, "weights missing from attr %s " + "in entry %s\n", vi->vi_ad->ad_cname.bv_val, + rs->sr_entry->e_name.bv_val, 0 ); + break; + } + index[i] = strtol( ptr+1, &end, 0 ); + if ( *end != '}' ) { + Debug(LDAP_DEBUG_TRACE, "weights misformatted " + "in entry %s\n", + rs->sr_entry->e_name.bv_val, 0, 0 ); + break; + } + /* Strip out weights */ + ptr = a->a_nvals[i].bv_val; + end++; + for (;*end;) + *ptr++ = *end++; + *ptr = '\0'; + a->a_nvals[i].bv_len = ptr - a->a_nvals[i].bv_val; + + if ( a->a_vals != a->a_nvals ) { + ptr = a->a_vals[i].bv_val; + end = ber_bvchr( &a->a_vals[i], '}' ); + assert( end != NULL ); + end++; + for (;*end;) + *ptr++ = *end++; + *ptr = '\0'; + a->a_vals[i].bv_len = ptr - a->a_vals[i].bv_val; + } + } + /* An attr was missing weights here, ignore it */ + if ( i<n ) { + op->o_tmpfree( index, op->o_tmpmemctx ); + continue; + } + /* Insertion sort */ + for ( i=1; i<n; i++) { + long idx = index[i]; + struct berval tmp = a->a_vals[i], ntmp; + if ( gotnvals ) ntmp = a->a_nvals[i]; + j = i; + while (( j>0 ) && (index[j-1] > idx )) { + index[j] = index[j-1]; + a->a_vals[j] = a->a_vals[j-1]; + if ( gotnvals ) a->a_nvals[j] = a->a_nvals[j-1]; + j--; + } + index[j] = idx; + a->a_vals[j] = tmp; + if ( gotnvals ) a->a_nvals[j] = ntmp; + } + /* Check for secondary sort */ + if ( vi->vi_sort ^ VALSORT_WEIGHTED ) { + for ( i=0; i<n;) { + for (j=i+1; j<n; j++) { + if (index[i] != index[j]) + break; + } + if( j-i > 1 ) + do_sort( op, a, i, j-i, vi->vi_sort ); + i = j; + } + } + op->o_tmpfree( index, op->o_tmpmemctx ); + } else { + do_sort( op, a, 0, n, vi->vi_sort ); + } + } + return SLAP_CB_CONTINUE; +} + +static int +valsort_add( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + valsort_info *vi = on->on_bi.bi_private; + + Attribute *a; + int i; + char *ptr, *end; + + /* See if any weighted sorting applies to this entry */ + for ( ;vi;vi=vi->vi_next ) { + if ( !dnIsSuffix( &op->o_req_ndn, &vi->vi_dn )) + continue; + if ( !(vi->vi_sort & VALSORT_WEIGHTED )) + continue; + a = attr_find( op->ora_e->e_attrs, vi->vi_ad ); + if ( !a ) + continue; + for (i=0; !BER_BVISNULL( &a->a_vals[i] ); i++) { + ptr = ber_bvchr(&a->a_vals[i], '{' ); + if ( !ptr ) { + Debug(LDAP_DEBUG_TRACE, "weight missing from attribute %s\n", + vi->vi_ad->ad_cname.bv_val, 0, 0); + send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION, + "weight missing from attribute" ); + return rs->sr_err; + } + strtol( ptr+1, &end, 0 ); + if ( *end != '}' ) { + Debug(LDAP_DEBUG_TRACE, "weight is misformatted in %s\n", + vi->vi_ad->ad_cname.bv_val, 0, 0); + send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION, + "weight is misformatted" ); + return rs->sr_err; + } + } + } + return SLAP_CB_CONTINUE; +} + +static int +valsort_modify( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + valsort_info *vi = on->on_bi.bi_private; + + Modifications *ml; + int i; + char *ptr, *end; + + /* See if any weighted sorting applies to this entry */ + for ( ;vi;vi=vi->vi_next ) { + if ( !dnIsSuffix( &op->o_req_ndn, &vi->vi_dn )) + continue; + if ( !(vi->vi_sort & VALSORT_WEIGHTED )) + continue; + for (ml = op->orm_modlist; ml; ml=ml->sml_next ) { + /* Must be a Delete Attr op, so no values to consider */ + if ( !ml->sml_values ) + continue; + if ( ml->sml_desc == vi->vi_ad ) + break; + } + if ( !ml ) + continue; + for (i=0; !BER_BVISNULL( &ml->sml_values[i] ); i++) { + ptr = ber_bvchr(&ml->sml_values[i], '{' ); + if ( !ptr ) { + Debug(LDAP_DEBUG_TRACE, "weight missing from attribute %s\n", + vi->vi_ad->ad_cname.bv_val, 0, 0); + send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION, + "weight missing from attribute" ); + return rs->sr_err; + } + strtol( ptr+1, &end, 0 ); + if ( *end != '}' ) { + Debug(LDAP_DEBUG_TRACE, "weight is misformatted in %s\n", + vi->vi_ad->ad_cname.bv_val, 0, 0); + send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION, + "weight is misformatted" ); + return rs->sr_err; + } + } + } + return SLAP_CB_CONTINUE; +} + +static int +valsort_db_open( + BackendDB *be, + ConfigReply *cr +) +{ + return overlay_register_control( be, LDAP_CONTROL_VALSORT ); +} + +static int +valsort_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + valsort_info *vi = on->on_bi.bi_private, *next; + +#ifdef SLAP_CONFIG_DELETE + overlay_unregister_control( be, LDAP_CONTROL_VALSORT ); +#endif /* SLAP_CONFIG_DELETE */ + + for (; vi; vi = next) { + next = vi->vi_next; + ch_free( vi->vi_dn.bv_val ); + ch_free( vi ); + } + + return 0; +} + +static int +valsort_parseCtrl( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + ber_tag_t tag; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_int_t flag = 0; + + if ( BER_BVISNULL( &ctrl->ldctl_value )) { + rs->sr_text = "valSort control value is absent"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISEMPTY( &ctrl->ldctl_value )) { + rs->sr_text = "valSort control value is empty"; + return LDAP_PROTOCOL_ERROR; + } + + ber_init2( ber, &ctrl->ldctl_value, 0 ); + if (( tag = ber_scanf( ber, "{b}", &flag )) == LBER_ERROR ) { + rs->sr_text = "valSort control: flag decoding error"; + return LDAP_PROTOCOL_ERROR; + } + + op->o_ctrlflag[valsort_cid] = ctrl->ldctl_iscritical ? + SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL; + if ( flag ) + op->o_ctrlflag[valsort_cid] |= SLAP_CONTROL_DATA0; + + return LDAP_SUCCESS; +} + +static slap_overinst valsort; + +int valsort_initialize( void ) +{ + int rc; + + valsort.on_bi.bi_type = "valsort"; + valsort.on_bi.bi_db_destroy = valsort_destroy; + valsort.on_bi.bi_db_open = valsort_db_open; + + valsort.on_bi.bi_op_add = valsort_add; + valsort.on_bi.bi_op_modify = valsort_modify; + + valsort.on_response = valsort_response; + + valsort.on_bi.bi_cf_ocs = valsort_cfocs; + + rc = register_supported_control( LDAP_CONTROL_VALSORT, + SLAP_CTRL_SEARCH | SLAP_CTRL_HIDE, NULL, valsort_parseCtrl, + &valsort_cid ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "Failed to register control %d\n", rc, 0, 0 ); + return rc; + } + + syn_numericString = syn_find( "1.3.6.1.4.1.1466.115.121.1.36" ); + + rc = config_register_schema( valsort_cfats, valsort_cfocs ); + if ( rc ) return rc; + + return overlay_register(&valsort); +} + +#if SLAPD_OVER_VALSORT == SLAPD_MOD_DYNAMIC +int init_module( int argc, char *argv[]) { + return valsort_initialize(); +} +#endif + +#endif /* SLAPD_OVER_VALSORT */ diff --git a/servers/slapd/passwd.c b/servers/slapd/passwd.c new file mode 100644 index 0000000..798de56 --- /dev/null +++ b/servers/slapd/passwd.c @@ -0,0 +1,626 @@ +/* passwd.c - password extended operation routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/unistd.h> + +#ifdef SLAPD_CRYPT +#include <ac/crypt.h> +#endif + +#include "slap.h" + +#include <lber_pvt.h> +#include <lutil.h> +#include <lutil_sha1.h> + +const struct berval slap_EXOP_MODIFY_PASSWD = BER_BVC(LDAP_EXOP_MODIFY_PASSWD); + +static const char *defhash[] = { +#ifdef LUTIL_SHA1_BYTES + "{SSHA}", +#else + "{SMD5}", +#endif + NULL +}; + +int passwd_extop( + Operation *op, + SlapReply *rs ) +{ + struct berval id = {0, NULL}, hash, *rsp = NULL; + req_pwdexop_s *qpw = &op->oq_pwdexop; + req_extended_s qext = op->oq_extended; + Modifications *ml; + slap_callback cb = { NULL, slap_null_cb, NULL, NULL }; + int i, nhash; + char **hashes, idNul; + int rc; + BackendDB *op_be; + int freenewpw = 0; + struct berval dn = BER_BVNULL, ndn = BER_BVNULL; + + assert( ber_bvcmp( &slap_EXOP_MODIFY_PASSWD, &op->ore_reqoid ) == 0 ); + + if( op->o_dn.bv_len == 0 ) { + Statslog( LDAP_DEBUG_STATS, "%s PASSMOD\n", + op->o_log_prefix, 0, 0, 0, 0 ); + rs->sr_text = "only authenticated users may change passwords"; + return LDAP_STRONG_AUTH_REQUIRED; + } + + qpw->rs_old.bv_len = 0; + qpw->rs_old.bv_val = NULL; + qpw->rs_new.bv_len = 0; + qpw->rs_new.bv_val = NULL; + qpw->rs_mods = NULL; + qpw->rs_modtail = NULL; + + rs->sr_err = slap_passwd_parse( op->ore_reqdata, &id, + &qpw->rs_old, &qpw->rs_new, &rs->sr_text ); + + if ( !BER_BVISNULL( &id )) { + idNul = id.bv_val[id.bv_len]; + id.bv_val[id.bv_len] = '\0'; + } + if ( rs->sr_err == LDAP_SUCCESS && !BER_BVISEMPTY( &id ) ) { + Statslog( LDAP_DEBUG_STATS, "%s PASSMOD id=\"%s\"%s%s\n", + op->o_log_prefix, id.bv_val, + qpw->rs_old.bv_val ? " old" : "", + qpw->rs_new.bv_val ? " new" : "", 0 ); + } else { + Statslog( LDAP_DEBUG_STATS, "%s PASSMOD%s%s\n", + op->o_log_prefix, + qpw->rs_old.bv_val ? " old" : "", + qpw->rs_new.bv_val ? " new" : "", 0, 0 ); + } + + if ( rs->sr_err != LDAP_SUCCESS ) { + if ( !BER_BVISNULL( &id )) + id.bv_val[id.bv_len] = idNul; + return rs->sr_err; + } + + if ( !BER_BVISEMPTY( &id ) ) { + rs->sr_err = dnPrettyNormal( NULL, &id, &dn, &ndn, op->o_tmpmemctx ); + id.bv_val[id.bv_len] = idNul; + if ( rs->sr_err != LDAP_SUCCESS ) { + rs->sr_text = "Invalid DN"; + rc = rs->sr_err; + goto error_return; + } + op->o_req_dn = dn; + op->o_req_ndn = ndn; + op->o_bd = select_backend( &op->o_req_ndn, 1 ); + + } else { + ber_dupbv_x( &dn, &op->o_dn, op->o_tmpmemctx ); + ber_dupbv_x( &ndn, &op->o_ndn, op->o_tmpmemctx ); + op->o_req_dn = dn; + op->o_req_ndn = ndn; + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + op->o_bd = op->o_conn->c_authz_backend; + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + } + + if( op->o_bd == NULL ) { + if ( qpw->rs_old.bv_val != NULL ) { + rs->sr_text = "unwilling to verify old password"; + rc = LDAP_UNWILLING_TO_PERFORM; + goto error_return; + } + +#ifdef HAVE_CYRUS_SASL + rc = slap_sasl_setpass( op, rs ); +#else + rs->sr_text = "no authz backend"; + rc = LDAP_OTHER; +#endif + goto error_return; + } + + if ( op->o_req_ndn.bv_len == 0 ) { + rs->sr_text = "no password is associated with the Root DSE"; + rc = LDAP_UNWILLING_TO_PERFORM; + goto error_return; + } + + /* If we've got a glued backend, check the real backend */ + op_be = op->o_bd; + if ( SLAP_GLUE_INSTANCE( op->o_bd )) { + op->o_bd = select_backend( &op->o_req_ndn, 0 ); + } + + if (backend_check_restrictions( op, rs, + (struct berval *)&slap_EXOP_MODIFY_PASSWD ) != LDAP_SUCCESS) { + rc = rs->sr_err; + goto error_return; + } + + /* check for referrals */ + if ( backend_check_referrals( op, rs ) != LDAP_SUCCESS ) { + rc = rs->sr_err; + goto error_return; + } + + /* This does not apply to multi-provider case */ + if(!( !SLAP_SINGLE_SHADOW( op->o_bd ) || be_isupdate( op ))) { + /* we SHOULD return a referral in this case */ + BerVarray defref = op->o_bd->be_update_refs + ? op->o_bd->be_update_refs : default_referral; + + if( defref != NULL ) { + rs->sr_ref = referral_rewrite( op->o_bd->be_update_refs, + NULL, NULL, LDAP_SCOPE_DEFAULT ); + if(rs->sr_ref) { + rs->sr_flags |= REP_REF_MUSTBEFREED; + } else { + rs->sr_ref = defref; + } + rc = LDAP_REFERRAL; + goto error_return; + + } + + rs->sr_text = "shadow context; no update referral"; + rc = LDAP_UNWILLING_TO_PERFORM; + goto error_return; + } + + /* generate a new password if none was provided */ + if ( qpw->rs_new.bv_len == 0 ) { + slap_passwd_generate( &qpw->rs_new ); + if ( qpw->rs_new.bv_len ) { + rsp = slap_passwd_return( &qpw->rs_new ); + freenewpw = 1; + } + } + if ( qpw->rs_new.bv_len == 0 ) { + rs->sr_text = "password generation failed"; + rc = LDAP_OTHER; + goto error_return; + } + + op->o_bd = op_be; + + /* Give the backend a chance to handle this itself */ + if ( op->o_bd->be_extended ) { + rs->sr_err = op->o_bd->be_extended( op, rs ); + if ( rs->sr_err != LDAP_UNWILLING_TO_PERFORM && + rs->sr_err != SLAP_CB_CONTINUE ) + { + rc = rs->sr_err; + if ( rsp ) { + rs->sr_rspdata = rsp; + rsp = NULL; + } + goto error_return; + } + } + + /* The backend didn't handle it, so try it here */ + if( op->o_bd && !op->o_bd->be_modify ) { + rs->sr_text = "operation not supported for current user"; + rc = LDAP_UNWILLING_TO_PERFORM; + goto error_return; + } + + if ( qpw->rs_old.bv_val != NULL ) { + Entry *e = NULL; + + rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, + slap_schema.si_ad_userPassword, 0, &e ); + if ( rc == LDAP_SUCCESS && e ) { + Attribute *a = attr_find( e->e_attrs, + slap_schema.si_ad_userPassword ); + if ( a ) + rc = slap_passwd_check( op, e, a, &qpw->rs_old, &rs->sr_text ); + else + rc = 1; + be_entry_release_r( op, e ); + if ( rc == LDAP_SUCCESS ) + goto old_good; + } + rs->sr_text = "unwilling to verify old password"; + rc = LDAP_UNWILLING_TO_PERFORM; + goto error_return; + } + +old_good: + ml = ch_malloc( sizeof(Modifications) ); + if ( !qpw->rs_modtail ) qpw->rs_modtail = &ml->sml_next; + + if ( default_passwd_hash ) { + for ( nhash = 0; default_passwd_hash[nhash]; nhash++ ); + hashes = default_passwd_hash; + } else { + nhash = 1; + hashes = (char **)defhash; + } + ml->sml_numvals = nhash; + ml->sml_values = ch_malloc( (nhash+1)*sizeof(struct berval) ); + for ( i=0; hashes[i]; i++ ) { + slap_passwd_hash_type( &qpw->rs_new, &hash, hashes[i], &rs->sr_text ); + if ( hash.bv_len == 0 ) { + if ( !rs->sr_text ) { + rs->sr_text = "password hash failed"; + } + break; + } + ml->sml_values[i] = hash; + } + ml->sml_values[i].bv_val = NULL; + ml->sml_nvalues = NULL; + ml->sml_desc = slap_schema.si_ad_userPassword; + ml->sml_type = ml->sml_desc->ad_cname; + ml->sml_op = LDAP_MOD_REPLACE; + ml->sml_flags = 0; + ml->sml_next = qpw->rs_mods; + qpw->rs_mods = ml; + + if ( hashes[i] ) { + rs->sr_err = LDAP_OTHER; + + } else { + slap_callback *sc = op->o_callback; + + op->o_tag = LDAP_REQ_MODIFY; + op->o_callback = &cb; + op->orm_modlist = qpw->rs_mods; + op->orm_no_opattrs = 0; + + cb.sc_private = qpw; /* let Modify know this was pwdMod, + * if it cares... */ + + rs->sr_err = op->o_bd->be_modify( op, rs ); + + /* be_modify() might have shuffled modifications */ + qpw->rs_mods = op->orm_modlist; + + if ( rs->sr_err == LDAP_SUCCESS ) { + rs->sr_rspdata = rsp; + + } else if ( rsp ) { + ber_bvfree( rsp ); + rsp = NULL; + } + op->o_tag = LDAP_REQ_EXTENDED; + op->o_callback = sc; + } + + rc = rs->sr_err; + op->oq_extended = qext; + +error_return:; + if ( qpw->rs_mods ) { + slap_mods_free( qpw->rs_mods, 1 ); + } + if ( freenewpw ) { + free( qpw->rs_new.bv_val ); + } + if ( !BER_BVISNULL( &dn ) ) { + op->o_tmpfree( dn.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &op->o_req_dn ); + } + if ( !BER_BVISNULL( &ndn ) ) { + op->o_tmpfree( ndn.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &op->o_req_ndn ); + } + + return rc; +} + +/* NOTE: The DN in *id is NOT NUL-terminated here. dnNormalize will + * reject it in this condition, the caller must NUL-terminate it. + * FIXME: should dnNormalize still be complaining about that? + */ +int slap_passwd_parse( struct berval *reqdata, + struct berval *id, + struct berval *oldpass, + struct berval *newpass, + const char **text ) +{ + int rc = LDAP_SUCCESS; + ber_tag_t tag; + ber_len_t len = -1; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + + if( reqdata == NULL ) { + return LDAP_SUCCESS; + } + + if( reqdata->bv_len == 0 ) { + *text = "empty request data field"; + return LDAP_PROTOCOL_ERROR; + } + + /* ber_init2 uses reqdata directly, doesn't allocate new buffers */ + ber_init2( ber, reqdata, 0 ); + + tag = ber_skip_tag( ber, &len ); + + if( tag != LBER_SEQUENCE ) { + Debug( LDAP_DEBUG_TRACE, + "slap_passwd_parse: decoding error\n", 0, 0, 0 ); + rc = LDAP_PROTOCOL_ERROR; + goto done; + } + + tag = ber_peek_tag( ber, &len ); + if( tag == LDAP_TAG_EXOP_MODIFY_PASSWD_ID ) { + if( id == NULL ) { + Debug( LDAP_DEBUG_TRACE, "slap_passwd_parse: ID not allowed.\n", + 0, 0, 0 ); + + *text = "user must change own password"; + rc = LDAP_UNWILLING_TO_PERFORM; + goto done; + } + + tag = ber_get_stringbv( ber, id, LBER_BV_NOTERM ); + + if( tag == LBER_ERROR ) { + Debug( LDAP_DEBUG_TRACE, "slap_passwd_parse: ID parse failed.\n", + 0, 0, 0 ); + + goto decoding_error; + } + + tag = ber_peek_tag( ber, &len ); + } + + if( tag == LDAP_TAG_EXOP_MODIFY_PASSWD_OLD ) { + if( oldpass == NULL ) { + Debug( LDAP_DEBUG_TRACE, "slap_passwd_parse: OLD not allowed.\n", + 0, 0, 0 ); + + *text = "use bind to verify old password"; + rc = LDAP_UNWILLING_TO_PERFORM; + goto done; + } + + tag = ber_get_stringbv( ber, oldpass, LBER_BV_NOTERM ); + + if( tag == LBER_ERROR ) { + Debug( LDAP_DEBUG_TRACE, "slap_passwd_parse: OLD parse failed.\n", + 0, 0, 0 ); + + goto decoding_error; + } + + if( oldpass->bv_len == 0 ) { + Debug( LDAP_DEBUG_TRACE, "slap_passwd_parse: OLD empty.\n", + 0, 0, 0 ); + + *text = "old password value is empty"; + rc = LDAP_UNWILLING_TO_PERFORM; + goto done; + } + + tag = ber_peek_tag( ber, &len ); + } + + if( tag == LDAP_TAG_EXOP_MODIFY_PASSWD_NEW ) { + if( newpass == NULL ) { + Debug( LDAP_DEBUG_TRACE, "slap_passwd_parse: NEW not allowed.\n", + 0, 0, 0 ); + + *text = "user specified passwords disallowed"; + rc = LDAP_UNWILLING_TO_PERFORM; + goto done; + } + + tag = ber_get_stringbv( ber, newpass, LBER_BV_NOTERM ); + + if( tag == LBER_ERROR ) { + Debug( LDAP_DEBUG_TRACE, "slap_passwd_parse: NEW parse failed.\n", + 0, 0, 0 ); + + goto decoding_error; + } + + if( newpass->bv_len == 0 ) { + Debug( LDAP_DEBUG_TRACE, "slap_passwd_parse: NEW empty.\n", + 0, 0, 0 ); + + *text = "new password value is empty"; + rc = LDAP_UNWILLING_TO_PERFORM; + goto done; + } + + tag = ber_peek_tag( ber, &len ); + } + + if( len != 0 ) { +decoding_error: + Debug( LDAP_DEBUG_TRACE, + "slap_passwd_parse: decoding error, len=%ld\n", + (long) len, 0, 0 ); + + *text = "data decoding error"; + rc = LDAP_PROTOCOL_ERROR; + } + +done: + return rc; +} + +struct berval * slap_passwd_return( + struct berval *cred ) +{ + int rc; + struct berval *bv = NULL; + BerElementBuffer berbuf; + /* opaque structure, size unknown but smaller than berbuf */ + BerElement *ber = (BerElement *)&berbuf; + + assert( cred != NULL ); + + Debug( LDAP_DEBUG_TRACE, "slap_passwd_return: %ld\n", + (long) cred->bv_len, 0, 0 ); + + ber_init_w_nullc( ber, LBER_USE_DER ); + + rc = ber_printf( ber, "{tON}", + LDAP_TAG_EXOP_MODIFY_PASSWD_GEN, cred ); + + if( rc >= 0 ) { + (void) ber_flatten( ber, &bv ); + } + + ber_free_buf( ber ); + + return bv; +} + +/* + * if "e" is provided, access to each value of the password is checked first + */ +int +slap_passwd_check( + Operation *op, + Entry *e, + Attribute *a, + struct berval *cred, + const char **text ) +{ + int result = 1; + struct berval *bv; + AccessControlState acl_state = ACL_STATE_INIT; + char credNul = cred->bv_val[cred->bv_len]; + +#ifdef SLAPD_SPASSWD + void *old_authctx = NULL; + + ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)slap_sasl_bind, + op->o_conn->c_sasl_authctx, 0, &old_authctx, NULL ); +#endif + + if ( credNul ) cred->bv_val[cred->bv_len] = 0; + + for ( bv = a->a_vals; bv->bv_val != NULL; bv++ ) { + /* if e is provided, check access */ + if ( e && access_allowed( op, e, a->a_desc, bv, + ACL_AUTH, &acl_state ) == 0 ) + { + continue; + } + + if ( !lutil_passwd( bv, cred, NULL, text ) ) { + result = 0; + break; + } + } + + if ( credNul ) cred->bv_val[cred->bv_len] = credNul; + +#ifdef SLAPD_SPASSWD + ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)slap_sasl_bind, + old_authctx, 0, NULL, NULL ); +#endif + + return result; +} + +void +slap_passwd_generate( struct berval *pass ) +{ + Debug( LDAP_DEBUG_TRACE, "slap_passwd_generate\n", 0, 0, 0 ); + BER_BVZERO( pass ); + + /* + * generate passwords of only 8 characters as some getpass(3) + * implementations truncate at 8 characters. + */ + lutil_passwd_generate( pass, 8 ); +} + +void +slap_passwd_hash_type( + struct berval * cred, + struct berval * new, + char *hash, + const char **text ) +{ + new->bv_len = 0; + new->bv_val = NULL; + + assert( hash != NULL ); + + lutil_passwd_hash( cred , hash, new, text ); +} +void +slap_passwd_hash( + struct berval * cred, + struct berval * new, + const char **text ) +{ + char *hash = NULL; + if ( default_passwd_hash ) { + hash = default_passwd_hash[0]; + } + if ( !hash ) { + hash = (char *)defhash[0]; + } + + slap_passwd_hash_type( cred, new, hash, text ); +} + +#ifdef SLAPD_CRYPT +static ldap_pvt_thread_mutex_t passwd_mutex; +static lutil_cryptfunc slapd_crypt; + +static int slapd_crypt( const char *key, const char *salt, char **hash ) +{ + char *cr; + int rc; + + ldap_pvt_thread_mutex_lock( &passwd_mutex ); + + cr = crypt( key, salt ); + if ( cr == NULL || cr[0] == '\0' ) { + /* salt must have been invalid */ + rc = LUTIL_PASSWD_ERR; + } else { + if ( hash ) { + *hash = ber_strdup( cr ); + rc = LUTIL_PASSWD_OK; + + } else { + rc = strcmp( salt, cr ) ? LUTIL_PASSWD_ERR : LUTIL_PASSWD_OK; + } + } + + ldap_pvt_thread_mutex_unlock( &passwd_mutex ); + return rc; +} +#endif /* SLAPD_CRYPT */ + +void slap_passwd_init() +{ +#ifdef SLAPD_CRYPT + ldap_pvt_thread_mutex_init( &passwd_mutex ); + lutil_cryptptr = slapd_crypt; +#endif +} + diff --git a/servers/slapd/phonetic.c b/servers/slapd/phonetic.c new file mode 100644 index 0000000..2331151 --- /dev/null +++ b/servers/slapd/phonetic.c @@ -0,0 +1,459 @@ +/* phonetic.c - routines to do phonetic matching */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/socket.h> +#include <ac/time.h> + +#include "slap.h" + +#if !defined(SLAPD_METAPHONE) && !defined(SLAPD_PHONETIC) +#define SLAPD_METAPHONE +#endif + +#define iswordbreak(x) (!isascii(x) || isspace((unsigned char) (x)) || \ + ispunct((unsigned char) (x)) || \ + isdigit((unsigned char) (x)) || (x) == '\0') + +#if 0 +static char * +first_word( char *s ) +{ + if ( s == NULL ) { + return( NULL ); + } + + while ( iswordbreak( *s ) ) { + if ( *s == '\0' ) { + return( NULL ); + } else { + s++; + } + } + + return( s ); +} + +static char * +next_word( char *s ) +{ + if ( s == NULL ) { + return( NULL ); + } + + while ( ! iswordbreak( *s ) ) { + s++; + } + + while ( iswordbreak( *s ) ) { + if ( *s == '\0' ) { + return( NULL ); + } else { + s++; + } + } + + return( s ); +} + +static char * +word_dup( char *w ) +{ + char *s, *ret; + char save; + + for ( s = w; !iswordbreak( *s ); s++ ) + ; /* NULL */ + save = *s; + *s = '\0'; + ret = ch_strdup( w ); + *s = save; + + return( ret ); +} +#endif /* 0 */ + +#ifndef MAXPHONEMELEN +#define MAXPHONEMELEN 4 +#endif + +#if defined(SLAPD_PHONETIC) + +/* lifted from isode-8.0 */ +char * +phonetic( char *s ) +{ + char code, adjacent, ch; + char *p; + int i; + char phoneme[MAXPHONEMELEN + 1]; + + p = s; + if ( p == NULL || *p == '\0' ) { + return( NULL ); + } + + adjacent = '0'; + phoneme[0] = TOUPPER((unsigned char)*p); + + phoneme[1] = '\0'; + for ( i = 0; i < 99 && (! iswordbreak(*p)); p++ ) { + ch = TOUPPER ((unsigned char)*p); + + code = '0'; + + switch (ch) { + case 'B': + case 'F': + case 'P': + case 'V': + code = (adjacent != '1') ? '1' : '0'; + break; + case 'S': + case 'C': + case 'G': + case 'J': + case 'K': + case 'Q': + case 'X': + case 'Z': + code = (adjacent != '2') ? '2' : '0'; + break; + case 'D': + case 'T': + code = (adjacent != '3') ? '3' : '0'; + break; + case 'L': + code = (adjacent != '4') ? '4' : '0'; + break; + case 'M': + case 'N': + code = (adjacent != '5') ? '5' : '0'; + break; + case 'R': + code = (adjacent != '6') ? '6' : '0'; + break; + default: + adjacent = '0'; + } + + if ( i == 0 ) { + adjacent = code; + i++; + } else if ( code != '0' ) { + if ( i == MAXPHONEMELEN ) + break; + adjacent = phoneme[i] = code; + i++; + } + } + + if ( i > 0 ) + phoneme[i] = '\0'; + + return( ch_strdup( phoneme ) ); +} + +#elif defined(SLAPD_METAPHONE) + +/* + * Metaphone was originally developed by Lawrence Philips and + * published in the "Computer Language" magazine in 1990. + */ +/* + * Metaphone copied from C Gazette, June/July 1991, pp 56-57, + * author Gary A. Parker, with changes by Bernard Tiffany of the + * University of Michigan, and more changes by Tim Howes of the + * University of Michigan. + */ + +/* Character coding array */ +static const char vsvfn[26] = { + 1, 16, 4, 16, 9, 2, 4, 16, 9, 2, 0, 2, 2, + /* A B C D E F G H I J K L M */ + 2, 1, 4, 0, 2, 4, 4, 1, 0, 0, 0, 8, 0}; + /* N O P Q R S T U V W X Y Z */ + +/* Macros to access character coding array */ +#define vowel(x) ((x) != '\0' && vsvfn[(x) - 'A'] & 1) /* AEIOU */ +#define same(x) ((x) != '\0' && vsvfn[(x) - 'A'] & 2) /* FJLMNR */ +#define varson(x) ((x) != '\0' && vsvfn[(x) - 'A'] & 4) /* CGPST */ +#define frontv(x) ((x) != '\0' && vsvfn[(x) - 'A'] & 8) /* EIY */ +#define noghf(x) ((x) != '\0' && vsvfn[(x) - 'A'] & 16) /* BDH */ + +char * +phonetic( char *Word ) +{ + char *n, *n_start, *n_end; /* pointers to string */ + char *metaph_end; /* pointers to metaph */ + char ntrans[40]; /* word with uppercase letters */ + int KSflag; /* state flag for X -> KS */ + char buf[MAXPHONEMELEN + 2]; + char *Metaph; + + /* + * Copy Word to internal buffer, dropping non-alphabetic characters + * and converting to upper case + */ + + for (n = ntrans + 4, n_end = ntrans + 35; !iswordbreak( *Word ) && + n < n_end; Word++) { + if (isalpha((unsigned char)*Word)) + *n++ = TOUPPER((unsigned char)*Word); + } + Metaph = buf; + *Metaph = '\0'; + if (n == ntrans + 4) { + return( ch_strdup( buf ) ); /* Return if null */ + } + n_end = n; /* Set n_end to end of string */ + + /* ntrans[0] will always be == 0 */ + ntrans[0] = '\0'; + ntrans[1] = '\0'; + ntrans[2] = '\0'; + ntrans[3] = '\0'; + *n++ = 0; + *n++ = 0; + *n++ = 0; + *n = 0; /* Pad with nulls */ + n = ntrans + 4; /* Assign pointer to start */ + + /* Check for PN, KN, GN, AE, WR, WH, and X at start */ + switch (*n) { + case 'P': + case 'K': + case 'G': + /* 'PN', 'KN', 'GN' becomes 'N' */ + if (*(n + 1) == 'N') + *n++ = 0; + break; + case 'A': + /* 'AE' becomes 'E' */ + if (*(n + 1) == 'E') + *n++ = 0; + break; + case 'W': + /* 'WR' becomes 'R', and 'WH' to 'H' */ + if (*(n + 1) == 'R') + *n++ = 0; + else if (*(n + 1) == 'H') { + *(n + 1) = *n; + *n++ = 0; + } + break; + case 'X': + /* 'X' becomes 'S' */ + *n = 'S'; + break; + } + + /* + * Now, loop step through string, stopping at end of string or when + * the computed 'metaph' is MAXPHONEMELEN characters long + */ + + KSflag = 0; /* state flag for KS translation */ + for (metaph_end = Metaph + MAXPHONEMELEN, n_start = n; + n <= n_end && Metaph < metaph_end; n++) { + if (KSflag) { + KSflag = 0; + *Metaph++ = 'S'; + } else { + /* Drop duplicates except for CC */ + if (*(n - 1) == *n && *n != 'C') + continue; + /* Check for F J L M N R or first letter vowel */ + if (same(*n) || (n == n_start && vowel(*n))) + *Metaph++ = *n; + else + switch (*n) { + case 'B': + + /* + * B unless in -MB + */ + if (n == (n_end - 1) && *(n - 1) != 'M') + *Metaph++ = *n; + break; + case 'C': + + /* + * X if in -CIA-, -CH- else S if in + * -CI-, -CE-, -CY- else dropped if + * in -SCI-, -SCE-, -SCY- else K + */ + if (*(n - 1) != 'S' || !frontv(*(n + 1))) { + if (*(n + 1) == 'I' && *(n + 2) == 'A') + *Metaph++ = 'X'; + else if (frontv(*(n + 1))) + *Metaph++ = 'S'; + else if (*(n + 1) == 'H') + *Metaph++ = ((n == n_start && !vowel(*(n + 2))) + || *(n - 1) == 'S') + ? (char) 'K' : (char) 'X'; + else + *Metaph++ = 'K'; + } + break; + case 'D': + + /* + * J if in DGE or DGI or DGY else T + */ + *Metaph++ = (*(n + 1) == 'G' && frontv(*(n + 2))) + ? (char) 'J' : (char) 'T'; + break; + case 'G': + + /* + * F if in -GH and not B--GH, D--GH, + * -H--GH, -H---GH else dropped if + * -GNED, -GN, -DGE-, -DGI-, -DGY- + * else J if in -GE-, -GI-, -GY- and + * not GG else K + */ + if ((*(n + 1) != 'J' || vowel(*(n + 2))) && + (*(n + 1) != 'N' || ((n + 1) < n_end && + (*(n + 2) != 'E' || *(n + 3) != 'D'))) && + (*(n - 1) != 'D' || !frontv(*(n + 1)))) + *Metaph++ = (frontv(*(n + 1)) && + *(n + 2) != 'G') ? (char) 'G' : (char) 'K'; + else if (*(n + 1) == 'H' && !noghf(*(n - 3)) && + *(n - 4) != 'H') + *Metaph++ = 'F'; + break; + case 'H': + + /* + * H if before a vowel and not after + * C, G, P, S, T else dropped + */ + if (!varson(*(n - 1)) && (!vowel(*(n - 1)) || + vowel(*(n + 1)))) + *Metaph++ = 'H'; + break; + case 'K': + + /* + * dropped if after C else K + */ + if (*(n - 1) != 'C') + *Metaph++ = 'K'; + break; + case 'P': + + /* + * F if before H, else P + */ + *Metaph++ = *(n + 1) == 'H' ? + (char) 'F' : (char) 'P'; + break; + case 'Q': + + /* + * K + */ + *Metaph++ = 'K'; + break; + case 'S': + + /* + * X in -SH-, -SIO- or -SIA- else S + */ + *Metaph++ = (*(n + 1) == 'H' || + (*(n + 1) == 'I' && (*(n + 2) == 'O' || + *(n + 2) == 'A'))) + ? (char) 'X' : (char) 'S'; + break; + case 'T': + + /* + * X in -TIA- or -TIO- else 0 (zero) + * before H else dropped if in -TCH- + * else T + */ + if (*(n + 1) == 'I' && (*(n + 2) == 'O' || + *(n + 2) == 'A')) + *Metaph++ = 'X'; + else if (*(n + 1) == 'H') + *Metaph++ = '0'; + else if (*(n + 1) != 'C' || *(n + 2) != 'H') + *Metaph++ = 'T'; + break; + case 'V': + + /* + * F + */ + *Metaph++ = 'F'; + break; + case 'W': + + /* + * W after a vowel, else dropped + */ + case 'Y': + + /* + * Y unless followed by a vowel + */ + if (vowel(*(n + 1))) + *Metaph++ = *n; + break; + case 'X': + + /* + * KS + */ + if (n == n_start) + *Metaph++ = 'S'; + else { + *Metaph++ = 'K'; /* Insert K, then S */ + KSflag = 1; + } + break; + case 'Z': + + /* + * S + */ + *Metaph++ = 'S'; + break; + } + } + } + + *Metaph = 0; /* Null terminate */ + return( ch_strdup( buf ) ); +} + +#endif /* SLAPD_METAPHONE */ diff --git a/servers/slapd/proto-slap.h b/servers/slapd/proto-slap.h new file mode 100644 index 0000000..7f8e604 --- /dev/null +++ b/servers/slapd/proto-slap.h @@ -0,0 +1,2195 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#ifndef PROTO_SLAP_H +#define PROTO_SLAP_H + +#include <ldap_cdefs.h> +#include "ldap_pvt.h" + +LDAP_BEGIN_DECL + +struct config_args_s; /* config.h */ +struct config_reply_s; /* config.h */ + +/* + * aci.c + */ +#ifdef SLAP_DYNACL +#ifdef SLAPD_ACI_ENABLED +LDAP_SLAPD_F (int) dynacl_aci_init LDAP_P(( void )); +#endif /* SLAPD_ACI_ENABLED */ +#endif /* SLAP_DYNACL */ + +/* + * acl.c + */ +LDAP_SLAPD_F (int) access_allowed_mask LDAP_P(( + Operation *op, + Entry *e, AttributeDescription *desc, struct berval *val, + slap_access_t access, + AccessControlState *state, + slap_mask_t *mask )); +#define access_allowed(op,e,desc,val,access,state) access_allowed_mask(op,e,desc,val,access,state,NULL) +LDAP_SLAPD_F (int) slap_access_allowed LDAP_P(( + Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + slap_access_t access, + AccessControlState *state, + slap_mask_t *maskp )); +LDAP_SLAPD_F (int) slap_access_always_allowed LDAP_P(( + Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + slap_access_t access, + AccessControlState *state, + slap_mask_t *maskp )); + +LDAP_SLAPD_F (int) acl_check_modlist LDAP_P(( + Operation *op, Entry *e, Modifications *ml )); + +LDAP_SLAPD_F (void) acl_append( AccessControl **l, AccessControl *a, int pos ); + +#ifdef SLAP_DYNACL +LDAP_SLAPD_F (int) slap_dynacl_register LDAP_P(( slap_dynacl_t *da )); +LDAP_SLAPD_F (slap_dynacl_t *) slap_dynacl_get LDAP_P(( const char *name )); +#endif /* SLAP_DYNACL */ +LDAP_SLAPD_F (int) acl_init LDAP_P(( void )); + +LDAP_SLAPD_F (int) acl_get_part LDAP_P(( + struct berval *list, + int ix, + char sep, + struct berval *bv )); +LDAP_SLAPD_F (int) acl_match_set LDAP_P(( + struct berval *subj, + Operation *op, + Entry *e, + struct berval *default_set_attribute )); +LDAP_SLAPD_F (int) acl_string_expand LDAP_P(( + struct berval *newbuf, struct berval *pattern, + struct berval *dnmatch, struct berval *valmatch, AclRegexMatches *matches )); + +/* + * aclparse.c + */ +LDAP_SLAPD_V (LDAP_CONST char *) style_strings[]; + +LDAP_SLAPD_F (int) parse_acl LDAP_P(( Backend *be, + const char *fname, int lineno, + int argc, char **argv, int pos )); + +LDAP_SLAPD_F (char *) access2str LDAP_P(( slap_access_t access )); +LDAP_SLAPD_F (slap_access_t) str2access LDAP_P(( const char *str )); + +#define ACCESSMASK_MAXLEN sizeof("unknown (+wrscan)") +LDAP_SLAPD_F (char *) accessmask2str LDAP_P(( slap_mask_t mask, char*, int debug )); +LDAP_SLAPD_F (slap_mask_t) str2accessmask LDAP_P(( const char *str )); +LDAP_SLAPD_F (void) acl_unparse LDAP_P(( AccessControl*, struct berval* )); +LDAP_SLAPD_F (void) acl_destroy LDAP_P(( AccessControl* )); +LDAP_SLAPD_F (void) acl_free LDAP_P(( AccessControl *a )); + + +/* + * ad.c + */ +LDAP_SLAPD_F (int) slap_str2ad LDAP_P(( + const char *, + AttributeDescription **ad, + const char **text )); + +LDAP_SLAPD_F (int) slap_bv2ad LDAP_P(( + struct berval *bv, + AttributeDescription **ad, + const char **text )); + +LDAP_SLAPD_F (void) ad_destroy LDAP_P(( AttributeDescription * )); +LDAP_SLAPD_F (int) ad_keystring LDAP_P(( struct berval *bv )); + +#define ad_cmp(l,r) (((l)->ad_cname.bv_len < (r)->ad_cname.bv_len) \ + ? -1 : (((l)->ad_cname.bv_len > (r)->ad_cname.bv_len) \ + ? 1 : strcasecmp((l)->ad_cname.bv_val, (r)->ad_cname.bv_val ))) + +LDAP_SLAPD_F (int) is_ad_subtype LDAP_P(( + AttributeDescription *sub, + AttributeDescription *super )); + +LDAP_SLAPD_F (int) ad_inlist LDAP_P(( + AttributeDescription *desc, + AttributeName *attrs )); + +LDAP_SLAPD_F (int) slap_str2undef_ad LDAP_P(( + const char *, + AttributeDescription **ad, + const char **text, + unsigned proxied )); + +LDAP_SLAPD_F (int) slap_bv2undef_ad LDAP_P(( + struct berval *bv, + AttributeDescription **ad, + const char **text, + unsigned proxied )); + +LDAP_SLAPD_F (AttributeDescription *) slap_bv2tmp_ad LDAP_P(( + struct berval *bv, + void *memctx )); + +LDAP_SLAPD_F (int) slap_ad_undef_promote LDAP_P(( + char *name, + AttributeType *nat )); + +LDAP_SLAPD_F (AttributeDescription *) ad_find_tags LDAP_P(( + AttributeType *type, + struct berval *tags )); + +LDAP_SLAPD_F (AttributeName *) str2anlist LDAP_P(( AttributeName *an, + char *str, const char *brkstr )); +LDAP_SLAPD_F (void) anlist_free LDAP_P(( AttributeName *an, + int freename, void *ctx )); + +LDAP_SLAPD_F (char **) anlist2charray_x LDAP_P(( + AttributeName *an, int dup, void *ctx )); +LDAP_SLAPD_F (char **) anlist2charray LDAP_P(( AttributeName *an, int dup )); +LDAP_SLAPD_F (char **) anlist2attrs LDAP_P(( AttributeName *anlist )); +LDAP_SLAPD_F (AttributeName *) file2anlist LDAP_P(( + AttributeName *, const char *, const char * )); +LDAP_SLAPD_F (int) an_find LDAP_P(( AttributeName *a, struct berval *s )); +LDAP_SLAPD_F (int) ad_define_option LDAP_P(( const char *name, + const char *fname, int lineno )); +LDAP_SLAPD_F (void) ad_unparse_options LDAP_P(( BerVarray *res )); + +LDAP_SLAPD_F (MatchingRule *) ad_mr( + AttributeDescription *ad, + unsigned usage ); + +LDAP_SLAPD_V( AttributeName * ) slap_anlist_no_attrs; +LDAP_SLAPD_V( AttributeName * ) slap_anlist_all_user_attributes; +LDAP_SLAPD_V( AttributeName * ) slap_anlist_all_operational_attributes; +LDAP_SLAPD_V( AttributeName * ) slap_anlist_all_attributes; + +LDAP_SLAPD_V( struct berval * ) slap_bv_no_attrs; +LDAP_SLAPD_V( struct berval * ) slap_bv_all_user_attrs; +LDAP_SLAPD_V( struct berval * ) slap_bv_all_operational_attrs; + +/* deprecated; only defined for backward compatibility */ +#define NoAttrs (*slap_bv_no_attrs) +#define AllUser (*slap_bv_all_user_attrs) +#define AllOper (*slap_bv_all_operational_attrs) + +/* + * add.c + */ +LDAP_SLAPD_F (int) slap_mods2entry LDAP_P(( Modifications *mods, Entry **e, + int initial, int dup, const char **text, char *textbuf, size_t textlen )); + +LDAP_SLAPD_F (int) slap_entry2mods LDAP_P(( Entry *e, + Modifications **mods, const char **text, + char *textbuf, size_t textlen )); +LDAP_SLAPD_F( int ) slap_add_opattrs( + Operation *op, + const char **text, + char *textbuf, size_t textlen, + int manage_ctxcsn ); + + +/* + * at.c + */ +LDAP_SLAPD_V(int) at_oc_cache; +LDAP_SLAPD_F (void) at_config LDAP_P(( + const char *fname, int lineno, + int argc, char **argv )); +LDAP_SLAPD_F (AttributeType *) at_find LDAP_P(( + const char *name )); +LDAP_SLAPD_F (AttributeType *) at_bvfind LDAP_P(( + struct berval *name )); +LDAP_SLAPD_F (int) at_find_in_list LDAP_P(( + AttributeType *sat, AttributeType **list )); +LDAP_SLAPD_F (int) at_append_to_list LDAP_P(( + AttributeType *sat, AttributeType ***listp )); +LDAP_SLAPD_F (int) at_delete_from_list LDAP_P(( + int pos, AttributeType ***listp )); +LDAP_SLAPD_F (int) at_schema_info LDAP_P(( Entry *e )); +LDAP_SLAPD_F (int) at_add LDAP_P(( + LDAPAttributeType *at, int user, + AttributeType **sat, AttributeType *prev, const char **err )); +LDAP_SLAPD_F (void) at_destroy LDAP_P(( void )); + +LDAP_SLAPD_F (int) is_at_subtype LDAP_P(( + AttributeType *sub, + AttributeType *super )); + +LDAP_SLAPD_F (const char *) at_syntax LDAP_P(( + AttributeType *at )); +LDAP_SLAPD_F (int) is_at_syntax LDAP_P(( + AttributeType *at, + const char *oid )); + +LDAP_SLAPD_F (int) at_start LDAP_P(( AttributeType **at )); +LDAP_SLAPD_F (int) at_next LDAP_P(( AttributeType **at )); +LDAP_SLAPD_F (void) at_delete LDAP_P(( AttributeType *at )); + +LDAP_SLAPD_F (void) at_unparse LDAP_P(( + BerVarray *bva, AttributeType *start, AttributeType *end, int system )); + +LDAP_SLAPD_F (int) register_at LDAP_P(( + const char *at, + AttributeDescription **ad, + int dupok )); + +/* + * attr.c + */ +LDAP_SLAPD_F (void) attr_free LDAP_P(( Attribute *a )); +LDAP_SLAPD_F (Attribute *) attr_dup LDAP_P(( Attribute *a )); + +#ifdef LDAP_COMP_MATCH +LDAP_SLAPD_F (void) comp_tree_free LDAP_P(( Attribute *a )); +#endif + +#define attr_mergeit( e, d, v ) attr_merge( e, d, v, NULL /* FIXME */ ) +#define attr_mergeit_one( e, d, v ) attr_merge_one( e, d, v, NULL /* FIXME */ ) + +LDAP_SLAPD_F (Attribute *) attr_alloc LDAP_P(( AttributeDescription *ad )); +LDAP_SLAPD_F (Attribute *) attrs_alloc LDAP_P(( int num )); +LDAP_SLAPD_F (int) attr_prealloc LDAP_P(( int num )); +LDAP_SLAPD_F (int) attr_valfind LDAP_P(( Attribute *a, + unsigned flags, + struct berval *val, + unsigned *slot, + void *ctx )); +LDAP_SLAPD_F (int) attr_valadd LDAP_P(( Attribute *a, + BerVarray vals, + BerVarray nvals, + int num )); +LDAP_SLAPD_F (int) attr_merge LDAP_P(( Entry *e, + AttributeDescription *desc, + BerVarray vals, + BerVarray nvals )); +LDAP_SLAPD_F (int) attr_merge_one LDAP_P(( Entry *e, + AttributeDescription *desc, + struct berval *val, + struct berval *nval )); +LDAP_SLAPD_F (int) attr_normalize LDAP_P(( AttributeDescription *desc, + BerVarray vals, BerVarray *nvalsp, void *memctx )); +LDAP_SLAPD_F (int) attr_normalize_one LDAP_P(( AttributeDescription *desc, + struct berval *val, struct berval *nval, void *memctx )); +LDAP_SLAPD_F (int) attr_merge_normalize LDAP_P(( Entry *e, + AttributeDescription *desc, + BerVarray vals, void *memctx )); +LDAP_SLAPD_F (int) attr_merge_normalize_one LDAP_P(( Entry *e, + AttributeDescription *desc, + struct berval *val, void *memctx )); +LDAP_SLAPD_F (Attribute *) attrs_find LDAP_P(( + Attribute *a, AttributeDescription *desc )); +LDAP_SLAPD_F (Attribute *) attr_find LDAP_P(( + Attribute *a, AttributeDescription *desc )); +LDAP_SLAPD_F (int) attr_delete LDAP_P(( + Attribute **attrs, AttributeDescription *desc )); + +LDAP_SLAPD_F (void) attrs_free LDAP_P(( Attribute *a )); +LDAP_SLAPD_F (Attribute *) attrs_dup LDAP_P(( Attribute *a )); +LDAP_SLAPD_F (int) attr_init LDAP_P(( void )); +LDAP_SLAPD_F (int) attr_destroy LDAP_P(( void )); + + +/* + * ava.c + */ +LDAP_SLAPD_F (int) get_ava LDAP_P(( + Operation *op, + BerElement *ber, + Filter *f, + unsigned usage, + const char **text )); +LDAP_SLAPD_F (void) ava_free LDAP_P(( + Operation *op, + AttributeAssertion *ava, + int freeit )); + +/* + * backend.c + */ + +#define be_match( be1, be2 ) ( (be1) == (be2) || \ + ( (be1) && (be2) && (be1)->be_nsuffix == (be2)->be_nsuffix ) ) + +LDAP_SLAPD_F (int) backend_init LDAP_P((void)); +LDAP_SLAPD_F (int) backend_add LDAP_P((BackendInfo *aBackendInfo)); +LDAP_SLAPD_F (int) backend_num LDAP_P((Backend *be)); +LDAP_SLAPD_F (int) backend_startup LDAP_P((Backend *be)); +LDAP_SLAPD_F (int) backend_startup_one LDAP_P((Backend *be, struct config_reply_s *cr)); +LDAP_SLAPD_F (int) backend_sync LDAP_P((Backend *be)); +LDAP_SLAPD_F (int) backend_shutdown LDAP_P((Backend *be)); +LDAP_SLAPD_F (int) backend_destroy LDAP_P((void)); +LDAP_SLAPD_F (void) backend_stopdown_one LDAP_P((BackendDB *bd )); +LDAP_SLAPD_F (void) backend_destroy_one LDAP_P((BackendDB *bd, int dynamic)); + +LDAP_SLAPD_F (BackendInfo *) backend_info LDAP_P(( const char *type )); +LDAP_SLAPD_F (BackendDB *) backend_db_init LDAP_P(( const char *type, + BackendDB *be, int idx, struct config_reply_s *cr )); +LDAP_SLAPD_F (void) backend_db_insert LDAP_P((BackendDB *bd, int idx)); +LDAP_SLAPD_F (void) backend_db_move LDAP_P((BackendDB *bd, int idx)); + +LDAP_SLAPD_F (BackendDB *) select_backend LDAP_P(( + struct berval * dn, + int noSubordinates )); + +LDAP_SLAPD_F (int) be_issuffix LDAP_P(( Backend *be, + struct berval *suffix )); +LDAP_SLAPD_F (int) be_issubordinate LDAP_P(( Backend *be, + struct berval *subordinate )); +LDAP_SLAPD_F (int) be_isroot LDAP_P(( Operation *op )); +LDAP_SLAPD_F (int) be_isroot_dn LDAP_P(( Backend *be, struct berval *ndn )); +LDAP_SLAPD_F (int) be_isroot_pw LDAP_P(( Operation *op )); +LDAP_SLAPD_F (int) be_rootdn_bind LDAP_P(( Operation *op, SlapReply *rs )); +LDAP_SLAPD_F (int) be_slurp_update LDAP_P(( Operation *op )); +#define be_isupdate( op ) be_slurp_update( (op) ) +LDAP_SLAPD_F (int) be_shadow_update LDAP_P(( Operation *op )); +LDAP_SLAPD_F (int) be_isupdate_dn LDAP_P(( Backend *be, struct berval *ndn )); +LDAP_SLAPD_F (struct berval *) be_root_dn LDAP_P(( Backend *be )); +LDAP_SLAPD_F (int) be_entry_get_rw LDAP_P(( Operation *o, + struct berval *ndn, ObjectClass *oc, + AttributeDescription *at, int rw, Entry **e )); +LDAP_SLAPD_F (int) be_entry_release_rw LDAP_P(( + Operation *o, Entry *e, int rw )); +#define be_entry_release_r( o, e ) be_entry_release_rw( o, e, 0 ) +#define be_entry_release_w( o, e ) be_entry_release_rw( o, e, 1 ) + +LDAP_SLAPD_F (int) backend_unbind LDAP_P((Operation *op, SlapReply *rs)); +LDAP_SLAPD_F (int) backend_connection_init LDAP_P((Connection *conn)); +LDAP_SLAPD_F (int) backend_connection_destroy LDAP_P((Connection *conn)); + +LDAP_SLAPD_F( int ) backend_check_controls LDAP_P(( + Operation *op, + SlapReply *rs )); +LDAP_SLAPD_F( int ) backend_check_restrictions LDAP_P(( + Operation *op, + SlapReply *rs, + struct berval *opdata )); + +LDAP_SLAPD_F( int ) backend_check_referrals LDAP_P(( + Operation *op, + SlapReply *rs )); + +LDAP_SLAPD_F (int) backend_group LDAP_P(( + Operation *op, + Entry *target, + struct berval *gr_ndn, + struct berval *op_ndn, + ObjectClass *group_oc, + AttributeDescription *group_at +)); + +LDAP_SLAPD_F (int) backend_attribute LDAP_P(( + Operation *op, + Entry *target, + struct berval *entry_ndn, + AttributeDescription *entry_at, + BerVarray *vals, + slap_access_t access +)); + +LDAP_SLAPD_F (int) backend_access LDAP_P(( + Operation *op, + Entry *target, + struct berval *edn, + AttributeDescription *entry_at, + struct berval *nval, + slap_access_t access, + slap_mask_t *mask )); + +LDAP_SLAPD_F (int) backend_operational LDAP_P(( + Operation *op, + SlapReply *rs +)); + +LDAP_SLAPD_F (ID) backend_tool_entry_first LDAP_P(( BackendDB *be )); + +LDAP_SLAPD_V(BackendInfo) slap_binfo[]; + +/* + * backglue.c + */ + +LDAP_SLAPD_F (int) glue_sub_init( void ); +LDAP_SLAPD_F (int) glue_sub_attach( int online ); +LDAP_SLAPD_F (int) glue_sub_add( BackendDB *be, int advert, int online ); +LDAP_SLAPD_F (int) glue_sub_del( BackendDB *be ); + +/* + * backover.c + */ +LDAP_SLAPD_F (int) overlay_register LDAP_P(( slap_overinst *on )); +LDAP_SLAPD_F (int) overlay_config LDAP_P(( BackendDB *be, const char *ov, + int idx, BackendInfo **res, ConfigReply *cr )); +LDAP_SLAPD_F (void) overlay_destroy_one LDAP_P(( + BackendDB *be, + slap_overinst *on )); +LDAP_SLAPD_F (slap_overinst *) overlay_next LDAP_P(( slap_overinst *on )); +LDAP_SLAPD_F (slap_overinst *) overlay_find LDAP_P(( const char *name )); +LDAP_SLAPD_F (int) overlay_is_over LDAP_P(( BackendDB *be )); +LDAP_SLAPD_F (int) overlay_is_inst LDAP_P(( BackendDB *be, const char *name )); +LDAP_SLAPD_F (int) overlay_register_control LDAP_P(( + BackendDB *be, + const char *oid )); +LDAP_SLAPD_F (int) overlay_op_walk LDAP_P(( + Operation *op, + SlapReply *rs, + slap_operation_t which, + slap_overinfo *oi, + slap_overinst *on )); +LDAP_SLAPD_F (int) overlay_entry_get_ov LDAP_P(( + Operation *op, + struct berval *dn, + ObjectClass *oc, + AttributeDescription *ad, + int rw, + Entry **e, + slap_overinst *ov )); +LDAP_SLAPD_F (int) overlay_entry_release_ov LDAP_P(( + Operation *op, + Entry *e, + int rw, + slap_overinst *ov )); +LDAP_SLAPD_F (void) overlay_insert LDAP_P(( + BackendDB *be, slap_overinst *on, slap_overinst ***prev, int idx )); +LDAP_SLAPD_F (void) overlay_move LDAP_P(( + BackendDB *be, slap_overinst *on, int idx )); +#ifdef SLAP_CONFIG_DELETE +LDAP_SLAPD_F (void) overlay_remove LDAP_P(( + BackendDB *be, slap_overinst *on, Operation *op )); +LDAP_SLAPD_F (void) overlay_unregister_control LDAP_P(( + BackendDB *be, + const char *oid )); +#endif /* SLAP_CONFIG_DELETE */ +LDAP_SLAPD_F (int) overlay_callback_after_backover LDAP_P(( + Operation *op, slap_callback *sc, int append )); + +/* + * bconfig.c + */ +LDAP_SLAPD_F (int) slap_loglevel_register LDAP_P (( slap_mask_t m, struct berval *s )); +LDAP_SLAPD_F (int) slap_loglevel_get LDAP_P(( struct berval *s, int *l )); +LDAP_SLAPD_F (int) str2loglevel LDAP_P(( const char *s, int *l )); +LDAP_SLAPD_F (int) loglevel2bvarray LDAP_P(( int l, BerVarray *bva )); +LDAP_SLAPD_F (const char *) loglevel2str LDAP_P(( int l )); +LDAP_SLAPD_F (int) loglevel2bv LDAP_P(( int l, struct berval *bv )); +LDAP_SLAPD_F (int) loglevel_print LDAP_P(( FILE *out )); +LDAP_SLAPD_F (int) slap_cf_aux_table_parse LDAP_P(( const char *word, void *bc, slap_cf_aux_table *tab0, LDAP_CONST char *tabmsg )); +LDAP_SLAPD_F (int) slap_cf_aux_table_unparse LDAP_P(( void *bc, struct berval *bv, slap_cf_aux_table *tab0 )); + +/* + * ch_malloc.c + */ +LDAP_SLAPD_V (BerMemoryFunctions) ch_mfuncs; +LDAP_SLAPD_F (void *) ch_malloc LDAP_P(( ber_len_t size )); +LDAP_SLAPD_F (void *) ch_realloc LDAP_P(( void *block, ber_len_t size )); +LDAP_SLAPD_F (void *) ch_calloc LDAP_P(( ber_len_t nelem, ber_len_t size )); +LDAP_SLAPD_F (char *) ch_strdup LDAP_P(( const char *string )); +LDAP_SLAPD_F (void) ch_free LDAP_P(( void * )); + +#ifndef CH_FREE +#undef free +#define free ch_free +#endif + +/* + * compare.c + */ + +LDAP_SLAPD_F (int) slap_compare_entry LDAP_P(( + Operation *op, + Entry *e, + AttributeAssertion *ava )); + +/* + * component.c + */ +#ifdef LDAP_COMP_MATCH +struct comp_attribute_aliasing; + +LDAP_SLAPD_F (int) test_comp_filter_entry LDAP_P(( + Operation* op, + Entry* e, + MatchingRuleAssertion* mr)); + +LDAP_SLAPD_F (int) dup_comp_filter LDAP_P(( + Operation* op, + struct berval *bv, + ComponentFilter *in_f, + ComponentFilter **out_f )); + +LDAP_SLAPD_F (int) get_aliased_filter_aa LDAP_P(( + Operation* op, + AttributeAssertion* a_assert, + struct comp_attribute_aliasing* aa, + const char** text )); + +LDAP_SLAPD_F (int) get_aliased_filter LDAP_P(( + Operation* op, + MatchingRuleAssertion* ma, + struct comp_attribute_aliasing* aa, + const char** text )); + +LDAP_SLAPD_F (int) get_comp_filter LDAP_P(( + Operation* op, + BerValue* bv, + ComponentFilter** filt, + const char **text )); + +LDAP_SLAPD_F (int) insert_component_reference LDAP_P(( + ComponentReference *cr, + ComponentReference** cr_list )); + +LDAP_SLAPD_F (int) is_component_reference LDAP_P(( + char *attr )); + +LDAP_SLAPD_F (int) extract_component_reference LDAP_P(( + char* attr, + ComponentReference** cr )); + +LDAP_SLAPD_F (int) componentFilterMatch LDAP_P(( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue )); + +LDAP_SLAPD_F (int) directoryComponentsMatch LDAP_P(( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue )); + +LDAP_SLAPD_F (int) allComponentsMatch LDAP_P(( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue )); + +LDAP_SLAPD_F (ComponentReference*) dup_comp_ref LDAP_P(( + Operation *op, + ComponentReference *cr )); + +LDAP_SLAPD_F (int) componentFilterValidate LDAP_P(( + Syntax *syntax, + struct berval* bv )); + +LDAP_SLAPD_F (int) allComponentsValidate LDAP_P(( + Syntax *syntax, + struct berval* bv )); + +LDAP_SLAPD_F (void) component_free LDAP_P(( + ComponentFilter *f )); + +LDAP_SLAPD_F (void) free_ComponentData LDAP_P(( + Attribute *a )); + +LDAP_SLAPD_V (test_membership_func*) is_aliased_attribute; + +LDAP_SLAPD_V (free_component_func*) component_destructor; + +LDAP_SLAPD_V (get_component_info_func*) get_component_description; + +LDAP_SLAPD_V (component_encoder_func*) component_encoder; + +LDAP_SLAPD_V (convert_attr_to_comp_func*) attr_converter; + +LDAP_SLAPD_V (alloc_nibble_func*) nibble_mem_allocator; + +LDAP_SLAPD_V (free_nibble_func*) nibble_mem_free; +#endif + +/* + * controls.c + */ +LDAP_SLAPD_V( struct slap_control_ids ) slap_cids; +LDAP_SLAPD_F (void) slap_free_ctrls LDAP_P(( + Operation *op, + LDAPControl **ctrls )); +LDAP_SLAPD_F (int) slap_add_ctrls LDAP_P(( + Operation *op, + SlapReply *rs, + LDAPControl **ctrls )); +LDAP_SLAPD_F (int) slap_parse_ctrl LDAP_P(( + Operation *op, + SlapReply *rs, + LDAPControl *control, + const char **text )); +LDAP_SLAPD_F (int) get_ctrls LDAP_P(( + Operation *op, + SlapReply *rs, + int senderrors )); +LDAP_SLAPD_F (int) register_supported_control2 LDAP_P(( + const char *controloid, + slap_mask_t controlmask, + char **controlexops, + SLAP_CTRL_PARSE_FN *controlparsefn, + unsigned flags, + int *controlcid )); +#define register_supported_control(oid, mask, exops, fn, cid) \ + register_supported_control2((oid), (mask), (exops), (fn), 0, (cid)) +#ifdef SLAP_CONFIG_DELETE +LDAP_SLAPD_F (int) unregister_supported_control LDAP_P(( + const char* controloid )); +#endif /* SLAP_CONFIG_DELETE */ +LDAP_SLAPD_F (int) slap_controls_init LDAP_P ((void)); +LDAP_SLAPD_F (void) controls_destroy LDAP_P ((void)); +LDAP_SLAPD_F (int) controls_root_dse_info LDAP_P ((Entry *e)); +LDAP_SLAPD_F (int) get_supported_controls LDAP_P (( + char ***ctrloidsp, slap_mask_t **ctrlmasks )); +LDAP_SLAPD_F (int) slap_find_control_id LDAP_P (( + const char *oid, int *cid )); +LDAP_SLAPD_F (int) slap_global_control LDAP_P (( + Operation *op, const char *oid, int *cid )); +LDAP_SLAPD_F (int) slap_remove_control LDAP_P(( + Operation *op, + SlapReply *rs, + int ctrl, + BI_chk_controls fnc )); + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING +LDAP_SLAPD_F (int) +slap_ctrl_session_tracking_add LDAP_P(( + Operation *op, + SlapReply *rs, + struct berval *ip, + struct berval *name, + struct berval *id, + LDAPControl *ctrl )); +LDAP_SLAPD_F (int) +slap_ctrl_session_tracking_request_add LDAP_P(( + Operation *op, SlapReply *rs, LDAPControl *ctrl )); +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ +#ifdef SLAP_CONTROL_X_WHATFAILED +LDAP_SLAPD_F (int) +slap_ctrl_whatFailed_add LDAP_P(( + Operation *op, + SlapReply *rs, + char **oids )); +#endif /* SLAP_CONTROL_X_WHATFAILED */ + +/* + * config.c + */ +LDAP_SLAPD_F (int) read_config LDAP_P(( const char *fname, const char *dir )); +LDAP_SLAPD_F (void) config_destroy LDAP_P ((void)); +LDAP_SLAPD_F (char **) slap_str2clist LDAP_P(( + char ***, char *, const char * )); +LDAP_SLAPD_F (int) bverb_to_mask LDAP_P(( + struct berval *bword, slap_verbmasks *v )); +LDAP_SLAPD_F (int) verb_to_mask LDAP_P(( + const char *word, slap_verbmasks *v )); +LDAP_SLAPD_F (int) verbs_to_mask LDAP_P(( + int argc, char *argv[], slap_verbmasks *v, slap_mask_t *m )); +LDAP_SLAPD_F (int) mask_to_verbs LDAP_P(( + slap_verbmasks *v, slap_mask_t m, BerVarray *bva )); +LDAP_SLAPD_F (int) mask_to_verbstring LDAP_P(( + slap_verbmasks *v, slap_mask_t m, char delim, struct berval *bv )); +LDAP_SLAPD_F (int) verbstring_to_mask LDAP_P(( + slap_verbmasks *v, char *str, char delim, slap_mask_t *m )); +LDAP_SLAPD_F (int) enum_to_verb LDAP_P(( + slap_verbmasks *v, slap_mask_t m, struct berval *bv )); +LDAP_SLAPD_F (int) slap_verbmasks_init LDAP_P(( slap_verbmasks **vp, slap_verbmasks *v )); +LDAP_SLAPD_F (int) slap_verbmasks_destroy LDAP_P(( slap_verbmasks *v )); +LDAP_SLAPD_F (int) slap_verbmasks_append LDAP_P(( slap_verbmasks **vp, + slap_mask_t m, struct berval *v, slap_mask_t *ignore )); +LDAP_SLAPD_F (int) slap_tls_get_config LDAP_P(( + LDAP *ld, int opt, char **val )); +LDAP_SLAPD_F (void) bindconf_tls_defaults LDAP_P(( slap_bindconf *bc )); +LDAP_SLAPD_F (int) bindconf_tls_parse LDAP_P(( + const char *word, slap_bindconf *bc )); +LDAP_SLAPD_F (int) bindconf_tls_unparse LDAP_P(( + slap_bindconf *bc, struct berval *bv )); +LDAP_SLAPD_F (int) bindconf_parse LDAP_P(( + const char *word, slap_bindconf *bc )); +LDAP_SLAPD_F (int) bindconf_unparse LDAP_P(( + slap_bindconf *bc, struct berval *bv )); +LDAP_SLAPD_F (int) bindconf_tls_set LDAP_P(( + slap_bindconf *bc, LDAP *ld )); +LDAP_SLAPD_F (void) bindconf_free LDAP_P(( slap_bindconf *bc )); +LDAP_SLAPD_F (int) slap_client_connect LDAP_P(( LDAP **ldp, slap_bindconf *sb )); +LDAP_SLAPD_F (int) config_generic_wrapper LDAP_P(( Backend *be, + const char *fname, int lineno, int argc, char **argv )); +LDAP_SLAPD_F (char *) anlist_unparse LDAP_P(( AttributeName *, char *, ber_len_t buflen )); +LDAP_SLAPD_F (int) slap_keepalive_parse( struct berval *val, void *bc, + slap_cf_aux_table *tab0, const char *tabmsg, int unparse ); + +#ifdef LDAP_SLAPI +LDAP_SLAPD_V (int) slapi_plugins_used; +#endif + +/* + * connection.c + */ +LDAP_SLAPD_F (int) connections_init LDAP_P((void)); +LDAP_SLAPD_F (int) connections_shutdown LDAP_P((void)); +LDAP_SLAPD_F (int) connections_destroy LDAP_P((void)); +LDAP_SLAPD_F (int) connections_timeout_idle LDAP_P((time_t)); +LDAP_SLAPD_F (void) connections_drop LDAP_P((void)); + +LDAP_SLAPD_F (Connection *) connection_client_setup LDAP_P(( + ber_socket_t s, + ldap_pvt_thread_start_t *func, + void *arg )); +LDAP_SLAPD_F (void) connection_client_enable LDAP_P(( Connection *c )); +LDAP_SLAPD_F (void) connection_client_stop LDAP_P(( Connection *c )); + +#ifdef LDAP_PF_LOCAL_SENDMSG +#define LDAP_PF_LOCAL_SENDMSG_ARG(arg) , arg +#else +#define LDAP_PF_LOCAL_SENDMSG_ARG(arg) +#endif + +LDAP_SLAPD_F (Connection *) connection_init LDAP_P(( + ber_socket_t s, + Listener* url, + const char* dnsname, + const char* peername, + int use_tls, + slap_ssf_t ssf, + struct berval *id + LDAP_PF_LOCAL_SENDMSG_ARG(struct berval *peerbv))); + +LDAP_SLAPD_F (void) connection_closing LDAP_P(( + Connection *c, const char *why )); +LDAP_SLAPD_F (int) connection_valid LDAP_P(( Connection *c )); +LDAP_SLAPD_F (const char *) connection_state2str LDAP_P(( int state )) + LDAP_GCCATTR((const)); + +LDAP_SLAPD_F (int) connection_read_activate LDAP_P((ber_socket_t s)); +LDAP_SLAPD_F (int) connection_write LDAP_P((ber_socket_t s)); + +LDAP_SLAPD_F (unsigned long) connections_nextid(void); + +LDAP_SLAPD_F (Connection *) connection_first LDAP_P(( ber_socket_t * )); +LDAP_SLAPD_F (Connection *) connection_next LDAP_P(( + Connection *, ber_socket_t *)); +LDAP_SLAPD_F (void) connection_done LDAP_P((Connection *)); + +LDAP_SLAPD_F (void) connection2anonymous LDAP_P((Connection *)); +LDAP_SLAPD_F (void) connection_fake_init LDAP_P(( + Connection *conn, + OperationBuffer *opbuf, + void *threadctx )); +LDAP_SLAPD_F (void) connection_fake_init2 LDAP_P(( + Connection *conn, + OperationBuffer *opbuf, + void *threadctx, + int newmem )); +LDAP_SLAPD_F (void) operation_fake_init LDAP_P(( + Connection *conn, + Operation *op, + void *threadctx, + int newmem )); +LDAP_SLAPD_F (void) connection_assign_nextid LDAP_P((Connection *)); + +/* + * cr.c + */ +LDAP_SLAPD_F (int) cr_schema_info( Entry *e ); +LDAP_SLAPD_F (void) cr_unparse LDAP_P(( + BerVarray *bva, ContentRule *start, ContentRule *end, int system )); + +LDAP_SLAPD_F (int) cr_add LDAP_P(( + LDAPContentRule *oc, + int user, + ContentRule **scr, + const char **err)); + +LDAP_SLAPD_F (void) cr_destroy LDAP_P(( void )); + +LDAP_SLAPD_F (ContentRule *) cr_find LDAP_P(( + const char *crname)); +LDAP_SLAPD_F (ContentRule *) cr_bvfind LDAP_P(( + struct berval *crname)); + +/* + * ctxcsn.c + */ + +LDAP_SLAPD_V( int ) slap_serverID; +LDAP_SLAPD_V( const struct berval ) slap_ldapsync_bv; +LDAP_SLAPD_V( const struct berval ) slap_ldapsync_cn_bv; +LDAP_SLAPD_F (void) slap_get_commit_csn LDAP_P(( + Operation *, struct berval *maxcsn, int *foundit )); +LDAP_SLAPD_F (void) slap_rewind_commit_csn LDAP_P(( Operation * )); +LDAP_SLAPD_F (void) slap_graduate_commit_csn LDAP_P(( Operation * )); +LDAP_SLAPD_F (Entry *) slap_create_context_csn_entry LDAP_P(( Backend *, struct berval *)); +LDAP_SLAPD_F (int) slap_get_csn LDAP_P(( Operation *, struct berval *, int )); +LDAP_SLAPD_F (void) slap_queue_csn LDAP_P(( Operation *, struct berval * )); + +/* + * daemon.c + */ +LDAP_SLAPD_F (void) slapd_add_internal(ber_socket_t s, int isactive); +LDAP_SLAPD_F (int) slapd_daemon_init( const char *urls ); +LDAP_SLAPD_F (int) slapd_daemon_destroy(void); +LDAP_SLAPD_F (int) slapd_daemon(void); +LDAP_SLAPD_F (Listener **) slapd_get_listeners LDAP_P((void)); +LDAP_SLAPD_F (void) slapd_remove LDAP_P((ber_socket_t s, Sockbuf *sb, + int wasactive, int wake, int locked )); + +LDAP_SLAPD_F (RETSIGTYPE) slap_sig_shutdown LDAP_P((int sig)); +LDAP_SLAPD_F (RETSIGTYPE) slap_sig_wake LDAP_P((int sig)); +LDAP_SLAPD_F (void) slap_wake_listener LDAP_P((void)); + +LDAP_SLAPD_F (void) slap_suspend_listeners LDAP_P((void)); +LDAP_SLAPD_F (void) slap_resume_listeners LDAP_P((void)); + +LDAP_SLAPD_F (void) slapd_set_write LDAP_P((ber_socket_t s, int wake)); +LDAP_SLAPD_F (void) slapd_clr_write LDAP_P((ber_socket_t s, int wake)); +LDAP_SLAPD_F (void) slapd_set_read LDAP_P((ber_socket_t s, int wake)); +LDAP_SLAPD_F (int) slapd_clr_read LDAP_P((ber_socket_t s, int wake)); +LDAP_SLAPD_F (void) slapd_clr_writetime LDAP_P((time_t old)); +LDAP_SLAPD_F (time_t) slapd_get_writetime LDAP_P((void)); + +LDAP_SLAPD_V (volatile sig_atomic_t) slapd_abrupt_shutdown; +LDAP_SLAPD_V (volatile sig_atomic_t) slapd_shutdown; +LDAP_SLAPD_V (int) slapd_register_slp; +LDAP_SLAPD_V (const char *) slapd_slp_attrs; +LDAP_SLAPD_V (slap_ssf_t) local_ssf; +LDAP_SLAPD_V (struct runqueue_s) slapd_rq; +LDAP_SLAPD_V (int) slapd_daemon_threads; +LDAP_SLAPD_V (int) slapd_daemon_mask; +#ifdef LDAP_TCP_BUFFER +LDAP_SLAPD_V (int) slapd_tcp_rmem; +LDAP_SLAPD_V (int) slapd_tcp_wmem; +#endif /* LDAP_TCP_BUFFER */ + +#ifdef HAVE_WINSOCK +LDAP_SLAPD_F (ber_socket_t) slapd_socknew(ber_socket_t s); +LDAP_SLAPD_F (ber_socket_t) slapd_sock2fd(ber_socket_t s); +LDAP_SLAPD_V (SOCKET *) slapd_ws_sockets; +#define SLAP_FD2SOCK(s) slapd_ws_sockets[s] +#define SLAP_SOCK2FD(s) slapd_sock2fd(s) +#define SLAP_SOCKNEW(s) slapd_socknew(s) +#else +#define SLAP_FD2SOCK(s) s +#define SLAP_SOCK2FD(s) s +#define SLAP_SOCKNEW(s) s +#endif + +/* + * dn.c + */ + +#define dn_match(dn1, dn2) ( ber_bvcmp((dn1), (dn2)) == 0 ) +#define bvmatch(bv1, bv2) ( ((bv1)->bv_len == (bv2)->bv_len) && (memcmp((bv1)->bv_val, (bv2)->bv_val, (bv1)->bv_len) == 0) ) + +LDAP_SLAPD_F (int) dnValidate LDAP_P(( + Syntax *syntax, + struct berval *val )); +LDAP_SLAPD_F (int) rdnValidate LDAP_P(( + Syntax *syntax, + struct berval *val )); + +LDAP_SLAPD_F (slap_mr_normalize_func) dnNormalize; + +LDAP_SLAPD_F (slap_mr_normalize_func) rdnNormalize; + +LDAP_SLAPD_F (slap_syntax_transform_func) dnPretty; + +LDAP_SLAPD_F (slap_syntax_transform_func) rdnPretty; + +LDAP_SLAPD_F (int) dnPrettyNormal LDAP_P(( + Syntax *syntax, + struct berval *val, + struct berval *pretty, + struct berval *normal, + void *ctx )); + +LDAP_SLAPD_F (int) dnMatch LDAP_P(( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue )); + +LDAP_SLAPD_F (int) dnRelativeMatch LDAP_P(( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue )); + +LDAP_SLAPD_F (int) rdnMatch LDAP_P(( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue )); + + +LDAP_SLAPD_F (int) dnIsSuffix LDAP_P(( + const struct berval *dn, const struct berval *suffix )); + +LDAP_SLAPD_F (int) dnIsWithinScope LDAP_P(( + struct berval *ndn, struct berval *nbase, int scope )); + +LDAP_SLAPD_F (int) dnIsSuffixScope LDAP_P(( + struct berval *ndn, struct berval *nbase, int scope )); + +LDAP_SLAPD_F (int) dnIsOneLevelRDN LDAP_P(( struct berval *rdn )); + +LDAP_SLAPD_F (int) dnExtractRdn LDAP_P(( + struct berval *dn, struct berval *rdn, void *ctx )); + +LDAP_SLAPD_F (int) rdn_validate LDAP_P(( struct berval * rdn )); + +LDAP_SLAPD_F (ber_len_t) dn_rdnlen LDAP_P(( Backend *be, struct berval *dn )); + +LDAP_SLAPD_F (void) build_new_dn LDAP_P(( + struct berval * new_dn, + struct berval * parent_dn, + struct berval * newrdn, + void *memctx )); + +LDAP_SLAPD_F (void) dnParent LDAP_P(( struct berval *dn, struct berval *pdn )); +LDAP_SLAPD_F (void) dnRdn LDAP_P(( struct berval *dn, struct berval *rdn )); + +LDAP_SLAPD_F (int) dnX509normalize LDAP_P(( void *x509_name, struct berval *out )); + +LDAP_SLAPD_F (int) dnX509peerNormalize LDAP_P(( void *ssl, struct berval *dn )); + +LDAP_SLAPD_F (int) dnPrettyNormalDN LDAP_P(( Syntax *syntax, struct berval *val, LDAPDN *dn, int flags, void *ctx )); +#define dnPrettyDN(syntax, val, dn, ctx) \ + dnPrettyNormalDN((syntax),(val),(dn), SLAP_LDAPDN_PRETTY, ctx) +#define dnNormalDN(syntax, val, dn, ctx) \ + dnPrettyNormalDN((syntax),(val),(dn), 0, ctx) + +typedef int (SLAP_CERT_MAP_FN) LDAP_P(( void *ssl, struct berval *dn )); +LDAP_SLAPD_F (int) register_certificate_map_function LDAP_P(( SLAP_CERT_MAP_FN *fn )); + +/* + * entry.c + */ +LDAP_SLAPD_V (const Entry) slap_entry_root; + +LDAP_SLAPD_F (int) entry_init LDAP_P((void)); +LDAP_SLAPD_F (int) entry_destroy LDAP_P((void)); + +LDAP_SLAPD_F (Entry *) str2entry LDAP_P(( char *s )); +LDAP_SLAPD_F (Entry *) str2entry2 LDAP_P(( char *s, int checkvals )); +LDAP_SLAPD_F (char *) entry2str LDAP_P(( Entry *e, int *len )); +LDAP_SLAPD_F (char *) entry2str_wrap LDAP_P(( Entry *e, int *len, ber_len_t wrap )); + +LDAP_SLAPD_F (ber_len_t) entry_flatsize LDAP_P(( Entry *e, int norm )); +LDAP_SLAPD_F (void) entry_partsize LDAP_P(( Entry *e, ber_len_t *len, + int *nattrs, int *nvals, int norm )); + +LDAP_SLAPD_F (int) entry_header LDAP_P(( EntryHeader *eh )); +LDAP_SLAPD_F (int) entry_decode_dn LDAP_P(( + EntryHeader *eh, struct berval *dn, struct berval *ndn )); +#ifdef SLAP_ZONE_ALLOC +LDAP_SLAPD_F (int) entry_decode LDAP_P(( + EntryHeader *eh, Entry **e, void *ctx )); +#else +LDAP_SLAPD_F (int) entry_decode LDAP_P(( + EntryHeader *eh, Entry **e )); +#endif +LDAP_SLAPD_F (int) entry_encode LDAP_P(( Entry *e, struct berval *bv )); + +LDAP_SLAPD_F (void) entry_clean LDAP_P(( Entry *e )); +LDAP_SLAPD_F (void) entry_free LDAP_P(( Entry *e )); +LDAP_SLAPD_F (int) entry_cmp LDAP_P(( Entry *a, Entry *b )); +LDAP_SLAPD_F (int) entry_dn_cmp LDAP_P(( const void *v_a, const void *v_b )); +LDAP_SLAPD_F (int) entry_id_cmp LDAP_P(( const void *v_a, const void *v_b )); +LDAP_SLAPD_F (Entry *) entry_dup LDAP_P(( Entry *e )); +LDAP_SLAPD_F (Entry *) entry_dup2 LDAP_P(( Entry *dest, Entry *src )); +LDAP_SLAPD_F (Entry *) entry_dup_bv LDAP_P(( Entry *e )); +LDAP_SLAPD_F (Entry *) entry_alloc LDAP_P((void)); +LDAP_SLAPD_F (int) entry_prealloc LDAP_P((int num)); + +/* + * extended.c + */ +LDAP_SLAPD_F (int) exop_root_dse_info LDAP_P ((Entry *e)); + +#define exop_is_write( op ) ((op->ore_flags & SLAP_EXOP_WRITES) != 0) + +LDAP_SLAPD_V( const struct berval ) slap_EXOP_CANCEL; +LDAP_SLAPD_V( const struct berval ) slap_EXOP_WHOAMI; +LDAP_SLAPD_V( const struct berval ) slap_EXOP_MODIFY_PASSWD; +LDAP_SLAPD_V( const struct berval ) slap_EXOP_START_TLS; +#ifdef LDAP_X_TXN +LDAP_SLAPD_V( const struct berval ) slap_EXOP_TXN_START; +LDAP_SLAPD_V( const struct berval ) slap_EXOP_TXN_END; +#endif + +typedef int (SLAP_EXTOP_MAIN_FN) LDAP_P(( Operation *op, SlapReply *rs )); + +typedef int (SLAP_EXTOP_GETOID_FN) LDAP_P(( + int index, struct berval *oid, int blen )); + +LDAP_SLAPD_F (int) load_extop2 LDAP_P(( + const struct berval *ext_oid, + slap_mask_t flags, + SLAP_EXTOP_MAIN_FN *ext_main, + unsigned tmpflags )); +#define load_extop(ext_oid, flags, ext_main) \ + load_extop2((ext_oid), (flags), (ext_main), 0) + +LDAP_SLAPD_F (int) extops_init LDAP_P(( void )); + +LDAP_SLAPD_F (int) extops_kill LDAP_P(( void )); + +LDAP_SLAPD_F (struct berval *) get_supported_extop LDAP_P((int index)); + +/* + * txn.c + */ +#ifdef LDAP_X_TXN +LDAP_SLAPD_F ( SLAP_CTRL_PARSE_FN ) txn_spec_ctrl; +LDAP_SLAPD_F ( SLAP_EXTOP_MAIN_FN ) txn_start_extop; +LDAP_SLAPD_F ( SLAP_EXTOP_MAIN_FN ) txn_end_extop; +#endif + +/* + * cancel.c + */ +LDAP_SLAPD_F ( SLAP_EXTOP_MAIN_FN ) cancel_extop; + +/* + * filter.c + */ +LDAP_SLAPD_F (int) get_filter LDAP_P(( + Operation *op, + BerElement *ber, + Filter **filt, + const char **text )); + +LDAP_SLAPD_F (void) filter_free LDAP_P(( Filter *f )); +LDAP_SLAPD_F (void) filter_free_x LDAP_P(( Operation *op, Filter *f, int freeme )); +LDAP_SLAPD_F (void) filter2bv LDAP_P(( Filter *f, struct berval *bv )); +LDAP_SLAPD_F (void) filter2bv_x LDAP_P(( Operation *op, Filter *f, struct berval *bv )); +LDAP_SLAPD_F (void) filter2bv_undef LDAP_P(( Filter *f, int noundef, struct berval *bv )); +LDAP_SLAPD_F (void) filter2bv_undef_x LDAP_P(( Operation *op, Filter *f, int noundef, struct berval *bv )); +LDAP_SLAPD_F (Filter *) filter_dup LDAP_P(( Filter *f, void *memctx )); + +LDAP_SLAPD_F (int) get_vrFilter LDAP_P(( Operation *op, BerElement *ber, + ValuesReturnFilter **f, + const char **text )); + +LDAP_SLAPD_F (void) vrFilter_free LDAP_P(( Operation *op, ValuesReturnFilter *f )); +LDAP_SLAPD_F (void) vrFilter2bv LDAP_P(( Operation *op, ValuesReturnFilter *f, struct berval *fstr )); + +LDAP_SLAPD_F (int) filter_has_subordinates LDAP_P(( Filter *filter )); +#define filter_escape_value( in, out ) ldap_bv2escaped_filter_value_x( (in), (out), 0, NULL ) +#define filter_escape_value_x( in, out, ctx ) ldap_bv2escaped_filter_value_x( (in), (out), 0, ctx ) + +LDAP_SLAPD_V (const Filter *) slap_filter_objectClass_pres; +LDAP_SLAPD_V (const struct berval *) slap_filterstr_objectClass_pres; + +LDAP_SLAPD_F (int) filter_init LDAP_P(( void )); +LDAP_SLAPD_F (void) filter_destroy LDAP_P(( void )); +/* + * filterentry.c + */ + +LDAP_SLAPD_F (int) test_filter LDAP_P(( Operation *op, Entry *e, Filter *f )); + +/* + * frontend.c + */ +LDAP_SLAPD_F (int) frontend_init LDAP_P(( void )); + +/* + * globals.c + */ + +LDAP_SLAPD_V( const struct berval ) slap_empty_bv; +LDAP_SLAPD_V( const struct berval ) slap_unknown_bv; +LDAP_SLAPD_V( const struct berval ) slap_true_bv; +LDAP_SLAPD_V( const struct berval ) slap_false_bv; +LDAP_SLAPD_V( struct slap_sync_cookie_s ) slap_sync_cookie; +LDAP_SLAPD_V( void * ) slap_tls_ctx; +LDAP_SLAPD_V( LDAP * ) slap_tls_ld; + +/* + * index.c + */ +LDAP_SLAPD_F (int) slap_str2index LDAP_P(( const char *str, slap_mask_t *idx )); +LDAP_SLAPD_F (void) slap_index2bvlen LDAP_P(( slap_mask_t idx, struct berval *bv )); +LDAP_SLAPD_F (void) slap_index2bv LDAP_P(( slap_mask_t idx, struct berval *bv )); + +/* + * init.c + */ +LDAP_SLAPD_F (int) slap_init LDAP_P((int mode, const char* name)); +LDAP_SLAPD_F (int) slap_startup LDAP_P(( Backend *be )); +LDAP_SLAPD_F (int) slap_shutdown LDAP_P(( Backend *be )); +LDAP_SLAPD_F (int) slap_destroy LDAP_P((void)); +LDAP_SLAPD_F (void) slap_counters_init LDAP_P((slap_counters_t *sc)); +LDAP_SLAPD_F (void) slap_counters_destroy LDAP_P((slap_counters_t *sc)); + +LDAP_SLAPD_V (char *) slap_known_controls[]; + +/* + * ldapsync.c + */ +LDAP_SLAPD_F (void) slap_compose_sync_cookie LDAP_P(( + Operation *, struct berval *, BerVarray, int, int )); +LDAP_SLAPD_F (void) slap_sync_cookie_free LDAP_P(( + struct sync_cookie *, int free_cookie )); +LDAP_SLAPD_F (int) slap_parse_csn_sid LDAP_P(( + struct berval * )); +LDAP_SLAPD_F (int *) slap_parse_csn_sids LDAP_P(( + BerVarray, int, void *memctx )); +LDAP_SLAPD_F (int) slap_sort_csn_sids LDAP_P(( + BerVarray, int *, int, void *memctx )); +LDAP_SLAPD_F (void) slap_insert_csn_sids LDAP_P(( + struct sync_cookie *ck, int, int, struct berval * )); +LDAP_SLAPD_F (int) slap_parse_sync_cookie LDAP_P(( + struct sync_cookie *, void *memctx )); +LDAP_SLAPD_F (void) slap_reparse_sync_cookie LDAP_P(( + struct sync_cookie *, void *memctx )); +LDAP_SLAPD_F (int) slap_init_sync_cookie_ctxcsn LDAP_P(( + struct sync_cookie * )); +LDAP_SLAPD_F (struct sync_cookie *) slap_dup_sync_cookie LDAP_P(( + struct sync_cookie *, struct sync_cookie * )); +LDAP_SLAPD_F (int) slap_build_syncUUID_set LDAP_P(( + Operation *, BerVarray *, Entry * )); + +/* + * limits.c + */ +LDAP_SLAPD_F (int) limits_parse LDAP_P(( + Backend *be, const char *fname, int lineno, + int argc, char **argv )); +LDAP_SLAPD_F (int) limits_parse_one LDAP_P(( const char *arg, + struct slap_limits_set *limit )); +LDAP_SLAPD_F (int) limits_check LDAP_P(( + Operation *op, SlapReply *rs )); +LDAP_SLAPD_F (int) limits_unparse_one LDAP_P(( + struct slap_limits_set *limit, int which, struct berval *bv, ber_len_t buflen )); +LDAP_SLAPD_F (int) limits_unparse LDAP_P(( + struct slap_limits *limit, struct berval *bv, ber_len_t buflen )); +LDAP_SLAPD_F (void) limits_free_one LDAP_P(( + struct slap_limits *lm )); +LDAP_SLAPD_F (void) limits_destroy LDAP_P(( struct slap_limits **lm )); + +/* + * lock.c + */ +LDAP_SLAPD_F (FILE *) lock_fopen LDAP_P(( const char *fname, + const char *type, FILE **lfp )); +LDAP_SLAPD_F (int) lock_fclose LDAP_P(( FILE *fp, FILE *lfp )); + +/* + * main.c + */ +LDAP_SLAPD_F (int) +parse_debug_level LDAP_P(( const char *arg, int *levelp, char ***unknowns )); +LDAP_SLAPD_F (int) +parse_syslog_level LDAP_P(( const char *arg, int *levelp )); +LDAP_SLAPD_F (int) +parse_syslog_user LDAP_P(( const char *arg, int *syslogUser )); +LDAP_SLAPD_F (int) +parse_debug_unknowns LDAP_P(( char **unknowns, int *levelp )); + +/* + * matchedValues.c + */ +LDAP_SLAPD_F (int) filter_matched_values( + Operation *op, + Attribute *a, + char ***e_flags ); + +/* + * modrdn.c + */ +LDAP_SLAPD_F (int) slap_modrdn2mods LDAP_P(( + Operation *op, + SlapReply *rs )); + +/* + * modify.c + */ +LDAP_SLAPD_F( int ) slap_mods_obsolete_check( + Operation *op, + Modifications *ml, + const char **text, + char *textbuf, size_t textlen ); + +LDAP_SLAPD_F( int ) slap_mods_no_user_mod_check( + Operation *op, + Modifications *ml, + const char **text, + char *textbuf, size_t textlen ); + +LDAP_SLAPD_F ( int ) slap_mods_no_repl_user_mod_check( + Operation *op, + Modifications *ml, + const char **text, + char *textbuf, + size_t textlen ); + +LDAP_SLAPD_F( int ) slap_mods_check( + Operation *op, + Modifications *ml, + const char **text, + char *textbuf, size_t textlen, void *ctx ); + +LDAP_SLAPD_F( int ) slap_sort_vals( + Modifications *ml, + const char **text, + int *dup, + void *ctx ); + +LDAP_SLAPD_F( void ) slap_timestamp( + time_t *tm, + struct berval *bv ); + +LDAP_SLAPD_F( void ) slap_mods_opattrs( + Operation *op, + Modifications **modsp, + int manage_ctxcsn ); + +LDAP_SLAPD_F( int ) slap_parse_modlist( + Operation *op, + SlapReply *rs, + BerElement *ber, + req_modify_s *ms ); + +/* + * mods.c + */ +LDAP_SLAPD_F( int ) modify_add_values( Entry *e, + Modification *mod, + int permissive, + const char **text, char *textbuf, size_t textlen ); +LDAP_SLAPD_F( int ) modify_delete_values( Entry *e, + Modification *mod, + int permissive, + const char **text, char *textbuf, size_t textlen ); +LDAP_SLAPD_F( int ) modify_delete_vindex( Entry *e, + Modification *mod, + int permissive, + const char **text, char *textbuf, size_t textlen, int *idx ); +LDAP_SLAPD_F( int ) modify_replace_values( Entry *e, + Modification *mod, + int permissive, + const char **text, char *textbuf, size_t textlen ); +LDAP_SLAPD_F( int ) modify_increment_values( Entry *e, + Modification *mod, + int permissive, + const char **text, char *textbuf, size_t textlen ); + +LDAP_SLAPD_F( void ) slap_mod_free( Modification *mod, int freeit ); +LDAP_SLAPD_F( void ) slap_mods_free( Modifications *mods, int freevals ); +LDAP_SLAPD_F( void ) slap_modlist_free( LDAPModList *ml ); + +/* + * module.c + */ +#ifdef SLAPD_MODULES + +LDAP_SLAPD_F (int) module_init LDAP_P(( void )); +LDAP_SLAPD_F (int) module_kill LDAP_P(( void )); + +LDAP_SLAPD_F (int) load_null_module( + const void *module, const char *file_name); +LDAP_SLAPD_F (int) load_extop_module( + const void *module, const char *file_name); + +LDAP_SLAPD_F (int) module_load LDAP_P(( + const char* file_name, + int argc, char *argv[] )); +LDAP_SLAPD_F (int) module_path LDAP_P(( const char* path )); +LDAP_SLAPD_F (int) module_unload LDAP_P(( const char* file_name )); + +LDAP_SLAPD_F (void *) module_handle LDAP_P(( const char* file_name )); + +LDAP_SLAPD_F (void *) module_resolve LDAP_P(( + const void *module, const char *name)); + +#endif /* SLAPD_MODULES */ + +/* mr.c */ +LDAP_SLAPD_F (MatchingRule *) mr_bvfind LDAP_P((struct berval *mrname)); +LDAP_SLAPD_F (MatchingRule *) mr_find LDAP_P((const char *mrname)); +LDAP_SLAPD_F (int) mr_add LDAP_P(( LDAPMatchingRule *mr, + slap_mrule_defs_rec *def, + MatchingRule * associated, + const char **err )); +LDAP_SLAPD_F (void) mr_destroy LDAP_P(( void )); + +LDAP_SLAPD_F (int) register_matching_rule LDAP_P(( + slap_mrule_defs_rec *def )); + +LDAP_SLAPD_F (void) mru_destroy LDAP_P(( void )); +LDAP_SLAPD_F (int) matching_rule_use_init LDAP_P(( void )); + +LDAP_SLAPD_F (int) mr_schema_info LDAP_P(( Entry *e )); +LDAP_SLAPD_F (int) mru_schema_info LDAP_P(( Entry *e )); + +LDAP_SLAPD_F (int) mr_usable_with_at LDAP_P(( MatchingRule *mr, + AttributeType *at )); +LDAP_SLAPD_F (int) mr_make_syntax_compat_with_mr LDAP_P(( + Syntax *syn, + MatchingRule *mr )); +LDAP_SLAPD_F (int) mr_make_syntax_compat_with_mrs LDAP_P(( + const char *syntax, + char *const *mrs )); + +/* + * mra.c + */ +LDAP_SLAPD_F (int) get_mra LDAP_P(( + Operation *op, + BerElement *ber, + Filter *f, + const char **text )); +LDAP_SLAPD_F (void) mra_free LDAP_P(( + Operation *op, + MatchingRuleAssertion *mra, + int freeit )); + +/* oc.c */ +LDAP_SLAPD_F (int) oc_add LDAP_P(( + LDAPObjectClass *oc, + int user, + ObjectClass **soc, + ObjectClass *prev, + const char **err)); +LDAP_SLAPD_F (void) oc_destroy LDAP_P(( void )); + +LDAP_SLAPD_F (ObjectClass *) oc_find LDAP_P(( + const char *ocname)); +LDAP_SLAPD_F (ObjectClass *) oc_bvfind LDAP_P(( + struct berval *ocname)); +LDAP_SLAPD_F (ObjectClass *) oc_bvfind_undef LDAP_P(( + struct berval *ocname)); +LDAP_SLAPD_F (int) is_object_subclass LDAP_P(( + ObjectClass *sup, + ObjectClass *sub )); + +LDAP_SLAPD_F (int) is_entry_objectclass LDAP_P(( + Entry *, ObjectClass *oc, unsigned flags )); +#define is_entry_objectclass_or_sub(e,oc) \ + (is_entry_objectclass((e),(oc),SLAP_OCF_CHECK_SUP)) +#define is_entry_alias(e) \ + (((e)->e_ocflags & SLAP_OC__END) \ + ? (((e)->e_ocflags & SLAP_OC_ALIAS) != 0) \ + : is_entry_objectclass((e), slap_schema.si_oc_alias, SLAP_OCF_SET_FLAGS)) +#define is_entry_referral(e) \ + (((e)->e_ocflags & SLAP_OC__END) \ + ? (((e)->e_ocflags & SLAP_OC_REFERRAL) != 0) \ + : is_entry_objectclass((e), slap_schema.si_oc_referral, SLAP_OCF_SET_FLAGS)) +#define is_entry_subentry(e) \ + (((e)->e_ocflags & SLAP_OC__END) \ + ? (((e)->e_ocflags & SLAP_OC_SUBENTRY) != 0) \ + : is_entry_objectclass((e), slap_schema.si_oc_subentry, SLAP_OCF_SET_FLAGS)) +#define is_entry_collectiveAttributeSubentry(e) \ + (((e)->e_ocflags & SLAP_OC__END) \ + ? (((e)->e_ocflags & SLAP_OC_COLLECTIVEATTRIBUTESUBENTRY) != 0) \ + : is_entry_objectclass((e), slap_schema.si_oc_collectiveAttributeSubentry, SLAP_OCF_SET_FLAGS)) +#define is_entry_dynamicObject(e) \ + (((e)->e_ocflags & SLAP_OC__END) \ + ? (((e)->e_ocflags & SLAP_OC_DYNAMICOBJECT) != 0) \ + : is_entry_objectclass((e), slap_schema.si_oc_dynamicObject, SLAP_OCF_SET_FLAGS)) +#define is_entry_glue(e) \ + (((e)->e_ocflags & SLAP_OC__END) \ + ? (((e)->e_ocflags & SLAP_OC_GLUE) != 0) \ + : is_entry_objectclass((e), slap_schema.si_oc_glue, SLAP_OCF_SET_FLAGS)) +#define is_entry_syncProviderSubentry(e) \ + (((e)->e_ocflags & SLAP_OC__END) \ + ? (((e)->e_ocflags & SLAP_OC_SYNCPROVIDERSUBENTRY) != 0) \ + : is_entry_objectclass((e), slap_schema.si_oc_syncProviderSubentry, SLAP_OCF_SET_FLAGS)) +#define is_entry_syncConsumerSubentry(e) \ + (((e)->e_ocflags & SLAP_OC__END) \ + ? (((e)->e_ocflags & SLAP_OC_SYNCCONSUMERSUBENTRY) != 0) \ + : is_entry_objectclass((e), slap_schema.si_oc_syncConsumerSubentry, SLAP_OCF_SET_FLAGS)) + +LDAP_SLAPD_F (int) oc_schema_info( Entry *e ); + +LDAP_SLAPD_F (int) oc_start LDAP_P(( ObjectClass **oc )); +LDAP_SLAPD_F (int) oc_next LDAP_P(( ObjectClass **oc )); +LDAP_SLAPD_F (void) oc_delete LDAP_P(( ObjectClass *oc )); + +LDAP_SLAPD_F (void) oc_unparse LDAP_P(( + BerVarray *bva, ObjectClass *start, ObjectClass *end, int system )); + +LDAP_SLAPD_F (int) register_oc LDAP_P(( + const char *desc, + ObjectClass **oc, + int dupok )); + +/* + * oidm.c + */ +LDAP_SLAPD_F(char *) oidm_find(char *oid); +LDAP_SLAPD_F (void) oidm_destroy LDAP_P(( void )); +LDAP_SLAPD_F (void) oidm_unparse LDAP_P(( + BerVarray *bva, OidMacro *start, OidMacro *end, int system )); +LDAP_SLAPD_F (int) parse_oidm LDAP_P(( + struct config_args_s *ca, int user, OidMacro **om )); + +/* + * operation.c + */ +LDAP_SLAPD_F (void) slap_op_init LDAP_P(( void )); +LDAP_SLAPD_F (void) slap_op_destroy LDAP_P(( void )); +LDAP_SLAPD_F (void) slap_op_groups_free LDAP_P(( Operation *op )); +LDAP_SLAPD_F (void) slap_op_free LDAP_P(( Operation *op, void *ctx )); +LDAP_SLAPD_F (void) slap_op_time LDAP_P(( time_t *t, int *n )); +LDAP_SLAPD_F (Operation *) slap_op_alloc LDAP_P(( + BerElement *ber, ber_int_t msgid, + ber_tag_t tag, ber_int_t id, void *ctx )); + +LDAP_SLAPD_F (slap_op_t) slap_req2op LDAP_P(( ber_tag_t tag )); + +/* + * operational.c + */ +LDAP_SLAPD_F (Attribute *) slap_operational_subschemaSubentry( Backend *be ); +LDAP_SLAPD_F (Attribute *) slap_operational_entryDN( Entry *e ); +LDAP_SLAPD_F (Attribute *) slap_operational_hasSubordinate( int has ); + +/* + * overlays.c + */ +LDAP_SLAPD_F (int) overlay_init( void ); + +/* + * passwd.c + */ +LDAP_SLAPD_F (SLAP_EXTOP_MAIN_FN) passwd_extop; + +LDAP_SLAPD_F (int) slap_passwd_check( + Operation *op, + Entry *e, + Attribute *a, + struct berval *cred, + const char **text ); + +LDAP_SLAPD_F (void) slap_passwd_generate( struct berval * ); + +LDAP_SLAPD_F (void) slap_passwd_hash( + struct berval *cred, + struct berval *hash, + const char **text ); + +LDAP_SLAPD_F (void) slap_passwd_hash_type( + struct berval *cred, + struct berval *hash, + char *htype, + const char **text ); + +LDAP_SLAPD_F (struct berval *) slap_passwd_return( + struct berval *cred ); + +LDAP_SLAPD_F (int) slap_passwd_parse( + struct berval *reqdata, + struct berval *id, + struct berval *oldpass, + struct berval *newpass, + const char **text ); + +LDAP_SLAPD_F (void) slap_passwd_init (void); + +/* + * phonetic.c + */ +LDAP_SLAPD_F (char *) phonetic LDAP_P(( char *s )); + +/* + * referral.c + */ +LDAP_SLAPD_F (int) validate_global_referral LDAP_P(( + const char *url )); + +LDAP_SLAPD_F (BerVarray) get_entry_referrals LDAP_P(( + Operation *op, Entry *e )); + +LDAP_SLAPD_F (BerVarray) referral_rewrite LDAP_P(( + BerVarray refs, + struct berval *base, + struct berval *target, + int scope )); + +LDAP_SLAPD_F (int) get_alias_dn LDAP_P(( + Entry *e, + struct berval *ndn, + int *err, + const char **text )); + +/* + * result.c + */ +#if USE_RS_ASSERT /*defined(USE_RS_ASSERT)?(USE_RS_ASSERT):defined(LDAP_TEST)*/ +#ifdef __GNUC__ +# define RS_FUNC_ __FUNCTION__ +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__) >= 199901L +# define RS_FUNC_ __func__ +#else +# define rs_assert_(file, line, func, cond) rs_assert__(file, line, cond) +#endif +LDAP_SLAPD_V(int) rs_suppress_assert; +LDAP_SLAPD_F(void) rs_assert_(const char*, unsigned, const char*, const char*); +# define RS_ASSERT(cond) ((rs_suppress_assert > 0 || (cond)) \ + ? (void) 0 : rs_assert_(__FILE__, __LINE__, RS_FUNC_, #cond)) +#else +# define RS_ASSERT(cond) ((void) 0) +# define rs_assert_ok(rs) ((void) (rs)) +# define rs_assert_ready(rs) ((void) (rs)) +# define rs_assert_done(rs) ((void) (rs)) +#endif +LDAP_SLAPD_F (void) (rs_assert_ok) LDAP_P(( const SlapReply *rs )); +LDAP_SLAPD_F (void) (rs_assert_ready) LDAP_P(( const SlapReply *rs )); +LDAP_SLAPD_F (void) (rs_assert_done) LDAP_P(( const SlapReply *rs )); + +#define rs_reinit(rs, type) do { \ + SlapReply *const rsRI = (rs); \ + rs_assert_done( rsRI ); \ + rsRI->sr_type = (type); \ + /* Got type before memset in case of rs_reinit(rs, rs->sr_type) */ \ + assert( !offsetof( SlapReply, sr_type ) ); \ + memset( (slap_reply_t *) rsRI + 1, 0, \ + sizeof(*rsRI) - sizeof(slap_reply_t) ); \ + } while ( 0 ) +LDAP_SLAPD_F (void) (rs_reinit) LDAP_P(( SlapReply *rs, slap_reply_t type )); +LDAP_SLAPD_F (void) rs_flush_entry LDAP_P(( Operation *op, + SlapReply *rs, slap_overinst *on )); +LDAP_SLAPD_F (void) rs_replace_entry LDAP_P(( Operation *op, + SlapReply *rs, slap_overinst *on, Entry *e )); +LDAP_SLAPD_F (int) rs_entry2modifiable LDAP_P(( Operation *op, + SlapReply *rs, slap_overinst *on )); +#define rs_ensure_entry_modifiable rs_entry2modifiable /* older name */ +LDAP_SLAPD_F (void) slap_send_ldap_result LDAP_P(( Operation *op, SlapReply *rs )); +LDAP_SLAPD_F (void) send_ldap_sasl LDAP_P(( Operation *op, SlapReply *rs )); +LDAP_SLAPD_F (void) send_ldap_disconnect LDAP_P(( Operation *op, SlapReply *rs )); +LDAP_SLAPD_F (void) slap_send_ldap_extended LDAP_P(( Operation *op, SlapReply *rs )); +LDAP_SLAPD_F (void) slap_send_ldap_intermediate LDAP_P(( Operation *op, SlapReply *rs )); +LDAP_SLAPD_F (void) slap_send_search_result LDAP_P(( Operation *op, SlapReply *rs )); +LDAP_SLAPD_F (int) slap_send_search_reference LDAP_P(( Operation *op, SlapReply *rs )); +LDAP_SLAPD_F (int) slap_send_search_entry LDAP_P(( Operation *op, SlapReply *rs )); +LDAP_SLAPD_F (int) slap_null_cb LDAP_P(( Operation *op, SlapReply *rs )); +LDAP_SLAPD_F (int) slap_freeself_cb LDAP_P(( Operation *op, SlapReply *rs )); + +LDAP_SLAPD_V( const struct berval ) slap_pre_read_bv; +LDAP_SLAPD_V( const struct berval ) slap_post_read_bv; +LDAP_SLAPD_F (int) slap_read_controls LDAP_P(( Operation *op, SlapReply *rs, + Entry *e, const struct berval *oid, LDAPControl **ctrl )); + +LDAP_SLAPD_F (int) str2result LDAP_P(( char *s, + int *code, char **matched, char **info )); +LDAP_SLAPD_F (int) slap_map_api2result LDAP_P(( SlapReply *rs )); +LDAP_SLAPD_F (slap_mask_t) slap_attr_flags LDAP_P(( AttributeName *an )); +LDAP_SLAPD_F (ber_tag_t) slap_req2res LDAP_P(( ber_tag_t tag )); + +LDAP_SLAPD_V( const struct berval ) slap_dummy_bv; + +/* + * root_dse.c + */ +LDAP_SLAPD_F (int) root_dse_init LDAP_P(( void )); +LDAP_SLAPD_F (int) root_dse_destroy LDAP_P(( void )); + +LDAP_SLAPD_F (int) root_dse_info LDAP_P(( + Connection *conn, + Entry **e, + const char **text )); + +LDAP_SLAPD_F (int) root_dse_read_file LDAP_P(( + const char *file)); + +LDAP_SLAPD_F (int) slap_discover_feature LDAP_P(( + slap_bindconf *sb, + const char *attr, + const char *val )); + +LDAP_SLAPD_F (int) supported_feature_load LDAP_P(( struct berval *f )); +LDAP_SLAPD_F (int) supported_feature_destroy LDAP_P(( void )); + +LDAP_SLAPD_F (int) entry_info_register LDAP_P(( SLAP_ENTRY_INFO_FN func, void *arg )); +LDAP_SLAPD_F (int) entry_info_unregister LDAP_P(( SLAP_ENTRY_INFO_FN func, void *arg )); +LDAP_SLAPD_F (void) entry_info_destroy LDAP_P(( void )); + +/* + * sasl.c + */ +LDAP_SLAPD_F (int) slap_sasl_init(void); +LDAP_SLAPD_F (char *) slap_sasl_secprops( const char * ); +LDAP_SLAPD_F (void) slap_sasl_secprops_unparse( struct berval * ); +LDAP_SLAPD_F (int) slap_sasl_destroy(void); + +LDAP_SLAPD_F (int) slap_sasl_open( Connection *c, int reopen ); +LDAP_SLAPD_F (char **) slap_sasl_mechs( Connection *c ); + +LDAP_SLAPD_F (int) slap_sasl_external( Connection *c, + slap_ssf_t ssf, /* relative strength of external security */ + struct berval *authid ); /* asserted authenication id */ + +LDAP_SLAPD_F (int) slap_sasl_reset( Connection *c ); +LDAP_SLAPD_F (int) slap_sasl_close( Connection *c ); + +LDAP_SLAPD_F (int) slap_sasl_bind LDAP_P(( Operation *op, SlapReply *rs )); + +LDAP_SLAPD_F (int) slap_sasl_setpass( + Operation *op, + SlapReply *rs ); + +LDAP_SLAPD_F (int) slap_sasl_getdn( Connection *conn, Operation *op, + struct berval *id, char *user_realm, struct berval *dn, int flags ); + +/* + * saslauthz.c + */ +LDAP_SLAPD_F (int) slap_parse_user LDAP_P(( + struct berval *id, struct berval *user, + struct berval *realm, struct berval *mech )); +LDAP_SLAPD_F (int) slap_sasl_matches LDAP_P(( + Operation *op, BerVarray rules, + struct berval *assertDN, struct berval *authc )); +LDAP_SLAPD_F (void) slap_sasl2dn LDAP_P(( + Operation *op, + struct berval *saslname, + struct berval *dn, + int flags )); +LDAP_SLAPD_F (int) slap_sasl_authorized LDAP_P(( + Operation *op, + struct berval *authcid, + struct berval *authzid )); +LDAP_SLAPD_F (int) slap_sasl_regexp_config LDAP_P(( + const char *match, const char *replace )); +LDAP_SLAPD_F (void) slap_sasl_regexp_unparse LDAP_P(( BerVarray *bva )); +LDAP_SLAPD_F (int) slap_sasl_setpolicy LDAP_P(( const char * )); +LDAP_SLAPD_F (const char *) slap_sasl_getpolicy LDAP_P(( void )); +#ifdef SLAP_AUTH_REWRITE +LDAP_SLAPD_F (int) slap_sasl_rewrite_config LDAP_P(( + const char *fname, + int lineno, + int argc, + char **argv )); +LDAP_SLAPD_F (void) slap_sasl_regexp_destroy LDAP_P(( void )); +#endif /* SLAP_AUTH_REWRITE */ +LDAP_SLAPD_F (int) authzValidate LDAP_P(( + Syntax *syn, struct berval *in )); +#if 0 +LDAP_SLAPD_F (int) authzMatch LDAP_P(( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue )); +#endif +LDAP_SLAPD_F (int) authzPretty LDAP_P(( + Syntax *syntax, + struct berval *val, + struct berval *out, + void *ctx )); +LDAP_SLAPD_F (int) authzNormalize LDAP_P(( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx )); + +/* + * schema.c + */ +LDAP_SLAPD_F (int) schema_info LDAP_P(( Entry **entry, const char **text )); + +/* + * schema_check.c + */ +LDAP_SLAPD_F( int ) oc_check_allowed( + AttributeType *type, + ObjectClass **socs, + ObjectClass *sc ); + +LDAP_SLAPD_F( int ) structural_class( + BerVarray ocs, + ObjectClass **sc, + ObjectClass ***socs, + const char **text, + char *textbuf, size_t textlen, void *ctx ); + +LDAP_SLAPD_F( int ) entry_schema_check( + Operation *op, + Entry *e, + Attribute *attrs, + int manage, + int add, + Attribute **socp, + const char** text, + char *textbuf, size_t textlen ); + +LDAP_SLAPD_F( int ) mods_structural_class( + Modifications *mods, + struct berval *oc, + const char** text, + char *textbuf, size_t textlen, void *ctx ); + +/* + * schema_init.c + */ +LDAP_SLAPD_V( int ) schema_init_done; +LDAP_SLAPD_F (int) slap_schema_init LDAP_P((void)); +LDAP_SLAPD_F (void) schema_destroy LDAP_P(( void )); + +LDAP_SLAPD_F( slap_mr_indexer_func ) octetStringIndexer; +LDAP_SLAPD_F( slap_mr_filter_func ) octetStringFilter; + +LDAP_SLAPD_F( int ) numericoidValidate LDAP_P(( + Syntax *syntax, + struct berval *in )); +LDAP_SLAPD_F( int ) numericStringValidate LDAP_P(( + Syntax *syntax, + struct berval *in )); +LDAP_SLAPD_F( int ) octetStringMatch LDAP_P(( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue )); +LDAP_SLAPD_F( int ) octetStringOrderingMatch LDAP_P(( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue )); + +/* + * schema_prep.c + */ +LDAP_SLAPD_V( struct slap_internal_schema ) slap_schema; +LDAP_SLAPD_F (int) slap_schema_load LDAP_P((void)); +LDAP_SLAPD_F (int) slap_schema_check LDAP_P((void)); + +/* + * schemaparse.c + */ +LDAP_SLAPD_F( int ) slap_valid_descr( const char * ); + +LDAP_SLAPD_F (int) parse_cr LDAP_P(( + struct config_args_s *ca, ContentRule **scr )); +LDAP_SLAPD_F (int) parse_oc LDAP_P(( + struct config_args_s *ca, ObjectClass **soc, ObjectClass *prev )); +LDAP_SLAPD_F (int) parse_at LDAP_P(( + struct config_args_s *ca, AttributeType **sat, AttributeType *prev )); +LDAP_SLAPD_F (char *) scherr2str LDAP_P((int code)) LDAP_GCCATTR((const)); +LDAP_SLAPD_F (int) dscompare LDAP_P(( const char *s1, const char *s2del, + char delim )); +LDAP_SLAPD_F (int) parse_syn LDAP_P(( + struct config_args_s *ca, Syntax **sat, Syntax *prev )); + +/* + * sessionlog.c + */ +LDAP_SLAPD_F (int) slap_send_session_log LDAP_P(( + Operation *, Operation *, SlapReply *)); +LDAP_SLAPD_F (int) slap_add_session_log LDAP_P(( + Operation *, Operation *, Entry * )); + +/* + * sl_malloc.c + */ +LDAP_SLAPD_F (void *) slap_sl_malloc LDAP_P(( + ber_len_t size, void *ctx )); +LDAP_SLAPD_F (void *) slap_sl_realloc LDAP_P(( + void *block, ber_len_t size, void *ctx )); +LDAP_SLAPD_F (void *) slap_sl_calloc LDAP_P(( + ber_len_t nelem, ber_len_t size, void *ctx )); +LDAP_SLAPD_F (void) slap_sl_free LDAP_P(( + void *, void *ctx )); + +LDAP_SLAPD_V (BerMemoryFunctions) slap_sl_mfuncs; + +LDAP_SLAPD_F (void) slap_sl_mem_init LDAP_P(( void )); +LDAP_SLAPD_F (void *) slap_sl_mem_create LDAP_P(( + ber_len_t size, int stack, void *ctx, int flag )); +LDAP_SLAPD_F (void) slap_sl_mem_setctx LDAP_P(( void *ctx, void *memctx )); +LDAP_SLAPD_F (void) slap_sl_mem_destroy LDAP_P(( void *key, void *data )); +LDAP_SLAPD_F (void *) slap_sl_context LDAP_P(( void *ptr )); + +/* + * starttls.c + */ +LDAP_SLAPD_F (SLAP_EXTOP_MAIN_FN) starttls_extop; + +/* + * str2filter.c + */ +LDAP_SLAPD_F (Filter *) str2filter LDAP_P(( const char *str )); +LDAP_SLAPD_F (Filter *) str2filter_x LDAP_P(( Operation *op, const char *str )); + +/* + * syncrepl.c + */ + +LDAP_SLAPD_F (int) syncrepl_add_glue LDAP_P(( + Operation*, Entry* )); +LDAP_SLAPD_F (void) syncrepl_diff_entry LDAP_P(( + Operation *op, Attribute *old, Attribute *anew, + Modifications **mods, Modifications **ml, int is_ctx )); +LDAP_SLAPD_F (void) syncinfo_free LDAP_P(( struct syncinfo_s *, int all )); + +/* syntax.c */ +LDAP_SLAPD_F (int) syn_is_sup LDAP_P(( + Syntax *syn, + Syntax *sup )); +LDAP_SLAPD_F (Syntax *) syn_find LDAP_P(( + const char *synname )); +LDAP_SLAPD_F (Syntax *) syn_find_desc LDAP_P(( + const char *syndesc, int *slen )); +LDAP_SLAPD_F (int) syn_add LDAP_P(( + LDAPSyntax *syn, + int user, + slap_syntax_defs_rec *def, + Syntax **ssyn, + Syntax *prev, + const char **err )); +LDAP_SLAPD_F (void) syn_destroy LDAP_P(( void )); + +LDAP_SLAPD_F (int) register_syntax LDAP_P(( + slap_syntax_defs_rec *def )); + +LDAP_SLAPD_F (int) syn_schema_info( Entry *e ); + +LDAP_SLAPD_F (int) syn_start LDAP_P(( Syntax **at )); +LDAP_SLAPD_F (int) syn_next LDAP_P(( Syntax **at )); +LDAP_SLAPD_F (void) syn_delete LDAP_P(( Syntax *at )); + +LDAP_SLAPD_F (void) syn_unparse LDAP_P(( + BerVarray *bva, Syntax *start, Syntax *end, int system )); + +/* + * user.c + */ +#if defined(HAVE_PWD_H) && defined(HAVE_GRP_H) +LDAP_SLAPD_F (void) slap_init_user LDAP_P(( char *username, char *groupname )); +#endif + +/* + * value.c + */ +LDAP_SLAPD_F (int) asserted_value_validate_normalize LDAP_P(( + AttributeDescription *ad, + MatchingRule *mr, + unsigned usage, + struct berval *in, + struct berval *out, + const char ** text, + void *ctx )); + +LDAP_SLAPD_F (int) value_match LDAP_P(( + int *match, + AttributeDescription *ad, + MatchingRule *mr, + unsigned flags, + struct berval *v1, + void *v2, + const char ** text )); +LDAP_SLAPD_F (int) value_find_ex LDAP_P(( + AttributeDescription *ad, + unsigned flags, + BerVarray values, + struct berval *value, + void *ctx )); + +LDAP_SLAPD_F (int) ordered_value_add LDAP_P(( + Entry *e, + AttributeDescription *ad, + Attribute *a, + BerVarray vals, + BerVarray nvals )); + +LDAP_SLAPD_F (int) ordered_value_validate LDAP_P(( + AttributeDescription *ad, + struct berval *in, + int mop )); + +LDAP_SLAPD_F (int) ordered_value_pretty LDAP_P(( + AttributeDescription *ad, + struct berval *val, + struct berval *out, + void *ctx )); + +LDAP_SLAPD_F (int) ordered_value_normalize LDAP_P(( + slap_mask_t usage, + AttributeDescription *ad, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx )); + +LDAP_SLAPD_F (int) ordered_value_match LDAP_P(( + int *match, + AttributeDescription *ad, + MatchingRule *mr, + unsigned flags, + struct berval *v1, + struct berval *v2, + const char ** text )); + +LDAP_SLAPD_F (void) ordered_value_renumber LDAP_P(( + Attribute *a )); + +LDAP_SLAPD_F (int) ordered_value_sort LDAP_P(( + Attribute *a, + int do_renumber )); + +LDAP_SLAPD_F (int) value_add LDAP_P(( + BerVarray *vals, + BerVarray addvals )); +LDAP_SLAPD_F (int) value_add_one LDAP_P(( + BerVarray *vals, + struct berval *addval )); + +/* assumes (x) > (y) returns 1 if true, 0 otherwise */ +#define SLAP_PTRCMP(x, y) ((x) < (y) ? -1 : (x) > (y)) + +#ifdef SLAP_ZONE_ALLOC +/* + * zn_malloc.c + */ +LDAP_SLAPD_F (void *) slap_zn_malloc LDAP_P((ber_len_t, void *)); +LDAP_SLAPD_F (void *) slap_zn_realloc LDAP_P((void *, ber_len_t, void *)); +LDAP_SLAPD_F (void *) slap_zn_calloc LDAP_P((ber_len_t, ber_len_t, void *)); +LDAP_SLAPD_F (void) slap_zn_free LDAP_P((void *, void *)); + +LDAP_SLAPD_F (void *) slap_zn_mem_create LDAP_P(( + ber_len_t, ber_len_t, ber_len_t, ber_len_t)); +LDAP_SLAPD_F (void) slap_zn_mem_destroy LDAP_P((void *)); +LDAP_SLAPD_F (int) slap_zn_validate LDAP_P((void *, void *, int)); +LDAP_SLAPD_F (int) slap_zn_invalidate LDAP_P((void *, void *)); +LDAP_SLAPD_F (int) slap_zh_rlock LDAP_P((void*)); +LDAP_SLAPD_F (int) slap_zh_runlock LDAP_P((void*)); +LDAP_SLAPD_F (int) slap_zh_wlock LDAP_P((void*)); +LDAP_SLAPD_F (int) slap_zh_wunlock LDAP_P((void*)); +LDAP_SLAPD_F (int) slap_zn_rlock LDAP_P((void*, void*)); +LDAP_SLAPD_F (int) slap_zn_runlock LDAP_P((void*, void*)); +LDAP_SLAPD_F (int) slap_zn_wlock LDAP_P((void*, void*)); +LDAP_SLAPD_F (int) slap_zn_wunlock LDAP_P((void*, void*)); +#endif + +/* + * Other... + */ +LDAP_SLAPD_V (unsigned int) index_substr_if_minlen; +LDAP_SLAPD_V (unsigned int) index_substr_if_maxlen; +LDAP_SLAPD_V (unsigned int) index_substr_any_len; +LDAP_SLAPD_V (unsigned int) index_substr_any_step; +LDAP_SLAPD_V (unsigned int) index_intlen; +/* all signed integers from strings of this size need more than intlen bytes */ +/* i.e. log(10)*(index_intlen_strlen-2) > log(2)*(8*(index_intlen)-1) */ +LDAP_SLAPD_V (unsigned int) index_intlen_strlen; +#define SLAP_INDEX_INTLEN_STRLEN(intlen) ((8*(intlen)-1) * 146/485 + 3) + +LDAP_SLAPD_V (ber_len_t) sockbuf_max_incoming; +LDAP_SLAPD_V (ber_len_t) sockbuf_max_incoming_auth; +LDAP_SLAPD_V (int) slap_conn_max_pending; +LDAP_SLAPD_V (int) slap_conn_max_pending_auth; + +LDAP_SLAPD_V (slap_mask_t) global_allows; +LDAP_SLAPD_V (slap_mask_t) global_disallows; + +LDAP_SLAPD_V (BerVarray) default_referral; +LDAP_SLAPD_V (const char) Versionstr[]; + +LDAP_SLAPD_V (int) global_gentlehup; +LDAP_SLAPD_V (int) global_idletimeout; +LDAP_SLAPD_V (int) global_writetimeout; +LDAP_SLAPD_V (char *) global_host; +LDAP_SLAPD_V (struct berval) global_host_bv; +LDAP_SLAPD_V (char *) global_realm; +LDAP_SLAPD_V (char *) sasl_host; +LDAP_SLAPD_V (char *) slap_sasl_auxprops; +LDAP_SLAPD_V (char **) default_passwd_hash; +LDAP_SLAPD_V (int) lber_debug; +LDAP_SLAPD_V (int) ldap_syslog; +LDAP_SLAPD_V (struct berval) default_search_base; +LDAP_SLAPD_V (struct berval) default_search_nbase; + +LDAP_SLAPD_V (slap_counters_t) slap_counters; + +LDAP_SLAPD_V (char *) slapd_pid_file; +LDAP_SLAPD_V (char *) slapd_args_file; +LDAP_SLAPD_V (time_t) starttime; + +/* use time(3) -- no mutex */ +#define slap_get_time() time( NULL ) + +LDAP_SLAPD_V (ldap_pvt_thread_pool_t) connection_pool; +LDAP_SLAPD_V (int) connection_pool_max; +LDAP_SLAPD_V (int) slap_tool_thread_max; + +LDAP_SLAPD_V (ldap_pvt_thread_mutex_t) entry2str_mutex; + +#ifndef LDAP_DEVEL + /* to be removed with 2.5 */ +#define gmtime_mutex ldap_int_gmtime_mutex +#endif /* ! LDAP_DEVEL */ + +LDAP_SLAPD_V (ldap_pvt_thread_mutex_t) ad_index_mutex; +LDAP_SLAPD_V (ldap_pvt_thread_mutex_t) ad_undef_mutex; +LDAP_SLAPD_V (ldap_pvt_thread_mutex_t) oc_undef_mutex; + +LDAP_SLAPD_V (ber_socket_t) dtblsize; + +LDAP_SLAPD_V (int) use_reverse_lookup; + +/* + * operations + */ +LDAP_SLAPD_F (int) do_abandon LDAP_P((Operation *op, SlapReply *rs)); +LDAP_SLAPD_F (int) do_add LDAP_P((Operation *op, SlapReply *rs)); +LDAP_SLAPD_F (int) do_bind LDAP_P((Operation *op, SlapReply *rs)); +LDAP_SLAPD_F (int) do_compare LDAP_P((Operation *op, SlapReply *rs)); +LDAP_SLAPD_F (int) do_delete LDAP_P((Operation *op, SlapReply *rs)); +LDAP_SLAPD_F (int) do_modify LDAP_P((Operation *op, SlapReply *rs)); +LDAP_SLAPD_F (int) do_modrdn LDAP_P((Operation *op, SlapReply *rs)); +LDAP_SLAPD_F (int) do_search LDAP_P((Operation *op, SlapReply *rs)); +LDAP_SLAPD_F (int) do_unbind LDAP_P((Operation *op, SlapReply *rs)); +LDAP_SLAPD_F (int) do_extended LDAP_P((Operation *op, SlapReply *rs)); + +/* + * frontend operations + */ +LDAP_SLAPD_F (int) fe_op_abandon LDAP_P((Operation *op, SlapReply *rs)); +LDAP_SLAPD_F (int) fe_op_add LDAP_P((Operation *op, SlapReply *rs)); +LDAP_SLAPD_F (int) fe_op_bind LDAP_P((Operation *op, SlapReply *rs)); +LDAP_SLAPD_F (int) fe_op_bind_success LDAP_P(( Operation *op, SlapReply *rs )); +LDAP_SLAPD_F (int) fe_op_compare LDAP_P((Operation *op, SlapReply *rs)); +LDAP_SLAPD_F (int) fe_op_delete LDAP_P((Operation *op, SlapReply *rs)); +LDAP_SLAPD_F (int) fe_op_modify LDAP_P((Operation *op, SlapReply *rs)); +LDAP_SLAPD_F (int) fe_op_modrdn LDAP_P((Operation *op, SlapReply *rs)); +LDAP_SLAPD_F (int) fe_op_search LDAP_P((Operation *op, SlapReply *rs)); +LDAP_SLAPD_F (int) fe_aux_operational LDAP_P((Operation *op, SlapReply *rs)); +#if 0 +LDAP_SLAPD_F (int) fe_op_unbind LDAP_P((Operation *op, SlapReply *rs)); +#endif +LDAP_SLAPD_F (int) fe_extended LDAP_P((Operation *op, SlapReply *rs)); +LDAP_SLAPD_F (int) fe_acl_group LDAP_P(( + Operation *op, + Entry *target, + struct berval *gr_ndn, + struct berval *op_ndn, + ObjectClass *group_oc, + AttributeDescription *group_at )); +LDAP_SLAPD_F (int) fe_acl_attribute LDAP_P(( + Operation *op, + Entry *target, + struct berval *edn, + AttributeDescription *entry_at, + BerVarray *vals, + slap_access_t access )); +LDAP_SLAPD_F (int) fe_access_allowed LDAP_P(( + Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + slap_access_t access, + AccessControlState *state, + slap_mask_t *maskp )); + +/* NOTE: this macro assumes that bv has been allocated + * by ber_* malloc functions or is { 0L, NULL } */ +#ifdef USE_MP_BIGNUM +# define UI2BVX(bv,ui,ctx) \ + do { \ + char *val; \ + ber_len_t len; \ + val = BN_bn2dec(ui); \ + if (val) { \ + len = strlen(val); \ + if ( len > (bv)->bv_len ) { \ + (bv)->bv_val = ber_memrealloc_x( (bv)->bv_val, len + 1, (ctx) ); \ + } \ + AC_MEMCPY((bv)->bv_val, val, len + 1); \ + (bv)->bv_len = len; \ + OPENSSL_free(val); \ + } else { \ + ber_memfree_x( (bv)->bv_val, (ctx) ); \ + BER_BVZERO( (bv) ); \ + } \ + } while ( 0 ) + +#elif defined( USE_MP_GMP ) +/* NOTE: according to the documentation, the result + * of mpz_sizeinbase() can exceed the length of the + * string representation of the number by 1 + */ +# define UI2BVX(bv,ui,ctx) \ + do { \ + ber_len_t len = mpz_sizeinbase( (ui), 10 ); \ + if ( len > (bv)->bv_len ) { \ + (bv)->bv_val = ber_memrealloc_x( (bv)->bv_val, len + 1, (ctx) ); \ + } \ + (void)mpz_get_str( (bv)->bv_val, 10, (ui) ); \ + if ( (bv)->bv_val[ len - 1 ] == '\0' ) { \ + len--; \ + } \ + (bv)->bv_len = len; \ + } while ( 0 ) + +#else +# ifdef USE_MP_LONG_LONG +# define UI2BV_FORMAT "%llu" +# elif defined USE_MP_LONG +# define UI2BV_FORMAT "%lu" +# elif defined HAVE_LONG_LONG +# define UI2BV_FORMAT "%llu" +# else +# define UI2BV_FORMAT "%lu" +# endif + +# define UI2BVX(bv,ui,ctx) \ + do { \ + char buf[LDAP_PVT_INTTYPE_CHARS(long)]; \ + ber_len_t len; \ + len = snprintf( buf, sizeof( buf ), UI2BV_FORMAT, (ui) ); \ + if ( len > (bv)->bv_len ) { \ + (bv)->bv_val = ber_memrealloc_x( (bv)->bv_val, len + 1, (ctx) ); \ + } \ + (bv)->bv_len = len; \ + AC_MEMCPY( (bv)->bv_val, buf, len + 1 ); \ + } while ( 0 ) +#endif + +#define UI2BV(bv,ui) UI2BVX(bv,ui,NULL) + +LDAP_END_DECL + +#endif /* PROTO_SLAP_H */ diff --git a/servers/slapd/referral.c b/servers/slapd/referral.c new file mode 100644 index 0000000..50da23a --- /dev/null +++ b/servers/slapd/referral.c @@ -0,0 +1,363 @@ +/* referral.c - muck with referrals */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/errno.h> +#include <ac/string.h> +#include <ac/ctype.h> +#include <ac/time.h> +#include <ac/unistd.h> + +#include "slap.h" + +/* + * This routine generates the DN appropriate to return in + * an LDAP referral. + */ +static char * referral_dn_muck( + const char * refDN, + struct berval * baseDN, + struct berval * targetDN ) +{ + int rc; + struct berval bvin; + struct berval nrefDN = BER_BVNULL; + struct berval nbaseDN = BER_BVNULL; + struct berval ntargetDN = BER_BVNULL; + + if( !baseDN ) { + /* no base, return target */ + return targetDN ? ch_strdup( targetDN->bv_val ) : NULL; + } + + if( refDN ) { + bvin.bv_val = (char *)refDN; + bvin.bv_len = strlen( refDN ); + + rc = dnPretty( NULL, &bvin, &nrefDN, NULL ); + if( rc != LDAP_SUCCESS ) { + /* Invalid refDN */ + return NULL; + } + } + + if( !targetDN ) { + /* continuation reference + * if refDN present return refDN + * else return baseDN + */ + return nrefDN.bv_len ? nrefDN.bv_val : ch_strdup( baseDN->bv_val ); + } + + rc = dnPretty( NULL, targetDN, &ntargetDN, NULL ); + if( rc != LDAP_SUCCESS ) { + /* Invalid targetDN */ + ch_free( nrefDN.bv_val ); + return NULL; + } + + if( nrefDN.bv_len ) { + rc = dnPretty( NULL, baseDN, &nbaseDN, NULL ); + if( rc != LDAP_SUCCESS ) { + /* Invalid baseDN */ + ch_free( nrefDN.bv_val ); + ch_free( ntargetDN.bv_val ); + return NULL; + } + + if( dn_match( &nbaseDN, &nrefDN ) ) { + ch_free( nrefDN.bv_val ); + ch_free( nbaseDN.bv_val ); + return ntargetDN.bv_val; + } + + { + struct berval muck; + + if( ntargetDN.bv_len < nbaseDN.bv_len ) { + ch_free( nrefDN.bv_val ); + ch_free( nbaseDN.bv_val ); + return ntargetDN.bv_val; + } + + rc = strcasecmp( + &ntargetDN.bv_val[ntargetDN.bv_len-nbaseDN.bv_len], + nbaseDN.bv_val ); + if( rc ) { + /* target not subordinate to base */ + ch_free( nrefDN.bv_val ); + ch_free( nbaseDN.bv_val ); + return ntargetDN.bv_val; + } + + muck.bv_len = ntargetDN.bv_len + nrefDN.bv_len - nbaseDN.bv_len; + muck.bv_val = ch_malloc( muck.bv_len + 1 ); + + strncpy( muck.bv_val, ntargetDN.bv_val, + ntargetDN.bv_len-nbaseDN.bv_len ); + strcpy( &muck.bv_val[ntargetDN.bv_len-nbaseDN.bv_len], + nrefDN.bv_val ); + + ch_free( nrefDN.bv_val ); + ch_free( nbaseDN.bv_val ); + ch_free( ntargetDN.bv_val ); + + return muck.bv_val; + } + } + + ch_free( nrefDN.bv_val ); + return ntargetDN.bv_val; +} + + +/* validate URL for global referral use + * LDAP URLs must not have: + * DN, attrs, scope, nor filter + * Any non-LDAP URL is okay + * + * XXYYZ: should return an error string + */ +int validate_global_referral( const char *url ) +{ + int rc; + LDAPURLDesc *lurl; + + rc = ldap_url_parse_ext( url, &lurl, LDAP_PVT_URL_PARSE_NONE ); + + switch( rc ) { + case LDAP_URL_SUCCESS: + break; + + case LDAP_URL_ERR_BADSCHEME: + /* not LDAP hence valid */ + Debug( LDAP_DEBUG_CONFIG, "referral \"%s\": not LDAP.\n", url, 0, 0 ); + return 0; + + default: + /* other error, bail */ + Debug( LDAP_DEBUG_ANY, + "referral: invalid URL (%s): %s (%d)\n", + url, "" /* ldap_url_error2str(rc) */, rc ); + return 1; + } + + rc = 0; + + if( lurl->lud_dn && *lurl->lud_dn ) { + Debug( LDAP_DEBUG_ANY, + "referral: URL (%s): contains DN\n", + url, 0, 0 ); + rc = 1; + + } else if( lurl->lud_attrs ) { + Debug( LDAP_DEBUG_ANY, + "referral: URL (%s): requests attributes\n", + url, 0, 0 ); + rc = 1; + + } else if( lurl->lud_scope != LDAP_SCOPE_DEFAULT ) { + Debug( LDAP_DEBUG_ANY, + "referral: URL (%s): contains explicit scope\n", + url, 0, 0 ); + rc = 1; + + } else if( lurl->lud_filter ) { + Debug( LDAP_DEBUG_ANY, + "referral: URL (%s): contains explicit filter\n", + url, 0, 0 ); + rc = 1; + } + + ldap_free_urldesc( lurl ); + return rc; +} + +BerVarray referral_rewrite( + BerVarray in, + struct berval *base, + struct berval *target, + int scope ) +{ + int i; + BerVarray refs; + struct berval *iv, *jv; + + if ( in == NULL ) { + return NULL; + } + + for ( i = 0; !BER_BVISNULL( &in[i] ); i++ ) { + /* just count them */ + } + + if ( i < 1 ) { + return NULL; + } + + refs = ch_malloc( ( i + 1 ) * sizeof( struct berval ) ); + + for ( iv = in, jv = refs; !BER_BVISNULL( iv ); iv++ ) { + LDAPURLDesc *url; + char *dn; + int rc; + + rc = ldap_url_parse_ext( iv->bv_val, &url, LDAP_PVT_URL_PARSE_NONE ); + if ( rc == LDAP_URL_ERR_BADSCHEME ) { + ber_dupbv( jv++, iv ); + continue; + + } else if ( rc != LDAP_URL_SUCCESS ) { + continue; + } + + dn = url->lud_dn; + url->lud_dn = referral_dn_muck( ( dn && *dn ) ? dn : NULL, + base, target ); + ldap_memfree( dn ); + + if ( url->lud_scope == LDAP_SCOPE_DEFAULT ) { + url->lud_scope = scope; + } + + jv->bv_val = ldap_url_desc2str( url ); + if ( jv->bv_val != NULL ) { + jv->bv_len = strlen( jv->bv_val ); + + } else { + ber_dupbv( jv, iv ); + } + jv++; + + ldap_free_urldesc( url ); + } + + if ( jv == refs ) { + ch_free( refs ); + refs = NULL; + + } else { + BER_BVZERO( jv ); + } + + return refs; +} + + +BerVarray get_entry_referrals( + Operation *op, + Entry *e ) +{ + Attribute *attr; + BerVarray refs; + unsigned i; + struct berval *iv, *jv; + + AttributeDescription *ad_ref = slap_schema.si_ad_ref; + + attr = attr_find( e->e_attrs, ad_ref ); + + if( attr == NULL ) return NULL; + + for( i=0; attr->a_vals[i].bv_val != NULL; i++ ) { + /* count references */ + } + + if( i < 1 ) return NULL; + + refs = ch_malloc( (i + 1) * sizeof(struct berval)); + + for( iv=attr->a_vals, jv=refs; iv->bv_val != NULL; iv++ ) { + unsigned k; + ber_dupbv( jv, iv ); + + /* trim the label */ + for( k=0; k<jv->bv_len; k++ ) { + if( isspace( (unsigned char) jv->bv_val[k] ) ) { + jv->bv_val[k] = '\0'; + jv->bv_len = k; + break; + } + } + + if( jv->bv_len > 0 ) { + jv++; + } else { + free( jv->bv_val ); + } + } + + if( jv == refs ) { + free( refs ); + refs = NULL; + + } else { + jv->bv_val = NULL; + } + + /* we should check that a referral value exists... */ + return refs; +} + + +int get_alias_dn( + Entry *e, + struct berval *ndn, + int *err, + const char **text ) +{ + Attribute *a; + AttributeDescription *aliasedObjectName + = slap_schema.si_ad_aliasedObjectName; + + a = attr_find( e->e_attrs, aliasedObjectName ); + + if( a == NULL ) { + /* + * there was an aliasedobjectname defined but no data. + */ + *err = LDAP_ALIAS_PROBLEM; + *text = "alias missing aliasedObjectName attribute"; + return -1; + } + + /* + * aliasedObjectName should be SINGLE-VALUED with a single value. + */ + if ( a->a_vals[0].bv_val == NULL ) { + /* + * there was an aliasedobjectname defined but no data. + */ + *err = LDAP_ALIAS_PROBLEM; + *text = "alias missing aliasedObjectName value"; + return -1; + } + + if( a->a_nvals[1].bv_val != NULL ) { + *err = LDAP_ALIAS_PROBLEM; + *text = "alias has multivalued aliasedObjectName"; + return -1; + } + + *ndn = a->a_nvals[0]; + + return 0; +} + diff --git a/servers/slapd/result.c b/servers/slapd/result.c new file mode 100644 index 0000000..2e1f003 --- /dev/null +++ b/servers/slapd/result.c @@ -0,0 +1,1859 @@ +/* result.c - routines to send ldap results, errors, and referrals */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/errno.h> +#include <ac/string.h> +#include <ac/ctype.h> +#include <ac/time.h> +#include <ac/unistd.h> + +#include "slap.h" + +const struct berval slap_dummy_bv = BER_BVNULL; + +int slap_null_cb( Operation *op, SlapReply *rs ) +{ + return 0; +} + +int slap_freeself_cb( Operation *op, SlapReply *rs ) +{ + assert( op->o_callback != NULL ); + + op->o_tmpfree( op->o_callback, op->o_tmpmemctx ); + op->o_callback = NULL; + + return SLAP_CB_CONTINUE; +} + +static char *v2ref( BerVarray ref, const char *text ) +{ + size_t len = 0, i = 0; + char *v2; + + if(ref == NULL) { + if (text) { + return ch_strdup(text); + } else { + return NULL; + } + } + + if ( text != NULL ) { + len = strlen( text ); + if (text[len-1] != '\n') { + i = 1; + } + } + + v2 = ch_malloc( len+i+sizeof("Referral:") ); + + if( text != NULL ) { + strcpy(v2, text); + if( i ) { + v2[len++] = '\n'; + } + } + strcpy( v2+len, "Referral:" ); + len += sizeof("Referral:"); + + for( i=0; ref[i].bv_val != NULL; i++ ) { + v2 = ch_realloc( v2, len + ref[i].bv_len + 1 ); + v2[len-1] = '\n'; + AC_MEMCPY(&v2[len], ref[i].bv_val, ref[i].bv_len ); + len += ref[i].bv_len; + if (ref[i].bv_val[ref[i].bv_len-1] != '/') { + ++len; + } + } + + v2[len-1] = '\0'; + return v2; +} + +ber_tag_t +slap_req2res( ber_tag_t tag ) +{ + switch( tag ) { + case LDAP_REQ_ADD: + case LDAP_REQ_BIND: + case LDAP_REQ_COMPARE: + case LDAP_REQ_EXTENDED: + case LDAP_REQ_MODIFY: + case LDAP_REQ_MODRDN: + tag++; + break; + + case LDAP_REQ_DELETE: + tag = LDAP_RES_DELETE; + break; + + case LDAP_REQ_ABANDON: + case LDAP_REQ_UNBIND: + tag = LBER_SEQUENCE; + break; + + case LDAP_REQ_SEARCH: + tag = LDAP_RES_SEARCH_RESULT; + break; + + default: + tag = LBER_SEQUENCE; + } + + return tag; +} + +/* SlapReply debugging, prodo-slap.h overrides it in OpenLDAP releases */ +#if defined(LDAP_TEST) || (defined(USE_RS_ASSERT) && (USE_RS_ASSERT)) + +int rs_suppress_assert = 0; + +/* RS_ASSERT() helper function */ +void rs_assert_(const char*file, unsigned line, const char*fn, const char*cond) +{ + int no_assert = rs_suppress_assert, save_errno = errno; + const char *s; + + if ( no_assert >= 0 ) { + if ( no_assert == 0 && (s = getenv( "NO_RS_ASSERT" )) && *s ) { + no_assert = rs_suppress_assert = atoi( s ); + } + if ( no_assert > 0 ) { + errno = save_errno; + return; + } + } + +#ifdef rs_assert_ /* proto-slap.h #defined away the fn parameter */ + fprintf( stderr,"%s:%u: " "RS_ASSERT(%s) failed.\n", file,line,cond ); +#else + fprintf( stderr,"%s:%u: %s: RS_ASSERT(%s) failed.\n", file,line,fn,cond ); +#endif + fflush( stderr ); + + errno = save_errno; + /* $NO_RS_ASSERT > 0: ignore rs_asserts, 0: abort, < 0: just warn */ + if ( !no_assert /* from $NO_RS_ASSERT */ ) abort(); +} + +/* SlapReply is consistent */ +void +(rs_assert_ok)( const SlapReply *rs ) +{ + const slap_mask_t flags = rs->sr_flags; + + if ( flags & REP_ENTRY_MASK ) { + RS_ASSERT( !(flags & REP_ENTRY_MUSTRELEASE) + || !(flags & (REP_ENTRY_MASK ^ REP_ENTRY_MUSTRELEASE)) ); + RS_ASSERT( rs->sr_entry != NULL ); + RS_ASSERT( (1 << rs->sr_type) & + ((1 << REP_SEARCH) | (1 << REP_SEARCHREF) | + (1 << REP_RESULT) | (1 << REP_GLUE_RESULT)) ); + } +#if defined(USE_RS_ASSERT) && (USE_RS_ASSERT) > 1 /* TODO: Enable when safe */ + if ( (flags & (REP_MATCHED_MASK | REP_REF_MASK | REP_CTRLS_MASK)) ) { + RS_ASSERT( !(flags & REP_MATCHED_MASK) || rs->sr_matched ); + RS_ASSERT( !(flags & REP_CTRLS_MASK ) || rs->sr_ctrls ); + /* Note: LDAP_REFERRAL + !sr_ref is OK, becomes LDAP_NO_SUCH_OBJECT */ + } +#if (USE_RS_ASSERT) > 2 + if ( rs->sr_err == LDAP_SUCCESS ) { + RS_ASSERT( rs->sr_text == NULL ); + RS_ASSERT( rs->sr_matched == NULL ); + } +#endif +#endif +} + +/* Ready for calling a new backend operation */ +void +(rs_assert_ready)( const SlapReply *rs ) +{ + RS_ASSERT( !rs->sr_entry ); +#if defined(USE_RS_ASSERT) && (USE_RS_ASSERT) > 1 /* TODO: Enable when safe */ + RS_ASSERT( !rs->sr_text ); + RS_ASSERT( !rs->sr_ref ); + RS_ASSERT( !rs->sr_matched ); + RS_ASSERT( !rs->sr_ctrls ); + RS_ASSERT( !rs->sr_flags ); +#if (USE_RS_ASSERT) > 2 + RS_ASSERT( rs->sr_err == LDAP_SUCCESS ); +#endif +#else + RS_ASSERT( !(rs->sr_flags & REP_ENTRY_MASK) ); +#endif +} + +/* Backend operation done */ +void +(rs_assert_done)( const SlapReply *rs ) +{ +#if defined(USE_RS_ASSERT) && (USE_RS_ASSERT) > 1 /* TODO: Enable when safe */ + RS_ASSERT( !(rs->sr_flags & ~(REP_ENTRY_MODIFIABLE|REP_NO_OPERATIONALS)) ); + rs_assert_ok( rs ); +#else + RS_ASSERT( !(rs->sr_flags & REP_ENTRY_MUSTFLUSH) ); +#endif +} + +#endif /* LDAP_TEST || USE_RS_ASSERT */ + +/* Reset a used SlapReply whose contents has been flushed (freed/released) */ +void +(rs_reinit)( SlapReply *rs, slap_reply_t type ) +{ + rs_reinit( rs, type ); /* proto-slap.h macro */ +} + +/* Obey and clear rs->sr_flags & REP_ENTRY_MASK. Clear sr_entry if freed. */ +void +rs_flush_entry( Operation *op, SlapReply *rs, slap_overinst *on ) +{ + rs_assert_ok( rs ); + + if ( (rs->sr_flags & REP_ENTRY_MUSTFLUSH) && rs->sr_entry != NULL ) { + if ( !(rs->sr_flags & REP_ENTRY_MUSTRELEASE) ) { + entry_free( rs->sr_entry ); + } else if ( on != NULL ) { + overlay_entry_release_ov( op, rs->sr_entry, 0, on ); + } else { + be_entry_release_rw( op, rs->sr_entry, 0 ); + } + rs->sr_entry = NULL; + } + + rs->sr_flags &= ~REP_ENTRY_MASK; +} + +/* Set rs->sr_entry after obeying and clearing sr_flags & REP_ENTRY_MASK. */ +void +rs_replace_entry( Operation *op, SlapReply *rs, slap_overinst *on, Entry *e ) +{ + rs_flush_entry( op, rs, on ); + rs->sr_entry = e; +} + +/* + * Ensure rs->sr_entry is modifiable, by duplicating it if necessary. + * Obey sr_flags. Set REP_ENTRY_<MODIFIABLE, and MUSTBEFREED if duplicated>. + * Return nonzero if rs->sr_entry was replaced. + */ +int +rs_entry2modifiable( Operation *op, SlapReply *rs, slap_overinst *on ) +{ + if ( rs->sr_flags & REP_ENTRY_MODIFIABLE ) { + rs_assert_ok( rs ); + return 0; + } + rs_replace_entry( op, rs, on, entry_dup( rs->sr_entry )); + rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED; + return 1; +} + +/* Check for any callbacks that want to be informed about being blocked + * on output. These callbacks are expected to leave the callback list + * unmodified. Their result is ignored. + */ +static void +slap_writewait_play( + Operation *op ) +{ + slap_callback *sc = op->o_callback; + + for ( ; sc; sc = sc->sc_next ) { + if ( sc->sc_writewait ) + sc->sc_writewait( op, sc ); + } +} + +static long send_ldap_ber( + Operation *op, + BerElement *ber ) +{ + Connection *conn = op->o_conn; + ber_len_t bytes; + long ret = 0; + + ber_get_option( ber, LBER_OPT_BER_BYTES_TO_WRITE, &bytes ); + + /* write only one pdu at a time - wait til it's our turn */ + ldap_pvt_thread_mutex_lock( &conn->c_write1_mutex ); + if (( op->o_abandon && !op->o_cancel ) || !connection_valid( conn ) || + conn->c_writers < 0 ) { + ldap_pvt_thread_mutex_unlock( &conn->c_write1_mutex ); + return 0; + } + + conn->c_writers++; + + while ( conn->c_writers > 0 && conn->c_writing ) { + ldap_pvt_thread_cond_wait( &conn->c_write1_cv, &conn->c_write1_mutex ); + } + + /* connection was closed under us */ + if ( conn->c_writers < 0 ) { + /* we're the last waiter, let the closer continue */ + if ( conn->c_writers == -1 ) + ldap_pvt_thread_cond_signal( &conn->c_write1_cv ); + conn->c_writers++; + ldap_pvt_thread_mutex_unlock( &conn->c_write1_mutex ); + return 0; + } + + /* Our turn */ + conn->c_writing = 1; + + /* write the pdu */ + while( 1 ) { + int err; + + if ( ber_flush2( conn->c_sb, ber, LBER_FLUSH_FREE_NEVER ) == 0 ) { + ret = bytes; + break; + } + + err = sock_errno(); + + /* + * we got an error. if it's ewouldblock, we need to + * wait on the socket being writable. otherwise, figure + * it's a hard error and return. + */ + + Debug( LDAP_DEBUG_CONNS, "ber_flush2 failed errno=%d reason=\"%s\"\n", + err, sock_errstr(err), 0 ); + + if ( err != EWOULDBLOCK && err != EAGAIN ) { + conn->c_writers--; + conn->c_writing = 0; + ldap_pvt_thread_mutex_unlock( &conn->c_write1_mutex ); + ldap_pvt_thread_mutex_lock( &conn->c_mutex ); + connection_closing( conn, "connection lost on write" ); + + ldap_pvt_thread_mutex_unlock( &conn->c_mutex ); + return -1; + } + + /* wait for socket to be write-ready */ + slap_writewait_play( op ); + ldap_pvt_thread_mutex_lock( &conn->c_write2_mutex ); + conn->c_writewaiter = 1; + slapd_set_write( conn->c_sd, 2 ); + + ldap_pvt_thread_mutex_unlock( &conn->c_write1_mutex ); + ldap_pvt_thread_pool_idle( &connection_pool ); + ldap_pvt_thread_cond_wait( &conn->c_write2_cv, &conn->c_write2_mutex ); + conn->c_writewaiter = 0; + ldap_pvt_thread_mutex_unlock( &conn->c_write2_mutex ); + ldap_pvt_thread_pool_unidle( &connection_pool ); + ldap_pvt_thread_mutex_lock( &conn->c_write1_mutex ); + if ( conn->c_writers < 0 ) { + ret = 0; + break; + } + } + + conn->c_writing = 0; + if ( conn->c_writers < 0 ) { + conn->c_writers++; + if ( !conn->c_writers ) + ldap_pvt_thread_cond_signal( &conn->c_write1_cv ); + } else { + conn->c_writers--; + ldap_pvt_thread_cond_signal( &conn->c_write1_cv ); + } + ldap_pvt_thread_mutex_unlock( &conn->c_write1_mutex ); + + return ret; +} + +static int +send_ldap_control( BerElement *ber, LDAPControl *c ) +{ + int rc; + + assert( c != NULL ); + + rc = ber_printf( ber, "{s" /*}*/, c->ldctl_oid ); + + if( c->ldctl_iscritical ) { + rc = ber_printf( ber, "b", + (ber_int_t) c->ldctl_iscritical ) ; + if( rc == -1 ) return rc; + } + + if( c->ldctl_value.bv_val != NULL ) { + rc = ber_printf( ber, "O", &c->ldctl_value ); + if( rc == -1 ) return rc; + } + + rc = ber_printf( ber, /*{*/"N}" ); + if( rc == -1 ) return rc; + + return 0; +} + +static int +send_ldap_controls( Operation *o, BerElement *ber, LDAPControl **c ) +{ + int rc; + + if( c == NULL ) + return 0; + + rc = ber_printf( ber, "t{"/*}*/, LDAP_TAG_CONTROLS ); + if( rc == -1 ) return rc; + + for( ; *c != NULL; c++) { + rc = send_ldap_control( ber, *c ); + if( rc == -1 ) return rc; + } + +#ifdef SLAP_CONTROL_X_SORTEDRESULTS + /* this is a hack to avoid having to modify op->s_ctrls */ + if( o->o_sortedresults ) { + BerElementBuffer berbuf; + BerElement *sber = (BerElement *) &berbuf; + LDAPControl sorted; + BER_BVZERO( &sorted.ldctl_value ); + sorted.ldctl_oid = LDAP_CONTROL_SORTRESPONSE; + sorted.ldctl_iscritical = 0; + + ber_init2( sber, NULL, LBER_USE_DER ); + + ber_printf( sber, "{e}", LDAP_UNWILLING_TO_PERFORM ); + + if( ber_flatten2( sber, &sorted.ldctl_value, 0 ) == -1 ) { + return -1; + } + + (void) ber_free_buf( sber ); + + rc = send_ldap_control( ber, &sorted ); + if( rc == -1 ) return rc; + } +#endif + + rc = ber_printf( ber, /*{*/"N}" ); + + return rc; +} + +/* + * slap_response_play() + * + * plays the callback list; rationale: a callback can + * - remove itself from the list, by setting op->o_callback = NULL; + * malloc()'ed callbacks should free themselves from inside the + * sc_response() function. + * - replace itself with another (list of) callback(s), by setting + * op->o_callback = a new (list of) callback(s); in this case, it + * is the callback's responsibility to to append existing subsequent + * callbacks to the end of the list that is passed to the sc_response() + * function. + * - modify the list of subsequent callbacks by modifying the value + * of the sc_next field from inside the sc_response() function; this + * case does not require any handling from inside slap_response_play() + * + * To stop execution of the playlist, the sc_response() function must return + * a value different from SLAP_SC_CONTINUE. + * + * The same applies to slap_cleanup_play(); only, there is no means to stop + * execution of the playlist, since all cleanup functions must be called. + */ +static int +slap_response_play( + Operation *op, + SlapReply *rs ) +{ + int rc; + + slap_callback *sc = op->o_callback, **scp; + + rc = SLAP_CB_CONTINUE; + for ( scp = ≻ *scp; ) { + slap_callback *sc_next = (*scp)->sc_next, **sc_nextp = &(*scp)->sc_next; + + op->o_callback = *scp; + if ( op->o_callback->sc_response ) { + rc = op->o_callback->sc_response( op, rs ); + if ( op->o_callback == NULL ) { + /* the callback has been removed; + * repair the list */ + *scp = sc_next; + sc_nextp = scp; + + } else if ( op->o_callback != *scp ) { + /* a new callback has been inserted + * in place of the existing one; repair the list */ + *scp = op->o_callback; + sc_nextp = scp; + } + if ( rc != SLAP_CB_CONTINUE ) break; + } + scp = sc_nextp; + } + + op->o_callback = sc; + return rc; +} + +static int +slap_cleanup_play( + Operation *op, + SlapReply *rs ) +{ + slap_callback *sc = op->o_callback, **scp; + + for ( scp = ≻ *scp; ) { + slap_callback *sc_next = (*scp)->sc_next, **sc_nextp = &(*scp)->sc_next; + + op->o_callback = *scp; + if ( op->o_callback->sc_cleanup ) { + (void)op->o_callback->sc_cleanup( op, rs ); + if ( op->o_callback == NULL ) { + /* the callback has been removed; + * repair the list */ + *scp = sc_next; + sc_nextp = scp; + + } else if ( op->o_callback != *scp ) { + /* a new callback has been inserted + * after the existing one; repair the list */ + /* a new callback has been inserted + * in place of the existing one; repair the list */ + *scp = op->o_callback; + sc_nextp = scp; + } + /* don't care about the result; do all cleanup */ + } + scp = sc_nextp; + } + + op->o_callback = sc; + return LDAP_SUCCESS; +} + +static int +send_ldap_response( + Operation *op, + SlapReply *rs ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *) &berbuf; + int rc = LDAP_SUCCESS; + long bytes; + + /* op was actually aborted, bypass everything if client didn't Cancel */ + if (( rs->sr_err == SLAPD_ABANDON ) && !op->o_cancel ) { + rc = SLAPD_ABANDON; + goto clean2; + } + + if ( op->o_callback ) { + rc = slap_response_play( op, rs ); + if ( rc != SLAP_CB_CONTINUE ) { + goto clean2; + } + } + + /* op completed, connection aborted, bypass sending response */ + if ( op->o_abandon && !op->o_cancel ) { + rc = SLAPD_ABANDON; + goto clean2; + } + +#ifdef LDAP_CONNECTIONLESS + if (op->o_conn && op->o_conn->c_is_udp) + ber = op->o_res_ber; + else +#endif + { + ber_init_w_nullc( ber, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + } + + rc = rs->sr_err; + if ( rc == SLAPD_ABANDON && op->o_cancel ) + rc = LDAP_CANCELLED; + + Debug( LDAP_DEBUG_TRACE, + "send_ldap_response: msgid=%d tag=%lu err=%d\n", + rs->sr_msgid, rs->sr_tag, rc ); + + if( rs->sr_ref ) { + Debug( LDAP_DEBUG_ARGS, "send_ldap_response: ref=\"%s\"\n", + rs->sr_ref[0].bv_val ? rs->sr_ref[0].bv_val : "NULL", + NULL, NULL ); + } + +#ifdef LDAP_CONNECTIONLESS + if (op->o_conn && op->o_conn->c_is_udp && + op->o_protocol == LDAP_VERSION2 ) + { + rc = ber_printf( ber, "t{ess" /*"}"*/, + rs->sr_tag, rc, + rs->sr_matched == NULL ? "" : rs->sr_matched, + rs->sr_text == NULL ? "" : rs->sr_text ); + } else +#endif + if ( rs->sr_type == REP_INTERMEDIATE ) { + rc = ber_printf( ber, "{it{" /*"}}"*/, + rs->sr_msgid, rs->sr_tag ); + + } else { + rc = ber_printf( ber, "{it{ess" /*"}}"*/, + rs->sr_msgid, rs->sr_tag, rc, + rs->sr_matched == NULL ? "" : rs->sr_matched, + rs->sr_text == NULL ? "" : rs->sr_text ); + } + + if( rc != -1 ) { + if ( rs->sr_ref != NULL ) { + assert( rs->sr_err == LDAP_REFERRAL ); + rc = ber_printf( ber, "t{W}", + LDAP_TAG_REFERRAL, rs->sr_ref ); + } else { + assert( rs->sr_err != LDAP_REFERRAL ); + } + } + + if( rc != -1 && rs->sr_type == REP_SASL && rs->sr_sasldata != NULL ) { + rc = ber_printf( ber, "tO", + LDAP_TAG_SASL_RES_CREDS, rs->sr_sasldata ); + } + + if( rc != -1 && + ( rs->sr_type == REP_EXTENDED || rs->sr_type == REP_INTERMEDIATE )) + { + if ( rs->sr_rspoid != NULL ) { + rc = ber_printf( ber, "ts", + rs->sr_type == REP_EXTENDED + ? LDAP_TAG_EXOP_RES_OID : LDAP_TAG_IM_RES_OID, + rs->sr_rspoid ); + } + if( rc != -1 && rs->sr_rspdata != NULL ) { + rc = ber_printf( ber, "tO", + rs->sr_type == REP_EXTENDED + ? LDAP_TAG_EXOP_RES_VALUE : LDAP_TAG_IM_RES_VALUE, + rs->sr_rspdata ); + } + } + + if( rc != -1 ) { + rc = ber_printf( ber, /*"{"*/ "N}" ); + } + + if( rc != -1 ) { + rc = send_ldap_controls( op, ber, rs->sr_ctrls ); + } + + if( rc != -1 ) { + rc = ber_printf( ber, /*"{"*/ "N}" ); + } + +#ifdef LDAP_CONNECTIONLESS + if( op->o_conn && op->o_conn->c_is_udp && op->o_protocol == LDAP_VERSION2 + && rc != -1 ) + { + rc = ber_printf( ber, /*"{"*/ "N}" ); + } +#endif + + if ( rc == -1 ) { + Debug( LDAP_DEBUG_ANY, "ber_printf failed\n", 0, 0, 0 ); + +#ifdef LDAP_CONNECTIONLESS + if (!op->o_conn || op->o_conn->c_is_udp == 0) +#endif + { + ber_free_buf( ber ); + } + goto cleanup; + } + + /* send BER */ + bytes = send_ldap_ber( op, ber ); +#ifdef LDAP_CONNECTIONLESS + if (!op->o_conn || op->o_conn->c_is_udp == 0) +#endif + { + ber_free_buf( ber ); + } + + if ( bytes < 0 ) { + Debug( LDAP_DEBUG_ANY, + "send_ldap_response: ber write failed\n", + 0, 0, 0 ); + + goto cleanup; + } + + ldap_pvt_thread_mutex_lock( &op->o_counters->sc_mutex ); + ldap_pvt_mp_add_ulong( op->o_counters->sc_pdu, 1 ); + ldap_pvt_mp_add_ulong( op->o_counters->sc_bytes, (unsigned long)bytes ); + ldap_pvt_thread_mutex_unlock( &op->o_counters->sc_mutex ); + +cleanup:; + /* Tell caller that we did this for real, as opposed to being + * overridden by a callback + */ + rc = SLAP_CB_CONTINUE; + +clean2:; + if ( op->o_callback ) { + (void)slap_cleanup_play( op, rs ); + } + + if ( rs->sr_flags & REP_MATCHED_MUSTBEFREED ) { + rs->sr_flags ^= REP_MATCHED_MUSTBEFREED; /* paranoia */ + if ( rs->sr_matched ) { + free( (char *)rs->sr_matched ); + rs->sr_matched = NULL; + } + } + + if ( rs->sr_flags & REP_REF_MUSTBEFREED ) { + rs->sr_flags ^= REP_REF_MUSTBEFREED; /* paranoia */ + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + } + + if ( rs->sr_flags & REP_CTRLS_MUSTBEFREED ) { + rs->sr_flags ^= REP_CTRLS_MUSTBEFREED; /* paranoia */ + if ( rs->sr_ctrls ) { + slap_free_ctrls( op, rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + } + + return rc; +} + + +void +send_ldap_disconnect( Operation *op, SlapReply *rs ) +{ +#define LDAP_UNSOLICITED_ERROR(e) \ + ( (e) == LDAP_PROTOCOL_ERROR \ + || (e) == LDAP_STRONG_AUTH_REQUIRED \ + || (e) == LDAP_UNAVAILABLE ) + + Debug( LDAP_DEBUG_TRACE, + "send_ldap_disconnect %d:%s\n", + rs->sr_err, rs->sr_text ? rs->sr_text : "", NULL ); + assert( LDAP_UNSOLICITED_ERROR( rs->sr_err ) ); + + /* TODO: Flush the entry if sr_type == REP_SEARCH/REP_SEARCHREF? */ + RS_ASSERT( !(rs->sr_flags & REP_ENTRY_MASK) ); + rs->sr_flags &= ~REP_ENTRY_MASK; /* paranoia */ + + rs->sr_type = REP_EXTENDED; + rs->sr_rspdata = NULL; + + if ( op->o_protocol < LDAP_VERSION3 ) { + rs->sr_rspoid = NULL; + rs->sr_tag = slap_req2res( op->o_tag ); + rs->sr_msgid = (rs->sr_tag != LBER_SEQUENCE) ? op->o_msgid : 0; + + } else { + rs->sr_rspoid = LDAP_NOTICE_DISCONNECT; + rs->sr_tag = LDAP_RES_EXTENDED; + rs->sr_msgid = LDAP_RES_UNSOLICITED; + } + + if ( send_ldap_response( op, rs ) == SLAP_CB_CONTINUE ) { + Statslog( LDAP_DEBUG_STATS, + "%s DISCONNECT tag=%lu err=%d text=%s\n", + op->o_log_prefix, rs->sr_tag, rs->sr_err, + rs->sr_text ? rs->sr_text : "", 0 ); + } +} + +void +slap_send_ldap_result( Operation *op, SlapReply *rs ) +{ + char *tmp = NULL; + const char *otext = rs->sr_text; + BerVarray oref = rs->sr_ref; + + rs->sr_type = REP_RESULT; + + /* Propagate Abandons so that cleanup callbacks can be processed */ + if ( rs->sr_err == SLAPD_ABANDON || op->o_abandon ) + goto abandon; + + Debug( LDAP_DEBUG_TRACE, + "send_ldap_result: %s p=%d\n", + op->o_log_prefix, op->o_protocol, 0 ); + Debug( LDAP_DEBUG_ARGS, + "send_ldap_result: err=%d matched=\"%s\" text=\"%s\"\n", + rs->sr_err, rs->sr_matched ? rs->sr_matched : "", + rs->sr_text ? rs->sr_text : "" ); + if( rs->sr_ref ) { + Debug( LDAP_DEBUG_ARGS, + "send_ldap_result: referral=\"%s\"\n", + rs->sr_ref[0].bv_val ? rs->sr_ref[0].bv_val : "NULL", + NULL, NULL ); + } + assert( !LDAP_API_ERROR( rs->sr_err ) ); + assert( rs->sr_err != LDAP_PARTIAL_RESULTS ); + + if ( rs->sr_err == LDAP_REFERRAL ) { + if( op->o_domain_scope ) rs->sr_ref = NULL; + + if( rs->sr_ref == NULL ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } else if ( op->o_protocol < LDAP_VERSION3 ) { + rs->sr_err = LDAP_PARTIAL_RESULTS; + } + } + + if ( op->o_protocol < LDAP_VERSION3 ) { + tmp = v2ref( rs->sr_ref, rs->sr_text ); + rs->sr_text = tmp; + rs->sr_ref = NULL; + } + +abandon: + rs->sr_tag = slap_req2res( op->o_tag ); + rs->sr_msgid = (rs->sr_tag != LBER_SEQUENCE) ? op->o_msgid : 0; + + if ( rs->sr_flags & REP_REF_MUSTBEFREED ) { + if ( rs->sr_ref == NULL ) { + rs->sr_flags ^= REP_REF_MUSTBEFREED; + ber_bvarray_free( oref ); + } + oref = NULL; /* send_ldap_response() will free rs->sr_ref if != NULL */ + } + + if ( send_ldap_response( op, rs ) == SLAP_CB_CONTINUE ) { + if ( op->o_tag == LDAP_REQ_SEARCH ) { + Statslog( LDAP_DEBUG_STATS, + "%s SEARCH RESULT tag=%lu err=%d nentries=%d text=%s\n", + op->o_log_prefix, rs->sr_tag, rs->sr_err, + rs->sr_nentries, rs->sr_text ? rs->sr_text : "" ); + } else { + Statslog( LDAP_DEBUG_STATS, + "%s RESULT tag=%lu err=%d text=%s\n", + op->o_log_prefix, rs->sr_tag, rs->sr_err, + rs->sr_text ? rs->sr_text : "", 0 ); + } + } + + if( tmp != NULL ) ch_free(tmp); + rs->sr_text = otext; + rs->sr_ref = oref; +} + +void +send_ldap_sasl( Operation *op, SlapReply *rs ) +{ + Debug( LDAP_DEBUG_TRACE, "send_ldap_sasl: err=%d len=%ld\n", + rs->sr_err, + rs->sr_sasldata ? (long) rs->sr_sasldata->bv_len : -1, NULL ); + + RS_ASSERT( !(rs->sr_flags & REP_ENTRY_MASK) ); + rs->sr_flags &= ~REP_ENTRY_MASK; /* paranoia */ + + rs->sr_type = REP_SASL; + rs->sr_tag = slap_req2res( op->o_tag ); + rs->sr_msgid = (rs->sr_tag != LBER_SEQUENCE) ? op->o_msgid : 0; + + if ( send_ldap_response( op, rs ) == SLAP_CB_CONTINUE ) { + Statslog( LDAP_DEBUG_STATS, + "%s RESULT tag=%lu err=%d text=%s\n", + op->o_log_prefix, rs->sr_tag, rs->sr_err, + rs->sr_text ? rs->sr_text : "", 0 ); + } +} + +void +slap_send_ldap_extended( Operation *op, SlapReply *rs ) +{ + Debug( LDAP_DEBUG_TRACE, + "send_ldap_extended: err=%d oid=%s len=%ld\n", + rs->sr_err, + rs->sr_rspoid ? rs->sr_rspoid : "", + rs->sr_rspdata != NULL ? rs->sr_rspdata->bv_len : 0 ); + + RS_ASSERT( !(rs->sr_flags & REP_ENTRY_MASK) ); + rs->sr_flags &= ~REP_ENTRY_MASK; /* paranoia */ + + rs->sr_type = REP_EXTENDED; + rs->sr_tag = slap_req2res( op->o_tag ); + rs->sr_msgid = (rs->sr_tag != LBER_SEQUENCE) ? op->o_msgid : 0; + + if ( send_ldap_response( op, rs ) == SLAP_CB_CONTINUE ) { + Statslog( LDAP_DEBUG_STATS, + "%s RESULT oid=%s err=%d text=%s\n", + op->o_log_prefix, rs->sr_rspoid ? rs->sr_rspoid : "", + rs->sr_err, rs->sr_text ? rs->sr_text : "", 0 ); + } +} + +void +slap_send_ldap_intermediate( Operation *op, SlapReply *rs ) +{ + Debug( LDAP_DEBUG_TRACE, + "send_ldap_intermediate: err=%d oid=%s len=%ld\n", + rs->sr_err, + rs->sr_rspoid ? rs->sr_rspoid : "", + rs->sr_rspdata != NULL ? rs->sr_rspdata->bv_len : 0 ); + + RS_ASSERT( !(rs->sr_flags & REP_ENTRY_MASK) ); + rs->sr_flags &= ~REP_ENTRY_MASK; /* paranoia */ + + rs->sr_type = REP_INTERMEDIATE; + rs->sr_tag = LDAP_RES_INTERMEDIATE; + rs->sr_msgid = op->o_msgid; + if ( send_ldap_response( op, rs ) == SLAP_CB_CONTINUE ) { + Statslog( LDAP_DEBUG_STATS2, + "%s INTERM oid=%s\n", + op->o_log_prefix, + rs->sr_rspoid ? rs->sr_rspoid : "", 0, 0, 0 ); + } +} + +#define set_ldap_error( rs, err, text ) do { \ + (rs)->sr_err = err; (rs)->sr_text = text; } while(0) + +/* + * returns: + * + * LDAP_SUCCESS entry sent + * LDAP_OTHER entry not sent (other) + * LDAP_INSUFFICIENT_ACCESS entry not sent (ACL) + * LDAP_UNAVAILABLE entry not sent (connection closed) + * LDAP_SIZELIMIT_EXCEEDED entry not sent (caller must send sizelimitExceeded) + */ + +int +slap_send_search_entry( Operation *op, SlapReply *rs ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *) &berbuf; + Attribute *a; + int i, j, rc = LDAP_UNAVAILABLE, bytes; + int userattrs; + AccessControlState acl_state = ACL_STATE_INIT; + int attrsonly; + AttributeDescription *ad_entry = slap_schema.si_ad_entry; + + /* a_flags: array of flags telling if the i-th element will be + * returned or filtered out + * e_flags: array of a_flags + */ + char **e_flags = NULL; + + rs->sr_type = REP_SEARCH; + + if ( op->ors_slimit >= 0 && rs->sr_nentries >= op->ors_slimit ) { + rc = LDAP_SIZELIMIT_EXCEEDED; + goto error_return; + } + + /* Every 64 entries, check for thread pool pause */ + if ( ( ( rs->sr_nentries & 0x3f ) == 0x3f ) && + ldap_pvt_thread_pool_pausing( &connection_pool ) > 0 ) + { + rc = LDAP_BUSY; + goto error_return; + } + + /* eventually will loop through generated operational attribute types + * currently implemented types include: + * entryDN, subschemaSubentry, and hasSubordinates */ + /* NOTE: moved before overlays callback circling because + * they may modify entry and other stuff in rs */ + /* check for special all operational attributes ("+") type */ + /* FIXME: maybe we could set this flag at the operation level; + * however, in principle the caller of send_search_entry() may + * change the attribute list at each call */ + rs->sr_attr_flags = slap_attr_flags( rs->sr_attrs ); + + rc = backend_operational( op, rs ); + if ( rc ) { + goto error_return; + } + + if ( op->o_callback ) { + rc = slap_response_play( op, rs ); + if ( rc != SLAP_CB_CONTINUE ) { + goto error_return; + } + } + + Debug( LDAP_DEBUG_TRACE, "=> send_search_entry: conn %lu dn=\"%s\"%s\n", + op->o_connid, rs->sr_entry->e_name.bv_val, + op->ors_attrsonly ? " (attrsOnly)" : "" ); + + attrsonly = op->ors_attrsonly; + + if ( !access_allowed( op, rs->sr_entry, ad_entry, NULL, ACL_READ, NULL )) { + Debug( LDAP_DEBUG_ACL, + "send_search_entry: conn %lu access to entry (%s) not allowed\n", + op->o_connid, rs->sr_entry->e_name.bv_val, 0 ); + + rc = LDAP_INSUFFICIENT_ACCESS; + goto error_return; + } + + if ( op->o_res_ber ) { + /* read back control or LDAP_CONNECTIONLESS */ + ber = op->o_res_ber; + } else { + struct berval bv; + + bv.bv_len = entry_flatsize( rs->sr_entry, 0 ); + bv.bv_val = op->o_tmpalloc( bv.bv_len, op->o_tmpmemctx ); + + ber_init2( ber, &bv, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + } + +#ifdef LDAP_CONNECTIONLESS + if ( op->o_conn && op->o_conn->c_is_udp ) { + /* CONNECTIONLESS */ + if ( op->o_protocol == LDAP_VERSION2 ) { + rc = ber_printf(ber, "t{O{" /*}}*/, + LDAP_RES_SEARCH_ENTRY, &rs->sr_entry->e_name ); + } else { + rc = ber_printf( ber, "{it{O{" /*}}}*/, op->o_msgid, + LDAP_RES_SEARCH_ENTRY, &rs->sr_entry->e_name ); + } + } else +#endif + if ( op->o_res_ber ) { + /* read back control */ + rc = ber_printf( ber, "t{O{" /*}}*/, + LDAP_RES_SEARCH_ENTRY, &rs->sr_entry->e_name ); + } else { + rc = ber_printf( ber, "{it{O{" /*}}}*/, op->o_msgid, + LDAP_RES_SEARCH_ENTRY, &rs->sr_entry->e_name ); + } + + if ( rc == -1 ) { + Debug( LDAP_DEBUG_ANY, + "send_search_entry: conn %lu ber_printf failed\n", + op->o_connid, 0, 0 ); + + if ( op->o_res_ber == NULL ) ber_free_buf( ber ); + set_ldap_error( rs, LDAP_OTHER, "encoding DN error" ); + rc = rs->sr_err; + goto error_return; + } + + /* check for special all user attributes ("*") type */ + userattrs = SLAP_USERATTRS( rs->sr_attr_flags ); + + /* create an array of arrays of flags. Each flag corresponds + * to particular value of attribute and equals 1 if value matches + * to ValuesReturnFilter or 0 if not + */ + if ( op->o_vrFilter != NULL ) { + int k = 0; + size_t size; + + for ( a = rs->sr_entry->e_attrs, i=0; a != NULL; a = a->a_next, i++ ) { + for ( j = 0; a->a_vals[j].bv_val != NULL; j++ ) k++; + } + + size = i * sizeof(char *) + k; + if ( size > 0 ) { + char *a_flags; + e_flags = slap_sl_calloc ( 1, i * sizeof(char *) + k, op->o_tmpmemctx ); + if( e_flags == NULL ) { + Debug( LDAP_DEBUG_ANY, + "send_search_entry: conn %lu slap_sl_calloc failed\n", + op->o_connid, 0, 0 ); + ber_free( ber, 1 ); + + set_ldap_error( rs, LDAP_OTHER, "out of memory" ); + goto error_return; + } + a_flags = (char *)(e_flags + i); + memset( a_flags, 0, k ); + for ( a=rs->sr_entry->e_attrs, i=0; a != NULL; a=a->a_next, i++ ) { + for ( j = 0; a->a_vals[j].bv_val != NULL; j++ ); + e_flags[i] = a_flags; + a_flags += j; + } + + rc = filter_matched_values(op, rs->sr_entry->e_attrs, &e_flags) ; + if ( rc == -1 ) { + Debug( LDAP_DEBUG_ANY, "send_search_entry: " + "conn %lu matched values filtering failed\n", + op->o_connid, 0, 0 ); + if ( op->o_res_ber == NULL ) ber_free_buf( ber ); + set_ldap_error( rs, LDAP_OTHER, + "matched values filtering error" ); + rc = rs->sr_err; + goto error_return; + } + } + } + + for ( a = rs->sr_entry->e_attrs, j = 0; a != NULL; a = a->a_next, j++ ) { + AttributeDescription *desc = a->a_desc; + int finish = 0; + + if ( rs->sr_attrs == NULL ) { + /* all user attrs request, skip operational attributes */ + if( is_at_operational( desc->ad_type ) ) { + continue; + } + + } else { + /* specific attrs requested */ + if ( is_at_operational( desc->ad_type ) ) { + /* if not explicitly requested */ + if ( !ad_inlist( desc, rs->sr_attrs )) { + /* if not all op attrs requested, skip */ + if ( !SLAP_OPATTRS( rs->sr_attr_flags )) + continue; + /* if DSA-specific and replicating, skip */ + if ( op->o_sync != SLAP_CONTROL_NONE && + desc->ad_type->sat_usage == LDAP_SCHEMA_DSA_OPERATION ) + continue; + } + } else { + if ( !userattrs && !ad_inlist( desc, rs->sr_attrs ) ) { + continue; + } + } + } + + if ( attrsonly ) { + if ( ! access_allowed( op, rs->sr_entry, desc, NULL, + ACL_READ, &acl_state ) ) + { + Debug( LDAP_DEBUG_ACL, "send_search_entry: " + "conn %lu access to attribute %s not allowed\n", + op->o_connid, desc->ad_cname.bv_val, 0 ); + continue; + } + + if (( rc = ber_printf( ber, "{O[" /*]}*/ , &desc->ad_cname )) == -1 ) { + Debug( LDAP_DEBUG_ANY, + "send_search_entry: conn %lu ber_printf failed\n", + op->o_connid, 0, 0 ); + + if ( op->o_res_ber == NULL ) ber_free_buf( ber ); + set_ldap_error( rs, LDAP_OTHER, + "encoding description error"); + rc = rs->sr_err; + goto error_return; + } + finish = 1; + + } else { + int first = 1; + for ( i = 0; a->a_nvals[i].bv_val != NULL; i++ ) { + if ( ! access_allowed( op, rs->sr_entry, + desc, &a->a_nvals[i], ACL_READ, &acl_state ) ) + { + Debug( LDAP_DEBUG_ACL, + "send_search_entry: conn %lu " + "access to attribute %s, value #%d not allowed\n", + op->o_connid, desc->ad_cname.bv_val, i ); + + continue; + } + + if ( op->o_vrFilter && e_flags[j][i] == 0 ){ + continue; + } + + if ( first ) { + first = 0; + finish = 1; + if (( rc = ber_printf( ber, "{O[" /*]}*/ , &desc->ad_cname )) == -1 ) { + Debug( LDAP_DEBUG_ANY, + "send_search_entry: conn %lu ber_printf failed\n", + op->o_connid, 0, 0 ); + + if ( op->o_res_ber == NULL ) ber_free_buf( ber ); + set_ldap_error( rs, LDAP_OTHER, + "encoding description error"); + rc = rs->sr_err; + goto error_return; + } + } + if (( rc = ber_printf( ber, "O", &a->a_vals[i] )) == -1 ) { + Debug( LDAP_DEBUG_ANY, + "send_search_entry: conn %lu " + "ber_printf failed.\n", op->o_connid, 0, 0 ); + + if ( op->o_res_ber == NULL ) ber_free_buf( ber ); + set_ldap_error( rs, LDAP_OTHER, + "encoding values error" ); + rc = rs->sr_err; + goto error_return; + } + } + } + + if ( finish && ( rc = ber_printf( ber, /*{[*/ "]N}" )) == -1 ) { + Debug( LDAP_DEBUG_ANY, + "send_search_entry: conn %lu ber_printf failed\n", + op->o_connid, 0, 0 ); + + if ( op->o_res_ber == NULL ) ber_free_buf( ber ); + set_ldap_error( rs, LDAP_OTHER, "encode end error" ); + rc = rs->sr_err; + goto error_return; + } + } + + /* NOTE: moved before overlays callback circling because + * they may modify entry and other stuff in rs */ + if ( rs->sr_operational_attrs != NULL && op->o_vrFilter != NULL ) { + int k = 0; + size_t size; + + for ( a = rs->sr_operational_attrs, i=0; a != NULL; a = a->a_next, i++ ) { + for ( j = 0; a->a_vals[j].bv_val != NULL; j++ ) k++; + } + + size = i * sizeof(char *) + k; + if ( size > 0 ) { + char *a_flags, **tmp; + + /* + * Reuse previous memory - we likely need less space + * for operational attributes + */ + tmp = slap_sl_realloc( e_flags, i * sizeof(char *) + k, + op->o_tmpmemctx ); + if ( tmp == NULL ) { + Debug( LDAP_DEBUG_ANY, + "send_search_entry: conn %lu " + "not enough memory " + "for matched values filtering\n", + op->o_connid, 0, 0 ); + if ( op->o_res_ber == NULL ) ber_free_buf( ber ); + set_ldap_error( rs, LDAP_OTHER, + "not enough memory for matched values filtering" ); + goto error_return; + } + e_flags = tmp; + a_flags = (char *)(e_flags + i); + memset( a_flags, 0, k ); + for ( a = rs->sr_operational_attrs, i=0; a != NULL; a = a->a_next, i++ ) { + for ( j = 0; a->a_vals[j].bv_val != NULL; j++ ); + e_flags[i] = a_flags; + a_flags += j; + } + rc = filter_matched_values(op, rs->sr_operational_attrs, &e_flags) ; + + if ( rc == -1 ) { + Debug( LDAP_DEBUG_ANY, + "send_search_entry: conn %lu " + "matched values filtering failed\n", + op->o_connid, 0, 0); + if ( op->o_res_ber == NULL ) ber_free_buf( ber ); + set_ldap_error( rs, LDAP_OTHER, + "matched values filtering error" ); + rc = rs->sr_err; + goto error_return; + } + } + } + + for (a = rs->sr_operational_attrs, j=0; a != NULL; a = a->a_next, j++ ) { + AttributeDescription *desc = a->a_desc; + + if ( rs->sr_attrs == NULL ) { + /* all user attrs request, skip operational attributes */ + if( is_at_operational( desc->ad_type ) ) { + continue; + } + + } else { + /* specific attrs requested */ + if( is_at_operational( desc->ad_type ) ) { + if ( !SLAP_OPATTRS( rs->sr_attr_flags ) && + !ad_inlist( desc, rs->sr_attrs ) ) + { + continue; + } + /* if DSA-specific and replicating, skip */ + if ( op->o_sync != SLAP_CONTROL_NONE && + desc->ad_type->sat_usage == LDAP_SCHEMA_DSA_OPERATION ) + continue; + } else { + if ( !userattrs && !ad_inlist( desc, rs->sr_attrs ) ) { + continue; + } + } + } + + if ( ! access_allowed( op, rs->sr_entry, desc, NULL, + ACL_READ, &acl_state ) ) + { + Debug( LDAP_DEBUG_ACL, + "send_search_entry: conn %lu " + "access to attribute %s not allowed\n", + op->o_connid, desc->ad_cname.bv_val, 0 ); + + continue; + } + + rc = ber_printf( ber, "{O[" /*]}*/ , &desc->ad_cname ); + if ( rc == -1 ) { + Debug( LDAP_DEBUG_ANY, + "send_search_entry: conn %lu " + "ber_printf failed\n", op->o_connid, 0, 0 ); + + if ( op->o_res_ber == NULL ) ber_free_buf( ber ); + set_ldap_error( rs, LDAP_OTHER, + "encoding description error" ); + rc = rs->sr_err; + goto error_return; + } + + if ( ! attrsonly ) { + for ( i = 0; a->a_vals[i].bv_val != NULL; i++ ) { + if ( ! access_allowed( op, rs->sr_entry, + desc, &a->a_vals[i], ACL_READ, &acl_state ) ) + { + Debug( LDAP_DEBUG_ACL, + "send_search_entry: conn %lu " + "access to %s, value %d not allowed\n", + op->o_connid, desc->ad_cname.bv_val, i ); + + continue; + } + + if ( op->o_vrFilter && e_flags[j][i] == 0 ){ + continue; + } + + if (( rc = ber_printf( ber, "O", &a->a_vals[i] )) == -1 ) { + Debug( LDAP_DEBUG_ANY, + "send_search_entry: conn %lu ber_printf failed\n", + op->o_connid, 0, 0 ); + + if ( op->o_res_ber == NULL ) ber_free_buf( ber ); + set_ldap_error( rs, LDAP_OTHER, + "encoding values error" ); + rc = rs->sr_err; + goto error_return; + } + } + } + + if (( rc = ber_printf( ber, /*{[*/ "]N}" )) == -1 ) { + Debug( LDAP_DEBUG_ANY, + "send_search_entry: conn %lu ber_printf failed\n", + op->o_connid, 0, 0 ); + + if ( op->o_res_ber == NULL ) ber_free_buf( ber ); + set_ldap_error( rs, LDAP_OTHER, "encode end error" ); + rc = rs->sr_err; + goto error_return; + } + } + + /* free e_flags */ + if ( e_flags ) { + slap_sl_free( e_flags, op->o_tmpmemctx ); + e_flags = NULL; + } + + rc = ber_printf( ber, /*{{*/ "}N}" ); + + if( rc != -1 ) { + rc = send_ldap_controls( op, ber, rs->sr_ctrls ); + } + + if( rc != -1 ) { +#ifdef LDAP_CONNECTIONLESS + if( op->o_conn && op->o_conn->c_is_udp ) { + if ( op->o_protocol != LDAP_VERSION2 ) { + rc = ber_printf( ber, /*{*/ "N}" ); + } + } else +#endif + if ( op->o_res_ber == NULL ) { + rc = ber_printf( ber, /*{*/ "N}" ); + } + } + + if ( rc == -1 ) { + Debug( LDAP_DEBUG_ANY, "ber_printf failed\n", 0, 0, 0 ); + + if ( op->o_res_ber == NULL ) ber_free_buf( ber ); + set_ldap_error( rs, LDAP_OTHER, "encode entry end error" ); + rc = rs->sr_err; + goto error_return; + } + + Statslog( LDAP_DEBUG_STATS2, "%s ENTRY dn=\"%s\"\n", + op->o_log_prefix, rs->sr_entry->e_nname.bv_val, 0, 0, 0 ); + + rs_flush_entry( op, rs, NULL ); + + if ( op->o_res_ber == NULL ) { + bytes = send_ldap_ber( op, ber ); + ber_free_buf( ber ); + + if ( bytes < 0 ) { + Debug( LDAP_DEBUG_ANY, + "send_search_entry: conn %lu ber write failed.\n", + op->o_connid, 0, 0 ); + + rc = LDAP_UNAVAILABLE; + goto error_return; + } + rs->sr_nentries++; + + ldap_pvt_thread_mutex_lock( &op->o_counters->sc_mutex ); + ldap_pvt_mp_add_ulong( op->o_counters->sc_bytes, (unsigned long)bytes ); + ldap_pvt_mp_add_ulong( op->o_counters->sc_entries, 1 ); + ldap_pvt_mp_add_ulong( op->o_counters->sc_pdu, 1 ); + ldap_pvt_thread_mutex_unlock( &op->o_counters->sc_mutex ); + } + + Debug( LDAP_DEBUG_TRACE, + "<= send_search_entry: conn %lu exit.\n", op->o_connid, 0, 0 ); + + rc = LDAP_SUCCESS; + +error_return:; + if ( op->o_callback ) { + (void)slap_cleanup_play( op, rs ); + } + + if ( e_flags ) { + slap_sl_free( e_flags, op->o_tmpmemctx ); + } + + /* FIXME: Can break if rs now contains an extended response */ + if ( rs->sr_operational_attrs ) { + attrs_free( rs->sr_operational_attrs ); + rs->sr_operational_attrs = NULL; + } + rs->sr_attr_flags = SLAP_ATTRS_UNDEFINED; + + if ( op->o_tag == LDAP_REQ_SEARCH && rs->sr_type == REP_SEARCH ) { + rs_flush_entry( op, rs, NULL ); + } else { + RS_ASSERT( (rs->sr_flags & REP_ENTRY_MASK) == 0 ); + } + + if ( rs->sr_flags & REP_CTRLS_MUSTBEFREED ) { + rs->sr_flags ^= REP_CTRLS_MUSTBEFREED; /* paranoia */ + if ( rs->sr_ctrls ) { + slap_free_ctrls( op, rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + } + + return( rc ); +} + +int +slap_send_search_reference( Operation *op, SlapReply *rs ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *) &berbuf; + int rc = 0; + int bytes; + char *edn = rs->sr_entry ? rs->sr_entry->e_name.bv_val : "(null)"; + + AttributeDescription *ad_ref = slap_schema.si_ad_ref; + AttributeDescription *ad_entry = slap_schema.si_ad_entry; + + rs->sr_type = REP_SEARCHREF; + if ( op->o_callback ) { + rc = slap_response_play( op, rs ); + if ( rc != SLAP_CB_CONTINUE ) { + goto rel; + } + } + + Debug( LDAP_DEBUG_TRACE, + "=> send_search_reference: dn=\"%s\"\n", + edn, 0, 0 ); + + if ( rs->sr_entry && ! access_allowed( op, rs->sr_entry, + ad_entry, NULL, ACL_READ, NULL ) ) + { + Debug( LDAP_DEBUG_ACL, + "send_search_reference: access to entry not allowed\n", + 0, 0, 0 ); + rc = 1; + goto rel; + } + + if ( rs->sr_entry && ! access_allowed( op, rs->sr_entry, + ad_ref, NULL, ACL_READ, NULL ) ) + { + Debug( LDAP_DEBUG_ACL, + "send_search_reference: access " + "to reference not allowed\n", + 0, 0, 0 ); + rc = 1; + goto rel; + } + + if( op->o_domain_scope ) { + Debug( LDAP_DEBUG_ANY, + "send_search_reference: domainScope control in (%s)\n", + edn, 0, 0 ); + rc = 0; + goto rel; + } + + if( rs->sr_ref == NULL ) { + Debug( LDAP_DEBUG_ANY, + "send_search_reference: null ref in (%s)\n", + edn, 0, 0 ); + rc = 1; + goto rel; + } + + if( op->o_protocol < LDAP_VERSION3 ) { + rc = 0; + /* save the references for the result */ + if( rs->sr_ref[0].bv_val != NULL ) { + if( value_add( &rs->sr_v2ref, rs->sr_ref ) ) + rc = LDAP_OTHER; + } + goto rel; + } + +#ifdef LDAP_CONNECTIONLESS + if( op->o_conn && op->o_conn->c_is_udp ) { + ber = op->o_res_ber; + } else +#endif + { + ber_init_w_nullc( ber, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + } + + rc = ber_printf( ber, "{it{W}" /*"}"*/ , op->o_msgid, + LDAP_RES_SEARCH_REFERENCE, rs->sr_ref ); + + if( rc != -1 ) { + rc = send_ldap_controls( op, ber, rs->sr_ctrls ); + } + + if( rc != -1 ) { + rc = ber_printf( ber, /*"{"*/ "N}" ); + } + + if ( rc == -1 ) { + Debug( LDAP_DEBUG_ANY, + "send_search_reference: ber_printf failed\n", 0, 0, 0 ); + +#ifdef LDAP_CONNECTIONLESS + if (!op->o_conn || op->o_conn->c_is_udp == 0) +#endif + ber_free_buf( ber ); + set_ldap_error( rs, LDAP_OTHER, "encode DN error" ); + goto rel; + } + + rc = 0; + rs_flush_entry( op, rs, NULL ); + +#ifdef LDAP_CONNECTIONLESS + if (!op->o_conn || op->o_conn->c_is_udp == 0) { +#endif + bytes = send_ldap_ber( op, ber ); + ber_free_buf( ber ); + + if ( bytes < 0 ) { + rc = LDAP_UNAVAILABLE; + } else { + ldap_pvt_thread_mutex_lock( &op->o_counters->sc_mutex ); + ldap_pvt_mp_add_ulong( op->o_counters->sc_bytes, (unsigned long)bytes ); + ldap_pvt_mp_add_ulong( op->o_counters->sc_refs, 1 ); + ldap_pvt_mp_add_ulong( op->o_counters->sc_pdu, 1 ); + ldap_pvt_thread_mutex_unlock( &op->o_counters->sc_mutex ); + } +#ifdef LDAP_CONNECTIONLESS + } +#endif + if ( rs->sr_ref != NULL ) { + int r; + + for ( r = 0; !BER_BVISNULL( &rs->sr_ref[ r ] ); r++ ) { + Statslog( LDAP_DEBUG_STATS2, "%s REF #%d \"%s\"\n", + op->o_log_prefix, r, rs->sr_ref[0].bv_val, + 0, 0 ); + } + + } else { + Statslog( LDAP_DEBUG_STATS2, "%s REF \"(null)\"\n", + op->o_log_prefix, 0, 0, 0, 0 ); + } + + Debug( LDAP_DEBUG_TRACE, "<= send_search_reference\n", 0, 0, 0 ); + + if ( 0 ) { +rel: + rs_flush_entry( op, rs, NULL ); + } + + if ( op->o_callback ) { + (void)slap_cleanup_play( op, rs ); + } + + if ( rs->sr_flags & REP_CTRLS_MUSTBEFREED ) { + rs->sr_flags ^= REP_CTRLS_MUSTBEFREED; /* paranoia */ + if ( rs->sr_ctrls ) { + slap_free_ctrls( op, rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + } + + return rc; +} + +int +str2result( + char *s, + int *code, + char **matched, + char **info ) +{ + int rc; + char *c; + + *code = LDAP_SUCCESS; + *matched = NULL; + *info = NULL; + + if ( strncasecmp( s, "RESULT", STRLENOF( "RESULT" ) ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "str2result (%s) expecting \"RESULT\"\n", + s, 0, 0 ); + + return( -1 ); + } + + rc = 0; + while ( (s = strchr( s, '\n' )) != NULL ) { + *s++ = '\0'; + if ( *s == '\0' ) { + break; + } + if ( (c = strchr( s, ':' )) != NULL ) { + c++; + } + + if ( strncasecmp( s, "code", STRLENOF( "code" ) ) == 0 ) { + char *next = NULL; + long retcode; + + if ( c == NULL ) { + Debug( LDAP_DEBUG_ANY, "str2result (%s) missing value\n", + s, 0, 0 ); + rc = -1; + continue; + } + + while ( isspace( (unsigned char) c[ 0 ] ) ) c++; + if ( c[ 0 ] == '\0' ) { + Debug( LDAP_DEBUG_ANY, "str2result (%s) missing or empty value\n", + s, 0, 0 ); + rc = -1; + continue; + } + + retcode = strtol( c, &next, 10 ); + if ( next == NULL || next == c ) { + Debug( LDAP_DEBUG_ANY, "str2result (%s) unable to parse value\n", + s, 0, 0 ); + rc = -1; + continue; + } + + while ( isspace( (unsigned char) next[ 0 ] ) && next[ 0 ] != '\n' ) + next++; + if ( next[ 0 ] != '\0' && next[ 0 ] != '\n' ) { + Debug( LDAP_DEBUG_ANY, "str2result (%s) extra cruft after value\n", + s, 0, 0 ); + rc = -1; + continue; + } + + /* FIXME: what if it's larger than max int? */ + *code = (int)retcode; + + } else if ( strncasecmp( s, "matched", STRLENOF( "matched" ) ) == 0 ) { + if ( c != NULL ) { + *matched = c; + } + } else if ( strncasecmp( s, "info", STRLENOF( "info" ) ) == 0 ) { + if ( c != NULL ) { + *info = c; + } + } else { + Debug( LDAP_DEBUG_ANY, "str2result (%s) unknown\n", + s, 0, 0 ); + + rc = -1; + } + } + + return( rc ); +} + +int slap_read_controls( + Operation *op, + SlapReply *rs, + Entry *e, + const struct berval *oid, + LDAPControl **ctrl ) +{ + int rc; + struct berval bv; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *) &berbuf; + LDAPControl c; + Operation myop; + + Debug( LDAP_DEBUG_ANY, "%s slap_read_controls: (%s) %s\n", + op->o_log_prefix, oid->bv_val, e->e_dn ); + + rs->sr_entry = e; + rs->sr_attrs = ( oid == &slap_pre_read_bv ) ? + op->o_preread_attrs : op->o_postread_attrs; + + bv.bv_len = entry_flatsize( rs->sr_entry, 0 ); + bv.bv_val = op->o_tmpalloc( bv.bv_len, op->o_tmpmemctx ); + + ber_init2( ber, &bv, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + /* create new operation */ + myop = *op; + /* FIXME: o_bd needed for ACL */ + myop.o_bd = op->o_bd; + myop.o_res_ber = ber; + myop.o_callback = NULL; + myop.ors_slimit = 1; + myop.ors_attrsonly = 0; + + rc = slap_send_search_entry( &myop, rs ); + if( rc ) return rc; + + rc = ber_flatten2( ber, &c.ldctl_value, 0 ); + + if( rc == -1 ) return LDAP_OTHER; + + c.ldctl_oid = oid->bv_val; + c.ldctl_iscritical = 0; + + if ( *ctrl == NULL ) { + /* first try */ + *ctrl = (LDAPControl *) slap_sl_calloc( 1, sizeof(LDAPControl), NULL ); + } else { + /* retry: free previous try */ + slap_sl_free( (*ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + } + + **ctrl = c; + return LDAP_SUCCESS; +} + +/* Map API errors to protocol errors... */ +int +slap_map_api2result( SlapReply *rs ) +{ + switch(rs->sr_err) { + case LDAP_SERVER_DOWN: + return LDAP_UNAVAILABLE; + case LDAP_LOCAL_ERROR: + return LDAP_OTHER; + case LDAP_ENCODING_ERROR: + case LDAP_DECODING_ERROR: + return LDAP_PROTOCOL_ERROR; + case LDAP_TIMEOUT: + return LDAP_UNAVAILABLE; + case LDAP_AUTH_UNKNOWN: + return LDAP_AUTH_METHOD_NOT_SUPPORTED; + case LDAP_FILTER_ERROR: + rs->sr_text = "Filter error"; + return LDAP_OTHER; + case LDAP_USER_CANCELLED: + rs->sr_text = "User cancelled"; + return LDAP_OTHER; + case LDAP_PARAM_ERROR: + return LDAP_PROTOCOL_ERROR; + case LDAP_NO_MEMORY: + return LDAP_OTHER; + case LDAP_CONNECT_ERROR: + return LDAP_UNAVAILABLE; + case LDAP_NOT_SUPPORTED: + return LDAP_UNWILLING_TO_PERFORM; + case LDAP_CONTROL_NOT_FOUND: + return LDAP_PROTOCOL_ERROR; + case LDAP_NO_RESULTS_RETURNED: + return LDAP_NO_SUCH_OBJECT; + case LDAP_MORE_RESULTS_TO_RETURN: + rs->sr_text = "More results to return"; + return LDAP_OTHER; + case LDAP_CLIENT_LOOP: + case LDAP_REFERRAL_LIMIT_EXCEEDED: + return LDAP_LOOP_DETECT; + default: + if ( LDAP_API_ERROR(rs->sr_err) ) return LDAP_OTHER; + return rs->sr_err; + } +} + + +slap_mask_t +slap_attr_flags( AttributeName *an ) +{ + slap_mask_t flags = SLAP_ATTRS_UNDEFINED; + + if ( an == NULL ) { + flags |= ( SLAP_OPATTRS_NO | SLAP_USERATTRS_YES ); + + } else { + flags |= an_find( an, slap_bv_all_operational_attrs ) + ? SLAP_OPATTRS_YES : SLAP_OPATTRS_NO; + flags |= an_find( an, slap_bv_all_user_attrs ) + ? SLAP_USERATTRS_YES : SLAP_USERATTRS_NO; + } + + return flags; +} diff --git a/servers/slapd/root_dse.c b/servers/slapd/root_dse.c new file mode 100644 index 0000000..502908c --- /dev/null +++ b/servers/slapd/root_dse.c @@ -0,0 +1,545 @@ +/* root_dse.c - Provides the Root DSA-Specific Entry */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> + +#include "slap.h" +#include <ldif.h> +#include "lber_pvt.h" + +#ifdef LDAP_SLAPI +#include "slapi/slapi.h" +#endif + +static struct berval builtin_supportedFeatures[] = { + BER_BVC(LDAP_FEATURE_MODIFY_INCREMENT), /* Modify/increment */ + BER_BVC(LDAP_FEATURE_ALL_OP_ATTRS), /* All Op Attrs (+) */ + BER_BVC(LDAP_FEATURE_OBJECTCLASS_ATTRS), /* OCs in Attrs List (@class) */ + BER_BVC(LDAP_FEATURE_ABSOLUTE_FILTERS), /* (&) and (|) search filters */ + BER_BVC(LDAP_FEATURE_LANGUAGE_TAG_OPTIONS), /* Language Tag Options */ + BER_BVC(LDAP_FEATURE_LANGUAGE_RANGE_OPTIONS), /* Language Range Options */ +#ifdef LDAP_DEVEL + BER_BVC(LDAP_FEATURE_SUBORDINATE_SCOPE), /* "children" search scope */ +#endif + BER_BVNULL +}; +static struct berval *supportedFeatures; + +static Entry *usr_attr = NULL; + +/* + * allow modules to register functions that muck with the root DSE entry + */ + +typedef struct entry_info_t { + SLAP_ENTRY_INFO_FN func; + void *arg; + struct entry_info_t *next; +} entry_info_t; + +static entry_info_t *extra_info; + +int +entry_info_register( SLAP_ENTRY_INFO_FN func, void *arg ) +{ + entry_info_t *ei = ch_calloc( 1, sizeof( entry_info_t ) ); + + ei->func = func; + ei->arg = arg; + + ei->next = extra_info; + extra_info = ei; + + return 0; +} + +int +entry_info_unregister( SLAP_ENTRY_INFO_FN func, void *arg ) +{ + entry_info_t **eip; + + for ( eip = &extra_info; *eip != NULL; eip = &(*eip)->next ) { + if ( (*eip)->func == func && (*eip)->arg == arg ) { + entry_info_t *ei = *eip; + + *eip = ei->next; + + ch_free( ei ); + + return 0; + } + } + + return -1; +} + +void +entry_info_destroy( void ) +{ + entry_info_t **eip; + + for ( eip = &extra_info; *eip != NULL; ) { + entry_info_t *ei = *eip; + + eip = &(*eip)->next; + + ch_free( ei ); + } +} + +/* + * Allow modules to register supported features + */ + +static int +supported_feature_init( void ) +{ + int i; + + if ( supportedFeatures != NULL ) { + return 0; + } + + for ( i = 0; !BER_BVISNULL( &builtin_supportedFeatures[ i ] ); i++ ) + ; + + supportedFeatures = ch_calloc( sizeof( struct berval ), i + 1 ); + if ( supportedFeatures == NULL ) { + return -1; + } + + for ( i = 0; !BER_BVISNULL( &builtin_supportedFeatures[ i ] ); i++ ) { + ber_dupbv( &supportedFeatures[ i ], &builtin_supportedFeatures[ i ] ); + } + BER_BVZERO( &supportedFeatures[ i ] ); + + return 0; +} + +int +supported_feature_destroy( void ) +{ + int i; + + if ( supportedFeatures == NULL ) { + return 0; + } + + for ( i = 0; !BER_BVISNULL( &supportedFeatures[ i ] ); i++ ) { + ch_free( supportedFeatures[ i ].bv_val ); + } + + ch_free( supportedFeatures ); + supportedFeatures = NULL; + + return 0; +} + +int +supported_feature_load( struct berval *f ) +{ + struct berval *tmp; + int i; + + supported_feature_init(); + + for ( i = 0; !BER_BVISNULL( &supportedFeatures[ i ] ); i++ ) + ; + + tmp = ch_realloc( supportedFeatures, sizeof( struct berval ) * ( i + 2 ) ); + if ( tmp == NULL ) { + return -1; + } + supportedFeatures = tmp; + + ber_dupbv( &supportedFeatures[ i ], f ); + BER_BVZERO( &supportedFeatures[ i + 1 ] ); + + return 0; +} + +int +root_dse_info( + Connection *conn, + Entry **entry, + const char **text ) +{ + Entry *e; + struct berval val; +#ifdef LDAP_SLAPI + struct berval *bv; +#endif + int i, j; + char ** supportedSASLMechanisms; + BackendDB *be; + + AttributeDescription *ad_structuralObjectClass + = slap_schema.si_ad_structuralObjectClass; + AttributeDescription *ad_objectClass + = slap_schema.si_ad_objectClass; + AttributeDescription *ad_namingContexts + = slap_schema.si_ad_namingContexts; +#ifdef LDAP_SLAPI + AttributeDescription *ad_supportedExtension + = slap_schema.si_ad_supportedExtension; +#endif + AttributeDescription *ad_supportedLDAPVersion + = slap_schema.si_ad_supportedLDAPVersion; + AttributeDescription *ad_supportedSASLMechanisms + = slap_schema.si_ad_supportedSASLMechanisms; + AttributeDescription *ad_supportedFeatures + = slap_schema.si_ad_supportedFeatures; + AttributeDescription *ad_monitorContext + = slap_schema.si_ad_monitorContext; + AttributeDescription *ad_configContext + = slap_schema.si_ad_configContext; + AttributeDescription *ad_ref + = slap_schema.si_ad_ref; + + e = entry_alloc(); + if( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "root_dse_info: entry_alloc failed", 0, 0, 0 ); + return LDAP_OTHER; + } + + e->e_attrs = NULL; + e->e_name.bv_val = ch_strdup( LDAP_ROOT_DSE ); + e->e_name.bv_len = sizeof( LDAP_ROOT_DSE )-1; + e->e_nname.bv_val = ch_strdup( LDAP_ROOT_DSE ); + e->e_nname.bv_len = sizeof( LDAP_ROOT_DSE )-1; + + /* the DN is an empty string so no pretty/normalization is needed */ + assert( !e->e_name.bv_len ); + assert( !e->e_nname.bv_len ); + + e->e_private = NULL; + + /* FIXME: is this really needed? */ + BER_BVSTR( &val, "top" ); + if( attr_merge_one( e, ad_objectClass, &val, NULL ) ) { +fail: + entry_free( e ); + return LDAP_OTHER; + } + + BER_BVSTR( &val, "OpenLDAProotDSE" ); + if( attr_merge_one( e, ad_objectClass, &val, NULL ) ) { + goto fail; + } + if( attr_merge_one( e, ad_structuralObjectClass, &val, NULL ) ) { + goto fail; + } + + LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) { + if ( be->be_suffix == NULL + || be->be_nsuffix == NULL ) { + /* no suffix! */ + continue; + } + if ( SLAP_DBHIDDEN( be )) { + continue; + } + if ( SLAP_MONITOR( be )) { + if( attr_merge_one( e, ad_monitorContext, + &be->be_suffix[0], + &be->be_nsuffix[0] ) ) + { + goto fail; + } + continue; + } + if ( SLAP_CONFIG( be )) { + if( attr_merge_one( e, ad_configContext, + &be->be_suffix[0], + & be->be_nsuffix[0] ) ) + { + goto fail; + } + continue; + } + if ( SLAP_GLUE_SUBORDINATE( be ) && !SLAP_GLUE_ADVERTISE( be ) ) { + continue; + } + for ( j = 0; be->be_suffix[j].bv_val != NULL; j++ ) { + if( attr_merge_one( e, ad_namingContexts, + &be->be_suffix[j], NULL ) ) + { + goto fail; + } + } + } + + /* altServer unsupported */ + + /* supportedControl */ + if ( controls_root_dse_info( e ) != 0 ) { + goto fail; + } + + /* supportedExtension */ + if ( exop_root_dse_info( e ) != 0 ) { + goto fail; + } + +#ifdef LDAP_SLAPI + /* netscape supportedExtension */ + for ( i = 0; (bv = slapi_int_get_supported_extop(i)) != NULL; i++ ) { + if( attr_merge_one( e, ad_supportedExtension, bv, NULL ) ) { + goto fail; + } + } +#endif /* LDAP_SLAPI */ + + /* supportedFeatures */ + if ( supportedFeatures == NULL ) { + supported_feature_init(); + } + + if( attr_merge( e, ad_supportedFeatures, supportedFeatures, NULL ) ) { + goto fail; + } + + /* supportedLDAPVersion */ + /* don't publish version 2 as we don't really support it + * (even when configured to accept version 2 Bind requests) + * and the value would never be used by true LDAPv2 (or LDAPv3) + * clients. + */ + for ( i=LDAP_VERSION3; i<=LDAP_VERSION_MAX; i++ ) { + char buf[sizeof("255")]; + snprintf(buf, sizeof buf, "%d", i); + val.bv_val = buf; + val.bv_len = strlen( val.bv_val ); + if( attr_merge_one( e, ad_supportedLDAPVersion, &val, NULL ) ) { + goto fail; + } + } + + /* supportedSASLMechanism */ + supportedSASLMechanisms = slap_sasl_mechs( conn ); + + if( supportedSASLMechanisms != NULL ) { + for ( i=0; supportedSASLMechanisms[i] != NULL; i++ ) { + val.bv_val = supportedSASLMechanisms[i]; + val.bv_len = strlen( val.bv_val ); + if( attr_merge_one( e, ad_supportedSASLMechanisms, &val, NULL ) ) { + ldap_charray_free( supportedSASLMechanisms ); + goto fail; + } + } + ldap_charray_free( supportedSASLMechanisms ); + } + + if ( default_referral != NULL ) { + if( attr_merge( e, ad_ref, default_referral, NULL /* FIXME */ ) ) { + goto fail; + } + } + + if( usr_attr != NULL) { + Attribute *a; + for( a = usr_attr->e_attrs; a != NULL; a = a->a_next ) { + if( attr_merge( e, a->a_desc, a->a_vals, + (a->a_nvals == a->a_vals) ? NULL : a->a_nvals ) ) + { + goto fail; + } + } + } + + if ( extra_info ) { + entry_info_t *ei = extra_info; + + for ( ; ei; ei = ei->next ) { + ei->func( ei->arg, e ); + } + } + + *entry = e; + return LDAP_SUCCESS; +} + +int +root_dse_init( void ) +{ + return 0; +} + +int +root_dse_destroy( void ) +{ + if ( usr_attr ) { + entry_free( usr_attr ); + usr_attr = NULL; + } + + return 0; +} + +/* + * Read the entries specified in fname and merge the attributes + * to the user defined rootDSE. Note thaat if we find any errors + * what so ever, we will discard the entire entries, print an + * error message and return. + */ +int +root_dse_read_file( const char *fname ) +{ + struct LDIFFP *fp; + int rc = 0, lmax = 0, ldifrc; + unsigned long lineno = 0; + char *buf = NULL; + + if ( (fp = ldif_open( fname, "r" )) == NULL ) { + Debug( LDAP_DEBUG_ANY, + "root_dse_read_file: could not open rootdse attr file \"%s\" - absolute path?\n", + fname, 0, 0 ); + perror( fname ); + return EXIT_FAILURE; + } + + usr_attr = entry_alloc(); + if( usr_attr == NULL ) { + Debug( LDAP_DEBUG_ANY, + "root_dse_read_file: entry_alloc failed", 0, 0, 0 ); + ldif_close( fp ); + return LDAP_OTHER; + } + usr_attr->e_attrs = NULL; + + while(( ldifrc = ldif_read_record( fp, &lineno, &buf, &lmax )) > 0 ) { + Entry *e = str2entry( buf ); + Attribute *a; + + if( e == NULL ) { + Debug( LDAP_DEBUG_ANY, "root_dse_read_file: " + "could not parse entry (file=\"%s\" line=%lu)\n", + fname, lineno, 0 ); + rc = LDAP_OTHER; + break; + } + + /* make sure the DN is the empty DN */ + if( e->e_nname.bv_len ) { + Debug( LDAP_DEBUG_ANY, + "root_dse_read_file: invalid rootDSE " + "- dn=\"%s\" (file=\"%s\" line=%lu)\n", + e->e_dn, fname, lineno ); + entry_free( e ); + rc = LDAP_OTHER; + break; + } + + /* + * we found a valid entry, so walk thru all the attributes in the + * entry, and add each attribute type and description to the + * usr_attr entry + */ + + for(a = e->e_attrs; a != NULL; a = a->a_next) { + if( attr_merge( usr_attr, a->a_desc, a->a_vals, + (a->a_nvals == a->a_vals) ? NULL : a->a_nvals ) ) + { + rc = LDAP_OTHER; + break; + } + } + + entry_free( e ); + if (rc) break; + } + + if ( ldifrc < 0 ) + rc = LDAP_OTHER; + + if (rc) { + entry_free( usr_attr ); + usr_attr = NULL; + } + + ch_free( buf ); + + ldif_close( fp ); + + Debug(LDAP_DEBUG_CONFIG, "rootDSE file=\"%s\" read.\n", fname, 0, 0); + return rc; +} + +int +slap_discover_feature( + slap_bindconf *sb, + const char *attr, + const char *val ) +{ + LDAP *ld = NULL; + LDAPMessage *res = NULL, *entry; + int rc, i; + struct berval bv_val, + **values = NULL; + char *attrs[ 2 ] = { NULL, NULL }; + + rc = slap_client_connect( &ld, sb ); + if ( rc != LDAP_SUCCESS ) { + goto done; + } + + attrs[ 0 ] = (char *) attr; + rc = ldap_search_ext_s( ld, "", LDAP_SCOPE_BASE, "(objectClass=*)", + attrs, 0, NULL, NULL, NULL, 0, &res ); + if ( rc != LDAP_SUCCESS ) { + goto done; + } + + entry = ldap_first_entry( ld, res ); + if ( entry == NULL ) { + goto done; + } + + values = ldap_get_values_len( ld, entry, attrs[ 0 ] ); + if ( values == NULL ) { + rc = LDAP_NO_SUCH_ATTRIBUTE; + goto done; + } + + ber_str2bv( val, 0, 0, &bv_val ); + for ( i = 0; values[ i ] != NULL; i++ ) { + if ( bvmatch( &bv_val, values[ i ] ) ) { + rc = LDAP_COMPARE_TRUE; + goto done; + } + } + + rc = LDAP_COMPARE_FALSE; + +done:; + if ( values != NULL ) { + ldap_value_free_len( values ); + } + + if ( res != NULL ) { + ldap_msgfree( res ); + } + + ldap_unbind_ext( ld, NULL, NULL ); + + return rc; +} + diff --git a/servers/slapd/sasl.c b/servers/slapd/sasl.c new file mode 100644 index 0000000..5144170 --- /dev/null +++ b/servers/slapd/sasl.c @@ -0,0 +1,1906 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif + +#include <ac/stdlib.h> +#include <ac/string.h> + +#include <lber.h> +#include <ldap_log.h> + +#include "slap.h" + +#ifdef ENABLE_REWRITE +#include <rewrite.h> +#endif + +#ifdef HAVE_CYRUS_SASL +# ifdef HAVE_SASL_SASL_H +# include <sasl/sasl.h> +# include <sasl/saslplug.h> +# else +# include <sasl.h> +# include <saslplug.h> +# endif + +# define SASL_CONST const + +#define SASL_VERSION_FULL ((SASL_VERSION_MAJOR << 16) |\ + (SASL_VERSION_MINOR << 8) | SASL_VERSION_STEP) + +#if SASL_VERSION_FULL >= 0x020119 /* 2.1.25 */ +typedef sasl_callback_ft slap_sasl_cb_ft; +#else +typedef int (*slap_sasl_cb_ft)(); +#endif + +static sasl_security_properties_t sasl_secprops; +#elif defined( SLAP_BUILTIN_SASL ) +/* + * built-in SASL implementation + * only supports EXTERNAL + */ +typedef struct sasl_ctx { + slap_ssf_t sc_external_ssf; + struct berval sc_external_id; +} SASL_CTX; + +#endif + +#include <lutil.h> + +static struct berval ext_bv = BER_BVC( "EXTERNAL" ); + +char *slap_sasl_auxprops; + +#ifdef HAVE_CYRUS_SASL + +/* Just use our internal auxprop by default */ +static int +slap_sasl_getopt( + void *context, + const char *plugin_name, + const char *option, + const char **result, + unsigned *len) +{ + if ( strcmp( option, "auxprop_plugin" )) { + return SASL_FAIL; + } + if ( slap_sasl_auxprops ) + *result = slap_sasl_auxprops; + else + *result = "slapd"; + return SASL_OK; +} + +int +slap_sasl_log( + void *context, + int priority, + const char *message) +{ + Connection *conn = context; + int level; + const char * label; + + if ( message == NULL ) { + return SASL_BADPARAM; + } + + switch (priority) { + case SASL_LOG_NONE: + level = LDAP_DEBUG_NONE; + label = "None"; + break; + case SASL_LOG_ERR: + level = LDAP_DEBUG_ANY; + label = "Error"; + break; + case SASL_LOG_FAIL: + level = LDAP_DEBUG_ANY; + label = "Failure"; + break; + case SASL_LOG_WARN: + level = LDAP_DEBUG_TRACE; + label = "Warning"; + break; + case SASL_LOG_NOTE: + level = LDAP_DEBUG_TRACE; + label = "Notice"; + break; + case SASL_LOG_DEBUG: + level = LDAP_DEBUG_TRACE; + label = "Debug"; + break; + case SASL_LOG_TRACE: + level = LDAP_DEBUG_TRACE; + label = "Trace"; + break; + case SASL_LOG_PASS: + level = LDAP_DEBUG_TRACE; + label = "Password Trace"; + break; + default: + return SASL_BADPARAM; + } + + Debug( level, "SASL [conn=%ld] %s: %s\n", + conn ? (long) conn->c_connid: -1L, + label, message ); + + + return SASL_OK; +} + +static const char *slap_propnames[] = { + "*slapConn", "*slapAuthcDNlen", "*slapAuthcDN", + "*slapAuthzDNlen", "*slapAuthzDN", NULL }; + +static Filter generic_filter = { LDAP_FILTER_PRESENT, { 0 }, NULL }; +static struct berval generic_filterstr = BER_BVC("(objectclass=*)"); + +#define SLAP_SASL_PROP_CONN 0 +#define SLAP_SASL_PROP_AUTHCLEN 1 +#define SLAP_SASL_PROP_AUTHC 2 +#define SLAP_SASL_PROP_AUTHZLEN 3 +#define SLAP_SASL_PROP_AUTHZ 4 +#define SLAP_SASL_PROP_COUNT 5 /* Number of properties we used */ + +typedef struct lookup_info { + int flags; + const struct propval *list; + sasl_server_params_t *sparams; +} lookup_info; + +static slap_response sasl_ap_lookup; + +static struct berval sc_cleartext = BER_BVC("{CLEARTEXT}"); + +static int +sasl_ap_lookup( Operation *op, SlapReply *rs ) +{ + BerVarray bv; + AttributeDescription *ad; + Attribute *a; + const char *text; + int rc, i; + lookup_info *sl = (lookup_info *)op->o_callback->sc_private; + + if (rs->sr_type != REP_SEARCH) return 0; + + for( i = 0; sl->list[i].name; i++ ) { + const char *name = sl->list[i].name; + + if ( name[0] == '*' ) { + if ( sl->flags & SASL_AUXPROP_AUTHZID ) continue; + /* Skip our private properties */ + if ( !strcmp( name, slap_propnames[0] )) { + i += SLAP_SASL_PROP_COUNT - 1; + continue; + } + name++; + } else if ( !(sl->flags & SASL_AUXPROP_AUTHZID ) ) + continue; + + if ( sl->list[i].values ) { + if ( !(sl->flags & SASL_AUXPROP_OVERRIDE) ) continue; + } + ad = NULL; + rc = slap_str2ad( name, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "slap_ap_lookup: str2ad(%s): %s\n", name, text, 0 ); + continue; + } + + /* If it's the rootdn and a rootpw was present, we already set + * it so don't override it here. + */ + if ( ad == slap_schema.si_ad_userPassword && sl->list[i].values && + be_isroot_dn( op->o_bd, &op->o_req_ndn )) + continue; + + a = attr_find( rs->sr_entry->e_attrs, ad ); + if ( !a ) continue; + if ( ! access_allowed( op, rs->sr_entry, ad, NULL, ACL_AUTH, NULL ) ) { + continue; + } + if ( sl->list[i].values && ( sl->flags & SASL_AUXPROP_OVERRIDE ) ) { + sl->sparams->utils->prop_erase( sl->sparams->propctx, + sl->list[i].name ); + } + for ( bv = a->a_vals; bv->bv_val; bv++ ) { + /* ITS#3846 don't give hashed passwords to SASL */ + if ( ad == slap_schema.si_ad_userPassword && + bv->bv_val[0] == '{' /*}*/ ) + { + if ( lutil_passwd_scheme( bv->bv_val ) ) { + /* If it's not a recognized scheme, just assume it's + * a cleartext password that happened to include brackets. + * + * If it's a recognized scheme, skip this value, unless the + * scheme is {CLEARTEXT}. In that case, skip over the + * scheme name and use the remainder. If there is nothing + * past the scheme name, skip this value. + */ +#ifdef SLAPD_CLEARTEXT + if ( !strncasecmp( bv->bv_val, sc_cleartext.bv_val, + sc_cleartext.bv_len )) { + struct berval cbv; + cbv.bv_len = bv->bv_len - sc_cleartext.bv_len; + if ( cbv.bv_len > 0 ) { + cbv.bv_val = bv->bv_val + sc_cleartext.bv_len; + sl->sparams->utils->prop_set( sl->sparams->propctx, + sl->list[i].name, cbv.bv_val, cbv.bv_len ); + } + } +#endif + continue; + } + } + sl->sparams->utils->prop_set( sl->sparams->propctx, + sl->list[i].name, bv->bv_val, bv->bv_len ); + } + } + return LDAP_SUCCESS; +} + +#if SASL_VERSION_FULL >= 0x020118 +static int +#else +static void +#endif +slap_auxprop_lookup( + void *glob_context, + sasl_server_params_t *sparams, + unsigned flags, + const char *user, + unsigned ulen) +{ + OperationBuffer opbuf = {{ NULL }}; + Operation *op = (Operation *)&opbuf; + int i, doit = 0; + Connection *conn = NULL; + lookup_info sl; + int rc = LDAP_SUCCESS; + + sl.list = sparams->utils->prop_get( sparams->propctx ); + sl.sparams = sparams; + sl.flags = flags; + + /* Find our DN and conn first */ + for( i = 0; sl.list[i].name; i++ ) { + if ( sl.list[i].name[0] == '*' ) { + if ( !strcmp( sl.list[i].name, slap_propnames[SLAP_SASL_PROP_CONN] ) ) { + if ( sl.list[i].values && sl.list[i].values[0] ) + AC_MEMCPY( &conn, sl.list[i].values[0], sizeof( conn ) ); + continue; + } + if ( flags & SASL_AUXPROP_AUTHZID ) { + if ( !strcmp( sl.list[i].name, slap_propnames[SLAP_SASL_PROP_AUTHZLEN] )) { + if ( sl.list[i].values && sl.list[i].values[0] ) + AC_MEMCPY( &op->o_req_ndn.bv_len, sl.list[i].values[0], + sizeof( op->o_req_ndn.bv_len ) ); + } else if ( !strcmp( sl.list[i].name, slap_propnames[SLAP_SASL_PROP_AUTHZ] )) { + if ( sl.list[i].values ) + op->o_req_ndn.bv_val = (char *)sl.list[i].values[0]; + break; + } + } + + if ( !strcmp( sl.list[i].name, slap_propnames[SLAP_SASL_PROP_AUTHCLEN] )) { + if ( sl.list[i].values && sl.list[i].values[0] ) + AC_MEMCPY( &op->o_req_ndn.bv_len, sl.list[i].values[0], + sizeof( op->o_req_ndn.bv_len ) ); + } else if ( !strcmp( sl.list[i].name, slap_propnames[SLAP_SASL_PROP_AUTHC] ) ) { + if ( sl.list[i].values ) { + op->o_req_ndn.bv_val = (char *)sl.list[i].values[0]; + if ( !(flags & SASL_AUXPROP_AUTHZID) ) + break; + } + } + } + } + + /* we don't know anything about this, ignore it */ + if ( !conn ) { + rc = LDAP_SUCCESS; + goto done; + } + + /* Now see what else needs to be fetched */ + for( i = 0; sl.list[i].name; i++ ) { + const char *name = sl.list[i].name; + + if ( name[0] == '*' ) { + if ( flags & SASL_AUXPROP_AUTHZID ) continue; + /* Skip our private properties */ + if ( !strcmp( name, slap_propnames[0] )) { + i += SLAP_SASL_PROP_COUNT - 1; + continue; + } + name++; + } else if ( !(flags & SASL_AUXPROP_AUTHZID ) ) + continue; + + if ( sl.list[i].values ) { + if ( !(flags & SASL_AUXPROP_OVERRIDE) ) continue; + } + doit = 1; + break; + } + + if (doit) { + slap_callback cb = { NULL, sasl_ap_lookup, NULL, NULL }; + + cb.sc_private = &sl; + + op->o_bd = select_backend( &op->o_req_ndn, 1 ); + + if ( op->o_bd ) { + /* For rootdn, see if we can use the rootpw */ + if ( be_isroot_dn( op->o_bd, &op->o_req_ndn ) && + !BER_BVISEMPTY( &op->o_bd->be_rootpw )) { + struct berval cbv = BER_BVNULL; + + /* If there's a recognized scheme, see if it's CLEARTEXT */ + if ( lutil_passwd_scheme( op->o_bd->be_rootpw.bv_val )) { + if ( !strncasecmp( op->o_bd->be_rootpw.bv_val, + sc_cleartext.bv_val, sc_cleartext.bv_len )) { + + /* If it's CLEARTEXT, skip past scheme spec */ + cbv.bv_len = op->o_bd->be_rootpw.bv_len - + sc_cleartext.bv_len; + if ( cbv.bv_len ) { + cbv.bv_val = op->o_bd->be_rootpw.bv_val + + sc_cleartext.bv_len; + } + } + /* No scheme, use the whole value */ + } else { + cbv = op->o_bd->be_rootpw; + } + if ( !BER_BVISEMPTY( &cbv )) { + for( i = 0; sl.list[i].name; i++ ) { + const char *name = sl.list[i].name; + + if ( name[0] == '*' ) { + if ( flags & SASL_AUXPROP_AUTHZID ) continue; + name++; + } else if ( !(flags & SASL_AUXPROP_AUTHZID ) ) + continue; + + if ( !strcasecmp(name,"userPassword") ) { + sl.sparams->utils->prop_set( sl.sparams->propctx, + sl.list[i].name, cbv.bv_val, cbv.bv_len ); + break; + } + } + } + } + + if ( op->o_bd->be_search ) { + SlapReply rs = {REP_RESULT}; + op->o_hdr = conn->c_sasl_bindop->o_hdr; + op->o_controls = opbuf.ob_controls; + op->o_tag = LDAP_REQ_SEARCH; + op->o_dn = conn->c_ndn; + op->o_ndn = conn->c_ndn; + op->o_callback = &cb; + slap_op_time( &op->o_time, &op->o_tincr ); + op->o_do_not_cache = 1; + op->o_is_auth_check = 1; + op->o_req_dn = op->o_req_ndn; + op->ors_scope = LDAP_SCOPE_BASE; + op->ors_deref = LDAP_DEREF_NEVER; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_slimit = 1; + op->ors_filter = &generic_filter; + op->ors_filterstr = generic_filterstr; + op->o_authz = conn->c_authz; + /* FIXME: we want all attributes, right? */ + op->ors_attrs = NULL; + + rc = op->o_bd->be_search( op, &rs ); + } + } + } +done:; +#if SASL_VERSION_FULL >= 0x020118 + return rc != LDAP_SUCCESS ? SASL_FAIL : SASL_OK; +#endif +} + +#if SASL_VERSION_FULL >= 0x020110 +static int +slap_auxprop_store( + void *glob_context, + sasl_server_params_t *sparams, + struct propctx *prctx, + const char *user, + unsigned ulen) +{ + Operation op = {0}; + Opheader oph; + SlapReply rs = {REP_RESULT}; + int rc, i; + unsigned j; + Connection *conn = NULL; + const struct propval *pr; + Modifications *modlist = NULL, **modtail = &modlist, *mod; + slap_callback cb = { NULL, slap_null_cb, NULL, NULL }; + char textbuf[SLAP_TEXT_BUFLEN]; + const char *text; + size_t textlen = sizeof(textbuf); + + /* just checking if we are enabled */ + if (!prctx) return SASL_OK; + + if (!sparams || !user) return SASL_BADPARAM; + + pr = sparams->utils->prop_get( sparams->propctx ); + + /* Find our DN and conn first */ + for( i = 0; pr[i].name; i++ ) { + if ( pr[i].name[0] == '*' ) { + if ( !strcmp( pr[i].name, slap_propnames[SLAP_SASL_PROP_CONN] ) ) { + if ( pr[i].values && pr[i].values[0] ) + AC_MEMCPY( &conn, pr[i].values[0], sizeof( conn ) ); + continue; + } + if ( !strcmp( pr[i].name, slap_propnames[SLAP_SASL_PROP_AUTHCLEN] )) { + if ( pr[i].values && pr[i].values[0] ) + AC_MEMCPY( &op.o_req_ndn.bv_len, pr[i].values[0], + sizeof( op.o_req_ndn.bv_len ) ); + } else if ( !strcmp( pr[i].name, slap_propnames[SLAP_SASL_PROP_AUTHC] ) ) { + if ( pr[i].values ) + op.o_req_ndn.bv_val = (char *)pr[i].values[0]; + } + } + } + if (!conn || !op.o_req_ndn.bv_val) return SASL_BADPARAM; + + op.o_bd = select_backend( &op.o_req_ndn, 1 ); + + if ( !op.o_bd || !op.o_bd->be_modify ) return SASL_FAIL; + + pr = sparams->utils->prop_get( prctx ); + if (!pr) return SASL_BADPARAM; + + for (i=0; pr[i].name; i++); + if (!i) return SASL_BADPARAM; + + for (i=0; pr[i].name; i++) { + mod = (Modifications *)ch_malloc( sizeof(Modifications) ); + mod->sml_op = LDAP_MOD_REPLACE; + mod->sml_flags = 0; + ber_str2bv( pr[i].name, 0, 0, &mod->sml_type ); + mod->sml_numvals = pr[i].nvalues; + mod->sml_values = (struct berval *)ch_malloc( (pr[i].nvalues + 1) * + sizeof(struct berval)); + for (j=0; j<pr[i].nvalues; j++) { + ber_str2bv( pr[i].values[j], 0, 1, &mod->sml_values[j]); + } + BER_BVZERO( &mod->sml_values[j] ); + mod->sml_nvalues = NULL; + mod->sml_desc = NULL; + *modtail = mod; + modtail = &mod->sml_next; + } + *modtail = NULL; + + rc = slap_mods_check( &op, modlist, &text, textbuf, textlen, NULL ); + + if ( rc == LDAP_SUCCESS ) { + rc = slap_mods_no_user_mod_check( &op, modlist, + &text, textbuf, textlen ); + + if ( rc == LDAP_SUCCESS ) { + if ( conn->c_sasl_bindop ) { + op.o_hdr = conn->c_sasl_bindop->o_hdr; + } else { + op.o_hdr = &oph; + memset( &oph, 0, sizeof(oph) ); + operation_fake_init( conn, &op, ldap_pvt_thread_pool_context(), 0 ); + } + op.o_tag = LDAP_REQ_MODIFY; + op.o_ndn = op.o_req_ndn; + op.o_callback = &cb; + slap_op_time( &op.o_time, &op.o_tincr ); + op.o_do_not_cache = 1; + op.o_is_auth_check = 1; + op.o_req_dn = op.o_req_ndn; + op.orm_modlist = modlist; + + rc = op.o_bd->be_modify( &op, &rs ); + } + } + slap_mods_free( modlist, 1 ); + return rc != LDAP_SUCCESS ? SASL_FAIL : SASL_OK; +} +#endif /* SASL_VERSION_FULL >= 2.1.16 */ + +static sasl_auxprop_plug_t slap_auxprop_plugin = { + 0, /* Features */ + 0, /* spare */ + NULL, /* glob_context */ + NULL, /* auxprop_free */ + slap_auxprop_lookup, + "slapd", /* name */ +#if SASL_VERSION_FULL >= 0x020110 + slap_auxprop_store /* the declaration of this member changed + * in cyrus SASL from 2.1.15 to 2.1.16 */ +#else + NULL +#endif +}; + +static int +slap_auxprop_init( + const sasl_utils_t *utils, + int max_version, + int *out_version, + sasl_auxprop_plug_t **plug, + const char *plugname) +{ + if ( !out_version || !plug ) return SASL_BADPARAM; + + if ( max_version < SASL_AUXPROP_PLUG_VERSION ) return SASL_BADVERS; + + *out_version = SASL_AUXPROP_PLUG_VERSION; + *plug = &slap_auxprop_plugin; + return SASL_OK; +} + +/* Convert a SASL authcid or authzid into a DN. Store the DN in an + * auxiliary property, so that we can refer to it in sasl_authorize + * without interfering with anything else. Also, the SASL username + * buffer is constrained to 256 characters, and our DNs could be + * much longer (SLAP_LDAPDN_MAXLEN, currently set to 8192) + */ +static int +slap_sasl_canonicalize( + sasl_conn_t *sconn, + void *context, + const char *in, + unsigned inlen, + unsigned flags, + const char *user_realm, + char *out, + unsigned out_max, + unsigned *out_len) +{ + Connection *conn = (Connection *)context; + struct propctx *props = sasl_auxprop_getctx( sconn ); + struct propval auxvals[ SLAP_SASL_PROP_COUNT ] = { { 0 } }; + struct berval dn; + int rc, which; + const char *names[2]; + struct berval bvin; + + *out_len = 0; + + Debug( LDAP_DEBUG_ARGS, "SASL Canonicalize [conn=%ld]: %s=\"%s\"\n", + conn ? (long) conn->c_connid : -1L, + (flags & SASL_CU_AUTHID) ? "authcid" : "authzid", + in ? in : "<empty>"); + + /* If name is too big, just truncate. We don't care, we're + * using DNs, not the usernames. + */ + if ( inlen > out_max ) + inlen = out_max-1; + + /* This is a Simple Bind using SPASSWD. That means the in-directory + * userPassword of the Binding user already points at SASL, so it + * cannot be used to actually satisfy a password comparison. Just + * ignore it, some other mech will process it. + */ + if ( !conn->c_sasl_bindop || + conn->c_sasl_bindop->orb_method != LDAP_AUTH_SASL ) goto done; + + /* See if we need to add request, can only do it once */ + prop_getnames( props, slap_propnames, auxvals ); + if ( !auxvals[0].name ) + prop_request( props, slap_propnames ); + + if ( flags & SASL_CU_AUTHID ) + which = SLAP_SASL_PROP_AUTHCLEN; + else + which = SLAP_SASL_PROP_AUTHZLEN; + + /* Need to store the Connection for auxprop_lookup */ + if ( !auxvals[SLAP_SASL_PROP_CONN].values ) { + names[0] = slap_propnames[SLAP_SASL_PROP_CONN]; + names[1] = NULL; + prop_set( props, names[0], (char *)&conn, sizeof( conn ) ); + } + + /* Already been here? */ + if ( auxvals[which].values ) + goto done; + + /* Normally we require an authzID to have a u: or dn: prefix. + * However, SASL frequently gives us an authzID that is just + * an exact copy of the authcID, without a prefix. We need to + * detect and allow this condition. If SASL calls canonicalize + * with SASL_CU_AUTHID|SASL_CU_AUTHZID this is a no-brainer. + * But if it's broken into two calls, we need to remember the + * authcID so that we can compare the authzID later. We store + * the authcID temporarily in conn->c_sasl_dn. We necessarily + * finish Canonicalizing before Authorizing, so there is no + * conflict with slap_sasl_authorize's use of this temp var. + * + * The SASL EXTERNAL mech is backwards from all the other mechs, + * it does authzID before the authcID. If we see that authzID + * has already been done, don't do anything special with authcID. + */ + if ( flags == SASL_CU_AUTHID && !auxvals[SLAP_SASL_PROP_AUTHZ].values ) { + conn->c_sasl_dn.bv_val = (char *) in; + conn->c_sasl_dn.bv_len = 0; + } else if ( flags == SASL_CU_AUTHZID && conn->c_sasl_dn.bv_val ) { + rc = strcmp( in, conn->c_sasl_dn.bv_val ); + conn->c_sasl_dn.bv_val = NULL; + /* They were equal, no work needed */ + if ( !rc ) goto done; + } + + bvin.bv_val = (char *)in; + bvin.bv_len = inlen; + rc = slap_sasl_getdn( conn, NULL, &bvin, (char *)user_realm, &dn, + (flags & SASL_CU_AUTHID) ? SLAP_GETDN_AUTHCID : SLAP_GETDN_AUTHZID ); + if ( rc != LDAP_SUCCESS ) { + sasl_seterror( sconn, 0, ldap_err2string( rc ) ); + return SASL_NOAUTHZ; + } + + names[0] = slap_propnames[which]; + names[1] = NULL; + prop_set( props, names[0], (char *)&dn.bv_len, sizeof( dn.bv_len ) ); + + which++; + names[0] = slap_propnames[which]; + prop_set( props, names[0], dn.bv_val, dn.bv_len ); + + Debug( LDAP_DEBUG_ARGS, "SASL Canonicalize [conn=%ld]: %s=\"%s\"\n", + conn ? (long) conn->c_connid : -1L, names[0]+1, + dn.bv_val ? dn.bv_val : "<EMPTY>" ); + + /* Not needed any more, SASL has copied it */ + if ( conn && conn->c_sasl_bindop ) + conn->c_sasl_bindop->o_tmpfree( dn.bv_val, conn->c_sasl_bindop->o_tmpmemctx ); + +done: + AC_MEMCPY( out, in, inlen ); + out[inlen] = '\0'; + + *out_len = inlen; + + return SASL_OK; +} + +static int +slap_sasl_authorize( + sasl_conn_t *sconn, + void *context, + char *requested_user, + unsigned rlen, + char *auth_identity, + unsigned alen, + const char *def_realm, + unsigned urlen, + struct propctx *props) +{ + Connection *conn = (Connection *)context; + /* actually: + * (SLAP_SASL_PROP_COUNT - 1) because we skip "conn", + * + 1 for NULL termination? + */ + struct propval auxvals[ SLAP_SASL_PROP_COUNT ] = { { 0 } }; + struct berval authcDN, authzDN = BER_BVNULL; + int rc; + + /* Simple Binds don't support proxy authorization, ignore it */ + if ( !conn->c_sasl_bindop || + conn->c_sasl_bindop->orb_method != LDAP_AUTH_SASL ) return SASL_OK; + + Debug( LDAP_DEBUG_ARGS, "SASL proxy authorize [conn=%ld]: " + "authcid=\"%s\" authzid=\"%s\"\n", + conn ? (long) conn->c_connid : -1L, auth_identity, requested_user ); + if ( conn->c_sasl_dn.bv_val ) { + BER_BVZERO( &conn->c_sasl_dn ); + } + + /* Skip SLAP_SASL_PROP_CONN */ + prop_getnames( props, slap_propnames+1, auxvals ); + + /* Should not happen */ + if ( !auxvals[0].values ) { + sasl_seterror( sconn, 0, "invalid authcid" ); + return SASL_NOAUTHZ; + } + + AC_MEMCPY( &authcDN.bv_len, auxvals[0].values[0], sizeof(authcDN.bv_len) ); + authcDN.bv_val = auxvals[1].values ? (char *)auxvals[1].values[0] : NULL; + conn->c_sasl_dn = authcDN; + + /* Nothing to do if no authzID was given */ + if ( !auxvals[2].name || !auxvals[2].values ) { + goto ok; + } + + AC_MEMCPY( &authzDN.bv_len, auxvals[2].values[0], sizeof(authzDN.bv_len) ); + authzDN.bv_val = auxvals[3].values ? (char *)auxvals[3].values[0] : NULL; + + rc = slap_sasl_authorized( conn->c_sasl_bindop, &authcDN, &authzDN ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "SASL Proxy Authorize [conn=%ld]: " + "proxy authorization disallowed (%d)\n", + conn ? (long) conn->c_connid : -1L, rc, 0 ); + + sasl_seterror( sconn, 0, "not authorized" ); + return SASL_NOAUTHZ; + } + + /* FIXME: we need yet another dup because slap_sasl_getdn() + * is using the bind operation slab */ + ber_dupbv( &conn->c_sasl_authz_dn, &authzDN ); + +ok: + if (conn->c_sasl_bindop) { + Statslog( LDAP_DEBUG_STATS, + "%s BIND authcid=\"%s\" authzid=\"%s\"\n", + conn->c_sasl_bindop->o_log_prefix, + auth_identity, requested_user, 0, 0 ); + } + + Debug( LDAP_DEBUG_TRACE, "SASL Authorize [conn=%ld]: " + " proxy authorization allowed authzDN=\"%s\"\n", + conn ? (long) conn->c_connid : -1L, + authzDN.bv_val ? authzDN.bv_val : "", 0 ); + return SASL_OK; +} + +static int +slap_sasl_err2ldap( int saslerr ) +{ + int rc; + + /* map SASL errors to LDAP resultCode returned by: + * sasl_server_new() + * SASL_OK, SASL_NOMEM + * sasl_server_step() + * SASL_OK, SASL_CONTINUE, SASL_TRANS, SASL_BADPARAM, SASL_BADPROT, + * ... + * sasl_server_start() + * + SASL_NOMECH + * sasl_setprop() + * SASL_OK, SASL_BADPARAM + */ + + switch (saslerr) { + case SASL_OK: + rc = LDAP_SUCCESS; + break; + case SASL_CONTINUE: + rc = LDAP_SASL_BIND_IN_PROGRESS; + break; + case SASL_FAIL: + case SASL_NOMEM: + rc = LDAP_OTHER; + break; + case SASL_NOMECH: + rc = LDAP_AUTH_METHOD_NOT_SUPPORTED; + break; + case SASL_BADAUTH: + case SASL_NOUSER: + case SASL_TRANS: + case SASL_EXPIRED: + rc = LDAP_INVALID_CREDENTIALS; + break; + case SASL_NOAUTHZ: + rc = LDAP_INSUFFICIENT_ACCESS; + break; + case SASL_TOOWEAK: + case SASL_ENCRYPT: + rc = LDAP_INAPPROPRIATE_AUTH; + break; + case SASL_UNAVAIL: + case SASL_TRYAGAIN: + rc = LDAP_UNAVAILABLE; + break; + case SASL_DISABLED: + rc = LDAP_UNWILLING_TO_PERFORM; + break; + default: + rc = LDAP_OTHER; + break; + } + + return rc; +} + +#ifdef SLAPD_SPASSWD + +static struct berval sasl_pwscheme = BER_BVC("{SASL}"); + +static int chk_sasl( + const struct berval *sc, + const struct berval * passwd, + const struct berval * cred, + const char **text ) +{ + unsigned int i; + int rtn; + void *ctx, *sconn = NULL; + + for( i=0; i<cred->bv_len; i++) { + if(cred->bv_val[i] == '\0') { + return LUTIL_PASSWD_ERR; /* NUL character in password */ + } + } + + if( cred->bv_val[i] != '\0' ) { + return LUTIL_PASSWD_ERR; /* cred must behave like a string */ + } + + for( i=0; i<passwd->bv_len; i++) { + if(passwd->bv_val[i] == '\0') { + return LUTIL_PASSWD_ERR; /* NUL character in password */ + } + } + + if( passwd->bv_val[i] != '\0' ) { + return LUTIL_PASSWD_ERR; /* passwd must behave like a string */ + } + + rtn = LUTIL_PASSWD_ERR; + + ctx = ldap_pvt_thread_pool_context(); + ldap_pvt_thread_pool_getkey( ctx, (void *)slap_sasl_bind, &sconn, NULL ); + + if( sconn != NULL ) { + int sc; + sc = sasl_checkpass( sconn, + passwd->bv_val, passwd->bv_len, + cred->bv_val, cred->bv_len ); + rtn = ( sc != SASL_OK ) ? LUTIL_PASSWD_ERR : LUTIL_PASSWD_OK; + } + + return rtn; +} +#endif /* SLAPD_SPASSWD */ + +#endif /* HAVE_CYRUS_SASL */ + +#ifdef ENABLE_REWRITE + +typedef struct slapd_map_data { + struct berval base; + struct berval filter; + AttributeName attrs[2]; + int scope; +} slapd_map_data; + +static void * +slapd_rw_config( const char *fname, int lineno, int argc, char **argv ) +{ + slapd_map_data *ret = NULL; + LDAPURLDesc *lud = NULL; + char *uri; + AttributeDescription *ad = NULL; + int rc, flen = 0; + struct berval dn, ndn; + + if ( argc != 1 ) { + Debug( LDAP_DEBUG_ANY, + "[%s:%d] slapd map needs URI\n", + fname, lineno, 0 ); + return NULL; + } + + uri = argv[0]; + if ( strncasecmp( uri, "uri=", STRLENOF( "uri=" ) ) == 0 ) { + uri += STRLENOF( "uri=" ); + } + + if ( ldap_url_parse( uri, &lud ) != LDAP_URL_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "[%s:%d] illegal URI '%s'\n", + fname, lineno, uri ); + return NULL; + } + + if ( strcasecmp( lud->lud_scheme, "ldap" )) { + Debug( LDAP_DEBUG_ANY, + "[%s:%d] illegal URI scheme '%s'\n", + fname, lineno, lud->lud_scheme ); + goto done; + } + + if (( lud->lud_host && lud->lud_host[0] ) || lud->lud_exts + || !lud->lud_dn ) { + Debug( LDAP_DEBUG_ANY, + "[%s:%d] illegal URI '%s'\n", + fname, lineno, uri ); + goto done; + } + + if ( lud->lud_attrs ) { + if ( lud->lud_attrs[1] ) { + Debug( LDAP_DEBUG_ANY, + "[%s:%d] only one attribute allowed in URI\n", + fname, lineno, 0 ); + goto done; + } + if ( strcasecmp( lud->lud_attrs[0], "dn" ) && + strcasecmp( lud->lud_attrs[0], "entryDN" )) { + const char *text; + rc = slap_str2ad( lud->lud_attrs[0], &ad, &text ); + if ( rc ) + goto done; + } + } + ber_str2bv( lud->lud_dn, 0, 0, &dn ); + if ( dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL )) + goto done; + + if ( lud->lud_filter ) { + flen = strlen( lud->lud_filter ) + 1; + } + ret = ch_malloc( sizeof( slapd_map_data ) + flen ); + ret->base = ndn; + if ( flen ) { + ret->filter.bv_val = (char *)(ret+1); + ret->filter.bv_len = flen - 1; + strcpy( ret->filter.bv_val, lud->lud_filter ); + } else { + BER_BVZERO( &ret->filter ); + } + ret->scope = lud->lud_scope; + if ( ad ) { + ret->attrs[0].an_name = ad->ad_cname; + } else { + BER_BVZERO( &ret->attrs[0].an_name ); + } + ret->attrs[0].an_desc = ad; + BER_BVZERO( &ret->attrs[1].an_name ); +done: + ldap_free_urldesc( lud ); + return ret; +} + +struct slapd_rw_info { + slapd_map_data *si_data; + struct berval si_val; +}; + +static int +slapd_rw_cb( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH ) { + struct slapd_rw_info *si = op->o_callback->sc_private; + + if ( si->si_data->attrs[0].an_desc ) { + Attribute *a; + + a = attr_find( rs->sr_entry->e_attrs, + si->si_data->attrs[0].an_desc ); + if ( a ) { + ber_dupbv( &si->si_val, a->a_vals ); + } + } else { + ber_dupbv( &si->si_val, &rs->sr_entry->e_name ); + } + } + return LDAP_SUCCESS; +} + +static int +slapd_rw_apply( void *private, const char *filter, struct berval *val ) +{ + slapd_map_data *sl = private; + slap_callback cb = { NULL }; + Connection conn = {0}; + OperationBuffer opbuf; + Operation *op; + void *thrctx; + SlapReply rs = {REP_RESULT}; + struct slapd_rw_info si; + char *ptr; + int rc; + + thrctx = ldap_pvt_thread_pool_context(); + connection_fake_init2( &conn, &opbuf, thrctx, 0 ); + op = &opbuf.ob_op; + + op->o_tag = LDAP_REQ_SEARCH; + op->o_req_dn = op->o_req_ndn = sl->base; + op->o_bd = select_backend( &op->o_req_ndn, 1 ); + if ( !op->o_bd ) { + return REWRITE_ERR; + } + si.si_data = sl; + BER_BVZERO( &si.si_val ); + op->ors_scope = sl->scope; + op->ors_deref = LDAP_DEREF_NEVER; + op->ors_slimit = 1; + op->ors_tlimit = SLAP_NO_LIMIT; + if ( sl->attrs[0].an_desc ) { + op->ors_attrs = sl->attrs; + } else { + op->ors_attrs = slap_anlist_no_attrs; + } + if ( filter ) { + rc = strlen( filter ); + } else { + rc = 0; + } + rc += sl->filter.bv_len; + ptr = op->ors_filterstr.bv_val = op->o_tmpalloc( rc + 1, op->o_tmpmemctx ); + if ( sl->filter.bv_len ) { + ptr = lutil_strcopy( ptr, sl->filter.bv_val ); + } else { + *ptr = '\0'; + } + if ( filter ) { + strcpy( ptr, filter ); + } + op->ors_filter = str2filter_x( op, op->ors_filterstr.bv_val ); + if ( !op->ors_filter ) { + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + return REWRITE_ERR; + } + + op->ors_attrsonly = 0; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + op->o_do_not_cache = 1; + + cb.sc_response = slapd_rw_cb; + cb.sc_private = &si; + op->o_callback = &cb; + + rc = op->o_bd->be_search( op, &rs ); + if ( rc == LDAP_SUCCESS && !BER_BVISNULL( &si.si_val )) { + *val = si.si_val; + rc = REWRITE_SUCCESS; + } else { + if ( !BER_BVISNULL( &si.si_val )) { + ch_free( si.si_val.bv_val ); + } + rc = REWRITE_ERR; + } + filter_free_x( op, op->ors_filter, 1 ); + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + return rc; +} + +static int +slapd_rw_destroy( void *private ) +{ + slapd_map_data *md = private; + + assert( private != NULL ); + + ch_free( md->base.bv_val ); + ch_free( md ); + + return 0; +} + +static const rewrite_mapper slapd_mapper = { + "slapd", + slapd_rw_config, + slapd_rw_apply, + slapd_rw_destroy +}; +#endif + +int slap_sasl_init( void ) +{ +#ifdef HAVE_CYRUS_SASL + int rc; + static sasl_callback_t server_callbacks[] = { + { SASL_CB_LOG, (slap_sasl_cb_ft)&slap_sasl_log, NULL }, + { SASL_CB_GETOPT, (slap_sasl_cb_ft)&slap_sasl_getopt, NULL }, + { SASL_CB_LIST_END, NULL, NULL } + }; +#endif + +#ifdef ENABLE_REWRITE + rewrite_mapper_register( &slapd_mapper ); +#endif + +#ifdef HAVE_CYRUS_SASL +#ifdef HAVE_SASL_VERSION + /* stringify the version number, sasl.h doesn't do it for us */ +#define VSTR0(maj, min, pat) #maj "." #min "." #pat +#define VSTR(maj, min, pat) VSTR0(maj, min, pat) +#define SASL_VERSION_STRING VSTR(SASL_VERSION_MAJOR, SASL_VERSION_MINOR, \ + SASL_VERSION_STEP) + + sasl_version( NULL, &rc ); + if ( ((rc >> 16) != ((SASL_VERSION_MAJOR << 8)|SASL_VERSION_MINOR)) || + (rc & 0xffff) < SASL_VERSION_STEP) + { + char version[sizeof("xxx.xxx.xxxxx")]; + sprintf( version, "%u.%d.%d", (unsigned)rc >> 24, (rc >> 16) & 0xff, + rc & 0xffff ); + Debug( LDAP_DEBUG_ANY, "slap_sasl_init: SASL library version mismatch:" + " expected %s, got %s\n", + SASL_VERSION_STRING, version, 0 ); + return -1; + } +#endif + + sasl_set_mutex( + ldap_pvt_sasl_mutex_new, + ldap_pvt_sasl_mutex_lock, + ldap_pvt_sasl_mutex_unlock, + ldap_pvt_sasl_mutex_dispose ); + + generic_filter.f_desc = slap_schema.si_ad_objectClass; + + rc = sasl_auxprop_add_plugin( "slapd", slap_auxprop_init ); + if( rc != SASL_OK ) { + Debug( LDAP_DEBUG_ANY, "slap_sasl_init: auxprop add plugin failed\n", + 0, 0, 0 ); + return -1; + } + + /* should provide callbacks for logging */ + /* server name should be configurable */ + rc = sasl_server_init( server_callbacks, "slapd" ); + + if( rc != SASL_OK ) { + Debug( LDAP_DEBUG_ANY, "slap_sasl_init: server init failed\n", + 0, 0, 0 ); + + return -1; + } + +#ifdef SLAPD_SPASSWD + lutil_passwd_add( &sasl_pwscheme, chk_sasl, NULL ); +#endif + + Debug( LDAP_DEBUG_TRACE, "slap_sasl_init: initialized!\n", + 0, 0, 0 ); + + /* default security properties */ + memset( &sasl_secprops, '\0', sizeof(sasl_secprops) ); + sasl_secprops.max_ssf = INT_MAX; + sasl_secprops.maxbufsize = 65536; + sasl_secprops.security_flags = SASL_SEC_NOPLAINTEXT|SASL_SEC_NOANONYMOUS; +#endif + + return 0; +} + +int slap_sasl_destroy( void ) +{ +#ifdef HAVE_CYRUS_SASL + sasl_done(); +#endif + free( sasl_host ); + sasl_host = NULL; + + return 0; +} + +static char * +slap_sasl_peer2ipport( struct berval *peer ) +{ + int isv6 = 0; + char *ipport, *p, + *addr = &peer->bv_val[ STRLENOF( "IP=" ) ]; + ber_len_t plen = peer->bv_len - STRLENOF( "IP=" ); + + /* IPv6? */ + if ( addr[0] == '[' ) { + isv6 = 1; + plen--; + } + ipport = ch_strdup( &addr[isv6] ); + + /* Convert IPv6/IPv4 addresses to address;port syntax. */ + p = strrchr( ipport, ':' ); + if ( p != NULL ) { + *p = ';'; + if ( isv6 ) { + assert( p[-1] == ']' ); + AC_MEMCPY( &p[-1], p, plen - ( p - ipport ) + 1 ); + } + + } else if ( isv6 ) { + /* trim ']' */ + plen--; + assert( addr[plen] == ']' ); + addr[plen] = '\0'; + } + + return ipport; +} + +int slap_sasl_open( Connection *conn, int reopen ) +{ + int sc = LDAP_SUCCESS; +#ifdef HAVE_CYRUS_SASL + int cb; + + sasl_conn_t *ctx = NULL; + sasl_callback_t *session_callbacks; + char *ipremoteport = NULL, *iplocalport = NULL; + + assert( conn->c_sasl_authctx == NULL ); + + if ( !reopen ) { + assert( conn->c_sasl_extra == NULL ); + + session_callbacks = + SLAP_CALLOC( 5, sizeof(sasl_callback_t)); + if( session_callbacks == NULL ) { + Debug( LDAP_DEBUG_ANY, + "slap_sasl_open: SLAP_MALLOC failed", 0, 0, 0 ); + return -1; + } + conn->c_sasl_extra = session_callbacks; + + session_callbacks[cb=0].id = SASL_CB_LOG; + session_callbacks[cb].proc = (slap_sasl_cb_ft)&slap_sasl_log; + session_callbacks[cb++].context = conn; + + session_callbacks[cb].id = SASL_CB_PROXY_POLICY; + session_callbacks[cb].proc = (slap_sasl_cb_ft)&slap_sasl_authorize; + session_callbacks[cb++].context = conn; + + session_callbacks[cb].id = SASL_CB_CANON_USER; + session_callbacks[cb].proc = (slap_sasl_cb_ft)&slap_sasl_canonicalize; + session_callbacks[cb++].context = conn; + + session_callbacks[cb].id = SASL_CB_LIST_END; + session_callbacks[cb].proc = NULL; + session_callbacks[cb++].context = NULL; + } else { + session_callbacks = conn->c_sasl_extra; + } + + conn->c_sasl_layers = 0; + + /* create new SASL context */ + if ( conn->c_sock_name.bv_len != 0 && + strncmp( conn->c_sock_name.bv_val, "IP=", STRLENOF( "IP=" ) ) == 0 ) + { + iplocalport = slap_sasl_peer2ipport( &conn->c_sock_name ); + } + + if ( conn->c_peer_name.bv_len != 0 && + strncmp( conn->c_peer_name.bv_val, "IP=", STRLENOF( "IP=" ) ) == 0 ) + { + ipremoteport = slap_sasl_peer2ipport( &conn->c_peer_name ); + } + + sc = sasl_server_new( "ldap", sasl_host, global_realm, + iplocalport, ipremoteport, session_callbacks, SASL_SUCCESS_DATA, &ctx ); + if ( iplocalport != NULL ) { + ch_free( iplocalport ); + } + if ( ipremoteport != NULL ) { + ch_free( ipremoteport ); + } + + if( sc != SASL_OK ) { + Debug( LDAP_DEBUG_ANY, "sasl_server_new failed: %d\n", + sc, 0, 0 ); + + return -1; + } + + conn->c_sasl_authctx = ctx; + + if( sc == SASL_OK ) { + sc = sasl_setprop( ctx, + SASL_SEC_PROPS, &sasl_secprops ); + + if( sc != SASL_OK ) { + Debug( LDAP_DEBUG_ANY, "sasl_setprop failed: %d\n", + sc, 0, 0 ); + + slap_sasl_close( conn ); + return -1; + } + } + + sc = slap_sasl_err2ldap( sc ); + +#elif defined(SLAP_BUILTIN_SASL) + /* built-in SASL implementation */ + SASL_CTX *ctx = (SASL_CTX *) SLAP_MALLOC(sizeof(SASL_CTX)); + if( ctx == NULL ) return -1; + + ctx->sc_external_ssf = 0; + BER_BVZERO( &ctx->sc_external_id ); + + conn->c_sasl_authctx = ctx; +#endif + + return sc; +} + +int slap_sasl_external( + Connection *conn, + slap_ssf_t ssf, + struct berval *auth_id ) +{ +#ifdef HAVE_CYRUS_SASL + int sc; + sasl_conn_t *ctx = conn->c_sasl_authctx; + sasl_ssf_t sasl_ssf = ssf; + + if ( ctx == NULL ) { + return LDAP_UNAVAILABLE; + } + + sc = sasl_setprop( ctx, SASL_SSF_EXTERNAL, &sasl_ssf ); + + if ( sc != SASL_OK ) { + return LDAP_OTHER; + } + + sc = sasl_setprop( ctx, SASL_AUTH_EXTERNAL, + auth_id ? auth_id->bv_val : NULL ); + + if ( sc != SASL_OK ) { + return LDAP_OTHER; + } +#elif defined(SLAP_BUILTIN_SASL) + /* built-in SASL implementation */ + SASL_CTX *ctx = conn->c_sasl_authctx; + if ( ctx == NULL ) return LDAP_UNAVAILABLE; + + ctx->sc_external_ssf = ssf; + if( auth_id ) { + ctx->sc_external_id = *auth_id; + BER_BVZERO( auth_id ); + } else { + BER_BVZERO( &ctx->sc_external_id ); + } +#endif + + return LDAP_SUCCESS; +} + +int slap_sasl_reset( Connection *conn ) +{ + return LDAP_SUCCESS; +} + +char ** slap_sasl_mechs( Connection *conn ) +{ + char **mechs = NULL; + +#ifdef HAVE_CYRUS_SASL + sasl_conn_t *ctx = conn->c_sasl_authctx; + + if( ctx == NULL ) ctx = conn->c_sasl_sockctx; + + if( ctx != NULL ) { + int sc; + SASL_CONST char *mechstr; + + sc = sasl_listmech( ctx, + NULL, NULL, ",", NULL, + &mechstr, NULL, NULL ); + + if( sc != SASL_OK ) { + Debug( LDAP_DEBUG_ANY, "slap_sasl_listmech failed: %d\n", + sc, 0, 0 ); + + return NULL; + } + + mechs = ldap_str2charray( mechstr, "," ); + } +#elif defined(SLAP_BUILTIN_SASL) + /* builtin SASL implementation */ + SASL_CTX *ctx = conn->c_sasl_authctx; + if ( ctx != NULL && ctx->sc_external_id.bv_val ) { + /* should check ssf */ + mechs = ldap_str2charray( "EXTERNAL", "," ); + } +#endif + + return mechs; +} + +int slap_sasl_close( Connection *conn ) +{ +#ifdef HAVE_CYRUS_SASL + sasl_conn_t *ctx = conn->c_sasl_authctx; + + if( ctx != NULL ) { + sasl_dispose( &ctx ); + } + if ( conn->c_sasl_sockctx && + conn->c_sasl_authctx != conn->c_sasl_sockctx ) + { + ctx = conn->c_sasl_sockctx; + sasl_dispose( &ctx ); + } + + conn->c_sasl_authctx = NULL; + conn->c_sasl_sockctx = NULL; + conn->c_sasl_done = 0; + + free( conn->c_sasl_extra ); + conn->c_sasl_extra = NULL; + +#elif defined(SLAP_BUILTIN_SASL) + SASL_CTX *ctx = conn->c_sasl_authctx; + if( ctx ) { + if( ctx->sc_external_id.bv_val ) { + free( ctx->sc_external_id.bv_val ); + BER_BVZERO( &ctx->sc_external_id ); + } + free( ctx ); + conn->c_sasl_authctx = NULL; + } +#endif + + return LDAP_SUCCESS; +} + +int slap_sasl_bind( Operation *op, SlapReply *rs ) +{ +#ifdef HAVE_CYRUS_SASL + sasl_conn_t *ctx = op->o_conn->c_sasl_authctx; + struct berval response; + unsigned reslen = 0; + int sc; + + Debug(LDAP_DEBUG_ARGS, + "==> sasl_bind: dn=\"%s\" mech=%s datalen=%ld\n", + op->o_req_dn.bv_len ? op->o_req_dn.bv_val : "", + op->o_conn->c_sasl_bind_in_progress ? "<continuing>" : + op->o_conn->c_sasl_bind_mech.bv_val, + op->orb_cred.bv_len ); + + if( ctx == NULL ) { + send_ldap_error( op, rs, LDAP_UNAVAILABLE, + "SASL unavailable on this session" ); + return rs->sr_err; + } + +#define START( ctx, mech, cred, clen, resp, rlen, err ) \ + sasl_server_start( ctx, mech, cred, clen, resp, rlen ) +#define STEP( ctx, cred, clen, resp, rlen, err ) \ + sasl_server_step( ctx, cred, clen, resp, rlen ) + + if ( !op->o_conn->c_sasl_bind_in_progress ) { + /* If we already authenticated once, must use a new context */ + if ( op->o_conn->c_sasl_done ) { + sasl_ssf_t ssf = 0; + sasl_ssf_t *ssfp = NULL; + const char *authid = NULL; + + sasl_getprop( ctx, SASL_SSF_EXTERNAL, (void *)&ssfp ); + if ( ssfp ) ssf = *ssfp; + + sasl_getprop( ctx, SASL_AUTH_EXTERNAL, (void *)&authid ); + if ( authid ) authid = ch_strdup( authid ); + + if ( ctx != op->o_conn->c_sasl_sockctx ) { + sasl_dispose( &ctx ); + } + op->o_conn->c_sasl_authctx = NULL; + + slap_sasl_open( op->o_conn, 1 ); + ctx = op->o_conn->c_sasl_authctx; + sasl_setprop( ctx, SASL_SSF_EXTERNAL, &ssf ); + if ( authid ) { + sasl_setprop( ctx, SASL_AUTH_EXTERNAL, authid ); + ch_free( (char *)authid ); + } + } + sc = START( ctx, + op->o_conn->c_sasl_bind_mech.bv_val, + op->orb_cred.bv_val, op->orb_cred.bv_len, + (SASL_CONST char **)&response.bv_val, &reslen, &rs->sr_text ); + + } else { + sc = STEP( ctx, + op->orb_cred.bv_val, op->orb_cred.bv_len, + (SASL_CONST char **)&response.bv_val, &reslen, &rs->sr_text ); + } + + response.bv_len = reslen; + + if ( sc == SASL_OK ) { + sasl_ssf_t *ssf = NULL; + + ber_dupbv_x( &op->orb_edn, &op->o_conn->c_sasl_dn, op->o_tmpmemctx ); + BER_BVZERO( &op->o_conn->c_sasl_dn ); + op->o_conn->c_sasl_done = 1; + + rs->sr_err = LDAP_SUCCESS; + + (void) sasl_getprop( ctx, SASL_SSF, (void *)&ssf ); + op->orb_ssf = ssf ? *ssf : 0; + + ctx = NULL; + if( op->orb_ssf ) { + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + op->o_conn->c_sasl_layers++; + + /* If there's an old layer, set sockctx to NULL to + * tell connection_read() to wait for us to finish. + * Otherwise there is a race condition: we have to + * send the Bind response using the old security + * context and then remove it before reading any + * new messages. + */ + if ( op->o_conn->c_sasl_sockctx ) { + ctx = op->o_conn->c_sasl_sockctx; + op->o_conn->c_sasl_sockctx = NULL; + } else { + op->o_conn->c_sasl_sockctx = op->o_conn->c_sasl_authctx; + } + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + } + + /* Must send response using old security layer */ + rs->sr_sasldata = (response.bv_len ? &response : NULL); + send_ldap_sasl( op, rs ); + + /* Now dispose of the old security layer. + */ + if ( ctx ) { + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + ldap_pvt_sasl_remove( op->o_conn->c_sb ); + op->o_conn->c_sasl_sockctx = op->o_conn->c_sasl_authctx; + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + sasl_dispose( &ctx ); + } + } else if ( sc == SASL_CONTINUE ) { + rs->sr_err = LDAP_SASL_BIND_IN_PROGRESS, + rs->sr_text = sasl_errdetail( ctx ); + rs->sr_sasldata = &response; + send_ldap_sasl( op, rs ); + + } else { + BER_BVZERO( &op->o_conn->c_sasl_dn ); + rs->sr_text = sasl_errdetail( ctx ); + rs->sr_err = slap_sasl_err2ldap( sc ), + send_ldap_result( op, rs ); + } + + Debug(LDAP_DEBUG_TRACE, "<== slap_sasl_bind: rc=%d\n", rs->sr_err, 0, 0); + +#elif defined(SLAP_BUILTIN_SASL) + /* built-in SASL implementation */ + SASL_CTX *ctx = op->o_conn->c_sasl_authctx; + + if ( ctx == NULL ) { + send_ldap_error( op, rs, LDAP_OTHER, + "Internal SASL Error" ); + + } else if ( bvmatch( &ext_bv, &op->o_conn->c_sasl_bind_mech ) ) { + /* EXTERNAL */ + + if( op->orb_cred.bv_len ) { + rs->sr_text = "proxy authorization not supported"; + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + send_ldap_result( op, rs ); + + } else { + op->orb_edn = ctx->sc_external_id; + rs->sr_err = LDAP_SUCCESS; + rs->sr_sasldata = NULL; + send_ldap_sasl( op, rs ); + } + + } else { + send_ldap_error( op, rs, LDAP_AUTH_METHOD_NOT_SUPPORTED, + "requested SASL mechanism not supported" ); + } +#else + send_ldap_error( op, rs, LDAP_AUTH_METHOD_NOT_SUPPORTED, + "SASL not supported" ); +#endif + + return rs->sr_err; +} + +char* slap_sasl_secprops( const char *in ) +{ +#ifdef HAVE_CYRUS_SASL + int rc = ldap_pvt_sasl_secprops( in, &sasl_secprops ); + + return rc == LDAP_SUCCESS ? NULL : "Invalid security properties"; +#else + return "SASL not supported"; +#endif +} + +void slap_sasl_secprops_unparse( struct berval *bv ) +{ +#ifdef HAVE_CYRUS_SASL + ldap_pvt_sasl_secprops_unparse( &sasl_secprops, bv ); +#endif +} + +#ifdef HAVE_CYRUS_SASL +int +slap_sasl_setpass( Operation *op, SlapReply *rs ) +{ + struct berval id = BER_BVNULL; /* needs to come from connection */ + struct berval new = BER_BVNULL; + struct berval old = BER_BVNULL; + + assert( ber_bvcmp( &slap_EXOP_MODIFY_PASSWD, &op->ore_reqoid ) == 0 ); + + rs->sr_err = sasl_getprop( op->o_conn->c_sasl_authctx, SASL_USERNAME, + (SASL_CONST void **)(char *)&id.bv_val ); + + if( rs->sr_err != SASL_OK ) { + rs->sr_text = "unable to retrieve SASL username"; + rs->sr_err = LDAP_OTHER; + goto done; + } + + Debug( LDAP_DEBUG_ARGS, "==> slap_sasl_setpass: \"%s\"\n", + id.bv_val ? id.bv_val : "", 0, 0 ); + + rs->sr_err = slap_passwd_parse( op->ore_reqdata, + NULL, &old, &new, &rs->sr_text ); + + if( rs->sr_err != LDAP_SUCCESS ) { + goto done; + } + + if( new.bv_len == 0 ) { + slap_passwd_generate(&new); + + if( new.bv_len == 0 ) { + rs->sr_text = "password generation failed."; + rs->sr_err = LDAP_OTHER; + goto done; + } + + rs->sr_rspdata = slap_passwd_return( &new ); + } + + rs->sr_err = sasl_setpass( op->o_conn->c_sasl_authctx, id.bv_val, + new.bv_val, new.bv_len, old.bv_val, old.bv_len, 0 ); + if( rs->sr_err != SASL_OK ) { + rs->sr_text = sasl_errdetail( op->o_conn->c_sasl_authctx ); + } + switch(rs->sr_err) { + case SASL_OK: + rs->sr_err = LDAP_SUCCESS; + break; + + case SASL_NOCHANGE: + case SASL_NOMECH: + case SASL_DISABLED: + case SASL_PWLOCK: + case SASL_FAIL: + case SASL_BADPARAM: + default: + rs->sr_err = LDAP_OTHER; + } + +done: + return rs->sr_err; +} +#endif /* HAVE_CYRUS_SASL */ + +/* Take any sort of identity string and return a DN with the "dn:" prefix. The + * string returned in *dn is in its own allocated memory, and must be free'd + * by the calling process. -Mark Adamson, Carnegie Mellon + * + * The "dn:" prefix is no longer used anywhere inside slapd. It is only used + * on strings passed in directly from SASL. -Howard Chu, Symas Corp. + */ + +#define SET_NONE 0 +#define SET_DN 1 +#define SET_U 2 + +int slap_sasl_getdn( Connection *conn, Operation *op, struct berval *id, + char *user_realm, struct berval *dn, int flags ) +{ + int rc, is_dn = SET_NONE, do_norm = 1; + struct berval dn2, *mech; + + assert( conn != NULL ); + assert( id != NULL ); + + Debug( LDAP_DEBUG_ARGS, "slap_sasl_getdn: conn %lu id=%s [len=%lu]\n", + conn->c_connid, + BER_BVISNULL( id ) ? "NULL" : ( BER_BVISEMPTY( id ) ? "<empty>" : id->bv_val ), + BER_BVISNULL( id ) ? 0 : ( BER_BVISEMPTY( id ) ? 0 : + (unsigned long) id->bv_len ) ); + + if ( !op ) { + op = conn->c_sasl_bindop; + } + assert( op != NULL ); + + BER_BVZERO( dn ); + + if ( !BER_BVISNULL( id ) ) { + /* Blatantly anonymous ID */ + static struct berval bv_anonymous = BER_BVC( "anonymous" ); + + if ( ber_bvstrcasecmp( id, &bv_anonymous ) == 0 ) { + return( LDAP_SUCCESS ); + } + + } else { + /* FIXME: if empty, should we stop? */ + BER_BVSTR( id, "" ); + } + + if ( !BER_BVISEMPTY( &conn->c_sasl_bind_mech ) ) { + mech = &conn->c_sasl_bind_mech; + } else { + mech = &conn->c_authmech; + } + + /* An authcID needs to be converted to authzID form. Set the + * values directly into *dn; they will be normalized later. (and + * normalizing always makes a new copy.) An ID from a TLS certificate + * is already normalized, so copy it and skip normalization. + */ + if( flags & SLAP_GETDN_AUTHCID ) { + if( bvmatch( mech, &ext_bv )) { + /* EXTERNAL DNs are already normalized */ + assert( !BER_BVISNULL( id ) ); + + do_norm = 0; + is_dn = SET_DN; + ber_dupbv_x( dn, id, op->o_tmpmemctx ); + + } else { + /* convert to u:<username> form */ + is_dn = SET_U; + *dn = *id; + } + } + + if( is_dn == SET_NONE ) { + if( !strncasecmp( id->bv_val, "u:", STRLENOF( "u:" ) ) ) { + is_dn = SET_U; + dn->bv_val = id->bv_val + STRLENOF( "u:" ); + dn->bv_len = id->bv_len - STRLENOF( "u:" ); + + } else if ( !strncasecmp( id->bv_val, "dn:", STRLENOF( "dn:" ) ) ) { + is_dn = SET_DN; + dn->bv_val = id->bv_val + STRLENOF( "dn:" ); + dn->bv_len = id->bv_len - STRLENOF( "dn:" ); + } + } + + /* No other possibilities from here */ + if( is_dn == SET_NONE ) { + BER_BVZERO( dn ); + return( LDAP_INAPPROPRIATE_AUTH ); + } + + /* Username strings */ + if( is_dn == SET_U ) { + /* ITS#3419: values may need escape */ + LDAPRDN DN[ 5 ]; + LDAPAVA *RDNs[ 4 ][ 2 ]; + LDAPAVA AVAs[ 4 ]; + int irdn; + + irdn = 0; + DN[ irdn ] = RDNs[ irdn ]; + RDNs[ irdn ][ 0 ] = &AVAs[ irdn ]; + AVAs[ irdn ].la_attr = slap_schema.si_ad_uid->ad_cname; + AVAs[ irdn ].la_value = *dn; + AVAs[ irdn ].la_flags = LDAP_AVA_NULL; + AVAs[ irdn ].la_private = NULL; + RDNs[ irdn ][ 1 ] = NULL; + + if ( user_realm && *user_realm ) { + irdn++; + DN[ irdn ] = RDNs[ irdn ]; + RDNs[ irdn ][ 0 ] = &AVAs[ irdn ]; + AVAs[ irdn ].la_attr = slap_schema.si_ad_cn->ad_cname; + ber_str2bv( user_realm, 0, 0, &AVAs[ irdn ].la_value ); + AVAs[ irdn ].la_flags = LDAP_AVA_NULL; + AVAs[ irdn ].la_private = NULL; + RDNs[ irdn ][ 1 ] = NULL; + } + + if ( !BER_BVISNULL( mech ) ) { + irdn++; + DN[ irdn ] = RDNs[ irdn ]; + RDNs[ irdn ][ 0 ] = &AVAs[ irdn ]; + AVAs[ irdn ].la_attr = slap_schema.si_ad_cn->ad_cname; + AVAs[ irdn ].la_value = *mech; + AVAs[ irdn ].la_flags = LDAP_AVA_NULL; + AVAs[ irdn ].la_private = NULL; + RDNs[ irdn ][ 1 ] = NULL; + } + + irdn++; + DN[ irdn ] = RDNs[ irdn ]; + RDNs[ irdn ][ 0 ] = &AVAs[ irdn ]; + AVAs[ irdn ].la_attr = slap_schema.si_ad_cn->ad_cname; + BER_BVSTR( &AVAs[ irdn ].la_value, "auth" ); + AVAs[ irdn ].la_flags = LDAP_AVA_NULL; + AVAs[ irdn ].la_private = NULL; + RDNs[ irdn ][ 1 ] = NULL; + + irdn++; + DN[ irdn ] = NULL; + + rc = ldap_dn2bv_x( DN, dn, LDAP_DN_FORMAT_LDAPV3, + op->o_tmpmemctx ); + if ( rc != LDAP_SUCCESS ) { + BER_BVZERO( dn ); + return rc; + } + + Debug( LDAP_DEBUG_TRACE, + "slap_sasl_getdn: u:id converted to %s\n", + dn->bv_val, 0, 0 ); + + } else { + + /* Dup the DN in any case, so we don't risk + * leaks or dangling pointers later, + * and the DN value is '\0' terminated */ + ber_dupbv_x( &dn2, dn, op->o_tmpmemctx ); + dn->bv_val = dn2.bv_val; + } + + /* All strings are in DN form now. Normalize if needed. */ + if ( do_norm ) { + rc = dnNormalize( 0, NULL, NULL, dn, &dn2, op->o_tmpmemctx ); + + /* User DNs were constructed above and must be freed now */ + slap_sl_free( dn->bv_val, op->o_tmpmemctx ); + + if ( rc != LDAP_SUCCESS ) { + BER_BVZERO( dn ); + return rc; + } + *dn = dn2; + } + + /* Run thru regexp */ + slap_sasl2dn( op, dn, &dn2, flags ); + if( !BER_BVISNULL( &dn2 ) ) { + slap_sl_free( dn->bv_val, op->o_tmpmemctx ); + *dn = dn2; + Debug( LDAP_DEBUG_TRACE, + "slap_sasl_getdn: dn:id converted to %s\n", + dn->bv_val, 0, 0 ); + } + + return( LDAP_SUCCESS ); +} diff --git a/servers/slapd/saslauthz.c b/servers/slapd/saslauthz.c new file mode 100644 index 0000000..2adfdb5 --- /dev/null +++ b/servers/slapd/saslauthz.c @@ -0,0 +1,2110 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * Portions Copyright 2000 Mark Adamson, Carnegie Mellon. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif + +#include <ac/stdlib.h> +#include <ac/string.h> +#include <ac/ctype.h> + +#include "slap.h" + +#include "lutil.h" + +#define SASLREGEX_REPLACE 10 + +#define LDAP_X_SCOPE_EXACT ((ber_int_t) 0x0010) +#define LDAP_X_SCOPE_REGEX ((ber_int_t) 0x0020) +#define LDAP_X_SCOPE_CHILDREN ((ber_int_t) 0x0030) +#define LDAP_X_SCOPE_SUBTREE ((ber_int_t) 0x0040) +#define LDAP_X_SCOPE_ONELEVEL ((ber_int_t) 0x0050) +#define LDAP_X_SCOPE_GROUP ((ber_int_t) 0x0060) +#define LDAP_X_SCOPE_USERS ((ber_int_t) 0x0070) + +/* + * IDs in DNauthzid form can now have a type specifier, that + * influences how they are used in related operations. + * + * syntax: dn[.{exact|regex}]:<val> + * + * dn.exact: the value must pass normalization and is used + * in exact DN match. + * dn.regex: the value is treated as a regular expression + * in matching DN values in authz{To|From} + * attributes. + * dn: for backwards compatibility reasons, the value + * is treated as a regular expression, and thus + * it is not normalized nor validated; it is used + * in exact or regex comparisons based on the + * context. + * + * IDs in DNauthzid form can now have a type specifier, that + * influences how they are used in related operations. + * + * syntax: u[.mech[/realm]]:<val> + * + * where mech is a SIMPLE, AUTHZ, or a SASL mechanism name + * and realm is mechanism specific realm (separate to those + * which are representable as part of the principal). + */ + +typedef struct sasl_regexp { + char *sr_match; /* regexp match pattern */ + char *sr_replace; /* regexp replace pattern */ + regex_t sr_workspace; /* workspace for regexp engine */ + int sr_offset[SASLREGEX_REPLACE+2]; /* offsets of $1,$2... in *replace */ +} SaslRegexp_t; + +static int nSaslRegexp = 0; +static SaslRegexp_t *SaslRegexp = NULL; + +#ifdef SLAP_AUTH_REWRITE +#include "rewrite.h" +struct rewrite_info *sasl_rwinfo = NULL; +#define AUTHID_CONTEXT "authid" +#endif /* SLAP_AUTH_REWRITE */ + +/* What SASL proxy authorization policies are allowed? */ +#define SASL_AUTHZ_NONE 0x00 +#define SASL_AUTHZ_FROM 0x01 +#define SASL_AUTHZ_TO 0x02 +#define SASL_AUTHZ_AND 0x10 + +static const char *policy_txt[] = { + "none", "from", "to", "any" +}; + +static int authz_policy = SASL_AUTHZ_NONE; + +static int +slap_sasl_match( Operation *opx, struct berval *rule, + struct berval *assertDN, struct berval *authc ); + +int slap_sasl_setpolicy( const char *arg ) +{ + int rc = LDAP_SUCCESS; + + if ( strcasecmp( arg, "none" ) == 0 ) { + authz_policy = SASL_AUTHZ_NONE; + } else if ( strcasecmp( arg, "from" ) == 0 ) { + authz_policy = SASL_AUTHZ_FROM; + } else if ( strcasecmp( arg, "to" ) == 0 ) { + authz_policy = SASL_AUTHZ_TO; + } else if ( strcasecmp( arg, "both" ) == 0 || strcasecmp( arg, "any" ) == 0 ) { + authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO; + } else if ( strcasecmp( arg, "all" ) == 0 ) { + authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO | SASL_AUTHZ_AND; + } else { + rc = LDAP_OTHER; + } + return rc; +} + +const char * slap_sasl_getpolicy() +{ + if ( authz_policy == (SASL_AUTHZ_FROM | SASL_AUTHZ_TO | SASL_AUTHZ_AND) ) + return "all"; + else + return policy_txt[authz_policy]; +} + +int slap_parse_user( struct berval *id, struct berval *user, + struct berval *realm, struct berval *mech ) +{ + char u; + + assert( id != NULL ); + assert( !BER_BVISNULL( id ) ); + assert( user != NULL ); + assert( realm != NULL ); + assert( mech != NULL ); + + u = id->bv_val[ 0 ]; + + if ( u != 'u' && u != 'U' ) { + /* called with something other than u: */ + return LDAP_PROTOCOL_ERROR; + } + + /* uauthzid form: + * u[.mech[/realm]]:user + */ + + user->bv_val = ber_bvchr( id, ':' ); + if ( BER_BVISNULL( user ) ) { + return LDAP_PROTOCOL_ERROR; + } + user->bv_val[ 0 ] = '\0'; + user->bv_val++; + user->bv_len = id->bv_len - ( user->bv_val - id->bv_val ); + + if ( id->bv_val[1] == '.' ) { + id->bv_val[1] = '\0'; + mech->bv_val = id->bv_val + 2; + mech->bv_len = user->bv_val - mech->bv_val - 1; + + realm->bv_val = ber_bvchr( mech, '/' ); + + if ( !BER_BVISNULL( realm ) ) { + realm->bv_val[ 0 ] = '\0'; + realm->bv_val++; + mech->bv_len = realm->bv_val - mech->bv_val - 1; + realm->bv_len = user->bv_val - realm->bv_val - 1; + } + + } else { + BER_BVZERO( mech ); + BER_BVZERO( realm ); + } + + if ( id->bv_val[ 1 ] != '\0' ) { + return LDAP_PROTOCOL_ERROR; + } + + if ( !BER_BVISNULL( mech ) ) { + if ( mech->bv_val != id->bv_val + 2 ) + return LDAP_PROTOCOL_ERROR; + + AC_MEMCPY( mech->bv_val - 2, mech->bv_val, mech->bv_len + 1 ); + mech->bv_val -= 2; + } + + if ( !BER_BVISNULL( realm ) ) { + if ( realm->bv_val < id->bv_val + 2 ) + return LDAP_PROTOCOL_ERROR; + + AC_MEMCPY( realm->bv_val - 2, realm->bv_val, realm->bv_len + 1 ); + realm->bv_val -= 2; + } + + /* leave "u:" before user */ + user->bv_val -= 2; + user->bv_len += 2; + user->bv_val[ 0 ] = u; + user->bv_val[ 1 ] = ':'; + + return LDAP_SUCCESS; +} + +int +authzValidate( + Syntax *syntax, + struct berval *in ) +{ + struct berval bv; + int rc = LDAP_INVALID_SYNTAX; + LDAPURLDesc *ludp = NULL; + int scope = -1; + + /* + * 1) <DN> + * 2) dn[.{exact|children|subtree|onelevel}]:{*|<DN>} + * 3) dn.regex:<pattern> + * 4) u[.mech[/realm]]:<ID> + * 5) group[/<groupClass>[/<memberAttr>]]:<DN> + * 6) <URL> + */ + + assert( in != NULL ); + assert( !BER_BVISNULL( in ) ); + + Debug( LDAP_DEBUG_TRACE, + "authzValidate: parsing %s\n", in->bv_val, 0, 0 ); + + /* + * 2) dn[.{exact|children|subtree|onelevel}]:{*|<DN>} + * 3) dn.regex:<pattern> + * + * <DN> must pass DN normalization + */ + if ( !strncasecmp( in->bv_val, "dn", STRLENOF( "dn" ) ) ) { + bv.bv_val = in->bv_val + STRLENOF( "dn" ); + + if ( bv.bv_val[ 0 ] == '.' ) { + bv.bv_val++; + + if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) { + bv.bv_val += STRLENOF( "exact:" ); + scope = LDAP_X_SCOPE_EXACT; + + } else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) { + bv.bv_val += STRLENOF( "regex:" ); + scope = LDAP_X_SCOPE_REGEX; + + } else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) { + bv.bv_val += STRLENOF( "children:" ); + scope = LDAP_X_SCOPE_CHILDREN; + + } else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) { + bv.bv_val += STRLENOF( "subtree:" ); + scope = LDAP_X_SCOPE_SUBTREE; + + } else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) { + bv.bv_val += STRLENOF( "onelevel:" ); + scope = LDAP_X_SCOPE_ONELEVEL; + + } else { + return LDAP_INVALID_SYNTAX; + } + + } else { + if ( bv.bv_val[ 0 ] != ':' ) { + return LDAP_INVALID_SYNTAX; + } + scope = LDAP_X_SCOPE_EXACT; + bv.bv_val++; + } + + bv.bv_val += strspn( bv.bv_val, " " ); + /* jump here in case no type specification was present + * and uri was not an URI... HEADS-UP: assuming EXACT */ +is_dn: bv.bv_len = in->bv_len - ( bv.bv_val - in->bv_val ); + + /* a single '*' means any DN without using regexes */ + if ( ber_bvccmp( &bv, '*' ) ) { + /* LDAP_X_SCOPE_USERS */ + return LDAP_SUCCESS; + } + + switch ( scope ) { + case LDAP_X_SCOPE_EXACT: + case LDAP_X_SCOPE_CHILDREN: + case LDAP_X_SCOPE_SUBTREE: + case LDAP_X_SCOPE_ONELEVEL: + return dnValidate( NULL, &bv ); + + case LDAP_X_SCOPE_REGEX: + return LDAP_SUCCESS; + } + + return rc; + + /* + * 4) u[.mech[/realm]]:<ID> + */ + } else if ( ( in->bv_val[ 0 ] == 'u' || in->bv_val[ 0 ] == 'U' ) + && ( in->bv_val[ 1 ] == ':' + || in->bv_val[ 1 ] == '/' + || in->bv_val[ 1 ] == '.' ) ) + { + char buf[ SLAP_LDAPDN_MAXLEN ]; + struct berval id, + user = BER_BVNULL, + realm = BER_BVNULL, + mech = BER_BVNULL; + + if ( sizeof( buf ) <= in->bv_len ) { + return LDAP_INVALID_SYNTAX; + } + + id.bv_len = in->bv_len; + id.bv_val = buf; + strncpy( buf, in->bv_val, sizeof( buf ) ); + + rc = slap_parse_user( &id, &user, &realm, &mech ); + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + + return rc; + + /* + * 5) group[/groupClass[/memberAttr]]:<DN> + * + * <groupClass> defaults to "groupOfNames" + * <memberAttr> defaults to "member" + * + * <DN> must pass DN normalization + */ + } else if ( strncasecmp( in->bv_val, "group", STRLENOF( "group" ) ) == 0 ) + { + struct berval group_dn = BER_BVNULL, + group_oc = BER_BVNULL, + member_at = BER_BVNULL; + + bv.bv_val = in->bv_val + STRLENOF( "group" ); + bv.bv_len = in->bv_len - STRLENOF( "group" ); + group_dn.bv_val = ber_bvchr( &bv, ':' ); + if ( group_dn.bv_val == NULL ) { + /* last chance: assume it's a(n exact) DN ... */ + bv.bv_val = in->bv_val; + scope = LDAP_X_SCOPE_EXACT; + goto is_dn; + } + + /* + * FIXME: we assume that "member" and "groupOfNames" + * are present in schema... + */ + if ( bv.bv_val[ 0 ] == '/' ) { + group_oc.bv_val = &bv.bv_val[ 1 ]; + group_oc.bv_len = group_dn.bv_val - group_oc.bv_val; + + member_at.bv_val = ber_bvchr( &group_oc, '/' ); + if ( member_at.bv_val ) { + AttributeDescription *ad = NULL; + const char *text = NULL; + + group_oc.bv_len = member_at.bv_val - group_oc.bv_val; + member_at.bv_val++; + member_at.bv_len = group_dn.bv_val - member_at.bv_val; + rc = slap_bv2ad( &member_at, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + } + + if ( oc_bvfind( &group_oc ) == NULL ) { + return LDAP_INVALID_SYNTAX; + } + } + + group_dn.bv_val++; + group_dn.bv_len = in->bv_len - ( group_dn.bv_val - in->bv_val ); + + rc = dnValidate( NULL, &group_dn ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + return rc; + } + + /* + * ldap:///<base>??<scope>?<filter> + * <scope> ::= {base|one|subtree} + * + * <scope> defaults to "base" + * <base> must pass DN normalization + * <filter> must pass str2filter() + */ + rc = ldap_url_parse( in->bv_val, &ludp ); + switch ( rc ) { + case LDAP_URL_SUCCESS: + /* FIXME: the check is pedantic, but I think it's necessary, + * because people tend to use things like ldaps:// which + * gives the idea SSL is being used. Maybe we could + * accept ldapi:// as well, but the point is that we use + * an URL as an easy means to define bits of a search with + * little parsing. + */ + if ( strcasecmp( ludp->lud_scheme, "ldap" ) != 0 ) { + /* + * must be ldap:/// + */ + rc = LDAP_INVALID_SYNTAX; + goto done; + } + break; + + case LDAP_URL_ERR_BADSCHEME: + /* + * last chance: assume it's a(n exact) DN ... + * + * NOTE: must pass DN normalization + */ + ldap_free_urldesc( ludp ); + bv.bv_val = in->bv_val; + scope = LDAP_X_SCOPE_EXACT; + goto is_dn; + + default: + rc = LDAP_INVALID_SYNTAX; + goto done; + } + + if ( ( ludp->lud_host && *ludp->lud_host ) + || ludp->lud_attrs || ludp->lud_exts ) + { + /* host part must be empty */ + /* attrs and extensions parts must be empty */ + rc = LDAP_INVALID_SYNTAX; + goto done; + } + + /* Grab the filter */ + if ( ludp->lud_filter ) { + Filter *f = str2filter( ludp->lud_filter ); + if ( f == NULL ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + filter_free( f ); + } + + /* Grab the searchbase */ + if ( ludp->lud_dn != NULL ) { + ber_str2bv( ludp->lud_dn, 0, 0, &bv ); + rc = dnValidate( NULL, &bv ); + } else { + rc = LDAP_INVALID_SYNTAX; + } + +done: + ldap_free_urldesc( ludp ); + return( rc ); +} + +static int +authzPrettyNormal( + struct berval *val, + struct berval *normalized, + void *ctx, + int normalize ) +{ + struct berval bv; + int rc = LDAP_INVALID_SYNTAX; + LDAPURLDesc *ludp = NULL; + char *lud_dn = NULL, + *lud_filter = NULL; + int scope = -1; + + /* + * 1) <DN> + * 2) dn[.{exact|children|subtree|onelevel}]:{*|<DN>} + * 3) dn.regex:<pattern> + * 4) u[.mech[/realm]]:<ID> + * 5) group[/<groupClass>[/<memberAttr>]]:<DN> + * 6) <URL> + */ + + assert( val != NULL ); + assert( !BER_BVISNULL( val ) ); + BER_BVZERO( normalized ); + + /* + * 2) dn[.{exact|children|subtree|onelevel}]:{*|<DN>} + * 3) dn.regex:<pattern> + * + * <DN> must pass DN normalization + */ + if ( !strncasecmp( val->bv_val, "dn", STRLENOF( "dn" ) ) ) { + struct berval out = BER_BVNULL, + prefix = BER_BVNULL; + char *ptr; + + bv.bv_val = val->bv_val + STRLENOF( "dn" ); + + if ( bv.bv_val[ 0 ] == '.' ) { + bv.bv_val++; + + if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) { + bv.bv_val += STRLENOF( "exact:" ); + scope = LDAP_X_SCOPE_EXACT; + + } else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) { + bv.bv_val += STRLENOF( "regex:" ); + scope = LDAP_X_SCOPE_REGEX; + + } else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) { + bv.bv_val += STRLENOF( "children:" ); + scope = LDAP_X_SCOPE_CHILDREN; + + } else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) { + bv.bv_val += STRLENOF( "subtree:" ); + scope = LDAP_X_SCOPE_SUBTREE; + + } else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) { + bv.bv_val += STRLENOF( "onelevel:" ); + scope = LDAP_X_SCOPE_ONELEVEL; + + } else { + return LDAP_INVALID_SYNTAX; + } + + } else { + if ( bv.bv_val[ 0 ] != ':' ) { + return LDAP_INVALID_SYNTAX; + } + scope = LDAP_X_SCOPE_EXACT; + bv.bv_val++; + } + + bv.bv_val += strspn( bv.bv_val, " " ); + /* jump here in case no type specification was present + * and uri was not an URI... HEADS-UP: assuming EXACT */ +is_dn: bv.bv_len = val->bv_len - ( bv.bv_val - val->bv_val ); + + /* a single '*' means any DN without using regexes */ + if ( ber_bvccmp( &bv, '*' ) ) { + ber_str2bv_x( "dn:*", STRLENOF( "dn:*" ), 1, normalized, ctx ); + return LDAP_SUCCESS; + } + + switch ( scope ) { + case LDAP_X_SCOPE_EXACT: + case LDAP_X_SCOPE_CHILDREN: + case LDAP_X_SCOPE_SUBTREE: + case LDAP_X_SCOPE_ONELEVEL: + if ( normalize ) { + rc = dnNormalize( 0, NULL, NULL, &bv, &out, ctx ); + } else { + rc = dnPretty( NULL, &bv, &out, ctx ); + } + if( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + break; + + case LDAP_X_SCOPE_REGEX: + normalized->bv_len = STRLENOF( "dn.regex:" ) + bv.bv_len; + normalized->bv_val = ber_memalloc_x( normalized->bv_len + 1, ctx ); + ptr = lutil_strcopy( normalized->bv_val, "dn.regex:" ); + ptr = lutil_strncopy( ptr, bv.bv_val, bv.bv_len ); + ptr[ 0 ] = '\0'; + return LDAP_SUCCESS; + + default: + return LDAP_INVALID_SYNTAX; + } + + /* prepare prefix */ + switch ( scope ) { + case LDAP_X_SCOPE_EXACT: + BER_BVSTR( &prefix, "dn:" ); + break; + + case LDAP_X_SCOPE_CHILDREN: + BER_BVSTR( &prefix, "dn.children:" ); + break; + + case LDAP_X_SCOPE_SUBTREE: + BER_BVSTR( &prefix, "dn.subtree:" ); + break; + + case LDAP_X_SCOPE_ONELEVEL: + BER_BVSTR( &prefix, "dn.onelevel:" ); + break; + + default: + assert( 0 ); + break; + } + + normalized->bv_len = prefix.bv_len + out.bv_len; + normalized->bv_val = ber_memalloc_x( normalized->bv_len + 1, ctx ); + + ptr = lutil_strcopy( normalized->bv_val, prefix.bv_val ); + ptr = lutil_strncopy( ptr, out.bv_val, out.bv_len ); + ptr[ 0 ] = '\0'; + ber_memfree_x( out.bv_val, ctx ); + + return LDAP_SUCCESS; + + /* + * 4) u[.mech[/realm]]:<ID> + */ + } else if ( ( val->bv_val[ 0 ] == 'u' || val->bv_val[ 0 ] == 'U' ) + && ( val->bv_val[ 1 ] == ':' + || val->bv_val[ 1 ] == '/' + || val->bv_val[ 1 ] == '.' ) ) + { + char buf[ SLAP_LDAPDN_MAXLEN ]; + struct berval id, + user = BER_BVNULL, + realm = BER_BVNULL, + mech = BER_BVNULL; + + if ( sizeof( buf ) <= val->bv_len ) { + return LDAP_INVALID_SYNTAX; + } + + id.bv_len = val->bv_len; + id.bv_val = buf; + strncpy( buf, val->bv_val, sizeof( buf ) ); + + rc = slap_parse_user( &id, &user, &realm, &mech ); + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + + ber_dupbv_x( normalized, val, ctx ); + + return rc; + + /* + * 5) group[/groupClass[/memberAttr]]:<DN> + * + * <groupClass> defaults to "groupOfNames" + * <memberAttr> defaults to "member" + * + * <DN> must pass DN normalization + */ + } else if ( strncasecmp( val->bv_val, "group", STRLENOF( "group" ) ) == 0 ) + { + struct berval group_dn = BER_BVNULL, + group_oc = BER_BVNULL, + member_at = BER_BVNULL, + out = BER_BVNULL; + char *ptr; + + bv.bv_val = val->bv_val + STRLENOF( "group" ); + bv.bv_len = val->bv_len - STRLENOF( "group" ); + group_dn.bv_val = ber_bvchr( &bv, ':' ); + if ( group_dn.bv_val == NULL ) { + /* last chance: assume it's a(n exact) DN ... */ + bv.bv_val = val->bv_val; + scope = LDAP_X_SCOPE_EXACT; + goto is_dn; + } + + /* + * FIXME: we assume that "member" and "groupOfNames" + * are present in schema... + */ + if ( bv.bv_val[ 0 ] == '/' ) { + ObjectClass *oc = NULL; + + group_oc.bv_val = &bv.bv_val[ 1 ]; + group_oc.bv_len = group_dn.bv_val - group_oc.bv_val; + + member_at.bv_val = ber_bvchr( &group_oc, '/' ); + if ( member_at.bv_val ) { + AttributeDescription *ad = NULL; + const char *text = NULL; + + group_oc.bv_len = member_at.bv_val - group_oc.bv_val; + member_at.bv_val++; + member_at.bv_len = group_dn.bv_val - member_at.bv_val; + rc = slap_bv2ad( &member_at, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + member_at = ad->ad_cname; + + } + + oc = oc_bvfind( &group_oc ); + if ( oc == NULL ) { + return LDAP_INVALID_SYNTAX; + } + + group_oc = oc->soc_cname; + } + + group_dn.bv_val++; + group_dn.bv_len = val->bv_len - ( group_dn.bv_val - val->bv_val ); + + if ( normalize ) { + rc = dnNormalize( 0, NULL, NULL, &group_dn, &out, ctx ); + } else { + rc = dnPretty( NULL, &group_dn, &out, ctx ); + } + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + normalized->bv_len = STRLENOF( "group" ":" ) + out.bv_len; + if ( !BER_BVISNULL( &group_oc ) ) { + normalized->bv_len += STRLENOF( "/" ) + group_oc.bv_len; + if ( !BER_BVISNULL( &member_at ) ) { + normalized->bv_len += STRLENOF( "/" ) + member_at.bv_len; + } + } + + normalized->bv_val = ber_memalloc_x( normalized->bv_len + 1, ctx ); + ptr = lutil_strcopy( normalized->bv_val, "group" ); + if ( !BER_BVISNULL( &group_oc ) ) { + ptr[ 0 ] = '/'; + ptr++; + ptr = lutil_strncopy( ptr, group_oc.bv_val, group_oc.bv_len ); + if ( !BER_BVISNULL( &member_at ) ) { + ptr[ 0 ] = '/'; + ptr++; + ptr = lutil_strncopy( ptr, member_at.bv_val, member_at.bv_len ); + } + } + ptr[ 0 ] = ':'; + ptr++; + ptr = lutil_strncopy( ptr, out.bv_val, out.bv_len ); + ptr[ 0 ] = '\0'; + ber_memfree_x( out.bv_val, ctx ); + + return rc; + } + + /* + * ldap:///<base>??<scope>?<filter> + * <scope> ::= {base|one|subtree} + * + * <scope> defaults to "base" + * <base> must pass DN normalization + * <filter> must pass str2filter() + */ + rc = ldap_url_parse( val->bv_val, &ludp ); + switch ( rc ) { + case LDAP_URL_SUCCESS: + /* FIXME: the check is pedantic, but I think it's necessary, + * because people tend to use things like ldaps:// which + * gives the idea SSL is being used. Maybe we could + * accept ldapi:// as well, but the point is that we use + * an URL as an easy means to define bits of a search with + * little parsing. + */ + if ( strcasecmp( ludp->lud_scheme, "ldap" ) != 0 ) { + /* + * must be ldap:/// + */ + rc = LDAP_INVALID_SYNTAX; + goto done; + } + + AC_MEMCPY( ludp->lud_scheme, "ldap", STRLENOF( "ldap" ) ); + break; + + case LDAP_URL_ERR_BADSCHEME: + /* + * last chance: assume it's a(n exact) DN ... + * + * NOTE: must pass DN normalization + */ + ldap_free_urldesc( ludp ); + bv.bv_val = val->bv_val; + scope = LDAP_X_SCOPE_EXACT; + goto is_dn; + + default: + rc = LDAP_INVALID_SYNTAX; + goto done; + } + + if ( ( ludp->lud_host && *ludp->lud_host ) + || ludp->lud_attrs || ludp->lud_exts ) + { + /* host part must be empty */ + /* attrs and extensions parts must be empty */ + rc = LDAP_INVALID_SYNTAX; + goto done; + } + + /* Grab the filter */ + if ( ludp->lud_filter ) { + struct berval filterstr; + Filter *f; + + lud_filter = ludp->lud_filter; + + f = str2filter( lud_filter ); + if ( f == NULL ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + filter2bv( f, &filterstr ); + filter_free( f ); + if ( BER_BVISNULL( &filterstr ) ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + + ludp->lud_filter = filterstr.bv_val; + } + + /* Grab the searchbase */ + if ( ludp->lud_dn ) { + struct berval out = BER_BVNULL; + + lud_dn = ludp->lud_dn; + + ber_str2bv( lud_dn, 0, 0, &bv ); + if ( normalize ) { + rc = dnNormalize( 0, NULL, NULL, &bv, &out, ctx ); + } else { + rc = dnPretty( NULL, &bv, &out, ctx ); + } + + if ( rc != LDAP_SUCCESS ) { + goto done; + } + + ludp->lud_dn = out.bv_val; + } else { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + + ludp->lud_port = 0; + normalized->bv_val = ldap_url_desc2str( ludp ); + if ( normalized->bv_val ) { + normalized->bv_len = strlen( normalized->bv_val ); + + } else { + rc = LDAP_INVALID_SYNTAX; + } + +done: + if ( lud_filter ) { + if ( ludp->lud_filter != lud_filter ) { + ber_memfree( ludp->lud_filter ); + } + ludp->lud_filter = lud_filter; + } + + if ( lud_dn ) { + if ( ludp->lud_dn != lud_dn ) { + slap_sl_free( ludp->lud_dn, ctx ); + } + ludp->lud_dn = lud_dn; + } + + ldap_free_urldesc( ludp ); + + return( rc ); +} + +int +authzNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + int rc; + + Debug( LDAP_DEBUG_TRACE, ">>> authzNormalize: <%s>\n", + val->bv_val, 0, 0 ); + + rc = authzPrettyNormal( val, normalized, ctx, 1 ); + + Debug( LDAP_DEBUG_TRACE, "<<< authzNormalize: <%s> (%d)\n", + normalized->bv_val, rc, 0 ); + + return rc; +} + +int +authzPretty( + Syntax *syntax, + struct berval *val, + struct berval *out, + void *ctx) +{ + int rc; + + Debug( LDAP_DEBUG_TRACE, ">>> authzPretty: <%s>\n", + val->bv_val, 0, 0 ); + + rc = authzPrettyNormal( val, out, ctx, 0 ); + + Debug( LDAP_DEBUG_TRACE, "<<< authzPretty: <%s> (%d)\n", + out->bv_val ? out->bv_val : "(null)" , rc, 0 ); + + return rc; +} + + +static int +slap_parseURI( + Operation *op, + struct berval *uri, + struct berval *base, + struct berval *nbase, + int *scope, + Filter **filter, + struct berval *fstr, + int normalize ) +{ + struct berval bv; + int rc; + LDAPURLDesc *ludp; + + struct berval idx; + + assert( uri != NULL && !BER_BVISNULL( uri ) ); + BER_BVZERO( base ); + BER_BVZERO( nbase ); + BER_BVZERO( fstr ); + *scope = -1; + *filter = NULL; + + Debug( LDAP_DEBUG_TRACE, + "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 ); + + rc = LDAP_PROTOCOL_ERROR; + + idx = *uri; + if ( idx.bv_val[ 0 ] == '{' ) { + char *ptr; + + ptr = ber_bvchr( &idx, '}' ) + 1; + + assert( ptr != (void *)1 ); + + idx.bv_len -= ptr - idx.bv_val; + idx.bv_val = ptr; + uri = &idx; + } + + /* + * dn[.<dnstyle>]:<dnpattern> + * <dnstyle> ::= {exact|regex|children|subtree|onelevel} + * + * <dnstyle> defaults to "exact" + * if <dnstyle> is not "regex", <dnpattern> must pass DN normalization + */ + if ( !strncasecmp( uri->bv_val, "dn", STRLENOF( "dn" ) ) ) { + bv.bv_val = uri->bv_val + STRLENOF( "dn" ); + + if ( bv.bv_val[ 0 ] == '.' ) { + bv.bv_val++; + + if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) { + bv.bv_val += STRLENOF( "exact:" ); + *scope = LDAP_X_SCOPE_EXACT; + + } else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) { + bv.bv_val += STRLENOF( "regex:" ); + *scope = LDAP_X_SCOPE_REGEX; + + } else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) { + bv.bv_val += STRLENOF( "children:" ); + *scope = LDAP_X_SCOPE_CHILDREN; + + } else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) { + bv.bv_val += STRLENOF( "subtree:" ); + *scope = LDAP_X_SCOPE_SUBTREE; + + } else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) { + bv.bv_val += STRLENOF( "onelevel:" ); + *scope = LDAP_X_SCOPE_ONELEVEL; + + } else { + return LDAP_PROTOCOL_ERROR; + } + + } else { + if ( bv.bv_val[ 0 ] != ':' ) { + return LDAP_PROTOCOL_ERROR; + } + *scope = LDAP_X_SCOPE_EXACT; + bv.bv_val++; + } + + bv.bv_val += strspn( bv.bv_val, " " ); + /* jump here in case no type specification was present + * and uri was not an URI... HEADS-UP: assuming EXACT */ +is_dn: bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val); + + /* a single '*' means any DN without using regexes */ + if ( ber_bvccmp( &bv, '*' ) ) { + *scope = LDAP_X_SCOPE_USERS; + } + + switch ( *scope ) { + case LDAP_X_SCOPE_EXACT: + case LDAP_X_SCOPE_CHILDREN: + case LDAP_X_SCOPE_SUBTREE: + case LDAP_X_SCOPE_ONELEVEL: + if ( normalize ) { + rc = dnNormalize( 0, NULL, NULL, &bv, nbase, op->o_tmpmemctx ); + if( rc != LDAP_SUCCESS ) { + *scope = -1; + } + } else { + ber_dupbv_x( nbase, &bv, op->o_tmpmemctx ); + rc = LDAP_SUCCESS; + } + break; + + case LDAP_X_SCOPE_REGEX: + ber_dupbv_x( nbase, &bv, op->o_tmpmemctx ); + + case LDAP_X_SCOPE_USERS: + rc = LDAP_SUCCESS; + break; + + default: + *scope = -1; + break; + } + + return rc; + + /* + * u:<uid> + */ + } else if ( ( uri->bv_val[ 0 ] == 'u' || uri->bv_val[ 0 ] == 'U' ) + && ( uri->bv_val[ 1 ] == ':' + || uri->bv_val[ 1 ] == '/' + || uri->bv_val[ 1 ] == '.' ) ) + { + Connection c = *op->o_conn; + char buf[ SLAP_LDAPDN_MAXLEN ]; + struct berval id, + user = BER_BVNULL, + realm = BER_BVNULL, + mech = BER_BVNULL; + + if ( sizeof( buf ) <= uri->bv_len ) { + return LDAP_INVALID_SYNTAX; + } + + id.bv_len = uri->bv_len; + id.bv_val = buf; + strncpy( buf, uri->bv_val, sizeof( buf ) ); + + rc = slap_parse_user( &id, &user, &realm, &mech ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( !BER_BVISNULL( &mech ) ) { + c.c_sasl_bind_mech = mech; + } else { + BER_BVSTR( &c.c_sasl_bind_mech, "AUTHZ" ); + } + + rc = slap_sasl_getdn( &c, op, &user, + realm.bv_val, nbase, SLAP_GETDN_AUTHZID ); + + if ( rc == LDAP_SUCCESS ) { + *scope = LDAP_X_SCOPE_EXACT; + } + + return rc; + + /* + * group[/<groupoc>[/<groupat>]]:<groupdn> + * + * groupoc defaults to "groupOfNames" + * groupat defaults to "member" + * + * <groupdn> must pass DN normalization + */ + } else if ( strncasecmp( uri->bv_val, "group", STRLENOF( "group" ) ) == 0 ) + { + struct berval group_dn = BER_BVNULL, + group_oc = BER_BVNULL, + member_at = BER_BVNULL; + char *tmp; + + bv.bv_val = uri->bv_val + STRLENOF( "group" ); + bv.bv_len = uri->bv_len - STRLENOF( "group" ); + group_dn.bv_val = ber_bvchr( &bv, ':' ); + if ( group_dn.bv_val == NULL ) { + /* last chance: assume it's a(n exact) DN ... */ + bv.bv_val = uri->bv_val; + *scope = LDAP_X_SCOPE_EXACT; + goto is_dn; + } + + if ( bv.bv_val[ 0 ] == '/' ) { + group_oc.bv_val = &bv.bv_val[ 1 ]; + group_oc.bv_len = group_dn.bv_val - group_oc.bv_val; + + member_at.bv_val = ber_bvchr( &group_oc, '/' ); + if ( member_at.bv_val ) { + group_oc.bv_len = member_at.bv_val - group_oc.bv_val; + member_at.bv_val++; + member_at.bv_len = group_dn.bv_val - member_at.bv_val; + + } else { + BER_BVSTR( &member_at, SLAPD_GROUP_ATTR ); + } + + } else { + BER_BVSTR( &group_oc, SLAPD_GROUP_CLASS ); + BER_BVSTR( &member_at, SLAPD_GROUP_ATTR ); + } + group_dn.bv_val++; + group_dn.bv_len = uri->bv_len - ( group_dn.bv_val - uri->bv_val ); + + if ( normalize ) { + rc = dnNormalize( 0, NULL, NULL, &group_dn, nbase, op->o_tmpmemctx ); + if ( rc != LDAP_SUCCESS ) { + *scope = -1; + return rc; + } + } else { + ber_dupbv_x( nbase, &group_dn, op->o_tmpmemctx ); + rc = LDAP_SUCCESS; + } + *scope = LDAP_X_SCOPE_GROUP; + + /* FIXME: caller needs to add value of member attribute + * and close brackets twice */ + fstr->bv_len = STRLENOF( "(&(objectClass=)(=" /* )) */ ) + + group_oc.bv_len + member_at.bv_len; + fstr->bv_val = ch_malloc( fstr->bv_len + 1 ); + + tmp = lutil_strncopy( fstr->bv_val, "(&(objectClass=" /* )) */ , + STRLENOF( "(&(objectClass=" /* )) */ ) ); + tmp = lutil_strncopy( tmp, group_oc.bv_val, group_oc.bv_len ); + tmp = lutil_strncopy( tmp, /* ( */ ")(" /* ) */ , + STRLENOF( /* ( */ ")(" /* ) */ ) ); + tmp = lutil_strncopy( tmp, member_at.bv_val, member_at.bv_len ); + tmp = lutil_strncopy( tmp, "=", STRLENOF( "=" ) ); + + return rc; + } + + /* + * ldap:///<base>??<scope>?<filter> + * <scope> ::= {base|one|subtree} + * + * <scope> defaults to "base" + * <base> must pass DN normalization + * <filter> must pass str2filter() + */ + rc = ldap_url_parse( uri->bv_val, &ludp ); + switch ( rc ) { + case LDAP_URL_SUCCESS: + /* FIXME: the check is pedantic, but I think it's necessary, + * because people tend to use things like ldaps:// which + * gives the idea SSL is being used. Maybe we could + * accept ldapi:// as well, but the point is that we use + * an URL as an easy means to define bits of a search with + * little parsing. + */ + if ( strcasecmp( ludp->lud_scheme, "ldap" ) != 0 ) { + /* + * must be ldap:/// + */ + rc = LDAP_PROTOCOL_ERROR; + goto done; + } + break; + + case LDAP_URL_ERR_BADSCHEME: + /* + * last chance: assume it's a(n exact) DN ... + * + * NOTE: must pass DN normalization + */ + ldap_free_urldesc( ludp ); + bv.bv_val = uri->bv_val; + *scope = LDAP_X_SCOPE_EXACT; + goto is_dn; + + default: + rc = LDAP_PROTOCOL_ERROR; + goto done; + } + + if ( ( ludp->lud_host && *ludp->lud_host ) + || ludp->lud_attrs || ludp->lud_exts ) + { + /* host part must be empty */ + /* attrs and extensions parts must be empty */ + rc = LDAP_PROTOCOL_ERROR; + goto done; + } + + /* Grab the scope */ + *scope = ludp->lud_scope; + + /* Grab the filter */ + if ( ludp->lud_filter ) { + *filter = str2filter_x( op, ludp->lud_filter ); + if ( *filter == NULL ) { + rc = LDAP_PROTOCOL_ERROR; + goto done; + } + ber_str2bv( ludp->lud_filter, 0, 0, fstr ); + } + + /* Grab the searchbase */ + ber_str2bv( ludp->lud_dn, 0, 0, base ); + if ( normalize ) { + rc = dnNormalize( 0, NULL, NULL, base, nbase, op->o_tmpmemctx ); + } else { + ber_dupbv_x( nbase, base, op->o_tmpmemctx ); + rc = LDAP_SUCCESS; + } + +done: + if( rc != LDAP_SUCCESS ) { + if( *filter ) { + filter_free_x( op, *filter, 1 ); + *filter = NULL; + } + BER_BVZERO( base ); + BER_BVZERO( fstr ); + } else { + /* Don't free these, return them to caller */ + ludp->lud_filter = NULL; + ludp->lud_dn = NULL; + } + + ldap_free_urldesc( ludp ); + return( rc ); +} + +#ifndef SLAP_AUTH_REWRITE +static int slap_sasl_rx_off(char *rep, int *off) +{ + const char *c; + int n; + + /* Precompile replace pattern. Find the $<n> placeholders */ + off[0] = -2; + n = 1; + for ( c = rep; *c; c++ ) { + if ( *c == '\\' && c[1] ) { + c++; + continue; + } + if ( *c == '$' ) { + if ( n == SASLREGEX_REPLACE ) { + Debug( LDAP_DEBUG_ANY, + "SASL replace pattern %s has too many $n " + "placeholders (max %d)\n", + rep, SASLREGEX_REPLACE, 0 ); + + return( LDAP_OTHER ); + } + off[n] = c - rep; + n++; + } + } + + /* Final placeholder, after the last $n */ + off[n] = c - rep; + n++; + off[n] = -1; + return( LDAP_SUCCESS ); +} +#endif /* ! SLAP_AUTH_REWRITE */ + +#ifdef SLAP_AUTH_REWRITE +int slap_sasl_rewrite_config( + const char *fname, + int lineno, + int argc, + char **argv +) +{ + int rc; + char *savearg0; + + /* init at first call */ + if ( sasl_rwinfo == NULL ) { + sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT ); + } + + /* strip "authid-" prefix for parsing */ + savearg0 = argv[0]; + argv[0] += STRLENOF( "authid-" ); + rc = rewrite_parse( sasl_rwinfo, fname, lineno, argc, argv ); + argv[0] = savearg0; + + return rc; +} + +static int +slap_sasl_rewrite_destroy( void ) +{ + if ( sasl_rwinfo ) { + rewrite_info_delete( &sasl_rwinfo ); + sasl_rwinfo = NULL; + } + + return 0; +} + +int slap_sasl_regexp_rewrite_config( + const char *fname, + int lineno, + const char *match, + const char *replace, + const char *context ) +{ + int rc; + char *argvRule[] = { "rewriteRule", NULL, NULL, ":@", NULL }; + + /* init at first call */ + if ( sasl_rwinfo == NULL ) { + char *argvEngine[] = { "rewriteEngine", "on", NULL }; + char *argvContext[] = { "rewriteContext", NULL, NULL }; + + /* initialize rewrite engine */ + sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT ); + + /* switch on rewrite engine */ + rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvEngine ); + if (rc != LDAP_SUCCESS) { + return rc; + } + + /* create generic authid context */ + argvContext[1] = AUTHID_CONTEXT; + rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvContext ); + if (rc != LDAP_SUCCESS) { + return rc; + } + } + + argvRule[1] = (char *)match; + argvRule[2] = (char *)replace; + rc = rewrite_parse( sasl_rwinfo, fname, lineno, 4, argvRule ); + + return rc; +} +#endif /* SLAP_AUTH_REWRITE */ + +int slap_sasl_regexp_config( const char *match, const char *replace ) +{ + int rc; + SaslRegexp_t *reg; + + SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp, + (nSaslRegexp + 1) * sizeof(SaslRegexp_t) ); + + reg = &SaslRegexp[nSaslRegexp]; + +#ifdef SLAP_AUTH_REWRITE + rc = slap_sasl_regexp_rewrite_config( "sasl-regexp", 0, + match, replace, AUTHID_CONTEXT ); +#else /* ! SLAP_AUTH_REWRITE */ + + /* Precompile matching pattern */ + rc = regcomp( ®->sr_workspace, match, REG_EXTENDED|REG_ICASE ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "SASL match pattern %s could not be compiled by regexp engine\n", + match, 0, 0 ); + +#ifdef ENABLE_REWRITE + /* Dummy block to force symbol references in librewrite */ + if ( slapMode == ( SLAP_SERVER_MODE|SLAP_TOOL_MODE )) { + rewrite_info_init( 0 ); + } +#endif + return( LDAP_OTHER ); + } + + rc = slap_sasl_rx_off( replace, reg->sr_offset ); +#endif /* ! SLAP_AUTH_REWRITE */ + if ( rc == LDAP_SUCCESS ) { + reg->sr_match = ch_strdup( match ); + reg->sr_replace = ch_strdup( replace ); + + nSaslRegexp++; + } + + return rc; +} + +void +slap_sasl_regexp_destroy( void ) +{ + if ( SaslRegexp ) { + int n; + + for ( n = 0; n < nSaslRegexp; n++ ) { + ch_free( SaslRegexp[ n ].sr_match ); + ch_free( SaslRegexp[ n ].sr_replace ); +#ifndef SLAP_AUTH_REWRITE + regfree( &SaslRegexp[ n ].sr_workspace ); +#endif /* SLAP_AUTH_REWRITE */ + } + + ch_free( SaslRegexp ); + } + +#ifdef SLAP_AUTH_REWRITE + slap_sasl_rewrite_destroy(); +#endif /* SLAP_AUTH_REWRITE */ +} + +void slap_sasl_regexp_unparse( BerVarray *out ) +{ + int i; + BerVarray bva = NULL; + char ibuf[32], *ptr; + struct berval idx; + + if ( !nSaslRegexp ) return; + + idx.bv_val = ibuf; + bva = ch_malloc( (nSaslRegexp+1) * sizeof(struct berval) ); + BER_BVZERO(bva+nSaslRegexp); + for ( i=0; i<nSaslRegexp; i++ ) { + idx.bv_len = sprintf( idx.bv_val, "{%d}", i); + bva[i].bv_len = idx.bv_len + strlen( SaslRegexp[i].sr_match ) + + strlen( SaslRegexp[i].sr_replace ) + 5; + bva[i].bv_val = ch_malloc( bva[i].bv_len+1 ); + ptr = lutil_strcopy( bva[i].bv_val, ibuf ); + *ptr++ = '"'; + ptr = lutil_strcopy( ptr, SaslRegexp[i].sr_match ); + ptr = lutil_strcopy( ptr, "\" \"" ); + ptr = lutil_strcopy( ptr, SaslRegexp[i].sr_replace ); + *ptr++ = '"'; + *ptr = '\0'; + } + *out = bva; +} + +#ifndef SLAP_AUTH_REWRITE +/* Perform replacement on regexp matches */ +static void slap_sasl_rx_exp( + const char *rep, + const int *off, + regmatch_t *str, + const char *saslname, + struct berval *out, + void *ctx ) +{ + int i, n, len, insert; + + /* Get the total length of the final URI */ + + n=1; + len = 0; + while( off[n] >= 0 ) { + /* Len of next section from replacement string (x,y,z above) */ + len += off[n] - off[n-1] - 2; + if( off[n+1] < 0) + break; + + /* Len of string from saslname that matched next $i (b,d above) */ + i = rep[ off[n] + 1 ] - '0'; + len += str[i].rm_eo - str[i].rm_so; + n++; + } + out->bv_val = slap_sl_malloc( len + 1, ctx ); + out->bv_len = len; + + /* Fill in URI with replace string, replacing $i as we go */ + n=1; + insert = 0; + while( off[n] >= 0) { + /* Paste in next section from replacement string (x,y,z above) */ + len = off[n] - off[n-1] - 2; + strncpy( out->bv_val+insert, rep + off[n-1] + 2, len); + insert += len; + if( off[n+1] < 0) + break; + + /* Paste in string from saslname that matched next $i (b,d above) */ + i = rep[ off[n] + 1 ] - '0'; + len = str[i].rm_eo - str[i].rm_so; + strncpy( out->bv_val+insert, saslname + str[i].rm_so, len ); + insert += len; + + n++; + } + + out->bv_val[insert] = '\0'; +} +#endif /* ! SLAP_AUTH_REWRITE */ + +/* Take the passed in SASL name and attempt to convert it into an + LDAP URI to find the matching LDAP entry, using the pattern matching + strings given in the saslregexp config file directive(s) */ + +static int slap_authz_regexp( struct berval *in, struct berval *out, + int flags, void *ctx ) +{ +#ifdef SLAP_AUTH_REWRITE + const char *context = AUTHID_CONTEXT; + + if ( sasl_rwinfo == NULL || BER_BVISNULL( in ) ) { + return 0; + } + + /* FIXME: if aware of authc/authz mapping, + * we could use different contexts ... */ + switch ( rewrite_session( sasl_rwinfo, context, in->bv_val, NULL, + &out->bv_val ) ) + { + case REWRITE_REGEXEC_OK: + if ( !BER_BVISNULL( out ) ) { + char *val = out->bv_val; + ber_str2bv_x( val, 0, 1, out, ctx ); + if ( val != in->bv_val ) { + free( val ); + } + } else { + ber_dupbv_x( out, in, ctx ); + } + Debug( LDAP_DEBUG_ARGS, + "[rw] %s: \"%s\" -> \"%s\"\n", + context, in->bv_val, out->bv_val ); + return 1; + + case REWRITE_REGEXEC_UNWILLING: + case REWRITE_REGEXEC_ERR: + default: + return 0; + } + +#else /* ! SLAP_AUTH_REWRITE */ + char *saslname = in->bv_val; + SaslRegexp_t *reg; + regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */ + int i; + + memset( out, 0, sizeof( *out ) ); + + Debug( LDAP_DEBUG_TRACE, "slap_authz_regexp: converting SASL name %s\n", + saslname, 0, 0 ); + + if (( saslname == NULL ) || ( nSaslRegexp == 0 )) { + return( 0 ); + } + + /* Match the normalized SASL name to the saslregexp patterns */ + for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) { + if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE, + sr_strings, 0) == 0 ) + break; + } + + if( i >= nSaslRegexp ) return( 0 ); + + /* + * The match pattern may have been of the form "a(b.*)c(d.*)e" and the + * replace pattern of the form "x$1y$2z". The returned string needs + * to replace the $1,$2 with the strings that matched (b.*) and (d.*) + */ + slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset, + sr_strings, saslname, out, ctx ); + + Debug( LDAP_DEBUG_TRACE, + "slap_authz_regexp: converted SASL name to %s\n", + BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 ); + + return( 1 ); +#endif /* ! SLAP_AUTH_REWRITE */ +} + +/* This callback actually does some work...*/ +static int sasl_sc_sasl2dn( Operation *op, SlapReply *rs ) +{ + struct berval *ndn = op->o_callback->sc_private; + + if ( rs->sr_type != REP_SEARCH ) return LDAP_SUCCESS; + + /* We only want to be called once */ + if ( !BER_BVISNULL( ndn ) ) { + op->o_tmpfree( ndn->bv_val, op->o_tmpmemctx ); + BER_BVZERO( ndn ); + + Debug( LDAP_DEBUG_TRACE, + "%s: slap_sc_sasl2dn: search DN returned more than 1 entry\n", + op->o_log_prefix, 0, 0 ); + return LDAP_UNAVAILABLE; /* short-circuit the search */ + } + + ber_dupbv_x( ndn, &rs->sr_entry->e_nname, op->o_tmpmemctx ); + return LDAP_SUCCESS; +} + + +typedef struct smatch_info { + struct berval *dn; + int match; +} smatch_info; + +static int sasl_sc_smatch( Operation *o, SlapReply *rs ) +{ + smatch_info *sm = o->o_callback->sc_private; + + if (rs->sr_type != REP_SEARCH) return 0; + + if (dn_match(sm->dn, &rs->sr_entry->e_nname)) { + sm->match = 1; + return LDAP_UNAVAILABLE; /* short-circuit the search */ + } + + return 0; +} + +int +slap_sasl_matches( Operation *op, BerVarray rules, + struct berval *assertDN, struct berval *authc ) +{ + int rc = LDAP_INAPPROPRIATE_AUTH; + + if ( rules != NULL ) { + int i; + + for( i = 0; !BER_BVISNULL( &rules[i] ); i++ ) { + rc = slap_sasl_match( op, &rules[i], assertDN, authc ); + if ( rc == LDAP_SUCCESS ) break; + } + } + + return rc; +} + +/* + * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base + * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise, + * the rule must be used as an internal search for entries. If that search + * returns the *assertDN entry, the match is successful. + * + * The assertDN should not have the dn: prefix + */ + +static int +slap_sasl_match( Operation *opx, struct berval *rule, + struct berval *assertDN, struct berval *authc ) +{ + int rc; + regex_t reg; + smatch_info sm; + slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL }; + Operation op = {0}; + SlapReply rs = {REP_RESULT}; + struct berval base = BER_BVNULL; + + sm.dn = assertDN; + sm.match = 0; + cb.sc_private = &sm; + + Debug( LDAP_DEBUG_TRACE, + "===>slap_sasl_match: comparing DN %s to rule %s\n", + assertDN->bv_len ? assertDN->bv_val : "(null)", rule->bv_val, 0 ); + + /* NOTE: don't normalize rule if authz syntax is enabled */ + rc = slap_parseURI( opx, rule, &base, &op.o_req_ndn, + &op.ors_scope, &op.ors_filter, &op.ors_filterstr, 0 ); + + if( rc != LDAP_SUCCESS ) goto CONCLUDED; + + switch ( op.ors_scope ) { + case LDAP_X_SCOPE_EXACT: +exact_match: + if ( dn_match( &op.o_req_ndn, assertDN ) ) { + rc = LDAP_SUCCESS; + } else { + rc = LDAP_INAPPROPRIATE_AUTH; + } + goto CONCLUDED; + + case LDAP_X_SCOPE_CHILDREN: + case LDAP_X_SCOPE_SUBTREE: + case LDAP_X_SCOPE_ONELEVEL: + { + int d = assertDN->bv_len - op.o_req_ndn.bv_len; + + rc = LDAP_INAPPROPRIATE_AUTH; + + if ( d == 0 && op.ors_scope == LDAP_X_SCOPE_SUBTREE ) { + goto exact_match; + + } else if ( d > 0 ) { + struct berval bv; + + /* leave room for at least one char of attributeType, + * one for '=' and one for ',' */ + if ( d < (int) STRLENOF( "x=,") ) { + goto CONCLUDED; + } + + bv.bv_len = op.o_req_ndn.bv_len; + bv.bv_val = assertDN->bv_val + d; + + if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) { + switch ( op.ors_scope ) { + case LDAP_X_SCOPE_SUBTREE: + case LDAP_X_SCOPE_CHILDREN: + rc = LDAP_SUCCESS; + break; + + case LDAP_X_SCOPE_ONELEVEL: + { + struct berval pdn; + + dnParent( assertDN, &pdn ); + /* the common portion of the DN + * already matches, so only check + * if parent DN of assertedDN + * is all the pattern */ + if ( pdn.bv_len == op.o_req_ndn.bv_len ) { + rc = LDAP_SUCCESS; + } + break; + } + default: + /* at present, impossible */ + assert( 0 ); + } + } + } + goto CONCLUDED; + } + + case LDAP_X_SCOPE_REGEX: + rc = regcomp(®, op.o_req_ndn.bv_val, + REG_EXTENDED|REG_ICASE|REG_NOSUB); + if ( rc == 0 ) { + rc = regexec(®, assertDN->bv_val, 0, NULL, 0); + regfree( ® ); + } + if ( rc == 0 ) { + rc = LDAP_SUCCESS; + } else { + rc = LDAP_INAPPROPRIATE_AUTH; + } + goto CONCLUDED; + + case LDAP_X_SCOPE_GROUP: { + char *tmp; + + /* Now filterstr looks like "(&(objectClass=<group_oc>)(<member_at>=" + * we need to append the <assertDN> so that the <group_dn> is searched + * with scope "base", and the filter ensures that <assertDN> is + * member of the group */ + tmp = ch_realloc( op.ors_filterstr.bv_val, op.ors_filterstr.bv_len + + assertDN->bv_len + STRLENOF( /*"(("*/ "))" ) + 1 ); + if ( tmp == NULL ) { + rc = LDAP_NO_MEMORY; + goto CONCLUDED; + } + op.ors_filterstr.bv_val = tmp; + + tmp = lutil_strcopy( &tmp[op.ors_filterstr.bv_len], assertDN->bv_val ); + tmp = lutil_strcopy( tmp, /*"(("*/ "))" ); + + /* pass opx because str2filter_x may (and does) use o_tmpmfuncs */ + op.ors_filter = str2filter_x( opx, op.ors_filterstr.bv_val ); + if ( op.ors_filter == NULL ) { + rc = LDAP_PROTOCOL_ERROR; + goto CONCLUDED; + } + op.ors_scope = LDAP_SCOPE_BASE; + + /* hijack match DN: use that of the group instead of the assertDN; + * assertDN is now in the filter */ + sm.dn = &op.o_req_ndn; + + /* do the search */ + break; + } + + case LDAP_X_SCOPE_USERS: + if ( !BER_BVISEMPTY( assertDN ) ) { + rc = LDAP_SUCCESS; + } else { + rc = LDAP_INAPPROPRIATE_AUTH; + } + goto CONCLUDED; + + default: + break; + } + + /* Must run an internal search. */ + if ( op.ors_filter == NULL ) { + rc = LDAP_FILTER_ERROR; + goto CONCLUDED; + } + + Debug( LDAP_DEBUG_TRACE, + "slap_sasl_match: performing internal search (base=%s, scope=%d)\n", + op.o_req_ndn.bv_val, op.ors_scope, 0 ); + + op.o_bd = select_backend( &op.o_req_ndn, 1 ); + if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) { + rc = LDAP_INAPPROPRIATE_AUTH; + goto CONCLUDED; + } + + op.o_hdr = opx->o_hdr; + op.o_tag = LDAP_REQ_SEARCH; + op.o_ndn = *authc; + op.o_callback = &cb; + slap_op_time( &op.o_time, &op.o_tincr ); + op.o_do_not_cache = 1; + op.o_is_auth_check = 1; + /* use req_ndn as req_dn instead of non-pretty base of uri */ + if( !BER_BVISNULL( &base ) ) { + ch_free( base.bv_val ); + /* just in case... */ + BER_BVZERO( &base ); + } + ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx ); + op.ors_deref = LDAP_DEREF_NEVER; + op.ors_slimit = 1; + op.ors_tlimit = SLAP_NO_LIMIT; + op.ors_attrs = slap_anlist_no_attrs; + op.ors_attrsonly = 1; + + op.o_bd->be_search( &op, &rs ); + + if (sm.match) { + rc = LDAP_SUCCESS; + } else { + rc = LDAP_INAPPROPRIATE_AUTH; + } + +CONCLUDED: + if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx ); + if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx ); + if( op.ors_filter ) filter_free_x( opx, op.ors_filter, 1 ); + if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val ); + + Debug( LDAP_DEBUG_TRACE, + "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0); + + return( rc ); +} + + +/* + * This function answers the question, "Can this ID authorize to that ID?", + * based on authorization rules. The rules are stored in the *searchDN, in the + * attribute named by *attr. If any of those rules map to the *assertDN, the + * authorization is approved. + * + * The DNs should not have the dn: prefix + */ +static int +slap_sasl_check_authz( Operation *op, + struct berval *searchDN, + struct berval *assertDN, + AttributeDescription *ad, + struct berval *authc ) +{ + int rc, + do_not_cache = op->o_do_not_cache; + BerVarray vals = NULL; + + Debug( LDAP_DEBUG_TRACE, + "==>slap_sasl_check_authz: does %s match %s rule in %s?\n", + assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val); + + /* ITS#4760: don't cache group access */ + op->o_do_not_cache = 1; + rc = backend_attribute( op, NULL, searchDN, ad, &vals, ACL_AUTH ); + op->o_do_not_cache = do_not_cache; + if( rc != LDAP_SUCCESS ) goto COMPLETE; + + /* Check if the *assertDN matches any *vals */ + rc = slap_sasl_matches( op, vals, assertDN, authc ); + +COMPLETE: + if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, + "<==slap_sasl_check_authz: %s check returning %d\n", + ad->ad_cname.bv_val, rc, 0); + + return( rc ); +} + +/* + * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH") + * return the LDAP DN to which it matches. The SASL regexp rules in the config + * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a + * search with scope=base), just return the URI (or its searchbase). Otherwise + * an internal search must be done, and if that search returns exactly one + * entry, return the DN of that one entry. + */ +void +slap_sasl2dn( + Operation *opx, + struct berval *saslname, + struct berval *sasldn, + int flags ) +{ + int rc; + slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL }; + Operation op = {0}; + SlapReply rs = {REP_RESULT}; + struct berval regout = BER_BVNULL; + struct berval base = BER_BVNULL; + + Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: " + "converting SASL name %s to a DN\n", + saslname->bv_val, 0,0 ); + + BER_BVZERO( sasldn ); + cb.sc_private = sasldn; + + /* Convert the SASL name into a minimal URI */ + if( !slap_authz_regexp( saslname, ®out, flags, opx->o_tmpmemctx ) ) { + goto FINISHED; + } + + /* NOTE: always normalize regout because it results + * from string submatch expansion */ + rc = slap_parseURI( opx, ®out, &base, &op.o_req_ndn, + &op.ors_scope, &op.ors_filter, &op.ors_filterstr, 1 ); + if ( !BER_BVISNULL( ®out ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx ); + if ( rc != LDAP_SUCCESS ) { + goto FINISHED; + } + + /* Must do an internal search */ + op.o_bd = select_backend( &op.o_req_ndn, 1 ); + + switch ( op.ors_scope ) { + case LDAP_X_SCOPE_EXACT: + *sasldn = op.o_req_ndn; + BER_BVZERO( &op.o_req_ndn ); + /* intentionally continue to next case */ + + case LDAP_X_SCOPE_REGEX: + case LDAP_X_SCOPE_SUBTREE: + case LDAP_X_SCOPE_CHILDREN: + case LDAP_X_SCOPE_ONELEVEL: + case LDAP_X_SCOPE_GROUP: + case LDAP_X_SCOPE_USERS: + /* correctly parsed, but illegal */ + goto FINISHED; + + case LDAP_SCOPE_BASE: + case LDAP_SCOPE_ONELEVEL: + case LDAP_SCOPE_SUBTREE: + case LDAP_SCOPE_SUBORDINATE: + /* do a search */ + break; + + default: + /* catch unhandled cases (there shouldn't be) */ + assert( 0 ); + } + + Debug( LDAP_DEBUG_TRACE, + "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n", + op.o_req_ndn.bv_val, op.ors_scope, 0 ); + + if ( ( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL) ) { + goto FINISHED; + } + + /* Must run an internal search. */ + if ( op.ors_filter == NULL ) { + rc = LDAP_FILTER_ERROR; + goto FINISHED; + } + + op.o_hdr = opx->o_hdr; + op.o_tag = LDAP_REQ_SEARCH; + op.o_ndn = opx->o_conn->c_ndn; + op.o_callback = &cb; + slap_op_time( &op.o_time, &op.o_tincr ); + op.o_do_not_cache = 1; + op.o_is_auth_check = 1; + op.ors_deref = LDAP_DEREF_NEVER; + op.ors_slimit = 1; + op.ors_tlimit = SLAP_NO_LIMIT; + op.ors_attrs = slap_anlist_no_attrs; + op.ors_attrsonly = 1; + /* use req_ndn as req_dn instead of non-pretty base of uri */ + if( !BER_BVISNULL( &base ) ) { + ch_free( base.bv_val ); + /* just in case... */ + BER_BVZERO( &base ); + } + ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx ); + + op.o_bd->be_search( &op, &rs ); + +FINISHED: + if( opx == opx->o_conn->c_sasl_bindop && !BER_BVISEMPTY( sasldn ) ) { + opx->o_conn->c_authz_backend = op.o_bd; + } + if( !BER_BVISNULL( &op.o_req_dn ) ) { + slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx ); + } + if( !BER_BVISNULL( &op.o_req_ndn ) ) { + slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx ); + } + if( op.ors_filter ) { + filter_free_x( opx, op.ors_filter, 1 ); + } + if( !BER_BVISNULL( &op.ors_filterstr ) ) { + ch_free( op.ors_filterstr.bv_val ); + } + + Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n", + !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 ); + + return; +} + + +/* Check if a bind can SASL authorize to another identity. + * The DNs should not have the dn: prefix + */ + +int slap_sasl_authorized( Operation *op, + struct berval *authcDN, struct berval *authzDN ) +{ + int rc = LDAP_INAPPROPRIATE_AUTH; + + /* User binding as anonymous */ + if ( !authzDN || !authzDN->bv_len || !authzDN->bv_val ) { + rc = LDAP_SUCCESS; + goto DONE; + } + + /* User is anonymous */ + if ( !authcDN || !authcDN->bv_len || !authcDN->bv_val ) { + goto DONE; + } + + Debug( LDAP_DEBUG_TRACE, + "==>slap_sasl_authorized: can %s become %s?\n", + authcDN->bv_len ? authcDN->bv_val : "(null)", + authzDN->bv_len ? authzDN->bv_val : "(null)", 0 ); + + /* If person is authorizing to self, succeed */ + if ( dn_match( authcDN, authzDN ) ) { + rc = LDAP_SUCCESS; + goto DONE; + } + + /* Allow the manager to authorize as any DN in its own DBs. */ + { + Backend *zbe = select_backend( authzDN, 1 ); + if ( zbe && be_isroot_dn( zbe, authcDN )) { + rc = LDAP_SUCCESS; + goto DONE; + } + } + + /* Check source rules */ + if( authz_policy & SASL_AUTHZ_TO ) { + rc = slap_sasl_check_authz( op, authcDN, authzDN, + slap_schema.si_ad_saslAuthzTo, authcDN ); + if(( rc == LDAP_SUCCESS ) ^ (( authz_policy & SASL_AUTHZ_AND) != 0)) { + if( rc != LDAP_SUCCESS ) + rc = LDAP_INAPPROPRIATE_AUTH; + goto DONE; + } + } + + /* Check destination rules */ + if( authz_policy & SASL_AUTHZ_FROM ) { + rc = slap_sasl_check_authz( op, authzDN, authcDN, + slap_schema.si_ad_saslAuthzFrom, authcDN ); + if( rc == LDAP_SUCCESS ) { + goto DONE; + } + } + + rc = LDAP_INAPPROPRIATE_AUTH; + +DONE: + + Debug( LDAP_DEBUG_TRACE, + "<== slap_sasl_authorized: return %d\n", rc, 0, 0 ); + + return( rc ); +} diff --git a/servers/slapd/schema.c b/servers/slapd/schema.c new file mode 100644 index 0000000..b3d9929 --- /dev/null +++ b/servers/slapd/schema.c @@ -0,0 +1,167 @@ +/* schema.c - routines to manage schema definitions */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "lutil.h" + + +int +schema_info( Entry **entry, const char **text ) +{ + AttributeDescription *ad_structuralObjectClass + = slap_schema.si_ad_structuralObjectClass; + AttributeDescription *ad_objectClass + = slap_schema.si_ad_objectClass; + AttributeDescription *ad_createTimestamp + = slap_schema.si_ad_createTimestamp; + AttributeDescription *ad_modifyTimestamp + = slap_schema.si_ad_modifyTimestamp; + + Entry *e; + struct berval vals[5]; + struct berval nvals[5]; + + e = entry_alloc(); + if( e == NULL ) { + /* Out of memory, do something about it */ + Debug( LDAP_DEBUG_ANY, + "schema_info: entry_alloc failed - out of memory.\n", 0, 0, 0 ); + *text = "out of memory"; + return LDAP_OTHER; + } + + e->e_attrs = NULL; + /* backend-specific schema info should be created by the + * backend itself + */ + ber_dupbv( &e->e_name, &frontendDB->be_schemadn ); + ber_dupbv( &e->e_nname, &frontendDB->be_schemandn ); + e->e_private = NULL; + + BER_BVSTR( &vals[0], "subentry" ); + if( attr_merge_one( e, ad_structuralObjectClass, vals, NULL ) ) { + /* Out of memory, do something about it */ + entry_free( e ); + *text = "out of memory"; + return LDAP_OTHER; + } + + BER_BVSTR( &vals[0], "top" ); + BER_BVSTR( &vals[1], "subentry" ); + BER_BVSTR( &vals[2], "subschema" ); + BER_BVSTR( &vals[3], "extensibleObject" ); + BER_BVZERO( &vals[4] ); + if ( attr_merge( e, ad_objectClass, vals, NULL ) ) { + /* Out of memory, do something about it */ + entry_free( e ); + *text = "out of memory"; + return LDAP_OTHER; + } + + { + int rc; + AttributeDescription *desc = NULL; + struct berval rdn = frontendDB->be_schemadn; + vals[0].bv_val = ber_bvchr( &rdn, '=' ); + + if( vals[0].bv_val == NULL ) { + *text = "improperly configured subschema subentry"; + return LDAP_OTHER; + } + + vals[0].bv_val++; + vals[0].bv_len = rdn.bv_len - (vals[0].bv_val - rdn.bv_val); + rdn.bv_len -= vals[0].bv_len + 1; + + rc = slap_bv2ad( &rdn, &desc, text ); + + if( rc != LDAP_SUCCESS ) { + entry_free( e ); + *text = "improperly configured subschema subentry"; + return LDAP_OTHER; + } + + nvals[0].bv_val = ber_bvchr( &frontendDB->be_schemandn, '=' ); + assert( nvals[0].bv_val != NULL ); + nvals[0].bv_val++; + nvals[0].bv_len = frontendDB->be_schemandn.bv_len - + (nvals[0].bv_val - frontendDB->be_schemandn.bv_val); + + if ( attr_merge_one( e, desc, vals, nvals ) ) { + /* Out of memory, do something about it */ + entry_free( e ); + *text = "out of memory"; + return LDAP_OTHER; + } + } + + { + char timebuf[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + + /* + * According to RFC 4512: + + Servers SHOULD maintain the 'creatorsName', 'createTimestamp', + 'modifiersName', and 'modifyTimestamp' attributes for all entries of + the DIT. + + * to be conservative, we declare schema created + * AND modified at server startup time ... + */ + + vals[0].bv_val = timebuf; + vals[0].bv_len = sizeof( timebuf ); + + slap_timestamp( &starttime, vals ); + + if( attr_merge_one( e, ad_createTimestamp, vals, NULL ) ) { + /* Out of memory, do something about it */ + entry_free( e ); + *text = "out of memory"; + return LDAP_OTHER; + } + if( attr_merge_one( e, ad_modifyTimestamp, vals, NULL ) ) { + /* Out of memory, do something about it */ + entry_free( e ); + *text = "out of memory"; + return LDAP_OTHER; + } + } + + if ( syn_schema_info( e ) + || mr_schema_info( e ) + || mru_schema_info( e ) + || at_schema_info( e ) + || oc_schema_info( e ) + || cr_schema_info( e ) ) + { + /* Out of memory, do something about it */ + entry_free( e ); + *text = "out of memory"; + return LDAP_OTHER; + } + + *entry = e; + return LDAP_SUCCESS; +} diff --git a/servers/slapd/schema/README b/servers/slapd/schema/README new file mode 100644 index 0000000..b45baac --- /dev/null +++ b/servers/slapd/schema/README @@ -0,0 +1,80 @@ +This directory contains user application schema definitions for use +with slapd(8). + +File Description +---- ----------- +collective.schema Collective attributes (experimental) +corba.schema Corba Object +core.schema OpenLDAP "core" +cosine.schema COSINE Pilot +duaconf.schema Client Configuration (work in progress) +dyngroup.schema Dynamic Group (experimental) +inetorgperson.schema InetOrgPerson +java.schema Java Object +misc.schema Miscellaneous Schema (experimental) +nadf.schema North American Directory Forum (obsolete) +nis.schema Network Information Service (experimental) +openldap.schema OpenLDAP Project (FYI) +ppolicy.schema Password Policy Schema (work in progress) + +Additional "generally useful" schema definitions can be submitted +using the OpenLDAP Issue Tracking System <http://www.openldap.org/its/>. +Submissions should include a stable reference to a mature, open +technical specification (e.g., an RFC) for the schema. + +The core.ldif and openldap.ldif files are equivalent to their +corresponding .schema files. They have been provided as examples +for use with the dynamic configuration backend. These example files +are not actually necessary since slapd will automatically convert any +included *.schema files into LDIF when converting a slapd.conf file +to a configuration database, but they serve as a model of how to +convert schema files in general. + +--- + +This notice applies to all files in this directory. + +Copyright 1998-2021 The OpenLDAP Foundation, Redwood City, California, USA +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted only as authorized by the OpenLDAP +Public License. A copy of this license is available at +http://www.OpenLDAP.org/license.html or in file LICENSE in the +top-level directory of the distribution. + +--- + +This notice applies to all schema in this directory which are derived +from RFCs and other IETF documents. + +Portions Copyright 1991-2004, The Internet Society. All Rights Reserved. + +This document and translations of it may be copied and furnished +to others, and derivative works that comment on or otherwise explain +it or assist in its implementation may be prepared, copied, published +and distributed, in whole or in part, without restriction of any +kind, provided that the above copyright notice and this paragraph +are included on all such copies and derivative works. However, +this document itself may not be modified in any way, such as by +removing the copyright notice or references to the Internet Society +or other Internet organizations, except as needed for the purpose +of developing Internet standards in which case the procedures for +copyrights defined in the Internet Standards process must be +followed, or as required to translate it into languages other than +English. + +The limited permissions granted above are perpetual and will not +be revoked by the Internet Society or its successors or assigns. + +This document and the information contained herein is provided on +an "AS IS" basis and THE AUTHORS, THE INTERNET SOCIETY, AND THE +INTERNET ENGINEERING TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE +OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY +IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. + + +--- +$OpenLDAP$ diff --git a/servers/slapd/schema/collective.ldif b/servers/slapd/schema/collective.ldif new file mode 100644 index 0000000..8402951 --- /dev/null +++ b/servers/slapd/schema/collective.ldif @@ -0,0 +1,48 @@ +# collective.ldif -- Collective attribute schema +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +# +## Portions Copyright (C) The Internet Society (2003). +## Please see full copyright statement below. +# +# From RFC 3671 [portions trimmed]: +# Collective Attributes in LDAP +# +# This file was automatically generated from collective.schema; see that file +# for complete references. +# +dn: cn=collective,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: collective +olcAttributeTypes: {0}( 2.5.4.7.1 NAME 'c-l' SUP l COLLECTIVE ) +olcAttributeTypes: {1}( 2.5.4.8.1 NAME 'c-st' SUP st COLLECTIVE ) +olcAttributeTypes: {2}( 2.5.4.9.1 NAME 'c-street' SUP street COLLECTIVE ) +olcAttributeTypes: {3}( 2.5.4.10.1 NAME 'c-o' SUP o COLLECTIVE ) +olcAttributeTypes: {4}( 2.5.4.11.1 NAME 'c-ou' SUP ou COLLECTIVE ) +olcAttributeTypes: {5}( 2.5.4.16.1 NAME 'c-PostalAddress' SUP postalAddress CO + LLECTIVE ) +olcAttributeTypes: {6}( 2.5.4.17.1 NAME 'c-PostalCode' SUP postalCode COLLECTI + VE ) +olcAttributeTypes: {7}( 2.5.4.18.1 NAME 'c-PostOfficeBox' SUP postOfficeBox CO + LLECTIVE ) +olcAttributeTypes: {8}( 2.5.4.19.1 NAME 'c-PhysicalDeliveryOfficeName' SUP phy + sicalDeliveryOfficeName COLLECTIVE ) +olcAttributeTypes: {9}( 2.5.4.20.1 NAME 'c-TelephoneNumber' SUP telephoneNumbe + r COLLECTIVE ) +olcAttributeTypes: {10}( 2.5.4.21.1 NAME 'c-TelexNumber' SUP telexNumber COLLE + CTIVE ) +olcAttributeTypes: {11}( 2.5.4.23.1 NAME 'c-FacsimileTelephoneNumber' SUP facs + imileTelephoneNumber COLLECTIVE ) +olcAttributeTypes: {12}( 2.5.4.25.1 NAME 'c-InternationalISDNNumber' SUP inter + nationalISDNNumber COLLECTIVE ) diff --git a/servers/slapd/schema/corba.ldif b/servers/slapd/schema/corba.ldif new file mode 100644 index 0000000..0b81686 --- /dev/null +++ b/servers/slapd/schema/corba.ldif @@ -0,0 +1,42 @@ +# corba.ldif -- Corba Object Schema +# depends upon core.ldif +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +# +## Portions Copyright (C) The Internet Society (1999). +## Please see full copyright statement below. +# +# From RFC 2714 [portions trimmed]: +# Schema for Representing CORBA Object References in an LDAP Directory +# +# This file was automatically generated from corba.schema; see that file +# for complete references. +# +dn: cn=corba,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: corba +olcAttributeTypes: {0}( 1.3.6.1.4.1.42.2.27.4.1.14 NAME 'corbaIor' DESC 'Strin + gified interoperable object reference of a CORBA object' EQUALITY caseIgnoreI + A5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) +olcAttributeTypes: {1}( 1.3.6.1.4.1.42.2.27.4.1.15 NAME 'corbaRepositoryId' DE + SC 'Repository ids of interfaces implemented by a CORBA object' EQUALITY case + ExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcObjectClasses: {0}( 1.3.6.1.4.1.42.2.27.4.2.10 NAME 'corbaContainer' DESC ' + Container for a CORBA object' SUP top STRUCTURAL MUST cn ) +olcObjectClasses: {1}( 1.3.6.1.4.1.42.2.27.4.2.9 NAME 'corbaObject' DESC 'CORB + A object representation' SUP top ABSTRACT MAY ( corbaRepositoryId $ descripti + on ) ) +olcObjectClasses: {2}( 1.3.6.1.4.1.42.2.27.4.2.11 NAME 'corbaObjectReference' + DESC 'CORBA interoperable object reference' SUP corbaObject AUXILIARY MUST co + rbaIor ) diff --git a/servers/slapd/schema/cosine.ldif b/servers/slapd/schema/cosine.ldif new file mode 100644 index 0000000..4357642 --- /dev/null +++ b/servers/slapd/schema/cosine.ldif @@ -0,0 +1,200 @@ +# RFC1274: Cosine and Internet X.500 schema +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +# +# RFC1274: Cosine and Internet X.500 schema +# +# This file contains LDAPv3 schema derived from X.500 COSINE "pilot" +# schema. As this schema was defined for X.500(89), some +# oddities were introduced in the mapping to LDAPv3. The +# mappings were based upon: draft-ietf-asid-ldapv3-attributes-03.txt +# (a work in progress) +# +# Note: It seems that the pilot schema evolved beyond what was +# described in RFC1274. However, this document attempts to describes +# RFC1274 as published. +# +# Depends on core.ldif +# +# This file was automatically generated from cosine.schema; see that +# file for complete background. +# +dn: cn=cosine,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: cosine +olcAttributeTypes: ( 0.9.2342.19200300.100.1.2 NAME 'textEncodedORAddress' + EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1. + 1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.4 NAME 'info' DESC 'RFC1274: g + eneral information' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{2048} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.5 NAME ( 'drink' 'favouriteDri + nk' ) DESC 'RFC1274: favorite drink' EQUALITY caseIgnoreMatch SUBSTR caseIgno + reSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.6 NAME 'roomNumber' DESC 'RFC1 + 274: room number' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch S + YNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.7 NAME 'photo' DESC 'RFC1274: + photo (G3 fax)' SYNTAX 1.3.6.1.4.1.1466.115.121.1.23{25000} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.8 NAME 'userClass' DESC 'RFC12 + 74: category of user' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMat + ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.9 NAME 'host' DESC 'RFC1274: h + ost computer' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTA + X 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.10 NAME 'manager' DESC 'RFC127 + 4: DN of manager' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115 + .121.1.12 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.11 NAME 'documentIdentifier' D + ESC 'RFC1274: unique identifier of document' EQUALITY caseIgnoreMatch SUBSTR + caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.12 NAME 'documentTitle' DESC ' + RFC1274: title of document' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstri + ngsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.13 NAME 'documentVersion' DES + C 'RFC1274: version of document' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSu + bstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.14 NAME 'documentAuthor' DESC + 'RFC1274: DN of author of document' EQUALITY distinguishedNameMatch SYNTAX 1 + .3.6.1.4.1.1466.115.121.1.12 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.15 NAME 'documentLocation' DE + SC 'RFC1274: location of document original' EQUALITY caseIgnoreMatch SUBSTR c + aseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.20 NAME ( 'homePhone' 'homeTe + lephoneNumber' ) DESC 'RFC1274: home telephone number' EQUALITY telephoneNumb + erMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121 + .1.50 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.21 NAME 'secretary' DESC 'RFC + 1274: DN of secretary' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.146 + 6.115.121.1.12 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.22 NAME 'otherMailbox' SYNTAX + 1.3.6.1.4.1.1466.115.121.1.39 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.26 NAME 'aRecord' EQUALITY ca + seIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.27 NAME 'mDRecord' EQUALITY c + aseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.28 NAME 'mXRecord' EQUALITY c + aseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.29 NAME 'nSRecord' EQUALITY c + aseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.30 NAME 'sOARecord' EQUALITY + caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.31 NAME 'cNAMERecord' EQUALIT + Y caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.38 NAME 'associatedName' DESC + 'RFC1274: DN of entry associated with domain' EQUALITY distinguishedNameMatc + h SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.39 NAME 'homePostalAddress' D + ESC 'RFC1274: home postal address' EQUALITY caseIgnoreListMatch SUBSTR caseIg + noreListSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.40 NAME 'personalTitle' DESC + 'RFC1274: personal title' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstring + sMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.41 NAME ( 'mobile' 'mobileTel + ephoneNumber' ) DESC 'RFC1274: mobile telephone number' EQUALITY telephoneNum + berMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.12 + 1.1.50 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.42 NAME ( 'pager' 'pagerTelep + honeNumber' ) DESC 'RFC1274: pager telephone number' EQUALITY telephoneNumber + Match SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1 + .50 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.43 NAME ( 'co' 'friendlyCount + ryName' ) DESC 'RFC1274: friendly country name' EQUALITY caseIgnoreMatch SUBS + TR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.44 NAME 'uniqueIdentifier' DE + SC 'RFC1274: unique identifer' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.14 + 66.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.45 NAME 'organizationalStatus + ' DESC 'RFC1274: organizational status' EQUALITY caseIgnoreMatch SUBSTR caseI + gnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.46 NAME 'janetMailbox' DESC ' + RFC1274: Janet mailbox' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5Subst + ringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.47 NAME 'mailPreferenceOption + ' DESC 'RFC1274: mail preference option' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.48 NAME 'buildingName' DESC ' + RFC1274: name of building' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstrin + gsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.49 NAME 'dSAQuality' DESC 'RF + C1274: DSA Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1.19 SINGLE-VALUE ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.50 NAME 'singleLevelQuality' + DESC 'RFC1274: Single Level Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1.13 SIN + GLE-VALUE ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.51 NAME 'subtreeMinimumQualit + y' DESC 'RFC1274: Subtree Mininum Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1. + 13 SINGLE-VALUE ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.52 NAME 'subtreeMaximumQualit + y' DESC 'RFC1274: Subtree Maximun Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1. + 13 SINGLE-VALUE ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.53 NAME 'personalSignature' D + ESC 'RFC1274: Personal Signature (G3 fax)' SYNTAX 1.3.6.1.4.1.1466.115.121.1. + 23 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.54 NAME 'dITRedirect' DESC 'R + FC1274: DIT Redirect' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466 + .115.121.1.12 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.55 NAME 'audio' DESC 'RFC1274 + : audio (u-law)' SYNTAX 1.3.6.1.4.1.1466.115.121.1.4{25000} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.56 NAME 'documentPublisher' D + ESC 'RFC1274: publisher of document' EQUALITY caseIgnoreMatch SUBSTR caseIgno + reSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.4 NAME ( 'pilotPerson' 'newPilo + tPerson' ) SUP person STRUCTURAL MAY ( userid $ textEncodedORAddress $ rfc822 + Mailbox $ favouriteDrink $ roomNumber $ userClass $ homeTelephoneNumber $ hom + ePostalAddress $ secretary $ personalTitle $ preferredDeliveryMethod $ busine + ssCategory $ janetMailbox $ otherMailbox $ mobileTelephoneNumber $ pagerTelep + honeNumber $ organizationalStatus $ mailPreferenceOption $ personalSignature + ) ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.5 NAME 'account' SUP top STRUCT + URAL MUST userid MAY ( description $ seeAlso $ localityName $ organizationNam + e $ organizationalUnitName $ host ) ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.6 NAME 'document' SUP top STRUC + TURAL MUST documentIdentifier MAY ( commonName $ description $ seeAlso $ loca + lityName $ organizationName $ organizationalUnitName $ documentTitle $ docume + ntVersion $ documentAuthor $ documentLocation $ documentPublisher ) ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.7 NAME 'room' SUP top STRUCTURA + L MUST commonName MAY ( roomNumber $ description $ seeAlso $ telephoneNumber + ) ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.9 NAME 'documentSeries' SUP top + STRUCTURAL MUST commonName MAY ( description $ seeAlso $ telephonenumber $ l + ocalityName $ organizationName $ organizationalUnitName ) ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.13 NAME 'domain' SUP top STRUCT + URAL MUST domainComponent MAY ( associatedName $ organizationName $ descripti + on $ businessCategory $ seeAlso $ searchGuide $ userPassword $ localityName $ + stateOrProvinceName $ streetAddress $ physicalDeliveryOfficeName $ postalAdd + ress $ postalCode $ postOfficeBox $ streetAddress $ facsimileTelephoneNumber + $ internationalISDNNumber $ telephoneNumber $ teletexTerminalIdentifier $ tel + exNumber $ preferredDeliveryMethod $ destinationIndicator $ registeredAddress + $ x121Address ) ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.14 NAME 'RFC822localPart' SUP d + omain STRUCTURAL MAY ( commonName $ surname $ description $ seeAlso $ telepho + neNumber $ physicalDeliveryOfficeName $ postalAddress $ postalCode $ postOffi + ceBox $ streetAddress $ facsimileTelephoneNumber $ internationalISDNNumber $ + telephoneNumber $ teletexTerminalIdentifier $ telexNumber $ preferredDelivery + Method $ destinationIndicator $ registeredAddress $ x121Address ) ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.15 NAME 'dNSDomain' SUP domain + STRUCTURAL MAY ( ARecord $ MDRecord $ MXRecord $ NSRecord $ SOARecord $ CNAME + Record ) ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.17 NAME 'domainRelatedObject' D + ESC 'RFC1274: an object related to an domain' SUP top AUXILIARY MUST associat + edDomain ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.18 NAME 'friendlyCountry' SUP c + ountry STRUCTURAL MUST friendlyCountryName ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.20 NAME 'pilotOrganization' SU + P ( organization $ organizationalUnit ) STRUCTURAL MAY buildingName ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.21 NAME 'pilotDSA' SUP dsa STR + UCTURAL MAY dSAQuality ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.22 NAME 'qualityLabelledData' + SUP top AUXILIARY MUST dsaQuality MAY ( subtreeMinimumQuality $ subtreeMaximu + mQuality ) ) diff --git a/servers/slapd/schema/duaconf.ldif b/servers/slapd/schema/duaconf.ldif new file mode 100644 index 0000000..8b267b8 --- /dev/null +++ b/servers/slapd/schema/duaconf.ldif @@ -0,0 +1,83 @@ +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +# +# DUA schema from draft-joslin-config-schema (a work in progress) +# +# This file was automatically generated from duaconf.schema; see that file +# for complete references. +# +dn: cn=duaconf,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: duaconf +olcObjectIdentifier: {0}DUAConfSchemaOID 1.3.6.1.4.1.11.1.3.1 +olcAttributeTypes: {0}( DUAConfSchemaOID:1.0 NAME 'defaultServerList' DESC 'De + fault LDAP server host address used by a DUA' EQUALITY caseIgnoreMatch SYNTAX + 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) +olcAttributeTypes: {1}( DUAConfSchemaOID:1.1 NAME 'defaultSearchBase' DESC 'De + fault LDAP base DN used by a DUA' EQUALITY distinguishedNameMatch SYNTAX 1.3. + 6.1.4.1.1466.115.121.1.12 SINGLE-VALUE ) +olcAttributeTypes: {2}( DUAConfSchemaOID:1.2 NAME 'preferredServerList' DESC ' + Preferred LDAP server host addresses to be used by a DUA' EQUALITY + caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) +olcAttributeTypes: {3}( DUAConfSchemaOID:1.3 NAME 'searchTimeLimit' DESC 'Maxi + mum time in seconds a DUA should allow for a search to complete' E + QUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: {4}( DUAConfSchemaOID:1.4 NAME 'bindTimeLimit' DESC 'Maximu + m time in seconds a DUA should allow for the bind operation to com + plete' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALU + E ) +olcAttributeTypes: {5}( DUAConfSchemaOID:1.5 NAME 'followReferrals' DESC 'Tell + s DUA if it should follow referrals returned by a DSA search resul + t' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) +olcAttributeTypes: {6}( DUAConfSchemaOID:1.16 NAME 'dereferenceAliases' DESC ' + Tells DUA if it should dereference aliases' EQUALITY booleanMatch SYNTAX 1.3. + 6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) +olcAttributeTypes: {7}( DUAConfSchemaOID:1.6 NAME 'authenticationMethod' DESC + 'A keystring which identifies the type of authentication method us + ed to contact the DSA' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.1 + 21.1.15 SINGLE-VALUE ) +olcAttributeTypes: {8}( DUAConfSchemaOID:1.7 NAME 'profileTTL' DESC 'Time to l + ive, in seconds, before a client DUA should re-read this configura + tion profile' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SING + LE-VALUE ) +olcAttributeTypes: {9}( DUAConfSchemaOID:1.14 NAME 'serviceSearchDescriptor' D + ESC 'LDAP search descriptor list used by a DUA' EQUALITY caseExactMatch SYNTA + X 1.3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: {10}( DUAConfSchemaOID:1.9 NAME 'attributeMap' DESC 'Attrib + ute mappings used by a DUA' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.14 + 66.115.121.1.26 ) +olcAttributeTypes: {11}( DUAConfSchemaOID:1.10 NAME 'credentialLevel' DESC 'Id + entifies type of credentials a DUA should use when binding to the + LDAP server' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE ) +olcAttributeTypes: {12}( DUAConfSchemaOID:1.11 NAME 'objectclassMap' DESC 'Obj + ectclass mappings used by a DUA' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4 + .1.1466.115.121.1.26 ) +olcAttributeTypes: {13}( DUAConfSchemaOID:1.12 NAME 'defaultSearchScope' DESC + 'Default search scope used by a DUA' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6 + .1.4.1.1466.115.121.1.26 SINGLE-VALUE ) +olcAttributeTypes: {14}( DUAConfSchemaOID:1.13 NAME 'serviceCredentialLevel' D + ESC 'Identifies type of credentials a DUA should use when binding + to the LDAP server for a specific service' EQUALITY caseIgnoreIA5M + atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: {15}( DUAConfSchemaOID:1.15 NAME 'serviceAuthenticationMeth + od' DESC 'Authentication method used by a service of the DUA' EQUALITY caseIg + noreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcObjectClasses: {0}( DUAConfSchemaOID:2.5 NAME 'DUAConfigProfile' DESC 'Abst + raction of a base configuration for a DUA' SUP top STRUCTURAL MUST cn MAY ( d + efaultServerList $ preferredServerList $ defaultSearchBase $ defaultSearchSco + pe $ searchTimeLimit $ bindTimeLimit $ credentialLevel $ authenticationMethod + $ followReferrals $ dereferenceAliases $ serviceSearchDescriptor $ serviceCr + edentialLevel $ serviceAuthenticationMethod $ objectclassMap $ attributeMap $ + profileTTL ) ) diff --git a/servers/slapd/schema/dyngroup.ldif b/servers/slapd/schema/dyngroup.ldif new file mode 100644 index 0000000..0f828c5 --- /dev/null +++ b/servers/slapd/schema/dyngroup.ldif @@ -0,0 +1,71 @@ +# dyngroup.schema -- Dynamic Group schema +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +# +# Dynamic Group schema (experimental), as defined by Netscape. See +# http://www.redhat.com/docs/manuals/ent-server/pdf/esadmin611.pdf +# page 70 for details on how these groups were used. +# +# A description of the objectclass definition is available here: +# http://www.redhat.com/docs/manuals/dir-server/schema/7.1/oc_dir.html#1303745 +# +# depends upon: +# core.schema +# +# These definitions are considered experimental due to the lack of +# a formal specification (e.g., RFC). +# +# NOT RECOMMENDED FOR PRODUCTION USE! USE WITH CAUTION! +# +# The Netscape documentation describes this as an auxiliary objectclass +# but their implementations have always defined it as a structural class. +# The sloppiness here is because Netscape-derived servers don't actually +# implement the X.500 data model, and they don't honor the distinction +# between structural and auxiliary classes. This fact is noted here: +# http://forum.java.sun.com/thread.jspa?threadID=5016864&messageID=9034636 +# +# In accordance with other existing implementations, we define it as a +# structural class. +# +# Our definition of memberURL also does not match theirs but again +# their published definition and what works in practice do not agree. +# In other words, the Netscape definitions are broken and interoperability +# is not guaranteed. +# +# Also see the new DynGroup proposed spec at +# http://tools.ietf.org/html/draft-haripriya-dynamicgroup-02 +dn: cn=dyngroup,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: dyngroup +olcObjectIdentifier: {0}NetscapeRoot 2.16.840.1.113730 +olcObjectIdentifier: {1}NetscapeLDAP NetscapeRoot:3 +olcObjectIdentifier: {2}NetscapeLDAPattributeType NetscapeLDAP:1 +olcObjectIdentifier: {3}NetscapeLDAPobjectClass NetscapeLDAP:2 +olcObjectIdentifier: {4}OpenLDAPExp11 1.3.6.1.4.1.4203.666.11 +olcObjectIdentifier: {5}DynGroupBase OpenLDAPExp11:8 +olcObjectIdentifier: {6}DynGroupAttr DynGroupBase:1 +olcObjectIdentifier: {7}DynGroupOC DynGroupBase:2 +olcAttributeTypes: {0}( NetscapeLDAPattributeType:198 NAME 'memberURL' DESC 'I + dentifies an URL associated with each member of a group. Any type of labeled + URL can be used.' SUP labeledURI ) +olcAttributeTypes: {1}( DynGroupAttr:1 NAME 'dgIdentity' DESC 'Identity to use + when processing the memberURL' SUP distinguishedName SINGLE-VALUE ) +olcAttributeTypes: {2}( DynGroupAttr:2 NAME 'dgAuthz' DESC 'Optional authoriza + tion rules that determine who is allowed to assume the dgIdentity' EQUALITY a + uthzMatch SYNTAX 1.3.6.1.4.1.4203.666.2.7 X-ORDERED 'VALUES' ) +olcObjectClasses: {0}( NetscapeLDAPobjectClass:33 NAME 'groupOfURLs' SUP top S + TRUCTURAL MUST cn MAY ( memberURL $ businessCategory $ description $ o $ ou $ + owner $ seeAlso ) ) +olcObjectClasses: {1}( DynGroupOC:1 NAME 'dgIdentityAux' SUP top AUXILIARY MAY + ( dgIdentity $ dgAuthz ) ) diff --git a/servers/slapd/schema/dyngroup.schema b/servers/slapd/schema/dyngroup.schema new file mode 100644 index 0000000..58c33d6 --- /dev/null +++ b/servers/slapd/schema/dyngroup.schema @@ -0,0 +1,91 @@ +# dyngroup.schema -- Dynamic Group schema +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +# +# Dynamic Group schema (experimental), as defined by Netscape. See +# http://www.redhat.com/docs/manuals/ent-server/pdf/esadmin611.pdf +# page 70 for details on how these groups were used. +# +# A description of the objectclass definition is available here: +# http://www.redhat.com/docs/manuals/dir-server/schema/7.1/oc_dir.html#1303745 +# +# depends upon: +# core.schema +# +# These definitions are considered experimental due to the lack of +# a formal specification (e.g., RFC). +# +# NOT RECOMMENDED FOR PRODUCTION USE! USE WITH CAUTION! +# +# The Netscape documentation describes this as an auxiliary objectclass +# but their implementations have always defined it as a structural class. +# The sloppiness here is because Netscape-derived servers don't actually +# implement the X.500 data model, and they don't honor the distinction +# between structural and auxiliary classes. This fact is noted here: +# http://forum.java.sun.com/thread.jspa?threadID=5016864&messageID=9034636 +# +# In accordance with other existing implementations, we define it as a +# structural class. +# +# Our definition of memberURL also does not match theirs but again +# their published definition and what works in practice do not agree. +# In other words, the Netscape definitions are broken and interoperability +# is not guaranteed. +# +# Also see the new DynGroup proposed spec at +# http://tools.ietf.org/html/draft-haripriya-dynamicgroup-02 + +objectIdentifier NetscapeRoot 2.16.840.1.113730 + +objectIdentifier NetscapeLDAP NetscapeRoot:3 +objectIdentifier NetscapeLDAPattributeType NetscapeLDAP:1 +objectIdentifier NetscapeLDAPobjectClass NetscapeLDAP:2 + +objectIdentifier OpenLDAPExp11 1.3.6.1.4.1.4203.666.11 +objectIdentifier DynGroupBase OpenLDAPExp11:8 +objectIdentifier DynGroupAttr DynGroupBase:1 +objectIdentifier DynGroupOC DynGroupBase:2 + +attributetype ( NetscapeLDAPattributeType:198 + NAME 'memberURL' + DESC 'Identifies an URL associated with each member of a group. Any type of labeled URL can be used.' + SUP labeledURI ) + +attributetype ( DynGroupAttr:1 + NAME 'dgIdentity' + DESC 'Identity to use when processing the memberURL' + SUP distinguishedName SINGLE-VALUE ) + +attributeType ( DynGroupAttr:2 + NAME 'dgAuthz' + DESC 'Optional authorization rules that determine who is allowed to assume the dgIdentity' + EQUALITY authzMatch + SYNTAX 1.3.6.1.4.1.4203.666.2.7 + X-ORDERED 'VALUES' ) + +objectClass ( NetscapeLDAPobjectClass:33 + NAME 'groupOfURLs' + SUP top STRUCTURAL + MUST cn + MAY ( memberURL $ businessCategory $ description $ o $ ou $ + owner $ seeAlso ) ) + +# The Haripriya dyngroup schema still needs a lot of work. +# We're just adding support for the dgIdentity attribute for now... +objectClass ( DynGroupOC:1 + NAME 'dgIdentityAux' + SUP top AUXILIARY + MAY ( dgIdentity $ dgAuthz ) ) + + diff --git a/servers/slapd/schema/inetorgperson.ldif b/servers/slapd/schema/inetorgperson.ldif new file mode 100644 index 0000000..965bf6f --- /dev/null +++ b/servers/slapd/schema/inetorgperson.ldif @@ -0,0 +1,69 @@ +# InetOrgPerson (RFC2798) +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +# +# InetOrgPerson (RFC2798) +# +# Depends upon +# Definition of an X.500 Attribute Type and an Object Class to Hold +# Uniform Resource Identifiers (URIs) [RFC2079] +# (core.ldif) +# +# A Summary of the X.500(96) User Schema for use with LDAPv3 [RFC2256] +# (core.ldif) +# +# The COSINE and Internet X.500 Schema [RFC1274] (cosine.ldif) +# +# This file was automatically generated from inetorgperson.schema; see +# that file for complete references. +# +dn: cn=inetorgperson,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: inetorgperson +olcAttributeTypes: ( 2.16.840.1.113730.3.1.1 NAME 'carLicense' DESC 'RFC279 + 8: vehicle license or registration plate' EQUALITY caseIgnoreMatch SUBSTR cas + eIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: ( 2.16.840.1.113730.3.1.2 NAME 'departmentNumber' DESC ' + RFC2798: identifies a department within an organization' EQUALITY caseIgnoreM + atch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: ( 2.16.840.1.113730.3.1.241 NAME 'displayName' DESC 'RFC + 2798: preferred name to be used when displaying entries' EQUALITY caseIgnoreM + atch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SI + NGLE-VALUE ) +olcAttributeTypes: ( 2.16.840.1.113730.3.1.3 NAME 'employeeNumber' DESC 'RF + C2798: numerically identifies an employee within an organization' EQUALITY ca + seIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.12 + 1.1.15 SINGLE-VALUE ) +olcAttributeTypes: ( 2.16.840.1.113730.3.1.4 NAME 'employeeType' DESC 'RFC2 + 798: type of employment for a person' EQUALITY caseIgnoreMatch SUBSTR caseIgn + oreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.60 NAME 'jpegPhoto' DESC 'RFC2 + 798: a JPEG image' SYNTAX 1.3.6.1.4.1.1466.115.121.1.28 ) +olcAttributeTypes: ( 2.16.840.1.113730.3.1.39 NAME 'preferredLanguage' DESC + 'RFC2798: preferred written or spoken language for a person' EQUALITY caseIg + noreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1. + 15 SINGLE-VALUE ) +olcAttributeTypes: ( 2.16.840.1.113730.3.1.40 NAME 'userSMIMECertificate' D + ESC 'RFC2798: PKCS#7 SignedData used to support S/MIME' SYNTAX 1.3.6.1.4.1.14 + 66.115.121.1.5 ) +olcAttributeTypes: ( 2.16.840.1.113730.3.1.216 NAME 'userPKCS12' DESC 'RFC2 + 798: personal identity information, a PKCS #12 PFX' SYNTAX 1.3.6.1.4.1.1466.1 + 15.121.1.5 ) +olcObjectClasses: ( 2.16.840.1.113730.3.2.2 NAME 'inetOrgPerson' DESC 'RFC2 + 798: Internet Organizational Person' SUP organizationalPerson STRUCTURAL MAY + ( audio $ businessCategory $ carLicense $ departmentNumber $ displayName $ em + ployeeNumber $ employeeType $ givenName $ homePhone $ homePostalAddress $ ini + tials $ jpegPhoto $ labeledURI $ mail $ manager $ mobile $ o $ pager $ photo + $ roomNumber $ secretary $ uid $ userCertificate $ x500uniqueIdentifier $ pre + ferredLanguage $ userSMIMECertificate $ userPKCS12 ) ) diff --git a/servers/slapd/schema/java.ldif b/servers/slapd/schema/java.ldif new file mode 100644 index 0000000..dc7ae10 --- /dev/null +++ b/servers/slapd/schema/java.ldif @@ -0,0 +1,59 @@ +# java.ldif -- Java Object Schema +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +# +# Java Object Schema (defined in RFC 2713) +# depends upon core.ldif +# +# This file was automatically generated from java.schema; see that file +# for complete references. +# +dn: cn=java,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: java +olcAttributeTypes: {0}( 1.3.6.1.4.1.42.2.27.4.1.6 NAME 'javaClassName' DESC 'F + ully qualified name of distinguished Java class or interface' EQUALITY caseEx + actMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) +olcAttributeTypes: {1}( 1.3.6.1.4.1.42.2.27.4.1.7 NAME 'javaCodebase' DESC 'UR + L(s) specifying the location of class definition' EQUALITY caseExactIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: {2}( 1.3.6.1.4.1.42.2.27.4.1.13 NAME 'javaClassNames' DESC + 'Fully qualified Java class or interface name' EQUALITY caseExactMatch SYNTAX + 1.3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: {3}( 1.3.6.1.4.1.42.2.27.4.1.8 NAME 'javaSerializedData' DE + SC 'Serialized form of a Java object' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SI + NGLE-VALUE ) +olcAttributeTypes: {4}( 1.3.6.1.4.1.42.2.27.4.1.10 NAME 'javaFactory' DESC 'Fu + lly qualified Java class name of a JNDI object factory' EQUALITY caseExactMat + ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) +olcAttributeTypes: {5}( 1.3.6.1.4.1.42.2.27.4.1.11 NAME 'javaReferenceAddress' + DESC 'Addresses associated with a JNDI Reference' EQUALITY caseExactMatch SY + NTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: {6}( 1.3.6.1.4.1.42.2.27.4.1.12 NAME 'javaDoc' DESC 'The Ja + va documentation for the class' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1 + .1466.115.121.1.26 ) +olcObjectClasses: {0}( 1.3.6.1.4.1.42.2.27.4.2.1 NAME 'javaContainer' DESC 'Co + ntainer for a Java object' SUP top STRUCTURAL MUST cn ) +olcObjectClasses: {1}( 1.3.6.1.4.1.42.2.27.4.2.4 NAME 'javaObject' DESC 'Java + object representation' SUP top ABSTRACT MUST javaClassName MAY ( javaClassNam + es $ javaCodebase $ javaDoc $ description ) ) +olcObjectClasses: {2}( 1.3.6.1.4.1.42.2.27.4.2.5 NAME 'javaSerializedObject' D + ESC 'Java serialized object' SUP javaObject AUXILIARY MUST javaSerializedData + ) +olcObjectClasses: {3}( 1.3.6.1.4.1.42.2.27.4.2.8 NAME 'javaMarshalledObject' D + ESC 'Java marshalled object' SUP javaObject AUXILIARY MUST javaSerializedData + ) +olcObjectClasses: {4}( 1.3.6.1.4.1.42.2.27.4.2.7 NAME 'javaNamingReference' DE + SC 'JNDI reference' SUP javaObject AUXILIARY MAY ( javaReferenceAddress $ jav + aFactory ) ) diff --git a/servers/slapd/schema/misc.ldif b/servers/slapd/schema/misc.ldif new file mode 100644 index 0000000..83b571a --- /dev/null +++ b/servers/slapd/schema/misc.ldif @@ -0,0 +1,45 @@ +# misc.ldif -- assorted schema definitions +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +# +# Assorted definitions from several sources, including +# ''works in progress''. Contents of this file are +# subject to change (including deletion) without notice. +# +# Not recommended for production use! +# Use with extreme caution! +# +# This file was automatically generated from misc.schema; see that file +# for complete references. +# +dn: cn=misc,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: misc +olcAttributeTypes: {0}( 2.16.840.1.113730.3.1.13 NAME 'mailLocalAddress' DESC + 'RFC822 email address of this recipient' EQUALITY caseIgnoreIA5Match SYNTAX 1 + .3.6.1.4.1.1466.115.121.1.26{256} ) +olcAttributeTypes: {1}( 2.16.840.1.113730.3.1.18 NAME 'mailHost' DESC 'FQDN of + the SMTP/MTA of this recipient' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4 + .1.1466.115.121.1.26{256} SINGLE-VALUE ) +olcAttributeTypes: {2}( 2.16.840.1.113730.3.1.47 NAME 'mailRoutingAddress' DES + C 'RFC822 routing address of this recipient' EQUALITY caseIgnoreIA5Match SYNT + AX 1.3.6.1.4.1.1466.115.121.1.26{256} SINGLE-VALUE ) +olcAttributeTypes: {3}( 1.3.6.1.4.1.42.2.27.2.1.15 NAME 'rfc822MailMember' DES + C 'rfc822 mail address of group member(s)' EQUALITY caseIgnoreIA5Match SYNTAX + 1.3.6.1.4.1.1466.115.121.1.26 ) +olcObjectClasses: {0}( 2.16.840.1.113730.3.2.147 NAME 'inetLocalMailRecipient' + DESC 'Internet local mail recipient' SUP top AUXILIARY MAY ( mailLocalAddres + s $ mailHost $ mailRoutingAddress ) ) +olcObjectClasses: {1}( 1.3.6.1.4.1.42.2.27.1.2.5 NAME 'nisMailAlias' DESC 'NIS + mail alias' SUP top STRUCTURAL MUST cn MAY rfc822MailMember ) diff --git a/servers/slapd/schema/misc.schema b/servers/slapd/schema/misc.schema new file mode 100644 index 0000000..c38a9d0 --- /dev/null +++ b/servers/slapd/schema/misc.schema @@ -0,0 +1,75 @@ +# misc.schema -- assorted schema definitions +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +# +# Assorted definitions from several sources, including +# ''works in progress''. Contents of this file are +# subject to change (including deletion) without notice. +# +# Not recommended for production use! +# Use with extreme caution! + +#----------------------------------------------------------- +# draft-lachman-laser-ldap-mail-routing-02.txt !!!EXPIRED!!! +# (a work in progress) +# +attributetype ( 2.16.840.1.113730.3.1.13 + NAME 'mailLocalAddress' + DESC 'RFC822 email address of this recipient' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} ) + +attributetype ( 2.16.840.1.113730.3.1.18 + NAME 'mailHost' + DESC 'FQDN of the SMTP/MTA of this recipient' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} + SINGLE-VALUE ) + +attributetype ( 2.16.840.1.113730.3.1.47 + NAME 'mailRoutingAddress' + DESC 'RFC822 routing address of this recipient' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} + SINGLE-VALUE ) + +# I-D leaves this OID TBD. +# iPlanet uses 2.16.840.1.113.730.3.2.147 but that is an +# improperly delegated OID. A typo is likely. +objectclass ( 2.16.840.1.113730.3.2.147 + NAME 'inetLocalMailRecipient' + DESC 'Internet local mail recipient' + SUP top AUXILIARY + MAY ( mailLocalAddress $ mailHost $ mailRoutingAddress ) ) + +#----------------------------------------------------------- +# draft-srivastava-ldap-mail-00.txt !!!EXPIRED!!! +# (a work in progress) +# +attributetype ( 1.3.6.1.4.1.42.2.27.2.1.15 + NAME 'rfc822MailMember' + DESC 'rfc822 mail address of group member(s)' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) + +#----------------------------------------------------------- +# !!!no I-D!!! +# (a work in progress) +# +objectclass ( 1.3.6.1.4.1.42.2.27.1.2.5 + NAME 'nisMailAlias' + DESC 'NIS mail alias' + SUP top STRUCTURAL + MUST cn + MAY rfc822MailMember ) diff --git a/servers/slapd/schema/nis.ldif b/servers/slapd/schema/nis.ldif new file mode 100644 index 0000000..950dae3 --- /dev/null +++ b/servers/slapd/schema/nis.ldif @@ -0,0 +1,120 @@ +# NIS (RFC2307) +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +# +# Definitions from RFC2307 (Experimental) +# An Approach for Using LDAP as a Network Information Service +# +# Depends upon core.ldif and cosine.ldif +# +# This file was automatically generated from nis.schema; see that file +# for complete references. +# +dn: cn=nis,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: nis +olcAttributeTypes: ( 1.3.6.1.1.1.1.2 NAME 'gecos' DESC 'The GECOS field; th + e common name' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatc + h SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.3 NAME 'homeDirectory' DESC 'The absolut + e path to the home directory' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1 + 466.115.121.1.26 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.4 NAME 'loginShell' DESC 'The path to th + e login shell' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.2 + 6 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.5 NAME 'shadowLastChange' EQUALITY integ + erMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.6 NAME 'shadowMin' EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.7 NAME 'shadowMax' EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.8 NAME 'shadowWarning' EQUALITY integerM + atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.9 NAME 'shadowInactive' EQUALITY integer + Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.10 NAME 'shadowExpire' EQUALITY integerM + atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.11 NAME 'shadowFlag' EQUALITY integerMat + ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.12 NAME 'memberUid' EQUALITY caseExactI + A5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1. + 26 ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.13 NAME 'memberNisNetgroup' EQUALITY ca + seExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.11 + 5.121.1.26 ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.14 NAME 'nisNetgroupTriple' DESC 'Netgr + oup triple' SYNTAX 1.3.6.1.1.1.0.0 ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.15 NAME 'ipServicePort' EQUALITY intege + rMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.16 NAME 'ipServiceProtocol' SUP name ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.17 NAME 'ipProtocolNumber' EQUALITY int + egerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.18 NAME 'oncRpcNumber' EQUALITY integer + Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.19 NAME 'ipHostNumber' DESC 'IP address + ' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.20 NAME 'ipNetworkNumber' DESC 'IP netw + ork' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} SI + NGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.21 NAME 'ipNetmaskNumber' DESC 'IP netm + ask' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} SI + NGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.22 NAME 'macAddress' DESC 'MAC address' + EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.23 NAME 'bootParameter' DESC 'rpc.bootp + aramd parameter' SYNTAX 1.3.6.1.1.1.0.1 ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.24 NAME 'bootFile' DESC 'Boot image nam + e' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.26 NAME 'nisMapName' SUP name ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.27 NAME 'nisMapEntry' EQUALITY caseExac + tIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121. + 1.26{1024} SINGLE-VALUE ) +olcObjectClasses: ( 1.3.6.1.1.1.2.0 NAME 'posixAccount' DESC 'Abstraction o + f an account with POSIX attributes' SUP top AUXILIARY MUST ( cn $ uid $ uidNu + mber $ gidNumber $ homeDirectory ) MAY ( userPassword $ loginShell $ gecos $ + description ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.1 NAME 'shadowAccount' DESC 'Additional a + ttributes for shadow passwords' SUP top AUXILIARY MUST uid MAY ( userPassword + $ shadowLastChange $ shadowMin $ shadowMax $ shadowWarning $ shadowInactive + $ shadowExpire $ shadowFlag $ description ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.2 NAME 'posixGroup' DESC 'Abstraction of + a group of accounts' SUP top STRUCTURAL MUST ( cn $ gidNumber ) MAY ( userPas + sword $ memberUid $ description ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.3 NAME 'ipService' DESC 'Abstraction an I + nternet Protocol service' SUP top STRUCTURAL MUST ( cn $ ipServicePort $ ipSe + rviceProtocol ) MAY description ) +olcObjectClasses: ( 1.3.6.1.1.1.2.4 NAME 'ipProtocol' DESC 'Abstraction of + an IP protocol' SUP top STRUCTURAL MUST ( cn $ ipProtocolNumber $ description + ) MAY description ) +olcObjectClasses: ( 1.3.6.1.1.1.2.5 NAME 'oncRpc' DESC 'Abstraction of an O + NC/RPC binding' SUP top STRUCTURAL MUST ( cn $ oncRpcNumber $ description ) M + AY description ) +olcObjectClasses: ( 1.3.6.1.1.1.2.6 NAME 'ipHost' DESC 'Abstraction of a ho + st, an IP device' SUP top AUXILIARY MUST ( cn $ ipHostNumber ) MAY ( l $ desc + ription $ manager ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.7 NAME 'ipNetwork' DESC 'Abstraction of a + n IP network' SUP top STRUCTURAL MUST ( cn $ ipNetworkNumber ) MAY ( ipNetmas + kNumber $ l $ description $ manager ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.8 NAME 'nisNetgroup' DESC 'Abstraction of + a netgroup' SUP top STRUCTURAL MUST cn MAY ( nisNetgroupTriple $ memberNisNe + tgroup $ description ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.9 NAME 'nisMap' DESC 'A generic abstracti + on of a NIS map' SUP top STRUCTURAL MUST nisMapName MAY description ) +olcObjectClasses: ( 1.3.6.1.1.1.2.10 NAME 'nisObject' DESC 'An entry in a + NIS map' SUP top STRUCTURAL MUST ( cn $ nisMapEntry $ nisMapName ) MAY descri + ption ) +olcObjectClasses: ( 1.3.6.1.1.1.2.11 NAME 'ieee802Device' DESC 'A device w + ith a MAC address' SUP top AUXILIARY MAY macAddress ) +olcObjectClasses: ( 1.3.6.1.1.1.2.12 NAME 'bootableDevice' DESC 'A device + with boot parameters' SUP top AUXILIARY MAY ( bootFile $ bootParameter ) ) diff --git a/servers/slapd/schema/nis.schema b/servers/slapd/schema/nis.schema new file mode 100644 index 0000000..a54d70c --- /dev/null +++ b/servers/slapd/schema/nis.schema @@ -0,0 +1,237 @@ +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +# Definitions from RFC2307 (Experimental) +# An Approach for Using LDAP as a Network Information Service + +# Depends upon core.schema and cosine.schema + +# Note: The definitions in RFC2307 are given in syntaxes closely related +# to those in RFC2252, however, some liberties are taken that are not +# supported by RFC2252. This file has been written following RFC2252 +# strictly. + +# OID Base is iso(1) org(3) dod(6) internet(1) directory(1) nisSchema(1). +# i.e. nisSchema in RFC2307 is 1.3.6.1.1.1 +# +# Syntaxes are under 1.3.6.1.1.1.0 (two new syntaxes are defined) +# validaters for these syntaxes are incomplete, they only +# implement printable string validation (which is good as the +# common use of these syntaxes violates the specification). +# Attribute types are under 1.3.6.1.1.1.1 +# Object classes are under 1.3.6.1.1.1.2 + +# Attribute Type Definitions + +# builtin +#attributetype ( 1.3.6.1.1.1.1.0 NAME 'uidNumber' +# DESC 'An integer uniquely identifying a user in an administrative domain' +# EQUALITY integerMatch +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +# builtin +#attributetype ( 1.3.6.1.1.1.1.1 NAME 'gidNumber' +# DESC 'An integer uniquely identifying a group in an administrative domain' +# EQUALITY integerMatch +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.1.1.1.2 NAME 'gecos' + DESC 'The GECOS field; the common name' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.1.1.1.3 NAME 'homeDirectory' + DESC 'The absolute path to the home directory' + EQUALITY caseExactIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.1.1.1.4 NAME 'loginShell' + DESC 'The path to the login shell' + EQUALITY caseExactIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.1.1.1.5 NAME 'shadowLastChange' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.1.1.1.6 NAME 'shadowMin' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.1.1.1.7 NAME 'shadowMax' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.1.1.1.8 NAME 'shadowWarning' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.1.1.1.9 NAME 'shadowInactive' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.1.1.1.10 NAME 'shadowExpire' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.1.1.1.11 NAME 'shadowFlag' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.1.1.1.12 NAME 'memberUid' + EQUALITY caseExactIA5Match + SUBSTR caseExactIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) + +attributetype ( 1.3.6.1.1.1.1.13 NAME 'memberNisNetgroup' + EQUALITY caseExactIA5Match + SUBSTR caseExactIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) + +attributetype ( 1.3.6.1.1.1.1.14 NAME 'nisNetgroupTriple' + DESC 'Netgroup triple' + SYNTAX 1.3.6.1.1.1.0.0 ) + +attributetype ( 1.3.6.1.1.1.1.15 NAME 'ipServicePort' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.1.1.1.16 NAME 'ipServiceProtocol' + SUP name ) + +attributetype ( 1.3.6.1.1.1.1.17 NAME 'ipProtocolNumber' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.1.1.1.18 NAME 'oncRpcNumber' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.1.1.1.19 NAME 'ipHostNumber' + DESC 'IP address' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} ) + +attributetype ( 1.3.6.1.1.1.1.20 NAME 'ipNetworkNumber' + DESC 'IP network' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} SINGLE-VALUE ) + +attributetype ( 1.3.6.1.1.1.1.21 NAME 'ipNetmaskNumber' + DESC 'IP netmask' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} SINGLE-VALUE ) + +attributetype ( 1.3.6.1.1.1.1.22 NAME 'macAddress' + DESC 'MAC address' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} ) + +attributetype ( 1.3.6.1.1.1.1.23 NAME 'bootParameter' + DESC 'rpc.bootparamd parameter' + SYNTAX 1.3.6.1.1.1.0.1 ) + +attributetype ( 1.3.6.1.1.1.1.24 NAME 'bootFile' + DESC 'Boot image name' + EQUALITY caseExactIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) + +attributetype ( 1.3.6.1.1.1.1.26 NAME 'nisMapName' + SUP name ) + +attributetype ( 1.3.6.1.1.1.1.27 NAME 'nisMapEntry' + EQUALITY caseExactIA5Match + SUBSTR caseExactIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{1024} SINGLE-VALUE ) + +# Object Class Definitions + +objectclass ( 1.3.6.1.1.1.2.0 NAME 'posixAccount' + DESC 'Abstraction of an account with POSIX attributes' + SUP top AUXILIARY + MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory ) + MAY ( userPassword $ loginShell $ gecos $ description ) ) + +objectclass ( 1.3.6.1.1.1.2.1 NAME 'shadowAccount' + DESC 'Additional attributes for shadow passwords' + SUP top AUXILIARY + MUST uid + MAY ( userPassword $ shadowLastChange $ shadowMin $ + shadowMax $ shadowWarning $ shadowInactive $ + shadowExpire $ shadowFlag $ description ) ) + +objectclass ( 1.3.6.1.1.1.2.2 NAME 'posixGroup' + DESC 'Abstraction of a group of accounts' + SUP top STRUCTURAL + MUST ( cn $ gidNumber ) + MAY ( userPassword $ memberUid $ description ) ) + +objectclass ( 1.3.6.1.1.1.2.3 NAME 'ipService' + DESC 'Abstraction an Internet Protocol service' + SUP top STRUCTURAL + MUST ( cn $ ipServicePort $ ipServiceProtocol ) + MAY ( description ) ) + +objectclass ( 1.3.6.1.1.1.2.4 NAME 'ipProtocol' + DESC 'Abstraction of an IP protocol' + SUP top STRUCTURAL + MUST ( cn $ ipProtocolNumber $ description ) + MAY description ) + +objectclass ( 1.3.6.1.1.1.2.5 NAME 'oncRpc' + DESC 'Abstraction of an ONC/RPC binding' + SUP top STRUCTURAL + MUST ( cn $ oncRpcNumber $ description ) + MAY description ) + +objectclass ( 1.3.6.1.1.1.2.6 NAME 'ipHost' + DESC 'Abstraction of a host, an IP device' + SUP top AUXILIARY + MUST ( cn $ ipHostNumber ) + MAY ( l $ description $ manager ) ) + +objectclass ( 1.3.6.1.1.1.2.7 NAME 'ipNetwork' + DESC 'Abstraction of an IP network' + SUP top STRUCTURAL + MUST ( cn $ ipNetworkNumber ) + MAY ( ipNetmaskNumber $ l $ description $ manager ) ) + +objectclass ( 1.3.6.1.1.1.2.8 NAME 'nisNetgroup' + DESC 'Abstraction of a netgroup' + SUP top STRUCTURAL + MUST cn + MAY ( nisNetgroupTriple $ memberNisNetgroup $ description ) ) + +objectclass ( 1.3.6.1.1.1.2.9 NAME 'nisMap' + DESC 'A generic abstraction of a NIS map' + SUP top STRUCTURAL + MUST nisMapName + MAY description ) + +objectclass ( 1.3.6.1.1.1.2.10 NAME 'nisObject' + DESC 'An entry in a NIS map' + SUP top STRUCTURAL + MUST ( cn $ nisMapEntry $ nisMapName ) + MAY description ) + +objectclass ( 1.3.6.1.1.1.2.11 NAME 'ieee802Device' + DESC 'A device with a MAC address' + SUP top AUXILIARY + MAY macAddress ) + +objectclass ( 1.3.6.1.1.1.2.12 NAME 'bootableDevice' + DESC 'A device with boot parameters' + SUP top AUXILIARY + MAY ( bootFile $ bootParameter ) ) diff --git a/servers/slapd/schema/openldap.ldif b/servers/slapd/schema/openldap.ldif new file mode 100644 index 0000000..0c26a23 --- /dev/null +++ b/servers/slapd/schema/openldap.ldif @@ -0,0 +1,88 @@ +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +# +# +# OpenLDAP Project's directory schema items +# +# depends upon: +# core.schema +# cosine.schema +# inetorgperson.schema +# +# These are provided for informational purposes only. +# +# This openldap.ldif file is provided as a demonstration of how to +# convert a *.schema file into *.ldif format. The key points: +# In LDIF, a blank line terminates an entry. Blank lines in a *.schema +# file should be replaced with a single '#' to turn them into +# comments, or they should just be removed. +# In addition to the actual schema directives, the file needs a small +# header to make it a valid LDAP entry. This header must provide the +# dn of the entry, the objectClass, and the cn, as shown here: +# +dn: cn=openldap,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: openldap +# +# The schema directives need to be changed to LDAP Attributes. +# First a basic string substitution can be done on each of the keywords: +# objectIdentifier -> olcObjectIdentifier: +# objectClass -> olcObjectClasses: +# attributeType -> olcAttributeTypes: +# Then leading whitespace must be fixed. The slapd.conf format allows +# tabs or spaces to denote line continuation, while LDIF only allows +# the space character. +# Also slapd.conf preserves the continuation character, while LDIF strips +# it out. So a single TAB/SPACE in slapd.conf must be replaced with +# two SPACEs in LDIF, otherwise the continued text may get joined as +# a single word. +# The directives must be listed in a proper sequence: +# All olcObjectIdentifiers must be first, so they may be referenced by +# any following definitions. +# All olcAttributeTypes must be next, so they may be referenced by any +# following objectClass definitions. +# All olcObjectClasses must be after the olcAttributeTypes. +# And of course, any superior must occur before anything that inherits +# from it. +# +olcObjectIdentifier: OpenLDAProot 1.3.6.1.4.1.4203 +# +olcObjectIdentifier: OpenLDAP OpenLDAProot:1 +olcObjectIdentifier: OpenLDAPattributeType OpenLDAP:3 +olcObjectIdentifier: OpenLDAPobjectClass OpenLDAP:4 +# +olcObjectClasses: ( OpenLDAPobjectClass:3 + NAME 'OpenLDAPorg' + DESC 'OpenLDAP Organizational Object' + SUP organization + MAY ( buildingName $ displayName $ labeledURI ) ) +# +olcObjectClasses: ( OpenLDAPobjectClass:4 + NAME 'OpenLDAPou' + DESC 'OpenLDAP Organizational Unit Object' + SUP organizationalUnit + MAY ( buildingName $ displayName $ labeledURI $ o ) ) +# +olcObjectClasses: ( OpenLDAPobjectClass:5 + NAME 'OpenLDAPperson' + DESC 'OpenLDAP Person' + SUP ( pilotPerson $ inetOrgPerson ) + MUST ( uid $ cn ) + MAY ( givenName $ labeledURI $ o ) ) +# +olcObjectClasses: ( OpenLDAPobjectClass:6 + NAME 'OpenLDAPdisplayableObject' + DESC 'OpenLDAP Displayable Object' + AUXILIARY + MAY displayName ) diff --git a/servers/slapd/schema/openldap.schema b/servers/slapd/schema/openldap.schema new file mode 100644 index 0000000..be7280f --- /dev/null +++ b/servers/slapd/schema/openldap.schema @@ -0,0 +1,54 @@ +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +# +# OpenLDAP Project's directory schema items +# +# depends upon: +# core.schema +# cosine.schema +# inetorgperson.schema +# +# These are provided for informational purposes only. + +objectIdentifier OpenLDAProot 1.3.6.1.4.1.4203 + +objectIdentifier OpenLDAP OpenLDAProot:1 +objectIdentifier OpenLDAPattributeType OpenLDAP:3 +objectIdentifier OpenLDAPobjectClass OpenLDAP:4 + +objectClass ( OpenLDAPobjectClass:3 + NAME 'OpenLDAPorg' + DESC 'OpenLDAP Organizational Object' + SUP organization + MAY ( buildingName $ displayName $ labeledURI ) ) + +objectClass ( OpenLDAPobjectClass:4 + NAME 'OpenLDAPou' + DESC 'OpenLDAP Organizational Unit Object' + SUP organizationalUnit + MAY ( buildingName $ displayName $ labeledURI $ o ) ) + +objectClass ( OpenLDAPobjectClass:5 + NAME 'OpenLDAPperson' + DESC 'OpenLDAP Person' + SUP ( pilotPerson $ inetOrgPerson ) + MUST ( uid $ cn ) + MAY ( givenName $ labeledURI $ o ) ) + +objectClass ( OpenLDAPobjectClass:6 + NAME 'OpenLDAPdisplayableObject' + DESC 'OpenLDAP Displayable Object' + AUXILIARY + MAY displayName ) diff --git a/servers/slapd/schema/pmi.ldif b/servers/slapd/schema/pmi.ldif new file mode 100644 index 0000000..ee709ef --- /dev/null +++ b/servers/slapd/schema/pmi.ldif @@ -0,0 +1,123 @@ +# OpenLDAP X.509 PMI schema +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +# +## Portions Copyright (C) The Internet Society (1997-2006). +## All Rights Reserved. +# +# Includes LDAPv3 schema items from: +# ITU X.509 (08/2005) +# +# This file was automatically generated from pmi.schema; see that file +# for complete references. +# +dn: cn=pmi,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: pmi +olcObjectIdentifier: {0}id-oc-pmiUser 2.5.6.24 +olcObjectIdentifier: {1}id-oc-pmiAA 2.5.6.25 +olcObjectIdentifier: {2}id-oc-pmiSOA 2.5.6.26 +olcObjectIdentifier: {3}id-oc-attCertCRLDistributionPts 2.5.6.27 +olcObjectIdentifier: {4}id-oc-privilegePolicy 2.5.6.32 +olcObjectIdentifier: {5}id-oc-pmiDelegationPath 2.5.6.33 +olcObjectIdentifier: {6}id-oc-protectedPrivilegePolicy 2.5.6.34 +olcObjectIdentifier: {7}id-at-attributeCertificate 2.5.4.58 +olcObjectIdentifier: {8}id-at-attributeCertificateRevocationList 2.5.4.59 +olcObjectIdentifier: {9}id-at-aACertificate 2.5.4.61 +olcObjectIdentifier: {10}id-at-attributeDescriptorCertificate 2.5.4.62 +olcObjectIdentifier: {11}id-at-attributeAuthorityRevocationList 2.5.4.63 +olcObjectIdentifier: {12}id-at-privPolicy 2.5.4.71 +olcObjectIdentifier: {13}id-at-role 2.5.4.72 +olcObjectIdentifier: {14}id-at-delegationPath 2.5.4.73 +olcObjectIdentifier: {15}id-at-protPrivPolicy 2.5.4.74 +olcObjectIdentifier: {16}id-at-xMLPrivilegeInfo 2.5.4.75 +olcObjectIdentifier: {17}id-at-xMLPprotPrivPolicy 2.5.4.76 +olcObjectIdentifier: {18}id-mr 2.5.13 +olcObjectIdentifier: {19}id-mr-attributeCertificateMatch id-mr:42 +olcObjectIdentifier: {20}id-mr-attributeCertificateExactMatch id-mr:45 +olcObjectIdentifier: {21}id-mr-holderIssuerMatch id-mr:46 +olcObjectIdentifier: {22}id-mr-authAttIdMatch id-mr:53 +olcObjectIdentifier: {23}id-mr-roleSpecCertIdMatch id-mr:54 +olcObjectIdentifier: {24}id-mr-basicAttConstraintsMatch id-mr:55 +olcObjectIdentifier: {25}id-mr-delegatedNameConstraintsMatch id-mr:56 +olcObjectIdentifier: {26}id-mr-timeSpecMatch id-mr:57 +olcObjectIdentifier: {27}id-mr-attDescriptorMatch id-mr:58 +olcObjectIdentifier: {28}id-mr-acceptableCertPoliciesMatch id-mr:59 +olcObjectIdentifier: {29}id-mr-delegationPathMatch id-mr:61 +olcObjectIdentifier: {30}id-mr-sOAIdentifierMatch id-mr:66 +olcObjectIdentifier: {31}id-mr-indirectIssuerMatch id-mr:67 +olcObjectIdentifier: {32}AttributeCertificate 1.3.6.1.4.1.4203.666.11.10.2.1 +olcObjectIdentifier: {33}CertificateList 1.3.6.1.4.1.1466.115.121.1.9 +olcObjectIdentifier: {34}AttCertPath 1.3.6.1.4.1.4203.666.11.10.2.4 +olcObjectIdentifier: {35}PolicySyntax 1.3.6.1.4.1.4203.666.11.10.2.5 +olcObjectIdentifier: {36}RoleSyntax 1.3.6.1.4.1.4203.666.11.10.2.6 +olcLdapSyntaxes: {0}( 1.3.6.1.4.1.4203.666.11.10.2.4 DESC 'X.509 PMI attribute + cartificate path: SEQUENCE OF AttributeCertificate' X-SUBST '1.3.6.1.4.1.146 + 6.115.121.1.15' ) +olcLdapSyntaxes: {1}( 1.3.6.1.4.1.4203.666.11.10.2.5 DESC 'X.509 PMI policy sy + ntax' X-SUBST '1.3.6.1.4.1.1466.115.121.1.15' ) +olcLdapSyntaxes: {2}( 1.3.6.1.4.1.4203.666.11.10.2.6 DESC 'X.509 PMI role synt + ax' X-SUBST '1.3.6.1.4.1.1466.115.121.1.15' ) +olcAttributeTypes: {0}( id-at-role NAME 'role' DESC 'X.509 Role attribute, use + ;binary' SYNTAX RoleSyntax ) +olcAttributeTypes: {1}( id-at-xMLPrivilegeInfo NAME 'xmlPrivilegeInfo' DESC 'X + .509 XML privilege information attribute' SYNTAX 1.3.6.1.4.1.1466.115.121.1.1 + 5 ) +olcAttributeTypes: {2}( id-at-attributeCertificate NAME 'attributeCertificateA + ttribute' DESC 'X.509 Attribute certificate attribute, use ;binary' EQUALITY + attributeCertificateExactMatch SYNTAX AttributeCertificate ) +olcAttributeTypes: {3}( id-at-aACertificate NAME 'aACertificate' DESC 'X.509 A + A certificate attribute, use ;binary' EQUALITY attributeCertificateExactMatch + SYNTAX AttributeCertificate ) +olcAttributeTypes: {4}( id-at-attributeDescriptorCertificate NAME 'attributeDe + scriptorCertificate' DESC 'X.509 Attribute descriptor certificate attribute, + use ;binary' EQUALITY attributeCertificateExactMatch SYNTAX AttributeCertific + ate ) +olcAttributeTypes: {5}( id-at-attributeCertificateRevocationList NAME 'attribu + teCertificateRevocationList' DESC 'X.509 Attribute certificate revocation lis + t attribute, use ;binary' SYNTAX CertificateList X-EQUALITY 'certificateListE + xactMatch, not implemented yet' ) +olcAttributeTypes: {6}( id-at-attributeAuthorityRevocationList NAME 'attribute + AuthorityRevocationList' DESC 'X.509 AA certificate revocation list attribute + , use ;binary' SYNTAX CertificateList X-EQUALITY 'certificateListExactMatch, + not implemented yet' ) +olcAttributeTypes: {7}( id-at-delegationPath NAME 'delegationPath' DESC 'X.509 + Delegation path attribute, use ;binary' SYNTAX AttCertPath ) +olcAttributeTypes: {8}( id-at-privPolicy NAME 'privPolicy' DESC 'X.509 Privile + ge policy attribute, use ;binary' SYNTAX PolicySyntax ) +olcAttributeTypes: {9}( id-at-protPrivPolicy NAME 'protPrivPolicy' DESC 'X.509 + Protected privilege policy attribute, use ;binary' EQUALITY attributeCertifi + cateExactMatch SYNTAX AttributeCertificate ) +olcAttributeTypes: {10}( id-at-xMLPprotPrivPolicy NAME 'xmlPrivPolicy' DESC 'X + .509 XML Protected privilege policy attribute' SYNTAX 1.3.6.1.4.1.1466.115.12 + 1.1.15 ) +olcObjectClasses: {0}( id-oc-pmiUser NAME 'pmiUser' DESC 'X.509 PMI user objec + t class' SUP top AUXILIARY MAY attributeCertificateAttribute ) +olcObjectClasses: {1}( id-oc-pmiAA NAME 'pmiAA' DESC 'X.509 PMI AA object clas + s' SUP top AUXILIARY MAY ( aACertificate $ attributeCertificateRevocationList + $ attributeAuthorityRevocationList ) ) +olcObjectClasses: {2}( id-oc-pmiSOA NAME 'pmiSOA' DESC 'X.509 PMI SOA object c + lass' SUP top AUXILIARY MAY ( attributeCertificateRevocationList $ attributeA + uthorityRevocationList $ attributeDescriptorCertificate ) ) +olcObjectClasses: {3}( id-oc-attCertCRLDistributionPts NAME 'attCertCRLDistrib + utionPt' DESC 'X.509 Attribute certificate CRL distribution point object clas + s' SUP top AUXILIARY MAY ( attributeCertificateRevocationList $ attributeAuth + orityRevocationList ) ) +olcObjectClasses: {4}( id-oc-pmiDelegationPath NAME 'pmiDelegationPath' DESC ' + X.509 PMI delegation path' SUP top AUXILIARY MAY delegationPath ) +olcObjectClasses: {5}( id-oc-privilegePolicy NAME 'privilegePolicy' DESC 'X.50 + 9 Privilege policy object class' SUP top AUXILIARY MAY privPolicy ) +olcObjectClasses: {6}( id-oc-protectedPrivilegePolicy NAME 'protectedPrivilege + Policy' DESC 'X.509 Protected privilege policy object class' SUP top AUXILIAR + Y MAY protPrivPolicy ) diff --git a/servers/slapd/schema/ppolicy.ldif b/servers/slapd/schema/ppolicy.ldif new file mode 100644 index 0000000..18c96b1 --- /dev/null +++ b/servers/slapd/schema/ppolicy.ldif @@ -0,0 +1,87 @@ +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 2004-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +# +## Portions Copyright (C) The Internet Society (2004). +## Please see full copyright statement below. +# +# Definitions from Draft behera-ldap-password-policy-07 (a work in progress) +# Password Policy for LDAP Directories +# With extensions from Hewlett-Packard: +# pwdCheckModule etc. +# +# Contents of this file are subject to change (including deletion) +# without notice. +# +# Not recommended for production use! +# Use with extreme caution! +# +# This file was automatically generated from ppolicy.schema; see that file +# for complete references. +# +dn: cn=ppolicy,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: ppolicy +olcAttributeTypes: {0}( 1.3.6.1.4.1.42.2.27.8.1.1 NAME 'pwdAttribute' EQUALITY + objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 ) +olcAttributeTypes: {1}( 1.3.6.1.4.1.42.2.27.8.1.2 NAME 'pwdMinAge' EQUALITY in + tegerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) +olcAttributeTypes: {2}( 1.3.6.1.4.1.42.2.27.8.1.3 NAME 'pwdMaxAge' EQUALITY in + tegerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) +olcAttributeTypes: {3}( 1.3.6.1.4.1.42.2.27.8.1.4 NAME 'pwdInHistory' EQUALITY + integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1 + .27 SINGLE-VALUE ) +olcAttributeTypes: {4}( 1.3.6.1.4.1.42.2.27.8.1.5 NAME 'pwdCheckQuality' EQUAL + ITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.12 + 1.1.27 SINGLE-VALUE ) +olcAttributeTypes: {5}( 1.3.6.1.4.1.42.2.27.8.1.6 NAME 'pwdMinLength' EQUALITY + integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121. + 1.27 SINGLE-VALUE ) +olcAttributeTypes: {6}( 1.3.6.1.4.1.42.2.27.8.1.7 NAME 'pwdExpireWarning' EQUA + LITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115. + 121.1.27 SINGLE-VALUE ) +olcAttributeTypes: {7}( 1.3.6.1.4.1.42.2.27.8.1.8 NAME 'pwdGraceAuthNLimit' EQ + UALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.11 + 5.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: {8}( 1.3.6.1.4.1.42.2.27.8.1.9 NAME 'pwdLockout' EQUALITY b + ooleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) +olcAttributeTypes: {9}( 1.3.6.1.4.1.42.2.27.8.1.10 NAME 'pwdLockoutDuration' E + QUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.1 + 15.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: {10}( 1.3.6.1.4.1.42.2.27.8.1.11 NAME 'pwdMaxFailure' EQUAL + ITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.1 + 21.1.27 SINGLE-VALUE ) +olcAttributeTypes: {11}( 1.3.6.1.4.1.42.2.27.8.1.12 NAME 'pwdFailureCountInter + val' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1. + 1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: {12}( 1.3.6.1.4.1.42.2.27.8.1.13 NAME 'pwdMustChange' EQUAL + ITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) +olcAttributeTypes: {13}( 1.3.6.1.4.1.42.2.27.8.1.14 NAME 'pwdAllowUserChange' + EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) +olcAttributeTypes: {14}( 1.3.6.1.4.1.42.2.27.8.1.15 NAME 'pwdSafeModify' EQUAL + ITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) +olcAttributeTypes: {15}( 1.3.6.1.4.1.4754.1.99.1 NAME 'pwdCheckModule' DESC 'L + oadable module that instantiates "check_password() function' EQUALITY caseExa + ctIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) +olcAttributeTypes: {16}( 1.3.6.1.4.1.42.2.27.8.1.30 NAME 'pwdMaxRecordedFailur + e' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1. + 1466.115.121.1.27 SINGLE-VALUE ) +olcObjectClasses: {0}( 1.3.6.1.4.1.4754.2.99.1 NAME 'pwdPolicyChecker' SUP top + AUXILIARY MAY pwdCheckModule ) +olcObjectClasses: {1}( 1.3.6.1.4.1.42.2.27.8.2.1 NAME 'pwdPolicy' SUP top AUXI + LIARY MUST pwdAttribute MAY ( pwdMinAge $ pwdMaxAge $ pwdInHistory $ pwdCheck + Quality $ pwdMinLength $ pwdExpireWarning $ pwdGraceAuthNLimit $ pwdLockout $ + pwdLockoutDuration $ pwdMaxFailure $ pwdFailureCountInterval $ pwdMustChange + $ pwdAllowUserChange $ pwdSafeModify $ pwdMaxRecordedFailure ) ) diff --git a/servers/slapd/schema_check.c b/servers/slapd/schema_check.c new file mode 100644 index 0000000..6ee3c4e --- /dev/null +++ b/servers/slapd/schema_check.c @@ -0,0 +1,938 @@ +/* schema_check.c - routines to enforce schema definitions */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" + +static char * oc_check_required( + Entry *e, + ObjectClass *oc, + struct berval *ocname ); + +static int entry_naming_check( + Entry *e, + int manage, + int add_naming, + const char** text, + char *textbuf, size_t textlen ); +/* + * entry_schema_check - check that entry e conforms to the schema required + * by its object class(es). + * + * returns 0 if so, non-zero otherwise. + */ + +int +entry_schema_check( + Operation *op, + Entry *e, + Attribute *oldattrs, + int manage, + int add, + Attribute **socp, + const char** text, + char *textbuf, size_t textlen ) +{ + Attribute *a, *asc = NULL, *aoc = NULL; + ObjectClass *sc, *oc, **socs = NULL; + AttributeType *at; + ContentRule *cr; + int rc, i; + AttributeDescription *ad_structuralObjectClass + = slap_schema.si_ad_structuralObjectClass; + AttributeDescription *ad_objectClass + = slap_schema.si_ad_objectClass; + int extensible = 0; + int subentry = is_entry_subentry( e ); + int collectiveSubentry = 0; + + if ( SLAP_NO_SCHEMA_CHECK( op->o_bd )) { + return LDAP_SUCCESS; + } + + if ( get_no_schema_check( op ) ) { + return LDAP_SUCCESS; + } + + if( subentry ) { + collectiveSubentry = is_entry_collectiveAttributeSubentry( e ); + } + + *text = textbuf; + + /* misc attribute checks */ + for ( a = e->e_attrs; a != NULL; a = a->a_next ) { + const char *type = a->a_desc->ad_cname.bv_val; + + /* there should be at least one value */ + assert( a->a_vals != NULL ); + assert( a->a_vals[0].bv_val != NULL ); + + if( a->a_desc->ad_type->sat_check ) { + rc = (a->a_desc->ad_type->sat_check)( + op->o_bd, e, a, text, textbuf, textlen ); + if( rc != LDAP_SUCCESS ) { + return rc; + } + } + + if( a->a_desc == ad_structuralObjectClass ) + asc = a; + else if ( a->a_desc == ad_objectClass ) + aoc = a; + + if( !collectiveSubentry && is_at_collective( a->a_desc->ad_type ) ) { + snprintf( textbuf, textlen, + "'%s' can only appear in collectiveAttributeSubentry", + type ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + /* if single value type, check for multiple values */ + if( is_at_single_value( a->a_desc->ad_type ) && + a->a_vals[1].bv_val != NULL ) + { + snprintf( textbuf, textlen, + "attribute '%s' cannot have multiple values", + type ); + + Debug( LDAP_DEBUG_ANY, + "Entry (%s), %s\n", + e->e_dn, textbuf, 0 ); + + return LDAP_CONSTRAINT_VIOLATION; + } + } + + /* check the object class attribute */ + if ( aoc == NULL ) { + Debug( LDAP_DEBUG_ANY, "No objectClass for entry (%s)\n", + e->e_dn, 0, 0 ); + + *text = "no objectClass attribute"; + return LDAP_OBJECT_CLASS_VIOLATION; + } + + assert( aoc->a_vals != NULL ); + assert( aoc->a_vals[0].bv_val != NULL ); + + /* check the structural object class attribute */ + if ( asc == NULL && !add ) { + Debug( LDAP_DEBUG_ANY, + "No structuralObjectClass for entry (%s)\n", + e->e_dn, 0, 0 ); + + *text = "no structuralObjectClass operational attribute"; + return LDAP_OTHER; + } + + rc = structural_class( aoc->a_vals, &oc, &socs, text, textbuf, textlen, + op->o_tmpmemctx ); + if( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( asc == NULL && add ) { + attr_merge_one( e, ad_structuralObjectClass, &oc->soc_cname, NULL ); + asc = attr_find( e->e_attrs, ad_structuralObjectClass ); + sc = oc; + goto got_soc; + } + + assert( asc->a_vals != NULL ); + assert( asc->a_vals[0].bv_val != NULL ); + assert( asc->a_vals[1].bv_val == NULL ); + + sc = oc_bvfind( &asc->a_vals[0] ); + if( sc == NULL ) { + snprintf( textbuf, textlen, + "unrecognized structuralObjectClass '%s'", + asc->a_vals[0].bv_val ); + + Debug( LDAP_DEBUG_ANY, + "entry_check_schema(%s): %s\n", + e->e_dn, textbuf, 0 ); + + rc = LDAP_OBJECT_CLASS_VIOLATION; + goto done; + } + + if( sc->soc_kind != LDAP_SCHEMA_STRUCTURAL ) { + snprintf( textbuf, textlen, + "structuralObjectClass '%s' is not STRUCTURAL", + asc->a_vals[0].bv_val ); + + Debug( LDAP_DEBUG_ANY, + "entry_check_schema(%s): %s\n", + e->e_dn, textbuf, 0 ); + + rc = LDAP_OTHER; + goto done; + } + +got_soc: + if( !manage && sc->soc_obsolete ) { + snprintf( textbuf, textlen, + "structuralObjectClass '%s' is OBSOLETE", + asc->a_vals[0].bv_val ); + + Debug( LDAP_DEBUG_ANY, + "entry_check_schema(%s): %s\n", + e->e_dn, textbuf, 0 ); + + rc = LDAP_OBJECT_CLASS_VIOLATION; + goto done; + } + + *text = textbuf; + + if ( oc == NULL ) { + snprintf( textbuf, textlen, + "unrecognized objectClass '%s'", + aoc->a_vals[0].bv_val ); + rc = LDAP_OBJECT_CLASS_VIOLATION; + goto done; + + } else if ( sc != oc ) { + if ( !manage && sc != slap_schema.si_oc_glue ) { + snprintf( textbuf, textlen, + "structural object class modification " + "from '%s' to '%s' not allowed", + asc->a_vals[0].bv_val, oc->soc_cname.bv_val ); + rc = LDAP_NO_OBJECT_CLASS_MODS; + goto done; + } + + assert( asc->a_vals != NULL ); + assert( !BER_BVISNULL( &asc->a_vals[0] ) ); + assert( BER_BVISNULL( &asc->a_vals[1] ) ); + assert( asc->a_nvals == asc->a_vals ); + + /* draft-zeilenga-ldap-relax: automatically modify + * structuralObjectClass if changed with relax */ + sc = oc; + ber_bvreplace( &asc->a_vals[ 0 ], &sc->soc_cname ); + if ( socp ) { + *socp = asc; + } + } + + /* naming check */ + if ( !is_entry_glue ( e ) ) { + rc = entry_naming_check( e, manage, add, text, textbuf, textlen ); + if( rc != LDAP_SUCCESS ) { + goto done; + } + } else { + /* Glue Entry */ + } + + /* find the content rule for the structural class */ + cr = cr_find( sc->soc_oid ); + + /* the cr must be same as the structural class */ + assert( !cr || !strcmp( cr->scr_oid, sc->soc_oid ) ); + + /* check that the entry has required attrs of the content rule */ + if( cr ) { + if( !manage && cr->scr_obsolete ) { + snprintf( textbuf, textlen, + "content rule '%s' is obsolete", + ldap_contentrule2name( &cr->scr_crule )); + + Debug( LDAP_DEBUG_ANY, + "Entry (%s): %s\n", + e->e_dn, textbuf, 0 ); + + rc = LDAP_OBJECT_CLASS_VIOLATION; + goto done; + } + + if( cr->scr_required ) for( i=0; cr->scr_required[i]; i++ ) { + at = cr->scr_required[i]; + + for ( a = e->e_attrs; a != NULL; a = a->a_next ) { + if( a->a_desc->ad_type == at ) { + break; + } + } + + /* not there => schema violation */ + if ( a == NULL ) { + snprintf( textbuf, textlen, + "content rule '%s' requires attribute '%s'", + ldap_contentrule2name( &cr->scr_crule ), + at->sat_cname.bv_val ); + + Debug( LDAP_DEBUG_ANY, + "Entry (%s): %s\n", + e->e_dn, textbuf, 0 ); + + rc = LDAP_OBJECT_CLASS_VIOLATION; + goto done; + } + } + + if( cr->scr_precluded ) for( i=0; cr->scr_precluded[i]; i++ ) { + at = cr->scr_precluded[i]; + + for ( a = e->e_attrs; a != NULL; a = a->a_next ) { + if( a->a_desc->ad_type == at ) { + break; + } + } + + /* there => schema violation */ + if ( a != NULL ) { + snprintf( textbuf, textlen, + "content rule '%s' precluded attribute '%s'", + ldap_contentrule2name( &cr->scr_crule ), + at->sat_cname.bv_val ); + + Debug( LDAP_DEBUG_ANY, + "Entry (%s): %s\n", + e->e_dn, textbuf, 0 ); + + rc = LDAP_OBJECT_CLASS_VIOLATION; + goto done; + } + } + } + + /* check that the entry has required attrs for each oc */ + for ( i = 0; socs[i]; i++ ) { + oc = socs[i]; + if ( !manage && oc->soc_obsolete ) { + /* disallow obsolete classes */ + snprintf( textbuf, textlen, + "objectClass '%s' is OBSOLETE", + aoc->a_vals[i].bv_val ); + + Debug( LDAP_DEBUG_ANY, + "entry_check_schema(%s): %s\n", + e->e_dn, textbuf, 0 ); + + rc = LDAP_OBJECT_CLASS_VIOLATION; + goto done; + } + + if ( oc->soc_check ) { + rc = (oc->soc_check)( op->o_bd, e, oc, + text, textbuf, textlen ); + if( rc != LDAP_SUCCESS ) { + goto done; + } + } + + if ( oc->soc_kind == LDAP_SCHEMA_ABSTRACT ) { + /* object class is abstract */ + if ( oc != slap_schema.si_oc_top && + !is_object_subclass( oc, sc )) + { + int j; + ObjectClass *xc = NULL; + for( j=0; socs[j]; j++ ) { + if( i != j ) { + xc = socs[j]; + + /* since we previous check against the + * structural object of this entry, the + * abstract class must be a (direct or indirect) + * superclass of one of the auxiliary classes of + * the entry. + */ + if ( xc->soc_kind == LDAP_SCHEMA_AUXILIARY && + is_object_subclass( oc, xc ) ) + { + xc = NULL; + break; + } + } + } + + if( xc != NULL ) { + snprintf( textbuf, textlen, "instantiation of " + "abstract objectClass '%s' not allowed", + aoc->a_vals[i].bv_val ); + + Debug( LDAP_DEBUG_ANY, + "entry_check_schema(%s): %s\n", + e->e_dn, textbuf, 0 ); + + rc = LDAP_OBJECT_CLASS_VIOLATION; + goto done; + } + } + + } else if ( oc->soc_kind != LDAP_SCHEMA_STRUCTURAL || oc == sc ) { + char *s; + + if( oc->soc_kind == LDAP_SCHEMA_AUXILIARY ) { + int k; + + if( cr ) { + int j; + + k = -1; + if( cr->scr_auxiliaries ) { + for( j = 0; cr->scr_auxiliaries[j]; j++ ) { + if( cr->scr_auxiliaries[j] == oc ) { + k = 0; + break; + } + } + } + if ( k ) { + snprintf( textbuf, textlen, + "class '%s' not allowed by content rule '%s'", + oc->soc_cname.bv_val, + ldap_contentrule2name( &cr->scr_crule ) ); + } + } else if ( global_disallows & SLAP_DISALLOW_AUX_WO_CR ) { + k = -1; + snprintf( textbuf, textlen, + "class '%s' not allowed by any content rule", + oc->soc_cname.bv_val ); + } else { + k = 0; + } + + if( k == -1 ) { + Debug( LDAP_DEBUG_ANY, + "Entry (%s): %s\n", + e->e_dn, textbuf, 0 ); + + rc = LDAP_OBJECT_CLASS_VIOLATION; + goto done; + } + } + + s = oc_check_required( e, oc, &aoc->a_vals[i] ); + if (s != NULL) { + snprintf( textbuf, textlen, + "object class '%s' requires attribute '%s'", + aoc->a_vals[i].bv_val, s ); + + Debug( LDAP_DEBUG_ANY, + "Entry (%s): %s\n", + e->e_dn, textbuf, 0 ); + + rc = LDAP_OBJECT_CLASS_VIOLATION; + goto done; + } + + if( oc == slap_schema.si_oc_extensibleObject ) { + extensible=1; + } + } + } + + if( extensible ) { + *text = NULL; + rc = LDAP_SUCCESS; + goto done; + } + + /* check that each attr in the entry is allowed by some oc */ + for ( a = e->e_attrs; a != NULL; a = a->a_next ) { + rc = LDAP_OBJECT_CLASS_VIOLATION; + + if( cr && cr->scr_required ) { + for( i=0; cr->scr_required[i]; i++ ) { + if( cr->scr_required[i] == a->a_desc->ad_type ) { + rc = LDAP_SUCCESS; + break; + } + } + } + + if( rc != LDAP_SUCCESS && cr && cr->scr_allowed ) { + for( i=0; cr->scr_allowed[i]; i++ ) { + if( cr->scr_allowed[i] == a->a_desc->ad_type ) { + rc = LDAP_SUCCESS; + break; + } + } + } + + if( rc != LDAP_SUCCESS ) + { + rc = oc_check_allowed( a->a_desc->ad_type, socs, sc ); + } + + if ( rc != LDAP_SUCCESS ) { + char *type = a->a_desc->ad_cname.bv_val; + + snprintf( textbuf, textlen, + "attribute '%s' not allowed", + type ); + + Debug( LDAP_DEBUG_ANY, + "Entry (%s), %s\n", + e->e_dn, textbuf, 0 ); + + goto done; + } + } + + *text = NULL; +done: + slap_sl_free( socs, op->o_tmpmemctx ); + return rc; +} + +static char * +oc_check_required( + Entry *e, + ObjectClass *oc, + struct berval *ocname ) +{ + AttributeType *at; + int i; + Attribute *a; + + Debug( LDAP_DEBUG_TRACE, + "oc_check_required entry (%s), objectClass \"%s\"\n", + e->e_dn, ocname->bv_val, 0 ); + + + /* check for empty oc_required */ + if(oc->soc_required == NULL) { + return NULL; + } + + /* for each required attribute */ + for ( i = 0; oc->soc_required[i] != NULL; i++ ) { + at = oc->soc_required[i]; + /* see if it's in the entry */ + for ( a = e->e_attrs; a != NULL; a = a->a_next ) { + if( a->a_desc->ad_type == at ) { + break; + } + } + /* not there => schema violation */ + if ( a == NULL ) { + return at->sat_cname.bv_val; + } + } + + return( NULL ); +} + +int oc_check_allowed( + AttributeType *at, + ObjectClass **socs, + ObjectClass *sc ) +{ + int i, j; + + Debug( LDAP_DEBUG_TRACE, + "oc_check_allowed type \"%s\"\n", + at->sat_cname.bv_val, 0, 0 ); + + /* always allow objectClass attribute */ + if ( strcasecmp( at->sat_cname.bv_val, "objectClass" ) == 0 ) { + return LDAP_SUCCESS; + } + + /* + * All operational attributions are allowed by schema rules. + */ + if( is_at_operational(at) ) { + return LDAP_SUCCESS; + } + + /* check to see if its allowed by the structuralObjectClass */ + if( sc ) { + /* does it require the type? */ + for ( j = 0; sc->soc_required != NULL && + sc->soc_required[j] != NULL; j++ ) + { + if( at == sc->soc_required[j] ) { + return LDAP_SUCCESS; + } + } + + /* does it allow the type? */ + for ( j = 0; sc->soc_allowed != NULL && + sc->soc_allowed[j] != NULL; j++ ) + { + if( at == sc->soc_allowed[j] ) { + return LDAP_SUCCESS; + } + } + } + + /* check that the type appears as req or opt in at least one oc */ + for ( i = 0; socs[i]; i++ ) { + /* if we know about the oc */ + ObjectClass *oc = socs[i]; + /* extensibleObject allows all */ + if ( oc == slap_schema.si_oc_extensibleObject ) { + return LDAP_SUCCESS; + } + if ( oc != NULL && oc->soc_kind != LDAP_SCHEMA_ABSTRACT && + ( sc == NULL || oc->soc_kind == LDAP_SCHEMA_AUXILIARY )) + { + /* does it require the type? */ + for ( j = 0; oc->soc_required != NULL && + oc->soc_required[j] != NULL; j++ ) + { + if( at == oc->soc_required[j] ) { + return LDAP_SUCCESS; + } + } + /* does it allow the type? */ + for ( j = 0; oc->soc_allowed != NULL && + oc->soc_allowed[j] != NULL; j++ ) + { + if( at == oc->soc_allowed[j] ) { + return LDAP_SUCCESS; + } + } + } + } + + /* not allowed by any oc */ + return LDAP_OBJECT_CLASS_VIOLATION; +} + +/* + * Determine the structural object class from a set of OIDs + */ +int structural_class( + BerVarray ocs, + ObjectClass **scp, + ObjectClass ***socsp, + const char **text, + char *textbuf, size_t textlen, + void *ctx ) +{ + int i, nocs; + ObjectClass *oc, **socs; + ObjectClass *sc = NULL; + int scn = -1; + + *text = "structural_class: internal error"; + + /* count them */ + for( i=0; ocs[i].bv_val; i++ ) ; + nocs = i; + + socs = slap_sl_malloc( (nocs+1) * sizeof(ObjectClass *), ctx ); + + for( i=0; ocs[i].bv_val; i++ ) { + socs[i] = oc_bvfind( &ocs[i] ); + + if( socs[i] == NULL ) { + snprintf( textbuf, textlen, + "unrecognized objectClass '%s'", + ocs[i].bv_val ); + *text = textbuf; + goto fail; + } + } + socs[i] = NULL; + + for( i=0; ocs[i].bv_val; i++ ) { + oc = socs[i]; + if( oc->soc_kind == LDAP_SCHEMA_STRUCTURAL ) { + if( sc == NULL || is_object_subclass( sc, oc ) ) { + sc = oc; + scn = i; + + } else if ( !is_object_subclass( oc, sc ) ) { + int j; + ObjectClass *xc = NULL; + + /* find common superior */ + for( j=i+1; ocs[j].bv_val; j++ ) { + xc = socs[j]; + + if( xc == NULL ) { + snprintf( textbuf, textlen, + "unrecognized objectClass '%s'", + ocs[j].bv_val ); + *text = textbuf; + goto fail; + } + + if( xc->soc_kind != LDAP_SCHEMA_STRUCTURAL ) { + xc = NULL; + continue; + } + + if( is_object_subclass( sc, xc ) && + is_object_subclass( oc, xc ) ) + { + /* found common subclass */ + break; + } + + xc = NULL; + } + + if( xc == NULL ) { + /* no common subclass */ + snprintf( textbuf, textlen, + "invalid structural object class chain (%s/%s)", + ocs[scn].bv_val, ocs[i].bv_val ); + *text = textbuf; + goto fail; + } + } + } + } + + if( scp ) { + *scp = sc; + } + + if( sc == NULL ) { + *text = "no structural object class provided"; + goto fail; + } + + if( scn < 0 ) { + *text = "invalid structural object class"; + goto fail; + } + + if ( socsp ) { + *socsp = socs; + } else { + slap_sl_free( socs, ctx ); + } + *text = NULL; + + return LDAP_SUCCESS; + +fail: + slap_sl_free( socs, ctx ); + return LDAP_OBJECT_CLASS_VIOLATION; +} + +/* + * Return structural object class from list of modifications + */ +int mods_structural_class( + Modifications *mods, + struct berval *sc, + const char **text, + char *textbuf, size_t textlen, void *ctx ) +{ + Modifications *ocmod = NULL; + ObjectClass *ssc; + int rc; + + for( ; mods != NULL; mods = mods->sml_next ) { + if( mods->sml_desc == slap_schema.si_ad_objectClass ) { + if( ocmod != NULL ) { + *text = "entry has multiple objectClass attributes"; + return LDAP_OBJECT_CLASS_VIOLATION; + } + ocmod = mods; + } + } + + if( ocmod == NULL ) { + *text = "entry has no objectClass attribute"; + return LDAP_OBJECT_CLASS_VIOLATION; + } + + if( ocmod->sml_values == NULL || ocmod->sml_values[0].bv_val == NULL ) { + *text = "objectClass attribute has no values"; + return LDAP_OBJECT_CLASS_VIOLATION; + } + + rc = structural_class( ocmod->sml_values, &ssc, NULL, + text, textbuf, textlen, ctx ); + if ( rc == LDAP_SUCCESS ) + *sc = ssc->soc_cname; + return rc; +} + + +static int +entry_naming_check( + Entry *e, + int manage, + int add_naming, + const char** text, + char *textbuf, size_t textlen ) +{ + /* naming check */ + LDAPRDN rdn = NULL; + const char *p = NULL; + ber_len_t cnt; + int rc = LDAP_SUCCESS; + + if ( BER_BVISEMPTY( &e->e_name )) { + return LDAP_SUCCESS; + } + + /* + * Get attribute type(s) and attribute value(s) of our RDN + */ + if ( ldap_bv2rdn( &e->e_name, &rdn, (char **)&p, + LDAP_DN_FORMAT_LDAP ) ) + { + *text = "unrecognized attribute type(s) in RDN"; + return LDAP_INVALID_DN_SYNTAX; + } + + /* Check that each AVA of the RDN is present in the entry */ + /* FIXME: Should also check that each AVA lists a distinct type */ + for ( cnt = 0; rdn[cnt]; cnt++ ) { + LDAPAVA *ava = rdn[cnt]; + AttributeDescription *desc = NULL; + Attribute *attr; + const char *errtext; + int add = 0; + + if( ava->la_flags & LDAP_AVA_BINARY ) { + snprintf( textbuf, textlen, + "value of naming attribute '%s' in unsupported BER form", + ava->la_attr.bv_val ); + rc = LDAP_NAMING_VIOLATION; + break; + } + + rc = slap_bv2ad( &ava->la_attr, &desc, &errtext ); + if ( rc != LDAP_SUCCESS ) { + snprintf( textbuf, textlen, "%s (in RDN)", errtext ); + break; + } + + if( desc->ad_type->sat_usage ) { + snprintf( textbuf, textlen, + "naming attribute '%s' is operational", + ava->la_attr.bv_val ); + rc = LDAP_NAMING_VIOLATION; + break; + } + + if( desc->ad_type->sat_collective ) { + snprintf( textbuf, textlen, + "naming attribute '%s' is collective", + ava->la_attr.bv_val ); + rc = LDAP_NAMING_VIOLATION; + break; + } + + if( !manage && desc->ad_type->sat_obsolete ) { + snprintf( textbuf, textlen, + "naming attribute '%s' is obsolete", + ava->la_attr.bv_val ); + rc = LDAP_NAMING_VIOLATION; + break; + } + + if( !desc->ad_type->sat_equality ) { + snprintf( textbuf, textlen, + "naming attribute '%s' has no equality matching rule", + ava->la_attr.bv_val ); + rc = LDAP_NAMING_VIOLATION; + break; + } + + if( !desc->ad_type->sat_equality->smr_match ) { + snprintf( textbuf, textlen, + "naming attribute '%s' has unsupported equality matching rule", + ava->la_attr.bv_val ); + rc = LDAP_NAMING_VIOLATION; + break; + } + + /* find the naming attribute */ + attr = attr_find( e->e_attrs, desc ); + if ( attr == NULL ) { + snprintf( textbuf, textlen, + "naming attribute '%s' is not present in entry", + ava->la_attr.bv_val ); + if ( add_naming ) { + add = 1; + + } else { + rc = LDAP_NAMING_VIOLATION; + } + + } else { + rc = attr_valfind( attr, SLAP_MR_VALUE_OF_ASSERTION_SYNTAX| + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH, + &ava->la_value, NULL, NULL ); + + if ( rc != 0 ) { + switch( rc ) { + case LDAP_INAPPROPRIATE_MATCHING: + snprintf( textbuf, textlen, + "inappropriate matching for naming attribute '%s'", + ava->la_attr.bv_val ); + break; + case LDAP_INVALID_SYNTAX: + snprintf( textbuf, textlen, + "value of naming attribute '%s' is invalid", + ava->la_attr.bv_val ); + break; + case LDAP_NO_SUCH_ATTRIBUTE: + if ( add_naming ) { + if ( is_at_single_value( desc->ad_type ) ) { + snprintf( textbuf, textlen, + "value of single-valued naming attribute '%s' conflicts with value present in entry", + ava->la_attr.bv_val ); + + } else { + add = 1; + rc = LDAP_SUCCESS; + } + + } else { + snprintf( textbuf, textlen, + "value of naming attribute '%s' is not present in entry", + ava->la_attr.bv_val ); + } + break; + default: + snprintf( textbuf, textlen, + "naming attribute '%s' is inappropriate", + ava->la_attr.bv_val ); + } + + if ( !add ) { + rc = LDAP_NAMING_VIOLATION; + } + } + } + + if ( add ) { + attr_merge_normalize_one( e, desc, &ava->la_value, NULL ); + + } else if ( rc != LDAP_SUCCESS ) { + break; + } + } + + ldap_rdnfree( rdn ); + return rc; +} + diff --git a/servers/slapd/schema_init.c b/servers/slapd/schema_init.c new file mode 100644 index 0000000..31be115 --- /dev/null +++ b/servers/slapd/schema_init.c @@ -0,0 +1,6890 @@ +/* schema_init.c - init builtin schema */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +/* + * Syntaxes - implementation notes: + * + * Validate function(syntax, value): + * Called before the other functions here to check if the value + * is valid according to the syntax. + * + * Pretty function(syntax, input value, output prettified...): + * If it exists, maps different notations of the same value to a + * unique representation which can be stored in the directory and + * possibly be passed to the Match/Indexer/Filter() functions. + * + * E.g. DN "2.5.4.3 = foo\,bar, o = BAZ" -> "cn=foo\2Cbar,o=BAZ", + * but unlike DN normalization, "BAZ" is not mapped to "baz". + */ + +/* + * Matching rules - implementation notes: + * + * Matching rules match an attribute value (often from the directory) + * against an asserted value (e.g. from a filter). + * + * Invoked with validated and commonly pretty/normalized arguments, thus + * a number of matching rules can simply use the octetString functions. + * + * Normalize function(...input value, output normalized...): + * If it exists, maps matching values to a unique representation + * which is passed to the Match/Indexer/Filter() functions. + * + * Different matching rules can normalize values of the same syntax + * differently. E.g. caseIgnore rules normalize to lowercase, + * caseExact rules do not. + * + * Match function(*output matchp, ...value, asserted value): + * On success, set *matchp. 0 means match. For ORDERING/most EQUALITY, + * less/greater than 0 means value less/greater than asserted. However: + * + * In extensible match filters, ORDERING rules match if value<asserted. + * + * EQUALITY rules may order values differently than ORDERING rules for + * speed, since EQUALITY ordering is only used for SLAP_AT_SORTED_VAL. + * Some EQUALITY rules do not order values (ITS#6722). + * + * Indexer function(...attribute values, *output keysp,...): + * Generates index keys for the attribute values. Backends can store + * them in an index, a {key->entry ID set} mapping, for the attribute. + * + * A search can look up the DN/scope and asserted values in the + * indexes, if any, to narrow down the number of entires to check + * against the search criteria. + * + * Filter function(...asserted value, *output keysp,...): + * Generates index key(s) for the asserted value, to be looked up in + * the index from the Indexer function. *keysp is an array because + * substring matching rules can generate multiple lookup keys. + * + * Index keys: + * A key is usually a hash of match type, attribute value and schema + * info, because one index can contain keys for many filtering types. + * + * Some indexes instead have EQUALITY keys ordered so that if + * key(val1) < key(val2), then val1 < val2 by the ORDERING rule. + * That way the ORDERING rule can use the EQUALITY index. + * + * Substring indexing: + * This chops the attribute values up in small chunks and indexes all + * possible chunks of certain sizes. Substring filtering looks up + * SOME of the asserted value's chunks, and the caller uses the + * intersection of the resulting entry ID sets. + * See the index_substr_* keywords in slapd.conf(5). + */ + +#include "portable.h" + +#include <stdio.h> +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif + +#include <ac/ctype.h> +#include <ac/errno.h> +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "../../libraries/liblber/lber-int.h" /* get ber_ptrlen() */ + +#include "ldap_utf8.h" + +#include "lutil.h" +#include "lutil_hash.h" +#define HASH_BYTES LUTIL_HASH_BYTES +#define HASH_CONTEXT lutil_HASH_CTX +#define HASH_Init(c) lutil_HASHInit(c) +#define HASH_Update(c,buf,len) lutil_HASHUpdate(c,buf,len) +#define HASH_Final(d,c) lutil_HASHFinal(d,c) + +/* approx matching rules */ +#define directoryStringApproxMatchOID "1.3.6.1.4.1.4203.666.4.4" +#define directoryStringApproxMatch approxMatch +#define directoryStringApproxIndexer approxIndexer +#define directoryStringApproxFilter approxFilter +#define IA5StringApproxMatchOID "1.3.6.1.4.1.4203.666.4.5" +#define IA5StringApproxMatch approxMatch +#define IA5StringApproxIndexer approxIndexer +#define IA5StringApproxFilter approxFilter + +/* Change Sequence Number (CSN) - much of this will change */ +#define csnMatch octetStringMatch +#define csnOrderingMatch octetStringOrderingMatch +#define csnIndexer generalizedTimeIndexer +#define csnFilter generalizedTimeFilter + +#define authzMatch octetStringMatch + +/* X.509 PMI ldapSyntaxes */ +/* FIXME: need to create temporary OIDs under OpenLDAP's arc; + * these are currently hijacked + * + * 1.3.6.1.4.1.4203.666 OpenLDAP + * 1.3.6.1.4.1.4203.666.11 self-contained works + * 1.3.6.1.4.1.4203.666.11.10 X.509 PMI + * 1.3.6.1.4.1.4203.666.11.10.2 X.509 PMI ldapSyntaxes + * 1.3.6.1.4.1.4203.666.11.10.2.1 AttributeCertificate (supported) + * 1.3.6.1.4.1.4203.666.11.10.2.2 AttributeCertificateExactAssertion (supported) + * 1.3.6.1.4.1.4203.666.11.10.2.3 AttributeCertificateAssertion (not supported) + * 1.3.6.1.4.1.4203.666.11.10.2.4 AttCertPath (X-SUBST'ed right now in pmi.schema) + * 1.3.6.1.4.1.4203.666.11.10.2.5 PolicySyntax (X-SUBST'ed right now in pmi.schema) + * 1.3.6.1.4.1.4203.666.11.10.2.6 RoleSyntax (X-SUBST'ed right now in pmi.schema) + */ +#if 0 /* from <draft-ietf-pkix-ldap-schema-02.txt> (expired) */ +#define attributeCertificateSyntaxOID "1.2.826.0.1.3344810.7.5" +#define attributeCertificateExactAssertionSyntaxOID "1.2.826.0.1.3344810.7.6" +#define attributeCertificateAssertionSyntaxOID "1.2.826.0.1.3344810.7.7" +#else /* from OpenLDAP's experimental oid arc */ +#define X509_PMI_SyntaxOID "1.3.6.1.4.1.4203.666.11.10.2" +#define attributeCertificateSyntaxOID X509_PMI_SyntaxOID ".1" +#define attributeCertificateExactAssertionSyntaxOID X509_PMI_SyntaxOID ".2" +#define attributeCertificateAssertionSyntaxOID X509_PMI_SyntaxOID ".3" +#endif + +unsigned int index_substr_if_minlen = SLAP_INDEX_SUBSTR_IF_MINLEN_DEFAULT; +unsigned int index_substr_if_maxlen = SLAP_INDEX_SUBSTR_IF_MAXLEN_DEFAULT; +unsigned int index_substr_any_len = SLAP_INDEX_SUBSTR_ANY_LEN_DEFAULT; +unsigned int index_substr_any_step = SLAP_INDEX_SUBSTR_ANY_STEP_DEFAULT; + +unsigned int index_intlen = SLAP_INDEX_INTLEN_DEFAULT; +unsigned int index_intlen_strlen = SLAP_INDEX_INTLEN_STRLEN( + SLAP_INDEX_INTLEN_DEFAULT ); + +ldap_pvt_thread_mutex_t ad_index_mutex; +ldap_pvt_thread_mutex_t ad_undef_mutex; +ldap_pvt_thread_mutex_t oc_undef_mutex; + +static int +generalizedTimeValidate( + Syntax *syntax, + struct berval *in ); + +#ifdef SUPPORT_OBSOLETE_UTC_SYNTAX +static int +utcTimeValidate( + Syntax *syntax, + struct berval *in ); +#endif /* SUPPORT_OBSOLETE_UTC_SYNTAX */ + +static int +inValidate( + Syntax *syntax, + struct berval *in ) +{ + /* no value allowed */ + return LDAP_INVALID_SYNTAX; +} + +static int +blobValidate( + Syntax *syntax, + struct berval *in ) +{ + /* any value allowed */ + return LDAP_SUCCESS; +} + +#define berValidate blobValidate + +static int +sequenceValidate( + Syntax *syntax, + struct berval *in ) +{ + if ( in->bv_len < 2 ) return LDAP_INVALID_SYNTAX; + if ( in->bv_val[0] != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + + return LDAP_SUCCESS; +} + +/* X.509 related stuff */ + +enum { + SLAP_X509_V1 = 0, + SLAP_X509_V2 = 1, + SLAP_X509_V3 = 2 +}; + +enum { + SLAP_TAG_UTCTIME = 0x17U, + SLAP_TAG_GENERALIZEDTIME = 0x18U +}; + + +#define SLAP_X509_OPTION (LBER_CLASS_CONTEXT|LBER_CONSTRUCTED) + +enum { + SLAP_X509_OPT_C_VERSION = SLAP_X509_OPTION + 0, + SLAP_X509_OPT_C_ISSUERUNIQUEID = LBER_CLASS_CONTEXT + 1, + SLAP_X509_OPT_C_SUBJECTUNIQUEID = LBER_CLASS_CONTEXT + 2, + SLAP_X509_OPT_C_EXTENSIONS = SLAP_X509_OPTION + 3 +}; + +enum { + SLAP_X509_OPT_CL_CRLEXTENSIONS = SLAP_X509_OPTION + 0 +}; + +/* +GeneralName ::= CHOICE { + otherName [0] INSTANCE OF OTHER-NAME, + rfc822Name [1] IA5String, + dNSName [2] IA5String, + x400Address [3] ORAddress, + directoryName [4] Name, + ediPartyName [5] EDIPartyName, + uniformResourceIdentifier [6] IA5String, + iPAddress [7] OCTET STRING, + registeredID [8] OBJECT IDENTIFIER } +*/ +enum { + SLAP_X509_GN_OTHERNAME = SLAP_X509_OPTION + 0, + SLAP_X509_GN_RFC822NAME = SLAP_X509_OPTION + 1, + SLAP_X509_GN_DNSNAME = SLAP_X509_OPTION + 2, + SLAP_X509_GN_X400ADDRESS = SLAP_X509_OPTION + 3, + SLAP_X509_GN_DIRECTORYNAME = SLAP_X509_OPTION + 4, + SLAP_X509_GN_EDIPARTYNAME = SLAP_X509_OPTION + 5, + SLAP_X509_GN_URI = SLAP_X509_OPTION + 6, + SLAP_X509_GN_IPADDRESS = SLAP_X509_OPTION + 7, + SLAP_X509_GN_REGISTEREDID = SLAP_X509_OPTION + 8 +}; + +/* X.509 PMI related stuff */ +enum { + SLAP_X509AC_V1 = 0, + SLAP_X509AC_V2 = 1 +}; + +enum { + SLAP_X509AC_ISSUER = SLAP_X509_OPTION + 0 +}; + +/* X.509 certificate validation */ +static int +certificateValidate( Syntax *syntax, struct berval *in ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_tag_t tag; + ber_len_t len; + ber_int_t version = SLAP_X509_V1; + + if ( BER_BVISNULL( in ) || BER_BVISEMPTY( in )) + return LDAP_INVALID_SYNTAX; + + ber_init2( ber, in, LBER_USE_DER ); + tag = ber_skip_tag( ber, &len ); /* Signed wrapper */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + tag = ber_skip_tag( ber, &len ); /* Sequence */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + tag = ber_peek_tag( ber, &len ); + /* Optional version */ + if ( tag == SLAP_X509_OPT_C_VERSION ) { + tag = ber_skip_tag( ber, &len ); + tag = ber_get_int( ber, &version ); + if ( tag != LBER_INTEGER ) return LDAP_INVALID_SYNTAX; + } + /* NOTE: don't try to parse Serial, because it might be longer + * than sizeof(ber_int_t); deferred to certificateExactNormalize() */ + tag = ber_skip_tag( ber, &len ); /* Serial */ + if ( tag != LBER_INTEGER ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); /* Signature Algorithm */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); /* Issuer DN */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); /* Validity */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); /* Subject DN */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); /* Subject PublicKeyInfo */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); + if ( tag == SLAP_X509_OPT_C_ISSUERUNIQUEID ) { /* issuerUniqueID */ + if ( version < SLAP_X509_V2 ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); + } + if ( tag == SLAP_X509_OPT_C_SUBJECTUNIQUEID ) { /* subjectUniqueID */ + if ( version < SLAP_X509_V2 ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); + } + if ( tag == SLAP_X509_OPT_C_EXTENSIONS ) { /* Extensions */ + if ( version < SLAP_X509_V3 ) return LDAP_INVALID_SYNTAX; + tag = ber_skip_tag( ber, &len ); + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); + } + /* signatureAlgorithm */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); + /* Signature */ + if ( tag != LBER_BITSTRING ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); + /* Must be at end now */ + if ( len || tag != LBER_DEFAULT ) return LDAP_INVALID_SYNTAX; + return LDAP_SUCCESS; +} + +/* X.509 certificate list validation */ +static int +checkTime( struct berval *in, struct berval *out ); + +static int +certificateListValidate( Syntax *syntax, struct berval *in ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_tag_t tag; + ber_len_t len, wrapper_len; + char *wrapper_start; + int wrapper_ok = 0; + ber_int_t version = SLAP_X509_V1; + struct berval bvdn, bvtu; + + ber_init2( ber, in, LBER_USE_DER ); + tag = ber_skip_tag( ber, &wrapper_len ); /* Signed wrapper */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + wrapper_start = ber->ber_ptr; + tag = ber_skip_tag( ber, &len ); /* Sequence */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + tag = ber_peek_tag( ber, &len ); + /* Optional version */ + if ( tag == LBER_INTEGER ) { + tag = ber_get_int( ber, &version ); + if ( tag != LBER_INTEGER || version != SLAP_X509_V2 ) return LDAP_INVALID_SYNTAX; + } + tag = ber_skip_tag( ber, &len ); /* Signature Algorithm */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + tag = ber_peek_tag( ber, &len ); /* Issuer DN */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + len = ber_ptrlen( ber ); + bvdn.bv_val = in->bv_val + len; + bvdn.bv_len = in->bv_len - len; + tag = ber_skip_tag( ber, &len ); + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); /* thisUpdate */ + /* Time is a CHOICE { UTCTime, GeneralizedTime } */ + if ( tag != SLAP_TAG_UTCTIME && tag != SLAP_TAG_GENERALIZEDTIME ) return LDAP_INVALID_SYNTAX; + bvtu.bv_val = (char *)ber->ber_ptr; + bvtu.bv_len = len; + ber_skip_data( ber, len ); + /* Optional nextUpdate */ + tag = ber_skip_tag( ber, &len ); + if ( tag == SLAP_TAG_UTCTIME || tag == SLAP_TAG_GENERALIZEDTIME ) { + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); + } + /* revokedCertificates - Sequence of Sequence, Optional */ + if ( tag == LBER_SEQUENCE ) { + ber_len_t seqlen; + ber_tag_t stag; + stag = ber_peek_tag( ber, &seqlen ); + if ( stag == LBER_SEQUENCE || !len ) { + /* RFC5280 requires non-empty, but X.509(2005) allows empty. */ + if ( len ) + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); + } + } + /* Optional Extensions - Sequence of Sequence */ + if ( tag == SLAP_X509_OPT_CL_CRLEXTENSIONS ) { /* ? */ + ber_len_t seqlen; + if ( version != SLAP_X509_V2 ) return LDAP_INVALID_SYNTAX; + tag = ber_peek_tag( ber, &seqlen ); + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); + } + /* signatureAlgorithm */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); + /* Signature */ + if ( tag != LBER_BITSTRING ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + if ( ber->ber_ptr == wrapper_start + wrapper_len ) wrapper_ok = 1; + tag = ber_skip_tag( ber, &len ); + /* Must be at end now */ + /* NOTE: OpenSSL tolerates CL with garbage past the end */ + if ( len || tag != LBER_DEFAULT ) { + struct berval issuer_dn = BER_BVNULL, thisUpdate; + char tubuf[STRLENOF("YYYYmmddHHMMSSZ") + 1]; + int rc; + + if ( ! wrapper_ok ) { + return LDAP_INVALID_SYNTAX; + } + + rc = dnX509normalize( &bvdn, &issuer_dn ); + if ( rc != LDAP_SUCCESS ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + + thisUpdate.bv_val = tubuf; + thisUpdate.bv_len = sizeof(tubuf); + if ( checkTime( &bvtu, &thisUpdate ) ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + + Debug( LDAP_DEBUG_ANY, + "certificateListValidate issuer=\"%s\", thisUpdate=%s: extra cruft past end of certificateList\n", + issuer_dn.bv_val, thisUpdate.bv_val, 0 ); + +done:; + if ( ! BER_BVISNULL( &issuer_dn ) ) { + ber_memfree( issuer_dn.bv_val ); + } + + return rc; + } + + return LDAP_SUCCESS; +} + +/* X.509 PMI Attribute Certificate Validate */ +static int +attributeCertificateValidate( Syntax *syntax, struct berval *in ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_tag_t tag; + ber_len_t len; + ber_int_t version; + int cont = 0; + + ber_init2( ber, in, LBER_USE_DER ); + + tag = ber_skip_tag( ber, &len ); /* Signed wrapper */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + + tag = ber_skip_tag( ber, &len ); /* Sequence */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + + tag = ber_peek_tag( ber, &len ); /* Version */ + if ( tag != LBER_INTEGER ) return LDAP_INVALID_SYNTAX; + tag = ber_get_int( ber, &version ); /* X.509 only allows v2 */ + if ( version != SLAP_X509AC_V2 ) return LDAP_INVALID_SYNTAX; + + tag = ber_skip_tag( ber, &len ); /* Holder */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + + tag = ber_skip_tag( ber, &len ); /* Issuer */ + if ( tag != SLAP_X509AC_ISSUER ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + + tag = ber_skip_tag( ber, &len ); /* Signature */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + + tag = ber_skip_tag( ber, &len ); /* Serial number */ + if ( tag != LBER_INTEGER ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + + tag = ber_skip_tag( ber, &len ); /* AttCertValidityPeriod */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + + tag = ber_skip_tag( ber, &len ); /* Attributes */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + + tag = ber_peek_tag( ber, &len ); + + if ( tag == LBER_BITSTRING ) { /* issuerUniqueID */ + tag = ber_skip_tag( ber, &len ); + ber_skip_data( ber, len ); + tag = ber_peek_tag( ber, &len ); + } + + if ( tag == LBER_SEQUENCE ) { /* extensions or signatureAlgorithm */ + tag = ber_skip_tag( ber, &len ); + ber_skip_data( ber, len ); + cont++; + tag = ber_peek_tag( ber, &len ); + } + + if ( tag == LBER_SEQUENCE ) { /* signatureAlgorithm */ + tag = ber_skip_tag( ber, &len ); + ber_skip_data( ber, len ); + cont++; + tag = ber_peek_tag( ber, &len ); + } + + if ( tag == LBER_BITSTRING ) { /* Signature */ + tag = ber_skip_tag( ber, &len ); + ber_skip_data( ber, len ); + cont++; + tag = ber_peek_tag( ber, &len ); + } + + /* Must be at end now */ + if ( len != 0 || tag != LBER_DEFAULT || cont < 2 ) return LDAP_INVALID_SYNTAX; + + return LDAP_SUCCESS; +} + +int +octetStringMatch( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + struct berval *asserted = (struct berval *) assertedValue; + ber_slen_t d = (ber_slen_t) value->bv_len - (ber_slen_t) asserted->bv_len; + + /* For speed, order first by length, then by contents */ + *matchp = d ? (sizeof(d) == sizeof(int) ? d : d < 0 ? -1 : 1) + : memcmp( value->bv_val, asserted->bv_val, value->bv_len ); + + return LDAP_SUCCESS; +} + +int +octetStringOrderingMatch( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + struct berval *asserted = (struct berval *) assertedValue; + ber_len_t v_len = value->bv_len; + ber_len_t av_len = asserted->bv_len; + + int match = memcmp( value->bv_val, asserted->bv_val, + (v_len < av_len ? v_len : av_len) ); + + if( match == 0 ) + match = sizeof(v_len) == sizeof(int) + ? (int) v_len - (int) av_len + : v_len < av_len ? -1 : v_len > av_len; + + /* If used in extensible match filter, match if value < asserted */ + if ( flags & SLAP_MR_EXT ) + match = (match >= 0); + + *matchp = match; + return LDAP_SUCCESS; +} + +/* Initialize HASHcontext from match type and schema info */ +static void +hashPreset( + HASH_CONTEXT *HASHcontext, + struct berval *prefix, + char pre, + Syntax *syntax, + MatchingRule *mr) +{ + HASH_Init(HASHcontext); + if(prefix && prefix->bv_len > 0) { + HASH_Update(HASHcontext, + (unsigned char *)prefix->bv_val, prefix->bv_len); + } + if(pre) HASH_Update(HASHcontext, (unsigned char*)&pre, sizeof(pre)); + HASH_Update(HASHcontext, (unsigned char*)syntax->ssyn_oid, syntax->ssyn_oidlen); + HASH_Update(HASHcontext, (unsigned char*)mr->smr_oid, mr->smr_oidlen); + return; +} + +/* Set HASHdigest from HASHcontext and value:len */ +static void +hashIter( + HASH_CONTEXT *HASHcontext, + unsigned char *HASHdigest, + unsigned char *value, + int len) +{ + HASH_CONTEXT ctx = *HASHcontext; + HASH_Update( &ctx, value, len ); + HASH_Final( HASHdigest, &ctx ); +} + +/* Index generation function: Attribute values -> index hash keys */ +int octetStringIndexer( + slap_mask_t use, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *prefix, + BerVarray values, + BerVarray *keysp, + void *ctx ) +{ + int i; + size_t slen, mlen; + BerVarray keys; + HASH_CONTEXT HASHcontext; + unsigned char HASHdigest[HASH_BYTES]; + struct berval digest; + digest.bv_val = (char *)HASHdigest; + digest.bv_len = sizeof(HASHdigest); + + for( i=0; !BER_BVISNULL( &values[i] ); i++ ) { + /* just count them */ + } + + /* we should have at least one value at this point */ + assert( i > 0 ); + + keys = slap_sl_malloc( sizeof( struct berval ) * (i+1), ctx ); + + slen = syntax->ssyn_oidlen; + mlen = mr->smr_oidlen; + + hashPreset( &HASHcontext, prefix, 0, syntax, mr); + for( i=0; !BER_BVISNULL( &values[i] ); i++ ) { + hashIter( &HASHcontext, HASHdigest, + (unsigned char *)values[i].bv_val, values[i].bv_len ); + ber_dupbv_x( &keys[i], &digest, ctx ); + } + + BER_BVZERO( &keys[i] ); + + *keysp = keys; + + return LDAP_SUCCESS; +} + +/* Index generation function: Asserted value -> index hash key */ +int octetStringFilter( + slap_mask_t use, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *prefix, + void * assertedValue, + BerVarray *keysp, + void *ctx ) +{ + size_t slen, mlen; + BerVarray keys; + HASH_CONTEXT HASHcontext; + unsigned char HASHdigest[HASH_BYTES]; + struct berval *value = (struct berval *) assertedValue; + struct berval digest; + digest.bv_val = (char *)HASHdigest; + digest.bv_len = sizeof(HASHdigest); + + slen = syntax->ssyn_oidlen; + mlen = mr->smr_oidlen; + + keys = slap_sl_malloc( sizeof( struct berval ) * 2, ctx ); + + hashPreset( &HASHcontext, prefix, 0, syntax, mr ); + hashIter( &HASHcontext, HASHdigest, + (unsigned char *)value->bv_val, value->bv_len ); + + ber_dupbv_x( keys, &digest, ctx ); + BER_BVZERO( &keys[1] ); + + *keysp = keys; + + return LDAP_SUCCESS; +} + +static int +octetStringSubstringsMatch( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + int match = 0; + SubstringsAssertion *sub = assertedValue; + struct berval left = *value; + int i; + ber_len_t inlen = 0; + + /* Add up asserted input length */ + if ( !BER_BVISNULL( &sub->sa_initial ) ) { + inlen += sub->sa_initial.bv_len; + } + if ( sub->sa_any ) { + for ( i = 0; !BER_BVISNULL( &sub->sa_any[i] ); i++ ) { + inlen += sub->sa_any[i].bv_len; + } + } + if ( !BER_BVISNULL( &sub->sa_final ) ) { + inlen += sub->sa_final.bv_len; + } + + if ( !BER_BVISNULL( &sub->sa_initial ) ) { + if ( inlen > left.bv_len ) { + match = 1; + goto done; + } + + match = memcmp( sub->sa_initial.bv_val, left.bv_val, + sub->sa_initial.bv_len ); + + if ( match != 0 ) { + goto done; + } + + left.bv_val += sub->sa_initial.bv_len; + left.bv_len -= sub->sa_initial.bv_len; + inlen -= sub->sa_initial.bv_len; + } + + if ( !BER_BVISNULL( &sub->sa_final ) ) { + if ( inlen > left.bv_len ) { + match = 1; + goto done; + } + + match = memcmp( sub->sa_final.bv_val, + &left.bv_val[left.bv_len - sub->sa_final.bv_len], + sub->sa_final.bv_len ); + + if ( match != 0 ) { + goto done; + } + + left.bv_len -= sub->sa_final.bv_len; + inlen -= sub->sa_final.bv_len; + } + + if ( sub->sa_any ) { + for ( i = 0; !BER_BVISNULL( &sub->sa_any[i] ); i++ ) { + ber_len_t idx; + char *p; + +retry: + if ( inlen > left.bv_len ) { + /* not enough length */ + match = 1; + goto done; + } + + if ( BER_BVISEMPTY( &sub->sa_any[i] ) ) { + continue; + } + + p = memchr( left.bv_val, *sub->sa_any[i].bv_val, left.bv_len ); + + if( p == NULL ) { + match = 1; + goto done; + } + + idx = p - left.bv_val; + + if ( idx >= left.bv_len ) { + /* this shouldn't happen */ + return LDAP_OTHER; + } + + left.bv_val = p; + left.bv_len -= idx; + + if ( sub->sa_any[i].bv_len > left.bv_len ) { + /* not enough left */ + match = 1; + goto done; + } + + match = memcmp( left.bv_val, + sub->sa_any[i].bv_val, + sub->sa_any[i].bv_len ); + + if ( match != 0 ) { + left.bv_val++; + left.bv_len--; + goto retry; + } + + left.bv_val += sub->sa_any[i].bv_len; + left.bv_len -= sub->sa_any[i].bv_len; + inlen -= sub->sa_any[i].bv_len; + } + } + +done: + *matchp = match; + return LDAP_SUCCESS; +} + +/* Substring index generation function: Attribute values -> index hash keys */ +static int +octetStringSubstringsIndexer( + slap_mask_t use, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *prefix, + BerVarray values, + BerVarray *keysp, + void *ctx ) +{ + ber_len_t i, nkeys; + size_t slen, mlen; + BerVarray keys; + + HASH_CONTEXT HCany, HCini, HCfin; + unsigned char HASHdigest[HASH_BYTES]; + struct berval digest; + digest.bv_val = (char *)HASHdigest; + digest.bv_len = sizeof(HASHdigest); + + nkeys = 0; + + for ( i = 0; !BER_BVISNULL( &values[i] ); i++ ) { + /* count number of indices to generate */ + if( flags & SLAP_INDEX_SUBSTR_INITIAL ) { + if( values[i].bv_len >= index_substr_if_maxlen ) { + nkeys += index_substr_if_maxlen - + (index_substr_if_minlen - 1); + } else if( values[i].bv_len >= index_substr_if_minlen ) { + nkeys += values[i].bv_len - (index_substr_if_minlen - 1); + } + } + + if( flags & SLAP_INDEX_SUBSTR_ANY ) { + if( values[i].bv_len >= index_substr_any_len ) { + nkeys += values[i].bv_len - (index_substr_any_len - 1); + } + } + + if( flags & SLAP_INDEX_SUBSTR_FINAL ) { + if( values[i].bv_len >= index_substr_if_maxlen ) { + nkeys += index_substr_if_maxlen - + (index_substr_if_minlen - 1); + } else if( values[i].bv_len >= index_substr_if_minlen ) { + nkeys += values[i].bv_len - (index_substr_if_minlen - 1); + } + } + } + + if( nkeys == 0 ) { + /* no keys to generate */ + *keysp = NULL; + return LDAP_SUCCESS; + } + + keys = slap_sl_malloc( sizeof( struct berval ) * (nkeys+1), ctx ); + + slen = syntax->ssyn_oidlen; + mlen = mr->smr_oidlen; + + if ( flags & SLAP_INDEX_SUBSTR_ANY ) + hashPreset( &HCany, prefix, SLAP_INDEX_SUBSTR_PREFIX, syntax, mr ); + if( flags & SLAP_INDEX_SUBSTR_INITIAL ) + hashPreset( &HCini, prefix, SLAP_INDEX_SUBSTR_INITIAL_PREFIX, syntax, mr ); + if( flags & SLAP_INDEX_SUBSTR_FINAL ) + hashPreset( &HCfin, prefix, SLAP_INDEX_SUBSTR_FINAL_PREFIX, syntax, mr ); + + nkeys = 0; + for ( i = 0; !BER_BVISNULL( &values[i] ); i++ ) { + ber_len_t j,max; + + if( ( flags & SLAP_INDEX_SUBSTR_ANY ) && + ( values[i].bv_len >= index_substr_any_len ) ) + { + max = values[i].bv_len - (index_substr_any_len - 1); + + for( j=0; j<max; j++ ) { + hashIter( &HCany, HASHdigest, + (unsigned char *)&values[i].bv_val[j], + index_substr_any_len ); + ber_dupbv_x( &keys[nkeys++], &digest, ctx ); + } + } + + /* skip if too short */ + if( values[i].bv_len < index_substr_if_minlen ) continue; + + max = index_substr_if_maxlen < values[i].bv_len + ? index_substr_if_maxlen : values[i].bv_len; + + for( j=index_substr_if_minlen; j<=max; j++ ) { + + if( flags & SLAP_INDEX_SUBSTR_INITIAL ) { + hashIter( &HCini, HASHdigest, + (unsigned char *)values[i].bv_val, j ); + ber_dupbv_x( &keys[nkeys++], &digest, ctx ); + } + + if( flags & SLAP_INDEX_SUBSTR_FINAL ) { + hashIter( &HCfin, HASHdigest, + (unsigned char *)&values[i].bv_val[values[i].bv_len-j], j ); + ber_dupbv_x( &keys[nkeys++], &digest, ctx ); + } + + } + } + + if( nkeys > 0 ) { + BER_BVZERO( &keys[nkeys] ); + *keysp = keys; + } else { + ch_free( keys ); + *keysp = NULL; + } + + return LDAP_SUCCESS; +} + +/* Substring index generation function: Assertion value -> index hash keys */ +static int +octetStringSubstringsFilter ( + slap_mask_t use, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *prefix, + void * assertedValue, + BerVarray *keysp, + void *ctx) +{ + SubstringsAssertion *sa; + char pre; + ber_len_t nkeys = 0; + size_t slen, mlen, klen; + BerVarray keys; + HASH_CONTEXT HASHcontext; + unsigned char HASHdigest[HASH_BYTES]; + struct berval *value; + struct berval digest; + + sa = (SubstringsAssertion *) assertedValue; + + if( flags & SLAP_INDEX_SUBSTR_INITIAL && + !BER_BVISNULL( &sa->sa_initial ) && + sa->sa_initial.bv_len >= index_substr_if_minlen ) + { + nkeys++; + if ( sa->sa_initial.bv_len > index_substr_if_maxlen && + ( flags & SLAP_INDEX_SUBSTR_ANY )) + { + nkeys += 1 + (sa->sa_initial.bv_len - index_substr_if_maxlen) / index_substr_any_step; + } + } + + if ( flags & SLAP_INDEX_SUBSTR_ANY && sa->sa_any != NULL ) { + ber_len_t i; + for( i=0; !BER_BVISNULL( &sa->sa_any[i] ); i++ ) { + if( sa->sa_any[i].bv_len >= index_substr_any_len ) { + /* don't bother accounting with stepping */ + nkeys += sa->sa_any[i].bv_len - + ( index_substr_any_len - 1 ); + } + } + } + + if( flags & SLAP_INDEX_SUBSTR_FINAL && + !BER_BVISNULL( &sa->sa_final ) && + sa->sa_final.bv_len >= index_substr_if_minlen ) + { + nkeys++; + if ( sa->sa_final.bv_len > index_substr_if_maxlen && + ( flags & SLAP_INDEX_SUBSTR_ANY )) + { + nkeys += 1 + (sa->sa_final.bv_len - index_substr_if_maxlen) / index_substr_any_step; + } + } + + if( nkeys == 0 ) { + *keysp = NULL; + return LDAP_SUCCESS; + } + + digest.bv_val = (char *)HASHdigest; + digest.bv_len = sizeof(HASHdigest); + + slen = syntax->ssyn_oidlen; + mlen = mr->smr_oidlen; + + keys = slap_sl_malloc( sizeof( struct berval ) * (nkeys+1), ctx ); + nkeys = 0; + + if( flags & SLAP_INDEX_SUBSTR_INITIAL && + !BER_BVISNULL( &sa->sa_initial ) && + sa->sa_initial.bv_len >= index_substr_if_minlen ) + { + pre = SLAP_INDEX_SUBSTR_INITIAL_PREFIX; + value = &sa->sa_initial; + + klen = index_substr_if_maxlen < value->bv_len + ? index_substr_if_maxlen : value->bv_len; + + hashPreset( &HASHcontext, prefix, pre, syntax, mr ); + hashIter( &HASHcontext, HASHdigest, + (unsigned char *)value->bv_val, klen ); + ber_dupbv_x( &keys[nkeys++], &digest, ctx ); + + /* If initial is too long and we have subany indexed, use it + * to match the excess... + */ + if (value->bv_len > index_substr_if_maxlen && (flags & SLAP_INDEX_SUBSTR_ANY)) + { + ber_len_t j; + pre = SLAP_INDEX_SUBSTR_PREFIX; + hashPreset( &HASHcontext, prefix, pre, syntax, mr); + for ( j=index_substr_if_maxlen-1; j <= value->bv_len - index_substr_any_len; j+=index_substr_any_step ) + { + hashIter( &HASHcontext, HASHdigest, + (unsigned char *)&value->bv_val[j], index_substr_any_len ); + ber_dupbv_x( &keys[nkeys++], &digest, ctx ); + } + } + } + + if( flags & SLAP_INDEX_SUBSTR_ANY && sa->sa_any != NULL ) { + ber_len_t i, j; + pre = SLAP_INDEX_SUBSTR_PREFIX; + klen = index_substr_any_len; + + for( i=0; !BER_BVISNULL( &sa->sa_any[i] ); i++ ) { + if( sa->sa_any[i].bv_len < index_substr_any_len ) { + continue; + } + + value = &sa->sa_any[i]; + + hashPreset( &HASHcontext, prefix, pre, syntax, mr); + for(j=0; + j <= value->bv_len - index_substr_any_len; + j += index_substr_any_step ) + { + hashIter( &HASHcontext, HASHdigest, + (unsigned char *)&value->bv_val[j], klen ); + ber_dupbv_x( &keys[nkeys++], &digest, ctx ); + } + } + } + + if( flags & SLAP_INDEX_SUBSTR_FINAL && + !BER_BVISNULL( &sa->sa_final ) && + sa->sa_final.bv_len >= index_substr_if_minlen ) + { + pre = SLAP_INDEX_SUBSTR_FINAL_PREFIX; + value = &sa->sa_final; + + klen = index_substr_if_maxlen < value->bv_len + ? index_substr_if_maxlen : value->bv_len; + + hashPreset( &HASHcontext, prefix, pre, syntax, mr ); + hashIter( &HASHcontext, HASHdigest, + (unsigned char *)&value->bv_val[value->bv_len-klen], klen ); + ber_dupbv_x( &keys[nkeys++], &digest, ctx ); + + /* If final is too long and we have subany indexed, use it + * to match the excess... + */ + if (value->bv_len > index_substr_if_maxlen && (flags & SLAP_INDEX_SUBSTR_ANY)) + { + ber_len_t j; + pre = SLAP_INDEX_SUBSTR_PREFIX; + hashPreset( &HASHcontext, prefix, pre, syntax, mr); + for ( j=0; j <= value->bv_len - index_substr_if_maxlen; j+=index_substr_any_step ) + { + hashIter( &HASHcontext, HASHdigest, + (unsigned char *)&value->bv_val[j], index_substr_any_len ); + ber_dupbv_x( &keys[nkeys++], &digest, ctx ); + } + } + } + + if( nkeys > 0 ) { + BER_BVZERO( &keys[nkeys] ); + *keysp = keys; + } else { + ch_free( keys ); + *keysp = NULL; + } + + return LDAP_SUCCESS; +} + +static int +bitStringValidate( + Syntax *syntax, + struct berval *in ) +{ + ber_len_t i; + + /* very unforgiving validation, requires no normalization + * before simplistic matching + */ + if( in->bv_len < 3 ) { + return LDAP_INVALID_SYNTAX; + } + + /* RFC 4517 Section 3.3.2 Bit String: + * BitString = SQUOTE *binary-digit SQUOTE "B" + * binary-digit = "0" / "1" + * + * where SQUOTE [RFC4512] is + * SQUOTE = %x27 ; single quote ("'") + * + * Example: '0101111101'B + */ + + if( in->bv_val[0] != '\'' || + in->bv_val[in->bv_len - 2] != '\'' || + in->bv_val[in->bv_len - 1] != 'B' ) + { + return LDAP_INVALID_SYNTAX; + } + + for( i = in->bv_len - 3; i > 0; i-- ) { + if( in->bv_val[i] != '0' && in->bv_val[i] != '1' ) { + return LDAP_INVALID_SYNTAX; + } + } + + return LDAP_SUCCESS; +} + +/* + * Syntaxes from RFC 4517 + * + +3.3.2. Bit String + + A value of the Bit String syntax is a sequence of binary digits. The + LDAP-specific encoding of a value of this syntax is defined by the + following ABNF: + + BitString = SQUOTE *binary-digit SQUOTE "B" + + binary-digit = "0" / "1" + + The <SQUOTE> rule is defined in [MODELS]. + + Example: + '0101111101'B + + The LDAP definition for the Bit String syntax is: + + ( 1.3.6.1.4.1.1466.115.121.1.6 DESC 'Bit String' ) + + This syntax corresponds to the BIT STRING ASN.1 type from [ASN.1]. + + ... + +3.3.21. Name and Optional UID + + A value of the Name and Optional UID syntax is the distinguished name + [MODELS] of an entity optionally accompanied by a unique identifier + that serves to differentiate the entity from others with an identical + distinguished name. + + The LDAP-specific encoding of a value of this syntax is defined by + the following ABNF: + + NameAndOptionalUID = distinguishedName [ SHARP BitString ] + + The <BitString> rule is defined in Section 3.3.2. The + <distinguishedName> rule is defined in [LDAPDN]. The <SHARP> rule is + defined in [MODELS]. + + Note that although the '#' character may occur in the string + representation of a distinguished name, no additional escaping of + this character is performed when a <distinguishedName> is encoded in + a <NameAndOptionalUID>. + + Example: + 1.3.6.1.4.1.1466.0=#04024869,O=Test,C=GB#'0101'B + + The LDAP definition for the Name and Optional UID syntax is: + + ( 1.3.6.1.4.1.1466.115.121.1.34 DESC 'Name And Optional UID' ) + + This syntax corresponds to the NameAndOptionalUID ASN.1 type from + [X.520]. + + * + * RFC 4512 says: + * + +1.4. Common ABNF Productions + + ... + SHARP = %x23 ; octothorpe (or sharp sign) ("#") + ... + SQUOTE = %x27 ; single quote ("'") + ... + + * + * Note: + * RFC 4514 clarifies that SHARP, i.e. "#", doesn't have to + * be escaped except when at the beginning of a value, the + * definition of Name and Optional UID appears to be flawed, + * because there is no clear means to determine whether the + * UID part is present or not. + * + * Example: + * + * cn=Someone,dc=example,dc=com#'1'B + * + * could be either a NameAndOptionalUID with trailing UID, i.e. + * + * DN = "cn=Someone,dc=example,dc=com" + * UID = "'1'B" + * + * or a NameAndOptionalUID with no trailing UID, and the AVA + * in the last RDN made of + * + * attributeType = dc + * attributeValue = com#'1'B + * + * in fact "com#'1'B" is a valid IA5 string. + * + * As a consequence, current slapd code takes the presence of + * #<valid BitString> at the end of the string representation + * of a NameAndOptionalUID to mean this is indeed a BitString. + * This is quite arbitrary - it has changed the past and might + * change in the future. + */ + + +static int +nameUIDValidate( + Syntax *syntax, + struct berval *in ) +{ + int rc; + struct berval dn, uid; + + if( BER_BVISEMPTY( in ) ) return LDAP_SUCCESS; + + ber_dupbv( &dn, in ); + if( !dn.bv_val ) return LDAP_OTHER; + + /* if there's a "#", try bitStringValidate()... */ + uid.bv_val = strrchr( dn.bv_val, '#' ); + if ( !BER_BVISNULL( &uid ) ) { + uid.bv_val++; + uid.bv_len = dn.bv_len - ( uid.bv_val - dn.bv_val ); + + rc = bitStringValidate( NULL, &uid ); + if ( rc == LDAP_SUCCESS ) { + /* in case of success, trim the UID, + * otherwise treat it as part of the DN */ + dn.bv_len -= uid.bv_len + 1; + uid.bv_val[-1] = '\0'; + } + } + + rc = dnValidate( NULL, &dn ); + + ber_memfree( dn.bv_val ); + return rc; +} + +int +nameUIDPretty( + Syntax *syntax, + struct berval *val, + struct berval *out, + void *ctx ) +{ + assert( val != NULL ); + assert( out != NULL ); + + + Debug( LDAP_DEBUG_TRACE, ">>> nameUIDPretty: <%s>\n", val->bv_val, 0, 0 ); + + if( BER_BVISEMPTY( val ) ) { + ber_dupbv_x( out, val, ctx ); + + } else if ( val->bv_len > SLAP_LDAPDN_MAXLEN ) { + return LDAP_INVALID_SYNTAX; + + } else { + int rc; + struct berval dnval = *val; + struct berval uidval = BER_BVNULL; + + uidval.bv_val = strrchr( val->bv_val, '#' ); + if ( !BER_BVISNULL( &uidval ) ) { + uidval.bv_val++; + uidval.bv_len = val->bv_len - ( uidval.bv_val - val->bv_val ); + + rc = bitStringValidate( NULL, &uidval ); + + if ( rc == LDAP_SUCCESS ) { + ber_dupbv_x( &dnval, val, ctx ); + uidval.bv_val--; + dnval.bv_len -= ++uidval.bv_len; + dnval.bv_val[dnval.bv_len] = '\0'; + + } else { + BER_BVZERO( &uidval ); + } + } + + rc = dnPretty( syntax, &dnval, out, ctx ); + if ( dnval.bv_val != val->bv_val ) { + slap_sl_free( dnval.bv_val, ctx ); + } + if( rc != LDAP_SUCCESS ) { + return rc; + } + + if( !BER_BVISNULL( &uidval ) ) { + char *tmp; + + tmp = slap_sl_realloc( out->bv_val, out->bv_len + + uidval.bv_len + 1, + ctx ); + if( tmp == NULL ) { + ber_memfree_x( out->bv_val, ctx ); + return LDAP_OTHER; + } + out->bv_val = tmp; + memcpy( out->bv_val + out->bv_len, uidval.bv_val, uidval.bv_len ); + out->bv_len += uidval.bv_len; + out->bv_val[out->bv_len] = '\0'; + } + } + + Debug( LDAP_DEBUG_TRACE, "<<< nameUIDPretty: <%s>\n", out->bv_val, 0, 0 ); + + return LDAP_SUCCESS; +} + +static int +uniqueMemberNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + struct berval out; + int rc; + + assert( SLAP_MR_IS_VALUE_OF_SYNTAX( usage ) != 0 ); + + ber_dupbv_x( &out, val, ctx ); + if ( BER_BVISEMPTY( &out ) ) { + *normalized = out; + + } else { + struct berval uid = BER_BVNULL; + + uid.bv_val = strrchr( out.bv_val, '#' ); + if ( !BER_BVISNULL( &uid ) ) { + uid.bv_val++; + uid.bv_len = out.bv_len - ( uid.bv_val - out.bv_val ); + + rc = bitStringValidate( NULL, &uid ); + if ( rc == LDAP_SUCCESS ) { + uid.bv_val[-1] = '\0'; + out.bv_len -= uid.bv_len + 1; + } else { + BER_BVZERO( &uid ); + } + } + + rc = dnNormalize( 0, NULL, NULL, &out, normalized, ctx ); + + if( rc != LDAP_SUCCESS ) { + slap_sl_free( out.bv_val, ctx ); + return LDAP_INVALID_SYNTAX; + } + + if( !BER_BVISNULL( &uid ) ) { + char *tmp; + + tmp = ch_realloc( normalized->bv_val, + normalized->bv_len + uid.bv_len + + STRLENOF("#") + 1 ); + if ( tmp == NULL ) { + ber_memfree_x( normalized->bv_val, ctx ); + return LDAP_OTHER; + } + + normalized->bv_val = tmp; + + /* insert the separator */ + normalized->bv_val[normalized->bv_len++] = '#'; + + /* append the UID */ + AC_MEMCPY( &normalized->bv_val[normalized->bv_len], + uid.bv_val, uid.bv_len ); + normalized->bv_len += uid.bv_len; + + /* terminate */ + normalized->bv_val[normalized->bv_len] = '\0'; + } + + slap_sl_free( out.bv_val, ctx ); + } + + return LDAP_SUCCESS; +} + +static int +uniqueMemberMatch( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + int match; + struct berval *asserted = (struct berval *) assertedValue; + struct berval assertedDN = *asserted; + struct berval assertedUID = BER_BVNULL; + struct berval valueDN = *value; + struct berval valueUID = BER_BVNULL; + int approx = ((flags & SLAP_MR_EQUALITY_APPROX) == SLAP_MR_EQUALITY_APPROX); + + if ( !BER_BVISEMPTY( asserted ) ) { + assertedUID.bv_val = strrchr( assertedDN.bv_val, '#' ); + if ( !BER_BVISNULL( &assertedUID ) ) { + assertedUID.bv_val++; + assertedUID.bv_len = assertedDN.bv_len + - ( assertedUID.bv_val - assertedDN.bv_val ); + + if ( bitStringValidate( NULL, &assertedUID ) == LDAP_SUCCESS ) { + assertedDN.bv_len -= assertedUID.bv_len + 1; + + } else { + BER_BVZERO( &assertedUID ); + } + } + } + + if ( !BER_BVISEMPTY( value ) ) { + + valueUID.bv_val = strrchr( valueDN.bv_val, '#' ); + if ( !BER_BVISNULL( &valueUID ) ) { + valueUID.bv_val++; + valueUID.bv_len = valueDN.bv_len + - ( valueUID.bv_val - valueDN.bv_val ); + + if ( bitStringValidate( NULL, &valueUID ) == LDAP_SUCCESS ) { + valueDN.bv_len -= valueUID.bv_len + 1; + + } else { + BER_BVZERO( &valueUID ); + } + } + } + + if( valueUID.bv_len && assertedUID.bv_len ) { + ber_slen_t d; + d = (ber_slen_t) valueUID.bv_len - (ber_slen_t) assertedUID.bv_len; + if ( d ) { + *matchp = sizeof(d) == sizeof(int) ? d : d < 0 ? -1 : 1; + return LDAP_SUCCESS; + } + + match = memcmp( valueUID.bv_val, assertedUID.bv_val, valueUID.bv_len ); + if( match ) { + *matchp = match; + return LDAP_SUCCESS; + } + + } else if ( !approx && valueUID.bv_len ) { + match = -1; + *matchp = match; + return LDAP_SUCCESS; + + } else if ( !approx && assertedUID.bv_len ) { + match = 1; + *matchp = match; + return LDAP_SUCCESS; + } + + return dnMatch( matchp, flags, syntax, mr, &valueDN, &assertedDN ); +} + +static int +uniqueMemberIndexer( + slap_mask_t use, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *prefix, + BerVarray values, + BerVarray *keysp, + void *ctx ) +{ + BerVarray dnvalues; + int rc; + int i; + for( i=0; !BER_BVISNULL( &values[i] ); i++ ) { + /* just count them */ + } + assert( i > 0 ); + + dnvalues = slap_sl_malloc( sizeof( struct berval ) * (i+1), ctx ); + + for( i=0; !BER_BVISNULL( &values[i] ); i++ ) { + struct berval assertedDN = values[i]; + struct berval assertedUID = BER_BVNULL; + + if ( !BER_BVISEMPTY( &assertedDN ) ) { + assertedUID.bv_val = strrchr( assertedDN.bv_val, '#' ); + if ( !BER_BVISNULL( &assertedUID ) ) { + assertedUID.bv_val++; + assertedUID.bv_len = assertedDN.bv_len + - ( assertedUID.bv_val - assertedDN.bv_val ); + + if ( bitStringValidate( NULL, &assertedUID ) == LDAP_SUCCESS ) { + assertedDN.bv_len -= assertedUID.bv_len + 1; + + } else { + BER_BVZERO( &assertedUID ); + } + } + } + + dnvalues[i] = assertedDN; + } + BER_BVZERO( &dnvalues[i] ); + + rc = octetStringIndexer( use, flags, syntax, mr, prefix, + dnvalues, keysp, ctx ); + + slap_sl_free( dnvalues, ctx ); + return rc; +} + +static int +uniqueMemberFilter( + slap_mask_t use, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *prefix, + void * assertedValue, + BerVarray *keysp, + void *ctx ) +{ + struct berval *asserted = (struct berval *) assertedValue; + struct berval assertedDN = *asserted; + struct berval assertedUID = BER_BVNULL; + + if ( !BER_BVISEMPTY( asserted ) ) { + assertedUID.bv_val = strrchr( assertedDN.bv_val, '#' ); + if ( !BER_BVISNULL( &assertedUID ) ) { + assertedUID.bv_val++; + assertedUID.bv_len = assertedDN.bv_len + - ( assertedUID.bv_val - assertedDN.bv_val ); + + if ( bitStringValidate( NULL, &assertedUID ) == LDAP_SUCCESS ) { + assertedDN.bv_len -= assertedUID.bv_len + 1; + + } else { + BER_BVZERO( &assertedUID ); + } + } + } + + return octetStringFilter( use, flags, syntax, mr, prefix, + &assertedDN, keysp, ctx ); +} + + +/* + * Handling boolean syntax and matching is quite rigid. + * A more flexible approach would be to allow a variety + * of strings to be normalized and prettied into TRUE + * and FALSE. + */ +static int +booleanValidate( + Syntax *syntax, + struct berval *in ) +{ + /* very unforgiving validation, requires no normalization + * before simplistic matching + */ + + if( in->bv_len == 4 ) { + if( bvmatch( in, &slap_true_bv ) ) { + return LDAP_SUCCESS; + } + } else if( in->bv_len == 5 ) { + if( bvmatch( in, &slap_false_bv ) ) { + return LDAP_SUCCESS; + } + } + + return LDAP_INVALID_SYNTAX; +} + +static int +booleanMatch( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + /* simplistic matching allowed by rigid validation */ + struct berval *asserted = (struct berval *) assertedValue; + *matchp = (int) asserted->bv_len - (int) value->bv_len; + return LDAP_SUCCESS; +} + +/*------------------------------------------------------------------- +LDAP/X.500 string syntax / matching rules have a few oddities. This +comment attempts to detail how slapd(8) treats them. + +Summary: + StringSyntax X.500 LDAP Matching/Comments + DirectoryString CHOICE UTF8 i/e + ignore insignificant spaces + PrintableString subset subset i/e + ignore insignificant spaces + PrintableString subset subset i/e + ignore insignificant spaces + NumericString subset subset ignore all spaces + IA5String ASCII ASCII i/e + ignore insignificant spaces + TeletexString T.61 T.61 i/e + ignore insignificant spaces + + TelephoneNumber subset subset i + ignore all spaces and "-" + + See RFC 4518 for details. + + +Directory String - + In X.500(93), a directory string can be either a PrintableString, + a bmpString, or a UniversalString (e.g., UCS (a subset of Unicode)). + In later versions, more CHOICEs were added. In all cases the string + must be non-empty. + + In LDAPv3, a directory string is a UTF-8 encoded UCS string. + A directory string cannot be zero length. + + For matching, there are both case ignore and exact rules. Both + also require that "insignificant" spaces be ignored. + spaces before the first non-space are ignored; + spaces after the last non-space are ignored; + spaces after a space are ignored. + Note: by these rules (and as clarified in X.520), a string of only + spaces is to be treated as if held one space, not empty (which + would be a syntax error). + +NumericString + In ASN.1, numeric string is just a string of digits and spaces + and could be empty. However, in X.500, all attribute values of + numeric string carry a non-empty constraint. For example: + + internationalISDNNumber ATTRIBUTE ::= { + WITH SYNTAX InternationalISDNNumber + EQUALITY MATCHING RULE numericStringMatch + SUBSTRINGS MATCHING RULE numericStringSubstringsMatch + ID id-at-internationalISDNNumber } + InternationalISDNNumber ::= + NumericString (SIZE(1..ub-international-isdn-number)) + + Unforunately, some assertion values are don't carry the same + constraint (but its unclear how such an assertion could ever + be true). In LDAP, there is one syntax (numericString) not two + (numericString with constraint, numericString without constraint). + This should be treated as numericString with non-empty constraint. + Note that while someone may have no ISDN number, there are no ISDN + numbers which are zero length. + + In matching, spaces are ignored. + +PrintableString + In ASN.1, Printable string is just a string of printable characters + and can be empty. In X.500, semantics much like NumericString (see + serialNumber for a like example) excepting uses insignificant space + handling instead of ignore all spaces. They must be non-empty. + +IA5String + Basically same as PrintableString. There are no examples in X.500, + but same logic applies. Empty strings are allowed. + +-------------------------------------------------------------------*/ + +static int +UTF8StringValidate( + Syntax *syntax, + struct berval *in ) +{ + int len; + unsigned char *u = (unsigned char *)in->bv_val, *end = in->bv_val + in->bv_len; + + if( BER_BVISEMPTY( in ) && syntax == slap_schema.si_syn_directoryString ) { + /* directory strings cannot be empty */ + return LDAP_INVALID_SYNTAX; + } + + for( ; u < end; u += len ) { + /* get the length indicated by the first byte */ + len = LDAP_UTF8_CHARLEN2( u, len ); + + /* very basic checks */ + switch( len ) { + case 6: + if( (u[5] & 0xC0) != 0x80 ) { + return LDAP_INVALID_SYNTAX; + } + case 5: + if( (u[4] & 0xC0) != 0x80 ) { + return LDAP_INVALID_SYNTAX; + } + case 4: + if( (u[3] & 0xC0) != 0x80 ) { + return LDAP_INVALID_SYNTAX; + } + case 3: + if( (u[2] & 0xC0 )!= 0x80 ) { + return LDAP_INVALID_SYNTAX; + } + case 2: + if( (u[1] & 0xC0) != 0x80 ) { + return LDAP_INVALID_SYNTAX; + } + case 1: + /* CHARLEN already validated it */ + break; + default: + return LDAP_INVALID_SYNTAX; + } + + /* make sure len corresponds with the offset + to the next character */ + if( LDAP_UTF8_OFFSET( (char *)u ) != len ) return LDAP_INVALID_SYNTAX; + } + + if( u > end ) { + return LDAP_INVALID_SYNTAX; + } + + return LDAP_SUCCESS; +} + +static int +UTF8StringNormalize( + slap_mask_t use, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + struct berval tmp, nvalue; + int flags, wasspace; + ber_len_t i; + + assert( SLAP_MR_IS_VALUE_OF_SYNTAX( use ) != 0 ); + + if( BER_BVISNULL( val ) ) { + /* assume we're dealing with a syntax (e.g., UTF8String) + * which allows empty strings + */ + BER_BVZERO( normalized ); + return LDAP_SUCCESS; + } + + flags = SLAP_MR_ASSOCIATED( mr, slap_schema.si_mr_caseExactMatch ) + ? LDAP_UTF8_NOCASEFOLD : LDAP_UTF8_CASEFOLD; + flags |= ( ( use & SLAP_MR_EQUALITY_APPROX ) == SLAP_MR_EQUALITY_APPROX ) + ? LDAP_UTF8_APPROX : 0; + + val = UTF8bvnormalize( val, &tmp, flags, ctx ); + /* out of memory or syntax error, the former is unlikely */ + if( val == NULL ) { + return LDAP_INVALID_SYNTAX; + } + + /* collapse spaces (in place) */ + nvalue.bv_len = 0; + nvalue.bv_val = tmp.bv_val; + + /* trim leading spaces? */ + wasspace = !((( use & SLAP_MR_SUBSTR_ANY ) == SLAP_MR_SUBSTR_ANY ) || + (( use & SLAP_MR_SUBSTR_FINAL ) == SLAP_MR_SUBSTR_FINAL )); + + for( i = 0; i < tmp.bv_len; i++) { + if ( ASCII_SPACE( tmp.bv_val[i] )) { + if( wasspace++ == 0 ) { + /* trim repeated spaces */ + nvalue.bv_val[nvalue.bv_len++] = tmp.bv_val[i]; + } + } else { + wasspace = 0; + nvalue.bv_val[nvalue.bv_len++] = tmp.bv_val[i]; + } + } + + if( !BER_BVISEMPTY( &nvalue ) ) { + /* trim trailing space? */ + if( wasspace && ( + (( use & SLAP_MR_SUBSTR_INITIAL ) != SLAP_MR_SUBSTR_INITIAL ) && + ( use & SLAP_MR_SUBSTR_ANY ) != SLAP_MR_SUBSTR_ANY )) + { + --nvalue.bv_len; + } + nvalue.bv_val[nvalue.bv_len] = '\0'; + + } else if ( tmp.bv_len ) { + /* string of all spaces is treated as one space */ + nvalue.bv_val[0] = ' '; + nvalue.bv_val[1] = '\0'; + nvalue.bv_len = 1; + } /* should never be entered with 0-length val */ + + *normalized = nvalue; + return LDAP_SUCCESS; +} + +static int +directoryStringSubstringsMatch( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + int match = 0; + SubstringsAssertion *sub = assertedValue; + struct berval left = *value; + ber_len_t i; + int priorspace=0; + + if ( !BER_BVISNULL( &sub->sa_initial ) ) { + if ( sub->sa_initial.bv_len > left.bv_len ) { + /* not enough left */ + match = 1; + goto done; + } + + match = memcmp( sub->sa_initial.bv_val, left.bv_val, + sub->sa_initial.bv_len ); + + if ( match != 0 ) { + goto done; + } + + left.bv_val += sub->sa_initial.bv_len; + left.bv_len -= sub->sa_initial.bv_len; + + priorspace = ASCII_SPACE( + sub->sa_initial.bv_val[sub->sa_initial.bv_len] ); + } + + if ( sub->sa_any ) { + for ( i = 0; !BER_BVISNULL( &sub->sa_any[i] ); i++ ) { + ber_len_t idx; + char *p; + + if( priorspace && !BER_BVISEMPTY( &sub->sa_any[i] ) + && ASCII_SPACE( sub->sa_any[i].bv_val[0] )) + { + /* allow next space to match */ + left.bv_val--; + left.bv_len++; + } + priorspace=0; + +retry: + if ( BER_BVISEMPTY( &sub->sa_any[i] ) ) { + continue; + } + + if ( sub->sa_any[i].bv_len > left.bv_len ) { + /* not enough left */ + match = 1; + goto done; + } + + p = memchr( left.bv_val, *sub->sa_any[i].bv_val, left.bv_len ); + + if( p == NULL ) { + match = 1; + goto done; + } + + idx = p - left.bv_val; + + if ( idx >= left.bv_len ) { + /* this shouldn't happen */ + return LDAP_OTHER; + } + + left.bv_val = p; + left.bv_len -= idx; + + if ( sub->sa_any[i].bv_len > left.bv_len ) { + /* not enough left */ + match = 1; + goto done; + } + + match = memcmp( left.bv_val, + sub->sa_any[i].bv_val, + sub->sa_any[i].bv_len ); + + if ( match != 0 ) { + left.bv_val++; + left.bv_len--; + goto retry; + } + + left.bv_val += sub->sa_any[i].bv_len; + left.bv_len -= sub->sa_any[i].bv_len; + + priorspace = ASCII_SPACE( + sub->sa_any[i].bv_val[sub->sa_any[i].bv_len] ); + } + } + + if ( !BER_BVISNULL( &sub->sa_final ) ) { + if( priorspace && !BER_BVISEMPTY( &sub->sa_final ) + && ASCII_SPACE( sub->sa_final.bv_val[0] )) + { + /* allow next space to match */ + left.bv_val--; + left.bv_len++; + } + + if ( sub->sa_final.bv_len > left.bv_len ) { + /* not enough left */ + match = 1; + goto done; + } + + match = memcmp( sub->sa_final.bv_val, + &left.bv_val[left.bv_len - sub->sa_final.bv_len], + sub->sa_final.bv_len ); + + if ( match != 0 ) { + goto done; + } + } + +done: + *matchp = match; + return LDAP_SUCCESS; +} + +#if defined(SLAPD_APPROX_INITIALS) +# define SLAPD_APPROX_DELIMITER "._ " +# define SLAPD_APPROX_WORDLEN 2 +#else +# define SLAPD_APPROX_DELIMITER " " +# define SLAPD_APPROX_WORDLEN 1 +#endif + +static int +approxMatch( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + struct berval *nval, *assertv; + char *val, **values, **words, *c; + int i, count, len, nextchunk=0, nextavail=0; + + /* Yes, this is necessary */ + nval = UTF8bvnormalize( value, NULL, LDAP_UTF8_APPROX, NULL ); + if( nval == NULL ) { + *matchp = 1; + return LDAP_SUCCESS; + } + + /* Yes, this is necessary */ + assertv = UTF8bvnormalize( ((struct berval *)assertedValue), + NULL, LDAP_UTF8_APPROX, NULL ); + if( assertv == NULL ) { + ber_bvfree( nval ); + *matchp = 1; + return LDAP_SUCCESS; + } + + /* Isolate how many words there are */ + for ( c = nval->bv_val, count = 1; *c; c++ ) { + c = strpbrk( c, SLAPD_APPROX_DELIMITER ); + if ( c == NULL ) break; + *c = '\0'; + count++; + } + + /* Get a phonetic copy of each word */ + words = (char **)ch_malloc( count * sizeof(char *) ); + values = (char **)ch_malloc( count * sizeof(char *) ); + for ( c = nval->bv_val, i = 0; i < count; i++, c += strlen(c) + 1 ) { + words[i] = c; + values[i] = phonetic(c); + } + + /* Work through the asserted value's words, to see if at least some + * of the words are there, in the same order. */ + len = 0; + while ( (ber_len_t) nextchunk < assertv->bv_len ) { + len = strcspn( assertv->bv_val + nextchunk, SLAPD_APPROX_DELIMITER); + if( len == 0 ) { + nextchunk++; + continue; + } +#if defined(SLAPD_APPROX_INITIALS) + else if( len == 1 ) { + /* Single letter words need to at least match one word's initial */ + for( i=nextavail; i<count; i++ ) + if( !strncasecmp( assertv->bv_val + nextchunk, words[i], 1 )) { + nextavail=i+1; + break; + } + } +#endif + else { + /* Isolate the next word in the asserted value and phonetic it */ + assertv->bv_val[nextchunk+len] = '\0'; + val = phonetic( assertv->bv_val + nextchunk ); + + /* See if this phonetic chunk is in the remaining words of *value */ + for( i=nextavail; i<count; i++ ){ + if( !strcmp( val, values[i] ) ){ + nextavail = i+1; + break; + } + } + ch_free( val ); + } + + /* This chunk in the asserted value was NOT within the *value. */ + if( i >= count ) { + nextavail=-1; + break; + } + + /* Go on to the next word in the asserted value */ + nextchunk += len+1; + } + + /* If some of the words were seen, call it a match */ + if( nextavail > 0 ) { + *matchp = 0; + } + else { + *matchp = 1; + } + + /* Cleanup allocs */ + ber_bvfree( assertv ); + for( i=0; i<count; i++ ) { + ch_free( values[i] ); + } + ch_free( values ); + ch_free( words ); + ber_bvfree( nval ); + + return LDAP_SUCCESS; +} + +static int +approxIndexer( + slap_mask_t use, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *prefix, + BerVarray values, + BerVarray *keysp, + void *ctx ) +{ + char *c; + int i,j, len, wordcount, keycount=0; + struct berval *newkeys; + BerVarray keys=NULL; + + for( j = 0; !BER_BVISNULL( &values[j] ); j++ ) { + struct berval val = BER_BVNULL; + /* Yes, this is necessary */ + UTF8bvnormalize( &values[j], &val, LDAP_UTF8_APPROX, NULL ); + assert( !BER_BVISNULL( &val ) ); + + /* Isolate how many words there are. There will be a key for each */ + for( wordcount = 0, c = val.bv_val; *c; c++) { + len = strcspn(c, SLAPD_APPROX_DELIMITER); + if( len >= SLAPD_APPROX_WORDLEN ) wordcount++; + c+= len; + if (*c == '\0') break; + *c = '\0'; + } + + /* Allocate/increase storage to account for new keys */ + newkeys = (struct berval *)ch_malloc( (keycount + wordcount + 1) + * sizeof(struct berval) ); + AC_MEMCPY( newkeys, keys, keycount * sizeof(struct berval) ); + if( keys ) ch_free( keys ); + keys = newkeys; + + /* Get a phonetic copy of each word */ + for( c = val.bv_val, i = 0; i < wordcount; c += len + 1 ) { + len = strlen( c ); + if( len < SLAPD_APPROX_WORDLEN ) continue; + ber_str2bv( phonetic( c ), 0, 0, &keys[keycount] ); + if( keys[keycount].bv_len ) { + keycount++; + } else { + ch_free( keys[keycount].bv_val ); + } + i++; + } + + ber_memfree( val.bv_val ); + } + BER_BVZERO( &keys[keycount] ); + *keysp = keys; + + return LDAP_SUCCESS; +} + +static int +approxFilter( + slap_mask_t use, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *prefix, + void * assertedValue, + BerVarray *keysp, + void *ctx ) +{ + char *c; + int i, count, len; + struct berval *val; + BerVarray keys; + + /* Yes, this is necessary */ + val = UTF8bvnormalize( ((struct berval *)assertedValue), + NULL, LDAP_UTF8_APPROX, NULL ); + if( val == NULL || BER_BVISNULL( val ) ) { + keys = (struct berval *)ch_malloc( sizeof(struct berval) ); + BER_BVZERO( &keys[0] ); + *keysp = keys; + ber_bvfree( val ); + return LDAP_SUCCESS; + } + + /* Isolate how many words there are. There will be a key for each */ + for( count = 0,c = val->bv_val; *c; c++) { + len = strcspn(c, SLAPD_APPROX_DELIMITER); + if( len >= SLAPD_APPROX_WORDLEN ) count++; + c+= len; + if (*c == '\0') break; + *c = '\0'; + } + + /* Allocate storage for new keys */ + keys = (struct berval *)ch_malloc( (count + 1) * sizeof(struct berval) ); + + /* Get a phonetic copy of each word */ + for( c = val->bv_val, i = 0; i < count; c += len + 1 ) { + len = strlen(c); + if( len < SLAPD_APPROX_WORDLEN ) continue; + ber_str2bv( phonetic( c ), 0, 0, &keys[i] ); + i++; + } + + ber_bvfree( val ); + + BER_BVZERO( &keys[count] ); + *keysp = keys; + + return LDAP_SUCCESS; +} + +/* Remove all spaces and '-' characters, unless the result would be empty */ +static int +telephoneNumberNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + char *q; + ber_len_t c; + + assert( SLAP_MR_IS_VALUE_OF_SYNTAX( usage ) != 0 ); + + /* Ensure q is big enough, though validator should have caught this */ + if ( BER_BVISEMPTY( val )) { + BER_BVZERO( normalized ); + return LDAP_INVALID_SYNTAX; + } + + q = normalized->bv_val = slap_sl_malloc( val->bv_len + 1, ctx ); + + for( c = 0; c < val->bv_len; c++ ) { + if ( ! ( ASCII_SPACE( val->bv_val[c] ) || val->bv_val[c] == '-' )) { + *q++ = val->bv_val[c]; + } + } + if ( q == normalized->bv_val ) { + *q++ = ' '; + } + *q = '\0'; + + normalized->bv_len = q - normalized->bv_val; + + return LDAP_SUCCESS; +} + +static int +postalAddressValidate( + Syntax *syntax, + struct berval *in ) +{ + struct berval bv = *in; + ber_len_t c; + + for ( c = 0; c < in->bv_len; c++ ) { + if ( in->bv_val[c] == '\\' ) { + c++; + if ( strncasecmp( &in->bv_val[c], "24", STRLENOF( "24" ) ) != 0 + && strncasecmp( &in->bv_val[c], "5C", STRLENOF( "5C" ) ) != 0 ) + { + return LDAP_INVALID_SYNTAX; + } + continue; + } + + if ( in->bv_val[c] == '$' ) { + bv.bv_len = &in->bv_val[c] - bv.bv_val; + if ( UTF8StringValidate( NULL, &bv ) != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + bv.bv_val = &in->bv_val[c] + 1; + } + } + + bv.bv_len = &in->bv_val[c] - bv.bv_val; + return UTF8StringValidate( NULL, &bv ); +} + +static int +postalAddressNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + BerVarray lines = NULL, nlines = NULL; + ber_len_t l, c; + int rc = LDAP_SUCCESS; + MatchingRule *xmr = NULL; + char *p; + + if ( SLAP_MR_ASSOCIATED( mr, slap_schema.si_mr_caseIgnoreListMatch ) ) { + xmr = slap_schema.si_mr_caseIgnoreMatch; + + } else { + xmr = slap_schema.si_mr_caseExactMatch; + } + + for ( l = 0, c = 0; c < val->bv_len; c++ ) { + if ( val->bv_val[c] == '$' ) { + l++; + } + } + + lines = slap_sl_calloc( sizeof( struct berval ), 2 * ( l + 2 ), ctx ); + nlines = &lines[l + 2]; + + lines[0].bv_val = val->bv_val; + for ( l = 0, c = 0; c < val->bv_len; c++ ) { + if ( val->bv_val[c] == '$' ) { + lines[l].bv_len = &val->bv_val[c] - lines[l].bv_val; + l++; + lines[l].bv_val = &val->bv_val[c + 1]; + } + } + lines[l].bv_len = &val->bv_val[c] - lines[l].bv_val; + + normalized->bv_len = c = l; + + for ( l = 0; l <= c; l++ ) { + /* NOTE: we directly normalize each line, + * without unescaping the values, since the special + * values '\24' ('$') and '\5C' ('\') are not affected + * by normalization */ + if ( !lines[l].bv_len ) { + nlines[l].bv_len = 0; + nlines[l].bv_val = NULL; + continue; + } + rc = UTF8StringNormalize( usage, NULL, xmr, &lines[l], &nlines[l], ctx ); + if ( rc != LDAP_SUCCESS ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + + normalized->bv_len += nlines[l].bv_len; + } + + normalized->bv_val = slap_sl_malloc( normalized->bv_len + 1, ctx ); + + p = normalized->bv_val; + for ( l = 0; l <= c ; l++ ) { + p = lutil_strbvcopy( p, &nlines[l] ); + *p++ = '$'; + } + *--p = '\0'; + + assert( p == &normalized->bv_val[normalized->bv_len] ); + +done:; + if ( nlines != NULL ) { + for ( l = 0; !BER_BVISNULL( &nlines[ l ] ); l++ ) { + slap_sl_free( nlines[l].bv_val, ctx ); + } + + slap_sl_free( lines, ctx ); + } + + return rc; +} + +int +numericoidValidate( + Syntax *syntax, + struct berval *in ) +{ + struct berval val = *in; + + if( BER_BVISEMPTY( &val ) ) { + /* disallow empty strings */ + return LDAP_INVALID_SYNTAX; + } + + while( OID_LEADCHAR( val.bv_val[0] ) ) { + if ( val.bv_len == 1 ) { + return LDAP_SUCCESS; + } + + if ( val.bv_val[0] == '0' && !OID_SEPARATOR( val.bv_val[1] )) { + break; + } + + val.bv_val++; + val.bv_len--; + + while ( OID_LEADCHAR( val.bv_val[0] )) { + val.bv_val++; + val.bv_len--; + + if ( val.bv_len == 0 ) { + return LDAP_SUCCESS; + } + } + + if( !OID_SEPARATOR( val.bv_val[0] )) { + break; + } + + val.bv_val++; + val.bv_len--; + } + + return LDAP_INVALID_SYNTAX; +} + +static int +integerValidate( + Syntax *syntax, + struct berval *in ) +{ + ber_len_t i; + struct berval val = *in; + + if ( BER_BVISEMPTY( &val ) ) return LDAP_INVALID_SYNTAX; + + if ( val.bv_val[0] == '-' ) { + val.bv_len--; + val.bv_val++; + + if( BER_BVISEMPTY( &val ) ) { /* bare "-" */ + return LDAP_INVALID_SYNTAX; + } + + if( val.bv_val[0] == '0' ) { /* "-0" */ + return LDAP_INVALID_SYNTAX; + } + + } else if ( val.bv_val[0] == '0' ) { + if( val.bv_len > 1 ) { /* "0<more>" */ + return LDAP_INVALID_SYNTAX; + } + + return LDAP_SUCCESS; + } + + for( i=0; i < val.bv_len; i++ ) { + if( !ASCII_DIGIT(val.bv_val[i]) ) { + return LDAP_INVALID_SYNTAX; + } + } + + return LDAP_SUCCESS; +} + +static int +integerMatch( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + struct berval *asserted = (struct berval *) assertedValue; + int vsign = 1, asign = 1; /* default sign = '+' */ + struct berval v, a; + int match; + + v = *value; + if( v.bv_val[0] == '-' ) { + vsign = -1; + v.bv_val++; + v.bv_len--; + } + + if( BER_BVISEMPTY( &v ) ) vsign = 0; + + a = *asserted; + if( a.bv_val[0] == '-' ) { + asign = -1; + a.bv_val++; + a.bv_len--; + } + + if( BER_BVISEMPTY( &a ) ) vsign = 0; + + match = vsign - asign; + if( match == 0 ) { + match = ( v.bv_len != a.bv_len + ? ( v.bv_len < a.bv_len ? -1 : 1 ) + : memcmp( v.bv_val, a.bv_val, v.bv_len )); + if( vsign < 0 ) match = -match; + } + + /* Ordering rule used in extensible match filter? */ + if ( (flags & SLAP_MR_EXT) && (mr->smr_usage & SLAP_MR_ORDERING) ) + match = (match >= 0); + + *matchp = match; + return LDAP_SUCCESS; +} + +/* 10**Chop < 256**Chopbytes and Chop > Chopbytes<<1 (for sign bit and itmp) */ +#define INDEX_INTLEN_CHOP 7 +#define INDEX_INTLEN_CHOPBYTES 3 + +static int +integerVal2Key( + struct berval *in, + struct berval *key, + struct berval *tmp, + void *ctx ) +{ + /* Integer index key format, designed for memcmp to collate correctly: + * if too large: one's complement sign*<approx exponent=chopped bytes>, + * two's complement value (sign-extended or chopped as needed), + * however in first byte above, the top <number of exponent-bytes + 1> + * bits are the inverse sign and next bit is the sign as delimiter. + */ + ber_slen_t k = index_intlen_strlen; + ber_len_t chop = 0; + unsigned signmask = ~0x7fU; + unsigned char lenbuf[sizeof(k) + 2], *lenp, neg = 0xff; + struct berval val = *in, itmp = *tmp; + + if ( val.bv_val[0] != '-' ) { + neg = 0; + --k; + } + + /* Chop least significant digits, increase length instead */ + if ( val.bv_len > (ber_len_t) k ) { + chop = (val.bv_len-k+2)/INDEX_INTLEN_CHOP; /* 2 fewer digits */ + val.bv_len -= chop * INDEX_INTLEN_CHOP; /* #digits chopped */ + chop *= INDEX_INTLEN_CHOPBYTES; /* #bytes added */ + } + + if ( lutil_str2bin( &val, &itmp, ctx )) { + return LDAP_INVALID_SYNTAX; + } + + /* Omit leading sign byte */ + if ( itmp.bv_val[0] == neg ) { + itmp.bv_val++; + itmp.bv_len--; + } + + k = (ber_slen_t) index_intlen - (ber_slen_t) (itmp.bv_len + chop); + if ( k > 0 ) { + assert( chop == 0 ); + memset( key->bv_val, neg, k ); /* sign-extend */ + } else if ( k != 0 || ((itmp.bv_val[0] ^ neg) & 0xc0) ) { + /* Got exponent -k, or no room for 2 sign bits */ + lenp = lenbuf + sizeof(lenbuf); + chop = - (ber_len_t) k; + do { + *--lenp = ((unsigned char) chop & 0xff) ^ neg; + signmask >>= 1; + } while ( (chop >>= 8) != 0 || (signmask >> 1) & (*lenp ^ neg) ); + /* With n bytes in lenbuf, the top n+1 bits of (signmask&0xff) + * are 1, and the top n+2 bits of lenp[0] are the sign bit. */ + k = (lenbuf + sizeof(lenbuf)) - lenp; + if ( k > (ber_slen_t) index_intlen ) + k = index_intlen; + memcpy( key->bv_val, lenp, k ); + itmp.bv_len = index_intlen - k; + } + memcpy( key->bv_val + k, itmp.bv_val, itmp.bv_len ); + key->bv_val[0] ^= (unsigned char) signmask & 0xff; /* invert sign */ + return 0; +} + +/* Index generation function: Ordered index */ +static int +integerIndexer( + slap_mask_t use, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *prefix, + BerVarray values, + BerVarray *keysp, + void *ctx ) +{ + char ibuf[64]; + struct berval itmp; + BerVarray keys; + ber_len_t vlen; + int i, rc; + unsigned maxstrlen = index_intlen_strlen + INDEX_INTLEN_CHOP-1; + + /* count the values and find max needed length */ + vlen = 0; + for( i = 0; !BER_BVISNULL( &values[i] ); i++ ) { + if ( vlen < values[i].bv_len ) + vlen = values[i].bv_len; + } + if ( vlen > maxstrlen ) + vlen = maxstrlen; + + /* we should have at least one value at this point */ + assert( i > 0 ); + + keys = slap_sl_malloc( sizeof( struct berval ) * (i+1), ctx ); + for ( i = 0; !BER_BVISNULL( &values[i] ); i++ ) { + keys[i].bv_len = index_intlen; + keys[i].bv_val = slap_sl_malloc( index_intlen, ctx ); + } + keys[i].bv_len = 0; + keys[i].bv_val = NULL; + + if ( vlen > sizeof(ibuf) ) { + itmp.bv_val = slap_sl_malloc( vlen, ctx ); + } else { + itmp.bv_val = ibuf; + } + itmp.bv_len = sizeof(ibuf); + + for ( i=0; !BER_BVISNULL( &values[i] ); i++ ) { + if ( itmp.bv_val != ibuf ) { + itmp.bv_len = values[i].bv_len; + if ( itmp.bv_len <= sizeof(ibuf) ) + itmp.bv_len = sizeof(ibuf); + else if ( itmp.bv_len > maxstrlen ) + itmp.bv_len = maxstrlen; + } + rc = integerVal2Key( &values[i], &keys[i], &itmp, ctx ); + if ( rc ) { + slap_sl_free( keys, ctx ); + goto func_leave; + } + } + *keysp = keys; +func_leave: + if ( itmp.bv_val != ibuf ) { + slap_sl_free( itmp.bv_val, ctx ); + } + return rc; +} + +/* Index generation function: Ordered index */ +static int +integerFilter( + slap_mask_t use, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *prefix, + void * assertedValue, + BerVarray *keysp, + void *ctx ) +{ + char ibuf[64]; + struct berval iv; + BerVarray keys; + struct berval *value; + int rc; + + value = (struct berval *) assertedValue; + + keys = slap_sl_malloc( sizeof( struct berval ) * 2, ctx ); + + keys[0].bv_len = index_intlen; + keys[0].bv_val = slap_sl_malloc( index_intlen, ctx ); + keys[1].bv_len = 0; + keys[1].bv_val = NULL; + + iv.bv_len = value->bv_len < index_intlen_strlen + INDEX_INTLEN_CHOP-1 + ? value->bv_len : index_intlen_strlen + INDEX_INTLEN_CHOP-1; + if ( iv.bv_len > (int) sizeof(ibuf) ) { + iv.bv_val = slap_sl_malloc( iv.bv_len, ctx ); + } else { + iv.bv_val = ibuf; + iv.bv_len = sizeof(ibuf); + } + + rc = integerVal2Key( value, keys, &iv, ctx ); + + if ( iv.bv_val != ibuf ) { + slap_sl_free( iv.bv_val, ctx ); + } + + if ( rc == 0 ) + *keysp = keys; + else + slap_sl_free( keys, ctx ); + + return rc; +} + +static int +countryStringValidate( + Syntax *syntax, + struct berval *val ) +{ + if( val->bv_len != 2 ) return LDAP_INVALID_SYNTAX; + + if( !SLAP_PRINTABLE(val->bv_val[0]) ) { + return LDAP_INVALID_SYNTAX; + } + if( !SLAP_PRINTABLE(val->bv_val[1]) ) { + return LDAP_INVALID_SYNTAX; + } + + return LDAP_SUCCESS; +} + +static int +printableStringValidate( + Syntax *syntax, + struct berval *val ) +{ + ber_len_t i; + + if( BER_BVISEMPTY( val ) ) return LDAP_INVALID_SYNTAX; + + for(i=0; i < val->bv_len; i++) { + if( !SLAP_PRINTABLE(val->bv_val[i]) ) { + return LDAP_INVALID_SYNTAX; + } + } + + return LDAP_SUCCESS; +} + +static int +printablesStringValidate( + Syntax *syntax, + struct berval *val ) +{ + ber_len_t i, len; + + if( BER_BVISEMPTY( val ) ) return LDAP_INVALID_SYNTAX; + + for(i=0,len=0; i < val->bv_len; i++) { + int c = val->bv_val[i]; + + if( c == '$' ) { + if( len == 0 ) { + return LDAP_INVALID_SYNTAX; + } + len = 0; + + } else if ( SLAP_PRINTABLE(c) ) { + len++; + } else { + return LDAP_INVALID_SYNTAX; + } + } + + if( len == 0 ) { + return LDAP_INVALID_SYNTAX; + } + + return LDAP_SUCCESS; +} + +static int +IA5StringValidate( + Syntax *syntax, + struct berval *val ) +{ + ber_len_t i; + + for(i=0; i < val->bv_len; i++) { + if( !LDAP_ASCII(val->bv_val[i]) ) { + return LDAP_INVALID_SYNTAX; + } + } + + return LDAP_SUCCESS; +} + +static int +IA5StringNormalize( + slap_mask_t use, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + char *p, *q, *end; + int casefold = !SLAP_MR_ASSOCIATED( mr, + slap_schema.si_mr_caseExactIA5Match ); + + assert( SLAP_MR_IS_VALUE_OF_SYNTAX( use ) != 0 ); + + p = val->bv_val; + end = val->bv_val + val->bv_len; + + /* Ignore initial whitespace */ + while ( p < end && ASCII_SPACE( *p ) ) p++; + + normalized->bv_len = p < end ? (val->bv_len - ( p - val->bv_val )) : 0; + normalized->bv_val = slap_sl_malloc( normalized->bv_len + 1, ctx ); + AC_MEMCPY( normalized->bv_val, p, normalized->bv_len ); + normalized->bv_val[normalized->bv_len] = '\0'; + + p = q = normalized->bv_val; + + while ( *p ) { + if ( ASCII_SPACE( *p ) ) { + *q++ = *p++; + + /* Ignore the extra whitespace */ + while ( ASCII_SPACE( *p ) ) { + p++; + } + + } else if ( casefold ) { + /* Most IA5 rules require casefolding */ + *q++ = TOLOWER(*p); p++; + + } else { + *q++ = *p++; + } + } + + assert( normalized->bv_val <= p ); + assert( q <= p ); + + /* + * If the string ended in space, backup the pointer one + * position. One is enough because the above loop collapsed + * all whitespace to a single space. + */ + if ( q > normalized->bv_val && ASCII_SPACE( q[-1] ) ) --q; + + /* null terminate */ + *q = '\0'; + + normalized->bv_len = q - normalized->bv_val; + + return LDAP_SUCCESS; +} + +static int +UUIDValidate( + Syntax *syntax, + struct berval *in ) +{ + int i; + if( in->bv_len != 36 ) { + return LDAP_INVALID_SYNTAX; + } + + for( i=0; i<36; i++ ) { + switch(i) { + case 8: + case 13: + case 18: + case 23: + if( in->bv_val[i] != '-' ) { + return LDAP_INVALID_SYNTAX; + } + break; + default: + if( !ASCII_HEX( in->bv_val[i]) ) { + return LDAP_INVALID_SYNTAX; + } + } + } + + return LDAP_SUCCESS; +} + +static int +UUIDPretty( + Syntax *syntax, + struct berval *in, + struct berval *out, + void *ctx ) +{ + int i; + int rc=LDAP_INVALID_SYNTAX; + + assert( in != NULL ); + assert( out != NULL ); + + if( in->bv_len != 36 ) return LDAP_INVALID_SYNTAX; + + out->bv_len = 36; + out->bv_val = slap_sl_malloc( out->bv_len + 1, ctx ); + + for( i=0; i<36; i++ ) { + switch(i) { + case 8: + case 13: + case 18: + case 23: + if( in->bv_val[i] != '-' ) { + goto handle_error; + } + out->bv_val[i] = '-'; + break; + + default: + if( !ASCII_HEX( in->bv_val[i]) ) { + goto handle_error; + } + out->bv_val[i] = TOLOWER( in->bv_val[i] ); + } + } + + rc = LDAP_SUCCESS; + out->bv_val[ out->bv_len ] = '\0'; + + if( 0 ) { +handle_error: + slap_sl_free( out->bv_val, ctx ); + out->bv_val = NULL; + } + + return rc; +} + +int +UUIDNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + unsigned char octet = '\0'; + int i; + int j; + + if ( SLAP_MR_IS_DENORMALIZE( usage ) ) { + /* NOTE: must be a normalized UUID */ + if( val->bv_len != 16 ) + return LDAP_INVALID_SYNTAX; + + normalized->bv_val = slap_sl_malloc( LDAP_LUTIL_UUIDSTR_BUFSIZE, ctx ); + normalized->bv_len = lutil_uuidstr_from_normalized( val->bv_val, + val->bv_len, normalized->bv_val, LDAP_LUTIL_UUIDSTR_BUFSIZE ); + if( normalized->bv_len != STRLENOF( "BADBADBA-DBAD-0123-4567-BADBADBADBAD" ) ) + return LDAP_INVALID_SYNTAX; + + return LDAP_SUCCESS; + } + + normalized->bv_len = 16; + normalized->bv_val = slap_sl_malloc( normalized->bv_len + 1, ctx ); + + for( i=0, j=0; i<36; i++ ) { + unsigned char nibble; + if( val->bv_val[i] == '-' ) { + continue; + + } else if( ASCII_DIGIT( val->bv_val[i] ) ) { + nibble = val->bv_val[i] - '0'; + + } else if( ASCII_HEXLOWER( val->bv_val[i] ) ) { + nibble = val->bv_val[i] - ('a'-10); + + } else if( ASCII_HEXUPPER( val->bv_val[i] ) ) { + nibble = val->bv_val[i] - ('A'-10); + + } else { + slap_sl_free( normalized->bv_val, ctx ); + BER_BVZERO( normalized ); + return LDAP_INVALID_SYNTAX; + } + + if( j & 1 ) { + octet |= nibble; + normalized->bv_val[j>>1] = octet; + } else { + octet = nibble << 4; + } + j++; + } + + normalized->bv_val[normalized->bv_len] = 0; + return LDAP_SUCCESS; +} + + + +int +numericStringValidate( + Syntax *syntax, + struct berval *in ) +{ + ber_len_t i; + + if( BER_BVISEMPTY( in ) ) return LDAP_INVALID_SYNTAX; + + for(i=0; i < in->bv_len; i++) { + if( !SLAP_NUMERIC(in->bv_val[i]) ) { + return LDAP_INVALID_SYNTAX; + } + } + + return LDAP_SUCCESS; +} + +static int +numericStringNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + /* removal all spaces */ + char *p, *q; + + assert( !BER_BVISEMPTY( val ) ); + + normalized->bv_val = slap_sl_malloc( val->bv_len + 1, ctx ); + + p = val->bv_val; + q = normalized->bv_val; + + while ( *p ) { + if ( ASCII_SPACE( *p ) ) { + /* Ignore whitespace */ + p++; + } else { + *q++ = *p++; + } + } + + /* we should have copied no more than is in val */ + assert( (q - normalized->bv_val) <= (p - val->bv_val) ); + + /* null terminate */ + *q = '\0'; + + normalized->bv_len = q - normalized->bv_val; + + if( BER_BVISEMPTY( normalized ) ) { + normalized->bv_val = slap_sl_realloc( normalized->bv_val, 2, ctx ); + normalized->bv_val[0] = ' '; + normalized->bv_val[1] = '\0'; + normalized->bv_len = 1; + } + + return LDAP_SUCCESS; +} + +/* + * Integer conversion macros that will use the largest available + * type. + */ +#if defined(HAVE_STRTOLL) && defined(HAVE_LONG_LONG) +# define SLAP_STRTOL(n,e,b) strtoll(n,e,b) +# define SLAP_LONG long long +#else +# define SLAP_STRTOL(n,e,b) strtol(n,e,b) +# define SLAP_LONG long +#endif /* HAVE_STRTOLL ... */ + +static int +integerBitAndMatch( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + SLAP_LONG lValue, lAssertedValue; + + errno = 0; + /* safe to assume integers are NUL terminated? */ + lValue = SLAP_STRTOL(value->bv_val, NULL, 10); + if( errno == ERANGE ) + { + return LDAP_CONSTRAINT_VIOLATION; + } + + lAssertedValue = SLAP_STRTOL(((struct berval *)assertedValue)->bv_val, + NULL, 10); + if( errno == ERANGE ) + { + return LDAP_CONSTRAINT_VIOLATION; + } + + *matchp = ((lValue & lAssertedValue) == lAssertedValue) ? 0 : 1; + return LDAP_SUCCESS; +} + +static int +integerBitOrMatch( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + SLAP_LONG lValue, lAssertedValue; + + errno = 0; + /* safe to assume integers are NUL terminated? */ + lValue = SLAP_STRTOL(value->bv_val, NULL, 10); + if( errno == ERANGE ) + { + return LDAP_CONSTRAINT_VIOLATION; + } + + lAssertedValue = SLAP_STRTOL( ((struct berval *)assertedValue)->bv_val, + NULL, 10); + if( errno == ERANGE ) + { + return LDAP_CONSTRAINT_VIOLATION; + } + + *matchp = ((lValue & lAssertedValue) != 0) ? 0 : -1; + return LDAP_SUCCESS; +} + +static int +checkNum( struct berval *in, struct berval *out ) +{ + /* parse serialNumber */ + ber_len_t neg = 0, extra = 0; + char first = '\0'; + + out->bv_val = in->bv_val; + out->bv_len = 0; + + if ( out->bv_val[0] == '-' ) { + neg++; + out->bv_len++; + } + + if ( strncasecmp( out->bv_val, "0x", STRLENOF("0x") ) == 0 ) { + first = out->bv_val[2]; + extra = 2; + + out->bv_len += STRLENOF("0x"); + for ( ; out->bv_len < in->bv_len; out->bv_len++ ) { + if ( !ASCII_HEX( out->bv_val[out->bv_len] ) ) break; + } + + } else if ( out->bv_val[0] == '\'' ) { + first = out->bv_val[1]; + extra = 3; + + out->bv_len += STRLENOF("'"); + + for ( ; out->bv_len < in->bv_len; out->bv_len++ ) { + if ( !ASCII_HEX( out->bv_val[out->bv_len] ) ) break; + } + if ( strncmp( &out->bv_val[out->bv_len], "'H", STRLENOF("'H") ) != 0 ) { + return -1; + } + out->bv_len += STRLENOF("'H"); + + } else { + first = out->bv_val[0]; + for ( ; out->bv_len < in->bv_len; out->bv_len++ ) { + if ( !ASCII_DIGIT( out->bv_val[out->bv_len] ) ) break; + } + } + + if ( !( out->bv_len > neg ) ) { + return -1; + } + + if ( ( out->bv_len > extra + 1 + neg ) && ( first == '0' ) ) { + return -1; + } + + return 0; +} + +static int +serialNumberAndIssuerCheck( + struct berval *in, + struct berval *sn, + struct berval *is, + void *ctx ) +{ + ber_len_t n; + + if( in->bv_len < 3 ) return LDAP_INVALID_SYNTAX; + + if( in->bv_val[0] != '{' || in->bv_val[in->bv_len-1] != '}' ) { + /* Parse old format */ + is->bv_val = ber_bvchr( in, '$' ); + if( BER_BVISNULL( is ) ) return LDAP_INVALID_SYNTAX; + + sn->bv_val = in->bv_val; + sn->bv_len = is->bv_val - in->bv_val; + + is->bv_val++; + is->bv_len = in->bv_len - (sn->bv_len + 1); + + /* eat leading zeros */ + for( n=0; n < (sn->bv_len-1); n++ ) { + if( sn->bv_val[n] != '0' ) break; + } + sn->bv_val += n; + sn->bv_len -= n; + + for( n=0; n < sn->bv_len; n++ ) { + if( !ASCII_DIGIT(sn->bv_val[n]) ) return LDAP_INVALID_SYNTAX; + } + + } else { + /* Parse GSER format */ + enum { + HAVE_NONE = 0x0, + HAVE_ISSUER = 0x1, + HAVE_SN = 0x2, + HAVE_ALL = ( HAVE_ISSUER | HAVE_SN ) + } have = HAVE_NONE; + + int numdquotes = 0, gotquote; + struct berval x = *in; + struct berval ni; + x.bv_val++; + x.bv_len -= 2; + + do { + /* eat leading spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) { + /* empty */; + } + + /* should be at issuer or serialNumber NamedValue */ + if ( strncasecmp( x.bv_val, "issuer", STRLENOF("issuer") ) == 0 ) { + if ( have & HAVE_ISSUER ) return LDAP_INVALID_SYNTAX; + + /* parse issuer */ + x.bv_val += STRLENOF("issuer"); + x.bv_len -= STRLENOF("issuer"); + + if ( x.bv_val[0] != ' ' ) return LDAP_INVALID_SYNTAX; + x.bv_val++; + x.bv_len--; + + /* eat leading spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) { + /* empty */; + } + + /* For backward compatibility, this part is optional */ + if ( strncasecmp( x.bv_val, "rdnSequence:", STRLENOF("rdnSequence:") ) == 0 ) { + x.bv_val += STRLENOF("rdnSequence:"); + x.bv_len -= STRLENOF("rdnSequence:"); + } + + if ( x.bv_val[0] != '"' ) return LDAP_INVALID_SYNTAX; + x.bv_val++; + x.bv_len--; + + is->bv_val = x.bv_val; + is->bv_len = 0; + + for ( gotquote=0; is->bv_len < x.bv_len; ) { + if ( is->bv_val[is->bv_len] != '"' ) { + is->bv_len++; + continue; + } + gotquote = 1; + if ( is->bv_val[is->bv_len+1] == '"' ) { + /* double dquote */ + numdquotes++; + is->bv_len += 2; + continue; + } + break; + } + if ( !gotquote ) return LDAP_INVALID_SYNTAX; + + x.bv_val += is->bv_len + 1; + x.bv_len -= is->bv_len + 1; + + have |= HAVE_ISSUER; + + } else if ( strncasecmp( x.bv_val, "serialNumber", STRLENOF("serialNumber") ) == 0 ) + { + if ( have & HAVE_SN ) return LDAP_INVALID_SYNTAX; + + /* parse serialNumber */ + x.bv_val += STRLENOF("serialNumber"); + x.bv_len -= STRLENOF("serialNumber"); + + if ( x.bv_val[0] != ' ' ) return LDAP_INVALID_SYNTAX; + x.bv_val++; + x.bv_len--; + + /* eat leading spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) { + /* empty */; + } + + if ( checkNum( &x, sn ) ) { + return LDAP_INVALID_SYNTAX; + } + + x.bv_val += sn->bv_len; + x.bv_len -= sn->bv_len; + + have |= HAVE_SN; + + } else { + return LDAP_INVALID_SYNTAX; + } + + /* eat leading spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) { + /* empty */; + } + + if ( have == HAVE_ALL ) { + break; + } + + if ( x.bv_val[0] != ',' ) { + return LDAP_INVALID_SYNTAX; + } + + x.bv_val++; + x.bv_len--; + } while ( 1 ); + + /* should have no characters left... */ + if ( x.bv_len ) return LDAP_INVALID_SYNTAX; + + if ( numdquotes == 0 ) { + ber_dupbv_x( &ni, is, ctx ); + + } else { + ber_len_t src, dst; + + ni.bv_len = is->bv_len - numdquotes; + ni.bv_val = ber_memalloc_x( ni.bv_len + 1, ctx ); + for ( src = 0, dst = 0; src < is->bv_len; src++, dst++ ) { + if ( is->bv_val[src] == '"' ) { + src++; + } + ni.bv_val[dst] = is->bv_val[src]; + } + ni.bv_val[dst] = '\0'; + } + + *is = ni; + } + + return 0; +} + +static int +serialNumberAndIssuerValidate( + Syntax *syntax, + struct berval *in ) +{ + int rc; + struct berval sn, i; + + Debug( LDAP_DEBUG_TRACE, ">>> serialNumberAndIssuerValidate: <%s>\n", + in->bv_val, 0, 0 ); + + rc = serialNumberAndIssuerCheck( in, &sn, &i, NULL ); + if ( rc ) { + goto done; + } + + /* validate DN -- doesn't handle double dquote */ + rc = dnValidate( NULL, &i ); + if ( rc ) { + rc = LDAP_INVALID_SYNTAX; + } + + if ( in->bv_val[0] == '{' && in->bv_val[in->bv_len-1] == '}' ) { + slap_sl_free( i.bv_val, NULL ); + } + + Debug( LDAP_DEBUG_TRACE, "<<< serialNumberAndIssuerValidate: <%s> err=%d\n", + in->bv_val, rc, 0 ); + +done:; + return rc; +} + +static int +serialNumberAndIssuerPretty( + Syntax *syntax, + struct berval *in, + struct berval *out, + void *ctx ) +{ + int rc; + struct berval sn, i, ni = BER_BVNULL; + char *p; + + assert( in != NULL ); + assert( out != NULL ); + + BER_BVZERO( out ); + + Debug( LDAP_DEBUG_TRACE, ">>> serialNumberAndIssuerPretty: <%s>\n", + in->bv_val, 0, 0 ); + + rc = serialNumberAndIssuerCheck( in, &sn, &i, ctx ); + if ( rc ) { + goto done; + } + + rc = dnPretty( syntax, &i, &ni, ctx ); + + if ( in->bv_val[0] == '{' && in->bv_val[in->bv_len-1] == '}' ) { + slap_sl_free( i.bv_val, ctx ); + } + + if ( rc ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + + /* make room from sn + "$" */ + out->bv_len = STRLENOF("{ serialNumber , issuer rdnSequence:\"\" }") + + sn.bv_len + ni.bv_len; + out->bv_val = slap_sl_malloc( out->bv_len + 1, ctx ); + + if ( out->bv_val == NULL ) { + out->bv_len = 0; + rc = LDAP_OTHER; + goto done; + } + + p = out->bv_val; + p = lutil_strcopy( p, "{ serialNumber " /*}*/ ); + p = lutil_strbvcopy( p, &sn ); + p = lutil_strcopy( p, ", issuer rdnSequence:\"" ); + p = lutil_strbvcopy( p, &ni ); + p = lutil_strcopy( p, /*{*/ "\" }" ); + + assert( p == &out->bv_val[out->bv_len] ); + +done:; + Debug( LDAP_DEBUG_TRACE, "<<< serialNumberAndIssuerPretty: <%s> => <%s>\n", + in->bv_val, rc == LDAP_SUCCESS ? out->bv_val : "(err)", 0 ); + + slap_sl_free( ni.bv_val, ctx ); + + return LDAP_SUCCESS; +} + +static int +slap_bin2hex( + struct berval *in, + struct berval *out, + void *ctx ) + +{ + /* Use hex format. '123456789abcdef'H */ + unsigned char *ptr, zero = '\0'; + char *sptr; + int first; + ber_len_t i, len, nlen; + + assert( in != NULL ); + assert( !BER_BVISNULL( in ) ); + assert( out != NULL ); + assert( !BER_BVISNULL( out ) ); + + ptr = (unsigned char *)in->bv_val; + len = in->bv_len; + + /* Check for minimal encodings */ + if ( len > 1 ) { + if ( ptr[0] & 0x80 ) { + if ( ( ptr[0] == 0xff ) && ( ptr[1] & 0x80 ) ) { + return -1; + } + + } else if ( ptr[0] == 0 ) { + if ( !( ptr[1] & 0x80 ) ) { + return -1; + } + len--; + ptr++; + } + + } else if ( len == 0 ) { + /* FIXME: this should not be possible, + * since a value of zero would have length 1 */ + len = 1; + ptr = &zero; + } + + first = !( ptr[0] & 0xf0U ); + nlen = len * 2 - first + STRLENOF("''H"); /* quotes, H */ + if ( nlen >= out->bv_len ) { + out->bv_val = slap_sl_malloc( nlen + 1, ctx ); + } + sptr = out->bv_val; + *sptr++ = '\''; + i = 0; + if ( first ) { + sprintf( sptr, "%01X", ( ptr[0] & 0x0fU ) ); + sptr++; + i = 1; + } + for ( ; i < len; i++ ) { + sprintf( sptr, "%02X", ptr[i] ); + sptr += 2; + } + *sptr++ = '\''; + *sptr++ = 'H'; + *sptr = '\0'; + + assert( sptr == &out->bv_val[nlen] ); + + out->bv_len = nlen; + + return 0; +} + +#define SLAP_SN_BUFLEN (64) + +/* + * This routine is called by certificateExactNormalize when + * certificateExactNormalize receives a search string instead of + * a certificate. This routine checks if the search value is valid + * and then returns the normalized value + */ +static int +serialNumberAndIssuerNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *in, + struct berval *out, + void *ctx ) +{ + struct berval sn, sn2, sn3, i, ni; + char sbuf2[SLAP_SN_BUFLEN]; + char sbuf3[SLAP_SN_BUFLEN]; + char *p; + int rc; + + assert( in != NULL ); + assert( out != NULL ); + + Debug( LDAP_DEBUG_TRACE, ">>> serialNumberAndIssuerNormalize: <%s>\n", + in->bv_val, 0, 0 ); + + rc = serialNumberAndIssuerCheck( in, &sn, &i, ctx ); + if ( rc ) { + return rc; + } + + rc = dnNormalize( usage, syntax, mr, &i, &ni, ctx ); + + if ( in->bv_val[0] == '{' && in->bv_val[in->bv_len-1] == '}' ) { + slap_sl_free( i.bv_val, ctx ); + } + + if ( rc ) { + return LDAP_INVALID_SYNTAX; + } + + /* Convert sn to canonical hex */ + sn2.bv_val = sbuf2; + if ( sn.bv_len > sizeof( sbuf2 ) ) { + sn2.bv_val = slap_sl_malloc( sn.bv_len, ctx ); + } + sn2.bv_len = sn.bv_len; + sn3.bv_val = sbuf3; + sn3.bv_len = sizeof(sbuf3); + if ( lutil_str2bin( &sn, &sn2, ctx ) || slap_bin2hex( &sn2, &sn3, ctx ) ) { + rc = LDAP_INVALID_SYNTAX; + goto func_leave; + } + + out->bv_len = STRLENOF( "{ serialNumber , issuer rdnSequence:\"\" }" ) + + sn3.bv_len + ni.bv_len; + out->bv_val = slap_sl_malloc( out->bv_len + 1, ctx ); + if ( out->bv_val == NULL ) { + out->bv_len = 0; + rc = LDAP_OTHER; + goto func_leave; + } + + p = out->bv_val; + + p = lutil_strcopy( p, "{ serialNumber " /*}*/ ); + p = lutil_strbvcopy( p, &sn3 ); + p = lutil_strcopy( p, ", issuer rdnSequence:\"" ); + p = lutil_strbvcopy( p, &ni ); + p = lutil_strcopy( p, /*{*/ "\" }" ); + + assert( p == &out->bv_val[out->bv_len] ); + +func_leave: + Debug( LDAP_DEBUG_TRACE, "<<< serialNumberAndIssuerNormalize: <%s> => <%s>\n", + in->bv_val, rc == LDAP_SUCCESS ? out->bv_val : "(err)", 0 ); + + if ( sn2.bv_val != sbuf2 ) { + slap_sl_free( sn2.bv_val, ctx ); + } + + if ( sn3.bv_val != sbuf3 ) { + slap_sl_free( sn3.bv_val, ctx ); + } + + slap_sl_free( ni.bv_val, ctx ); + + return rc; +} + +static int +certificateExactNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_tag_t tag; + ber_len_t len; + ber_int_t i; + char serialbuf2[SLAP_SN_BUFLEN]; + struct berval sn, sn2 = BER_BVNULL; + struct berval issuer_dn = BER_BVNULL, bvdn; + char *p; + int rc = LDAP_INVALID_SYNTAX; + + assert( val != NULL ); + + Debug( LDAP_DEBUG_TRACE, ">>> certificateExactNormalize: <%p, %lu>\n", + val->bv_val, val->bv_len, 0 ); + + if ( BER_BVISEMPTY( val ) ) goto done; + + if ( SLAP_MR_IS_VALUE_OF_ASSERTION_SYNTAX(usage) ) { + return serialNumberAndIssuerNormalize( 0, NULL, NULL, val, normalized, ctx ); + } + + assert( SLAP_MR_IS_VALUE_OF_ATTRIBUTE_SYNTAX(usage) != 0 ); + + ber_init2( ber, val, LBER_USE_DER ); + tag = ber_skip_tag( ber, &len ); /* Signed Sequence */ + tag = ber_skip_tag( ber, &len ); /* Sequence */ + tag = ber_peek_tag( ber, &len ); /* Optional version? */ + if ( tag == SLAP_X509_OPT_C_VERSION ) { + tag = ber_skip_tag( ber, &len ); + tag = ber_get_int( ber, &i ); /* version */ + } + + /* NOTE: move the test here from certificateValidate, + * so that we can validate certs with serial longer + * than sizeof(ber_int_t) */ + tag = ber_skip_tag( ber, &len ); /* serial */ + sn.bv_len = len; + sn.bv_val = (char *)ber->ber_ptr; + sn2.bv_val = serialbuf2; + sn2.bv_len = sizeof(serialbuf2); + if ( slap_bin2hex( &sn, &sn2, ctx ) ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + ber_skip_data( ber, len ); + + tag = ber_skip_tag( ber, &len ); /* SignatureAlg */ + ber_skip_data( ber, len ); + tag = ber_peek_tag( ber, &len ); /* IssuerDN */ + if ( len ) { + len = ber_ptrlen( ber ); + bvdn.bv_val = val->bv_val + len; + bvdn.bv_len = val->bv_len - len; + + rc = dnX509normalize( &bvdn, &issuer_dn ); + if ( rc != LDAP_SUCCESS ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + } + + normalized->bv_len = STRLENOF( "{ serialNumber , issuer rdnSequence:\"\" }" ) + + sn2.bv_len + issuer_dn.bv_len; + normalized->bv_val = ch_malloc( normalized->bv_len + 1 ); + + p = normalized->bv_val; + + p = lutil_strcopy( p, "{ serialNumber " /*}*/ ); + p = lutil_strbvcopy( p, &sn2 ); + p = lutil_strcopy( p, ", issuer rdnSequence:\"" ); + p = lutil_strbvcopy( p, &issuer_dn ); + p = lutil_strcopy( p, /*{*/ "\" }" ); + + rc = LDAP_SUCCESS; + +done: + Debug( LDAP_DEBUG_TRACE, "<<< certificateExactNormalize: <%p, %lu> => <%s>\n", + val->bv_val, val->bv_len, rc == LDAP_SUCCESS ? normalized->bv_val : "(err)" ); + + if ( issuer_dn.bv_val ) ber_memfree( issuer_dn.bv_val ); + if ( sn2.bv_val != serialbuf2 ) ber_memfree_x( sn2.bv_val, ctx ); + + return rc; +} + +/* X.509 PKI certificateList stuff */ +static int +checkTime( struct berval *in, struct berval *out ) +{ + int rc; + ber_len_t i; + char buf[STRLENOF("YYYYmmddHHMMSSZ") + 1]; + struct berval bv; + + assert( in != NULL ); + assert( !BER_BVISNULL( in ) ); + assert( !BER_BVISEMPTY( in ) ); + + if ( in->bv_len < STRLENOF( "YYmmddHHMMSSZ" ) ) { + return -1; + } + + if ( out != NULL ) { + assert( !BER_BVISNULL( out ) ); + assert( out->bv_len >= sizeof( buf ) ); + bv.bv_val = out->bv_val; + + } else { + bv.bv_val = buf; + } + + for ( i = 0; i < STRLENOF( "YYYYmmddHHMMSS" ); i++ ) { + if ( !ASCII_DIGIT( in->bv_val[i] ) ) break; + } + + if ( in->bv_val[i] != 'Z' ) { + return -1; + } + i++; + + if ( i != in->bv_len ) { + return -1; + } + + if ( i == STRLENOF( "YYYYmmddHHMMSSZ" ) ) { + lutil_strncopy( bv.bv_val, in->bv_val, i ); + bv.bv_len = i; + + } else if ( i == STRLENOF( "YYmmddHHMMSSZ" ) ) { + char *p = bv.bv_val; + if ( in->bv_val[0] < '7' ) { + p = lutil_strcopy( p, "20" ); + + } else { + p = lutil_strcopy( p, "19" ); + } + lutil_strncopy( p, in->bv_val, i ); + bv.bv_len = 2 + i; + + } else { + return -1; + } + + rc = generalizedTimeValidate( NULL, &bv ); + if ( rc == LDAP_SUCCESS && out != NULL ) { + if ( out->bv_len > bv.bv_len ) { + out->bv_val[ bv.bv_len ] = '\0'; + } + out->bv_len = bv.bv_len; + } + + return rc != LDAP_SUCCESS; +} + +static int +issuerAndThisUpdateCheck( + struct berval *in, + struct berval *is, + struct berval *tu, + void *ctx ) +{ + int numdquotes = 0; + struct berval x = *in; + struct berval ni = BER_BVNULL; + /* Parse GSER format */ + enum { + HAVE_NONE = 0x0, + HAVE_ISSUER = 0x1, + HAVE_THISUPDATE = 0x2, + HAVE_ALL = ( HAVE_ISSUER | HAVE_THISUPDATE ) + } have = HAVE_NONE; + + + if ( in->bv_len < STRLENOF( "{issuer \"\",thisUpdate \"YYMMDDhhmmssZ\"}" ) ) return LDAP_INVALID_SYNTAX; + + if ( in->bv_val[0] != '{' || in->bv_val[in->bv_len-1] != '}' ) { + return LDAP_INVALID_SYNTAX; + } + + x.bv_val++; + x.bv_len -= STRLENOF("{}"); + + do { + /* eat leading spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) { + /* empty */; + } + + /* should be at issuer or thisUpdate */ + if ( strncasecmp( x.bv_val, "issuer", STRLENOF("issuer") ) == 0 ) { + if ( have & HAVE_ISSUER ) return LDAP_INVALID_SYNTAX; + + /* parse issuer */ + x.bv_val += STRLENOF("issuer"); + x.bv_len -= STRLENOF("issuer"); + + if ( x.bv_val[0] != ' ' ) return LDAP_INVALID_SYNTAX; + x.bv_val++; + x.bv_len--; + + /* eat leading spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) { + /* empty */; + } + + /* For backward compatibility, this part is optional */ + if ( strncasecmp( x.bv_val, "rdnSequence:", STRLENOF("rdnSequence:") ) != 0 ) { + return LDAP_INVALID_SYNTAX; + } + x.bv_val += STRLENOF("rdnSequence:"); + x.bv_len -= STRLENOF("rdnSequence:"); + + if ( x.bv_val[0] != '"' ) return LDAP_INVALID_SYNTAX; + x.bv_val++; + x.bv_len--; + + is->bv_val = x.bv_val; + is->bv_len = 0; + + for ( ; is->bv_len < x.bv_len; ) { + if ( is->bv_val[is->bv_len] != '"' ) { + is->bv_len++; + continue; + } + if ( is->bv_val[is->bv_len+1] == '"' ) { + /* double dquote */ + numdquotes++; + is->bv_len += 2; + continue; + } + break; + } + x.bv_val += is->bv_len + 1; + x.bv_len -= is->bv_len + 1; + + have |= HAVE_ISSUER; + + } else if ( strncasecmp( x.bv_val, "thisUpdate", STRLENOF("thisUpdate") ) == 0 ) + { + if ( have & HAVE_THISUPDATE ) return LDAP_INVALID_SYNTAX; + + /* parse thisUpdate */ + x.bv_val += STRLENOF("thisUpdate"); + x.bv_len -= STRLENOF("thisUpdate"); + + if ( x.bv_val[0] != ' ' ) return LDAP_INVALID_SYNTAX; + x.bv_val++; + x.bv_len--; + + /* eat leading spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) { + /* empty */; + } + + if ( !x.bv_len || x.bv_val[0] != '"' ) return LDAP_INVALID_SYNTAX; + x.bv_val++; + x.bv_len--; + + tu->bv_val = x.bv_val; + tu->bv_len = 0; + + for ( ; tu->bv_len < x.bv_len; tu->bv_len++ ) { + if ( tu->bv_val[tu->bv_len] == '"' ) { + break; + } + } + x.bv_val += tu->bv_len + 1; + x.bv_len -= tu->bv_len + 1; + + have |= HAVE_THISUPDATE; + + } else { + return LDAP_INVALID_SYNTAX; + } + + /* eat leading spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) { + /* empty */; + } + + if ( have == HAVE_ALL ) { + break; + } + + if ( x.bv_val[0] != ',' ) { + return LDAP_INVALID_SYNTAX; + } + + x.bv_val++; + x.bv_len--; + } while ( 1 ); + + /* should have no characters left... */ + if ( x.bv_len ) return LDAP_INVALID_SYNTAX; + + if ( numdquotes == 0 ) { + ber_dupbv_x( &ni, is, ctx ); + + } else { + ber_len_t src, dst; + + ni.bv_len = is->bv_len - numdquotes; + ni.bv_val = ber_memalloc_x( ni.bv_len + 1, ctx ); + for ( src = 0, dst = 0; src < is->bv_len; src++, dst++ ) { + if ( is->bv_val[src] == '"' ) { + src++; + } + ni.bv_val[dst] = is->bv_val[src]; + } + ni.bv_val[dst] = '\0'; + } + + *is = ni; + + return 0; +} + +static int +issuerAndThisUpdateValidate( + Syntax *syntax, + struct berval *in ) +{ + int rc; + struct berval i, tu; + + Debug( LDAP_DEBUG_TRACE, ">>> issuerAndThisUpdateValidate: <%s>\n", + in->bv_val, 0, 0 ); + + rc = issuerAndThisUpdateCheck( in, &i, &tu, NULL ); + if ( rc ) { + goto done; + } + + /* validate DN -- doesn't handle double dquote */ + rc = dnValidate( NULL, &i ); + if ( rc ) { + rc = LDAP_INVALID_SYNTAX; + + } else if ( checkTime( &tu, NULL ) ) { + rc = LDAP_INVALID_SYNTAX; + } + + if ( in->bv_val[0] == '{' && in->bv_val[in->bv_len-1] == '}' ) { + slap_sl_free( i.bv_val, NULL ); + } + + Debug( LDAP_DEBUG_TRACE, "<<< issuerAndThisUpdateValidate: <%s> err=%d\n", + in->bv_val, rc, 0 ); + +done:; + return rc; +} + +static int +issuerAndThisUpdatePretty( + Syntax *syntax, + struct berval *in, + struct berval *out, + void *ctx ) +{ + int rc; + struct berval i, tu, ni = BER_BVNULL; + char *p; + + assert( in != NULL ); + assert( out != NULL ); + + BER_BVZERO( out ); + + Debug( LDAP_DEBUG_TRACE, ">>> issuerAndThisUpdatePretty: <%s>\n", + in->bv_val, 0, 0 ); + + rc = issuerAndThisUpdateCheck( in, &i, &tu, ctx ); + if ( rc ) { + goto done; + } + + rc = dnPretty( syntax, &i, &ni, ctx ); + + if ( in->bv_val[0] == '{' && in->bv_val[in->bv_len-1] == '}' ) { + slap_sl_free( i.bv_val, ctx ); + } + + if ( rc || checkTime( &tu, NULL ) ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + + /* make room */ + out->bv_len = STRLENOF("{ issuer rdnSequence:\"\", thisUpdate \"\" }") + + ni.bv_len + tu.bv_len; + out->bv_val = slap_sl_malloc( out->bv_len + 1, ctx ); + + if ( out->bv_val == NULL ) { + out->bv_len = 0; + rc = LDAP_OTHER; + goto done; + } + + p = out->bv_val; + p = lutil_strcopy( p, "{ issuer rdnSequence:\"" /*}*/ ); + p = lutil_strbvcopy( p, &ni ); + p = lutil_strcopy( p, "\", thisUpdate \"" ); + p = lutil_strbvcopy( p, &tu ); + p = lutil_strcopy( p, /*{*/ "\" }" ); + + assert( p == &out->bv_val[out->bv_len] ); + +done:; + Debug( LDAP_DEBUG_TRACE, "<<< issuerAndThisUpdatePretty: <%s> => <%s>\n", + in->bv_val, rc == LDAP_SUCCESS ? out->bv_val : "(err)", 0 ); + + slap_sl_free( ni.bv_val, ctx ); + + return rc; +} + +static int +issuerAndThisUpdateNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *in, + struct berval *out, + void *ctx ) +{ + struct berval i, ni, tu, tu2; + char sbuf[STRLENOF("YYYYmmddHHMMSSZ") + 1]; + char *p; + int rc; + + assert( in != NULL ); + assert( out != NULL ); + + Debug( LDAP_DEBUG_TRACE, ">>> issuerAndThisUpdateNormalize: <%s>\n", + in->bv_val, 0, 0 ); + + rc = issuerAndThisUpdateCheck( in, &i, &tu, ctx ); + if ( rc ) { + return rc; + } + + rc = dnNormalize( usage, syntax, mr, &i, &ni, ctx ); + + if ( in->bv_val[0] == '{' && in->bv_val[in->bv_len-1] == '}' ) { + slap_sl_free( i.bv_val, ctx ); + } + + tu2.bv_val = sbuf; + tu2.bv_len = sizeof( sbuf ); + if ( rc || checkTime( &tu, &tu2 ) ) { + return LDAP_INVALID_SYNTAX; + } + + out->bv_len = STRLENOF( "{ issuer rdnSequence:\"\", thisUpdate \"\" }" ) + + ni.bv_len + tu2.bv_len; + out->bv_val = slap_sl_malloc( out->bv_len + 1, ctx ); + + if ( out->bv_val == NULL ) { + out->bv_len = 0; + rc = LDAP_OTHER; + goto func_leave; + } + + p = out->bv_val; + + p = lutil_strcopy( p, "{ issuer rdnSequence:\"" /*}*/ ); + p = lutil_strbvcopy( p, &ni ); + p = lutil_strcopy( p, "\", thisUpdate \"" ); + p = lutil_strbvcopy( p, &tu2 ); + p = lutil_strcopy( p, /*{*/ "\" }" ); + + assert( p == &out->bv_val[out->bv_len] ); + +func_leave: + Debug( LDAP_DEBUG_TRACE, "<<< issuerAndThisUpdateNormalize: <%s> => <%s>\n", + in->bv_val, rc == LDAP_SUCCESS ? out->bv_val : "(err)", 0 ); + + slap_sl_free( ni.bv_val, ctx ); + + return rc; +} + +static int +certificateListExactNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_tag_t tag; + ber_len_t len; + ber_int_t version; + struct berval issuer_dn = BER_BVNULL, bvdn, + thisUpdate, bvtu; + char *p, tubuf[STRLENOF("YYYYmmddHHMMSSZ") + 1]; + int rc = LDAP_INVALID_SYNTAX; + + assert( val != NULL ); + + Debug( LDAP_DEBUG_TRACE, ">>> certificateListExactNormalize: <%p, %lu>\n", + val->bv_val, val->bv_len, 0 ); + + if ( BER_BVISEMPTY( val ) ) goto done; + + if ( SLAP_MR_IS_VALUE_OF_ASSERTION_SYNTAX(usage) ) { + return issuerAndThisUpdateNormalize( 0, NULL, NULL, val, normalized, ctx ); + } + + assert( SLAP_MR_IS_VALUE_OF_ATTRIBUTE_SYNTAX(usage) != 0 ); + + ber_init2( ber, val, LBER_USE_DER ); + tag = ber_skip_tag( ber, &len ); /* Signed wrapper */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + tag = ber_skip_tag( ber, &len ); /* Sequence */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + tag = ber_peek_tag( ber, &len ); + /* Optional version */ + if ( tag == LBER_INTEGER ) { + tag = ber_get_int( ber, &version ); + assert( tag == LBER_INTEGER ); + if ( version != SLAP_X509_V2 ) return LDAP_INVALID_SYNTAX; + } + tag = ber_skip_tag( ber, &len ); /* Signature Algorithm */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + ber_skip_data( ber, len ); + + tag = ber_peek_tag( ber, &len ); /* IssuerDN */ + if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX; + len = ber_ptrlen( ber ); + bvdn.bv_val = val->bv_val + len; + bvdn.bv_len = val->bv_len - len; + tag = ber_skip_tag( ber, &len ); + ber_skip_data( ber, len ); + + tag = ber_skip_tag( ber, &len ); /* thisUpdate */ + /* Time is a CHOICE { UTCTime, GeneralizedTime } */ + if ( tag != SLAP_TAG_UTCTIME && tag != SLAP_TAG_GENERALIZEDTIME ) return LDAP_INVALID_SYNTAX; + bvtu.bv_val = (char *)ber->ber_ptr; + bvtu.bv_len = len; + + rc = dnX509normalize( &bvdn, &issuer_dn ); + if ( rc != LDAP_SUCCESS ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + + thisUpdate.bv_val = tubuf; + thisUpdate.bv_len = sizeof(tubuf); + if ( checkTime( &bvtu, &thisUpdate ) ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + + normalized->bv_len = STRLENOF( "{ issuer rdnSequence:\"\", thisUpdate \"\" }" ) + + issuer_dn.bv_len + thisUpdate.bv_len; + normalized->bv_val = ch_malloc( normalized->bv_len + 1 ); + + p = normalized->bv_val; + + p = lutil_strcopy( p, "{ issuer rdnSequence:\"" ); + p = lutil_strbvcopy( p, &issuer_dn ); + p = lutil_strcopy( p, "\", thisUpdate \"" ); + p = lutil_strbvcopy( p, &thisUpdate ); + p = lutil_strcopy( p, /*{*/ "\" }" ); + + rc = LDAP_SUCCESS; + +done: + Debug( LDAP_DEBUG_TRACE, "<<< certificateListExactNormalize: <%p, %lu> => <%s>\n", + val->bv_val, val->bv_len, rc == LDAP_SUCCESS ? normalized->bv_val : "(err)" ); + + if ( issuer_dn.bv_val ) ber_memfree( issuer_dn.bv_val ); + + return rc; +} + +/* X.509 PMI serialNumberAndIssuerSerialCheck + +AttributeCertificateExactAssertion ::= SEQUENCE { + serialNumber CertificateSerialNumber, + issuer AttCertIssuer } + +CertificateSerialNumber ::= INTEGER + +AttCertIssuer ::= [0] SEQUENCE { +issuerName GeneralNames OPTIONAL, +baseCertificateID [0] IssuerSerial OPTIONAL, +objectDigestInfo [1] ObjectDigestInfo OPTIONAL } +-- At least one component shall be present + +GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName + +GeneralName ::= CHOICE { + otherName [0] INSTANCE OF OTHER-NAME, + rfc822Name [1] IA5String, + dNSName [2] IA5String, + x400Address [3] ORAddress, + directoryName [4] Name, + ediPartyName [5] EDIPartyName, + uniformResourceIdentifier [6] IA5String, + iPAddress [7] OCTET STRING, + registeredID [8] OBJECT IDENTIFIER } + +IssuerSerial ::= SEQUENCE { + issuer GeneralNames, + serial CertificateSerialNumber, + issuerUID UniqueIdentifier OPTIONAL } + +ObjectDigestInfo ::= SEQUENCE { + digestedObjectType ENUMERATED { + publicKey (0), + publicKeyCert (1), + otherObjectTypes (2) }, + otherObjectTypeID OBJECT IDENTIFIER OPTIONAL, + digestAlgorithm AlgorithmIdentifier, + objectDigest BIT STRING } + + * The way I interpret it, an assertion should look like + + { serialNumber 'dd'H, + issuer { issuerName { directoryName:rdnSequence:"cn=yyy" }, -- optional + baseCertificateID { serial '1d'H, + issuer { directoryName:rdnSequence:"cn=zzz" }, + issuerUID <value> -- optional + }, -- optional + objectDigestInfo { ... } -- optional + } + } + + * with issuerName, baseCertificateID and objectDigestInfo optional, + * at least one present; the way it's currently implemented, it is + + { serialNumber 'dd'H, + issuer { baseCertificateID { serial '1d'H, + issuer { directoryName:rdnSequence:"cn=zzz" } + } + } + } + + * with all the above parts mandatory. + */ +static int +serialNumberAndIssuerSerialCheck( + struct berval *in, + struct berval *sn, + struct berval *is, + struct berval *i_sn, /* contain serial of baseCertificateID */ + void *ctx ) +{ + /* Parse GSER format */ + enum { + HAVE_NONE = 0x0, + HAVE_SN = 0x1, + HAVE_ISSUER = 0x2, + HAVE_ALL = ( HAVE_SN | HAVE_ISSUER ) + } have = HAVE_NONE, have2 = HAVE_NONE; + int numdquotes = 0; + struct berval x = *in; + struct berval ni; + + if ( in->bv_len < 3 ) return LDAP_INVALID_SYNTAX; + + /* no old format */ + if ( in->bv_val[0] != '{' || in->bv_val[in->bv_len-1] != '}' ) return LDAP_INVALID_SYNTAX; + + x.bv_val++; + x.bv_len -= 2; + + do { + + /* eat leading spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) { + /* empty */; + } + + /* should be at issuer or serialNumber NamedValue */ + if ( strncasecmp( x.bv_val, "issuer", STRLENOF("issuer") ) == 0 ) { + if ( have & HAVE_ISSUER ) { + return LDAP_INVALID_SYNTAX; + } + + /* parse IssuerSerial */ + x.bv_val += STRLENOF("issuer"); + x.bv_len -= STRLENOF("issuer"); + + if ( x.bv_val[0] != ' ' ) return LDAP_INVALID_SYNTAX; + x.bv_val++; + x.bv_len--; + + /* eat leading spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) { + /* empty */; + } + + if ( x.bv_val[0] != '{' /*}*/ ) return LDAP_INVALID_SYNTAX; + x.bv_val++; + x.bv_len--; + + /* eat leading spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) { + /* empty */; + } + + if ( strncasecmp( x.bv_val, "baseCertificateID ", STRLENOF("baseCertificateID ") ) != 0 ) { + return LDAP_INVALID_SYNTAX; + } + x.bv_val += STRLENOF("baseCertificateID "); + x.bv_len -= STRLENOF("baseCertificateID "); + + /* eat leading spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) { + /* empty */; + } + + if ( x.bv_val[0] != '{' /*}*/ ) return LDAP_INVALID_SYNTAX; + x.bv_val++; + x.bv_len--; + + do { + /* eat leading spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) { + /* empty */; + } + + /* parse issuer of baseCertificateID */ + if ( strncasecmp( x.bv_val, "issuer ", STRLENOF("issuer ") ) == 0 ) { + if ( have2 & HAVE_ISSUER ) { + return LDAP_INVALID_SYNTAX; + } + + x.bv_val += STRLENOF("issuer "); + x.bv_len -= STRLENOF("issuer "); + + /* eat leading spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) { + /* empty */; + } + + if ( x.bv_val[0] != '{' /*}*/ ) return LDAP_INVALID_SYNTAX; + x.bv_val++; + x.bv_len--; + + /* eat leading spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) { + /* empty */; + } + + if ( strncasecmp( x.bv_val, "directoryName:rdnSequence:", STRLENOF("directoryName:rdnSequence:") ) != 0 ) { + return LDAP_INVALID_SYNTAX; + } + x.bv_val += STRLENOF("directoryName:rdnSequence:"); + x.bv_len -= STRLENOF("directoryName:rdnSequence:"); + + if ( x.bv_val[0] != '"' ) return LDAP_INVALID_SYNTAX; + x.bv_val++; + x.bv_len--; + + is->bv_val = x.bv_val; + is->bv_len = 0; + + for ( ; is->bv_len < x.bv_len; ) { + if ( is->bv_val[is->bv_len] != '"' ) { + is->bv_len++; + continue; + } + if ( is->bv_val[is->bv_len + 1] == '"' ) { + /* double dquote */ + numdquotes++; + is->bv_len += 2; + continue; + } + break; + } + x.bv_val += is->bv_len + 1; + x.bv_len -= is->bv_len + 1; + + /* eat leading spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) { + /* empty */; + } + + if ( x.bv_val[0] != /*{*/ '}' ) return LDAP_INVALID_SYNTAX; + x.bv_val++; + x.bv_len--; + + have2 |= HAVE_ISSUER; + + } else if ( strncasecmp( x.bv_val, "serial ", STRLENOF("serial ") ) == 0 ) { + if ( have2 & HAVE_SN ) { + return LDAP_INVALID_SYNTAX; + } + + x.bv_val += STRLENOF("serial "); + x.bv_len -= STRLENOF("serial "); + + /* eat leading spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len--) { + /* empty */; + } + + if ( checkNum( &x, i_sn ) ) { + return LDAP_INVALID_SYNTAX; + } + + x.bv_val += i_sn->bv_len; + x.bv_len -= i_sn->bv_len; + + have2 |= HAVE_SN; + + } else { + return LDAP_INVALID_SYNTAX; + } + + /* eat leading spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) { + /* empty */; + } + + if ( have2 == HAVE_ALL ) { + break; + } + + if ( x.bv_val[0] != ',' ) return LDAP_INVALID_SYNTAX; + x.bv_val++; + x.bv_len--; + } while ( 1 ); + + if ( x.bv_val[0] != /*{*/ '}' ) return LDAP_INVALID_SYNTAX; + x.bv_val++; + x.bv_len--; + + /* eat leading spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) { + /* empty */; + } + + if ( x.bv_val[0] != /*{*/ '}' ) return LDAP_INVALID_SYNTAX; + x.bv_val++; + x.bv_len--; + + have |= HAVE_ISSUER; + + } else if ( strncasecmp( x.bv_val, "serialNumber", STRLENOF("serialNumber") ) == 0 ) { + if ( have & HAVE_SN ) { + return LDAP_INVALID_SYNTAX; + } + + /* parse serialNumber */ + x.bv_val += STRLENOF("serialNumber"); + x.bv_len -= STRLENOF("serialNumber"); + + if ( x.bv_val[0] != ' ' ) return LDAP_INVALID_SYNTAX; + x.bv_val++; + x.bv_len--; + + /* eat leading spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) { + /* empty */; + } + + if ( checkNum( &x, sn ) ) { + return LDAP_INVALID_SYNTAX; + } + + x.bv_val += sn->bv_len; + x.bv_len -= sn->bv_len; + + have |= HAVE_SN; + + } else { + return LDAP_INVALID_SYNTAX; + } + + /* eat spaces */ + for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) { + /* empty */; + } + + if ( have == HAVE_ALL ) { + break; + } + + if ( x.bv_val[0] != ',' ) { + return LDAP_INVALID_SYNTAX; + } + x.bv_val++ ; + x.bv_len--; + } while ( 1 ); + + /* should have no characters left... */ + if( x.bv_len ) return LDAP_INVALID_SYNTAX; + + if ( numdquotes == 0 ) { + ber_dupbv_x( &ni, is, ctx ); + + } else { + ber_len_t src, dst; + + ni.bv_len = is->bv_len - numdquotes; + ni.bv_val = ber_memalloc_x( ni.bv_len + 1, ctx ); + for ( src = 0, dst = 0; src < is->bv_len; src++, dst++ ) { + if ( is->bv_val[src] == '"' ) { + src++; + } + ni.bv_val[dst] = is->bv_val[src]; + } + ni.bv_val[dst] = '\0'; + } + + *is = ni; + + /* need to handle double dquotes here */ + return 0; +} + +/* X.509 PMI serialNumberAndIssuerSerialValidate */ +static int +serialNumberAndIssuerSerialValidate( + Syntax *syntax, + struct berval *in ) +{ + int rc; + struct berval sn, i, i_sn; + + Debug( LDAP_DEBUG_TRACE, ">>> serialNumberAndIssuerSerialValidate: <%s>\n", + in->bv_val, 0, 0 ); + + rc = serialNumberAndIssuerSerialCheck( in, &sn, &i, &i_sn, NULL ); + if ( rc ) { + goto done; + } + + /* validate DN -- doesn't handle double dquote */ + rc = dnValidate( NULL, &i ); + if ( rc ) { + rc = LDAP_INVALID_SYNTAX; + } + + if ( in->bv_val[0] == '{' && in->bv_val[in->bv_len-1] == '}' ) { + slap_sl_free( i.bv_val, NULL ); + } + +done:; + Debug( LDAP_DEBUG_TRACE, "<<< serialNumberAndIssuerSerialValidate: <%s> err=%d\n", + in->bv_val, rc, 0 ); + + return rc; +} + +/* X.509 PMI serialNumberAndIssuerSerialPretty */ +static int +serialNumberAndIssuerSerialPretty( + Syntax *syntax, + struct berval *in, + struct berval *out, + void *ctx ) +{ + struct berval sn, i, i_sn, ni = BER_BVNULL; + char *p; + int rc; + + assert( in != NULL ); + assert( out != NULL ); + + Debug( LDAP_DEBUG_TRACE, ">>> serialNumberAndIssuerSerialPretty: <%s>\n", + in->bv_val, 0, 0 ); + + rc = serialNumberAndIssuerSerialCheck( in, &sn, &i, &i_sn, ctx ); + if ( rc ) { + goto done; + } + + rc = dnPretty( syntax, &i, &ni, ctx ); + + if ( in->bv_val[0] == '{' && in->bv_val[in->bv_len-1] == '}' ) { + slap_sl_free( i.bv_val, ctx ); + } + + if ( rc ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + + /* make room from sn + "$" */ + out->bv_len = STRLENOF("{ serialNumber , issuer { baseCertificateID { issuer { directoryName:rdnSequence:\"\" }, serial } } }") + + sn.bv_len + ni.bv_len + i_sn.bv_len; + out->bv_val = slap_sl_malloc( out->bv_len + 1, ctx ); + + if ( out->bv_val == NULL ) { + out->bv_len = 0; + rc = LDAP_OTHER; + goto done; + } + + p = out->bv_val; + p = lutil_strcopy( p, "{ serialNumber " ); + p = lutil_strbvcopy( p, &sn ); + p = lutil_strcopy( p, ", issuer { baseCertificateID { issuer { directoryName:rdnSequence:\"" ); + p = lutil_strbvcopy( p, &ni ); + p = lutil_strcopy( p, "\" }, serial " ); + p = lutil_strbvcopy( p, &i_sn ); + p = lutil_strcopy( p, " } } }" ); + + assert( p == &out->bv_val[out->bv_len] ); + +done:; + Debug( LDAP_DEBUG_TRACE, "<<< serialNumberAndIssuerSerialPretty: <%s> => <%s>\n", + in->bv_val, rc == LDAP_SUCCESS ? out->bv_val : "(err)", 0 ); + + slap_sl_free( ni.bv_val, ctx ); + + return rc; +} + +/* X.509 PMI serialNumberAndIssuerSerialNormalize */ +/* + * This routine is called by attributeCertificateExactNormalize + * when attributeCertificateExactNormalize receives a search + * string instead of a attribute certificate. This routine + * checks if the search value is valid and then returns the + * normalized value + */ +static int +serialNumberAndIssuerSerialNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *in, + struct berval *out, + void *ctx ) +{ + struct berval i, ni = BER_BVNULL, + sn, sn2 = BER_BVNULL, sn3 = BER_BVNULL, + i_sn, i_sn2 = BER_BVNULL, i_sn3 = BER_BVNULL; + char sbuf2[SLAP_SN_BUFLEN], i_sbuf2[SLAP_SN_BUFLEN], + sbuf3[SLAP_SN_BUFLEN], i_sbuf3[SLAP_SN_BUFLEN]; + char *p; + int rc; + + assert( in != NULL ); + assert( out != NULL ); + + Debug( LDAP_DEBUG_TRACE, ">>> serialNumberAndIssuerSerialNormalize: <%s>\n", + in->bv_val, 0, 0 ); + + rc = serialNumberAndIssuerSerialCheck( in, &sn, &i, &i_sn, ctx ); + if ( rc ) { + goto func_leave; + } + + rc = dnNormalize( usage, syntax, mr, &i, &ni, ctx ); + + if ( in->bv_val[0] == '{' && in->bv_val[in->bv_len-1] == '}' ) { + slap_sl_free( i.bv_val, ctx ); + } + + if ( rc ) { + rc = LDAP_INVALID_SYNTAX; + goto func_leave; + } + + /* Convert sn to canonical hex */ + sn2.bv_val = sbuf2; + sn2.bv_len = sn.bv_len; + if ( sn.bv_len > sizeof( sbuf2 ) ) { + sn2.bv_val = slap_sl_malloc( sn.bv_len, ctx ); + } + if ( lutil_str2bin( &sn, &sn2, ctx ) ) { + rc = LDAP_INVALID_SYNTAX; + goto func_leave; + } + + /* Convert i_sn to canonical hex */ + i_sn2.bv_val = i_sbuf2; + i_sn2.bv_len = i_sn.bv_len; + if ( i_sn.bv_len > sizeof( i_sbuf2 ) ) { + i_sn2.bv_val = slap_sl_malloc( i_sn.bv_len, ctx ); + } + if ( lutil_str2bin( &i_sn, &i_sn2, ctx ) ) { + rc = LDAP_INVALID_SYNTAX; + goto func_leave; + } + + sn3.bv_val = sbuf3; + sn3.bv_len = sizeof(sbuf3); + if ( slap_bin2hex( &sn2, &sn3, ctx ) ) { + rc = LDAP_INVALID_SYNTAX; + goto func_leave; + } + + i_sn3.bv_val = i_sbuf3; + i_sn3.bv_len = sizeof(i_sbuf3); + if ( slap_bin2hex( &i_sn2, &i_sn3, ctx ) ) { + rc = LDAP_INVALID_SYNTAX; + goto func_leave; + } + + out->bv_len = STRLENOF("{ serialNumber , issuer { baseCertificateID { issuer { directoryName:rdnSequence:\"\" }, serial } } }") + + sn3.bv_len + ni.bv_len + i_sn3.bv_len; + out->bv_val = slap_sl_malloc( out->bv_len + 1, ctx ); + + if ( out->bv_val == NULL ) { + out->bv_len = 0; + rc = LDAP_OTHER; + goto func_leave; + } + + p = out->bv_val; + + p = lutil_strcopy( p, "{ serialNumber " ); + p = lutil_strbvcopy( p, &sn3 ); + p = lutil_strcopy( p, ", issuer { baseCertificateID { issuer { directoryName:rdnSequence:\"" ); + p = lutil_strbvcopy( p, &ni ); + p = lutil_strcopy( p, "\" }, serial " ); + p = lutil_strbvcopy( p, &i_sn3 ); + p = lutil_strcopy( p, " } } }" ); + + assert( p == &out->bv_val[out->bv_len] ); + +func_leave: + Debug( LDAP_DEBUG_TRACE, "<<< serialNumberAndIssuerSerialNormalize: <%s> => <%s>\n", + in->bv_val, rc == LDAP_SUCCESS ? out->bv_val : "(err)", 0 ); + + if ( sn2.bv_val != sbuf2 ) { + slap_sl_free( sn2.bv_val, ctx ); + } + + if ( i_sn2.bv_val != i_sbuf2 ) { + slap_sl_free( i_sn2.bv_val, ctx ); + } + + if ( sn3.bv_val != sbuf3 ) { + slap_sl_free( sn3.bv_val, ctx ); + } + + if ( i_sn3.bv_val != i_sbuf3 ) { + slap_sl_free( i_sn3.bv_val, ctx ); + } + + slap_sl_free( ni.bv_val, ctx ); + + return rc; +} + +/* X.509 PMI attributeCertificateExactNormalize */ +static int +attributeCertificateExactNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_tag_t tag; + ber_len_t len; + char issuer_serialbuf[SLAP_SN_BUFLEN], serialbuf[SLAP_SN_BUFLEN]; + struct berval sn, i_sn, sn2 = BER_BVNULL, i_sn2 = BER_BVNULL; + struct berval issuer_dn = BER_BVNULL, bvdn; + char *p; + int rc = LDAP_INVALID_SYNTAX; + + if ( BER_BVISEMPTY( val ) ) { + return rc; + } + + if ( SLAP_MR_IS_VALUE_OF_ASSERTION_SYNTAX(usage) ) { + return serialNumberAndIssuerSerialNormalize( 0, NULL, NULL, val, normalized, ctx ); + } + + assert( SLAP_MR_IS_VALUE_OF_ATTRIBUTE_SYNTAX(usage) != 0 ); + + ber_init2( ber, val, LBER_USE_DER ); + tag = ber_skip_tag( ber, &len ); /* Signed Sequence */ + tag = ber_skip_tag( ber, &len ); /* Sequence */ + tag = ber_skip_tag( ber, &len ); /* (Mandatory) version; must be v2(1) */ + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); /* Holder Sequence */ + ber_skip_data( ber, len ); + + /* Issuer */ + tag = ber_skip_tag( ber, &len ); /* Sequence */ + /* issuerName (GeneralNames sequence; optional)? */ + tag = ber_skip_tag( ber, &len ); /* baseCertificateID (sequence; optional)? */ + tag = ber_skip_tag( ber, &len ); /* GeneralNames (sequence) */ + tag = ber_skip_tag( ber, &len ); /* directoryName (we only accept this form of GeneralName) */ + if ( tag != SLAP_X509_GN_DIRECTORYNAME ) { + return LDAP_INVALID_SYNTAX; + } + tag = ber_peek_tag( ber, &len ); /* sequence of RDN */ + len = ber_ptrlen( ber ); + bvdn.bv_val = val->bv_val + len; + bvdn.bv_len = val->bv_len - len; + rc = dnX509normalize( &bvdn, &issuer_dn ); + if ( rc != LDAP_SUCCESS ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + + tag = ber_skip_tag( ber, &len ); /* sequence of RDN */ + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); /* serial number */ + if ( tag != LBER_INTEGER ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + i_sn.bv_val = (char *)ber->ber_ptr; + i_sn.bv_len = len; + i_sn2.bv_val = issuer_serialbuf; + i_sn2.bv_len = sizeof(issuer_serialbuf); + if ( slap_bin2hex( &i_sn, &i_sn2, ctx ) ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + ber_skip_data( ber, len ); + + /* issuerUID (bitstring; optional)? */ + /* objectDigestInfo (sequence; optional)? */ + + tag = ber_skip_tag( ber, &len ); /* Signature (sequence) */ + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); /* serial number */ + if ( tag != LBER_INTEGER ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + sn.bv_val = (char *)ber->ber_ptr; + sn.bv_len = len; + sn2.bv_val = serialbuf; + sn2.bv_len = sizeof(serialbuf); + if ( slap_bin2hex( &sn, &sn2, ctx ) ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + ber_skip_data( ber, len ); + + normalized->bv_len = STRLENOF( "{ serialNumber , issuer { baseCertificateID { issuer { directoryName:rdnSequence:\"\" }, serial } } }" ) + + sn2.bv_len + issuer_dn.bv_len + i_sn2.bv_len; + normalized->bv_val = ch_malloc( normalized->bv_len + 1 ); + + p = normalized->bv_val; + + p = lutil_strcopy( p, "{ serialNumber " ); + p = lutil_strbvcopy( p, &sn2 ); + p = lutil_strcopy( p, ", issuer { baseCertificateID { issuer { directoryName:rdnSequence:\"" ); + p = lutil_strbvcopy( p, &issuer_dn ); + p = lutil_strcopy( p, "\" }, serial " ); + p = lutil_strbvcopy( p, &i_sn2 ); + p = lutil_strcopy( p, " } } }" ); + + Debug( LDAP_DEBUG_TRACE, "attributeCertificateExactNormalize: %s\n", + normalized->bv_val, NULL, NULL ); + + rc = LDAP_SUCCESS; + +done: + if ( issuer_dn.bv_val ) ber_memfree( issuer_dn.bv_val ); + if ( i_sn2.bv_val != issuer_serialbuf ) ber_memfree_x( i_sn2.bv_val, ctx ); + if ( sn2.bv_val != serialbuf ) ber_memfree_x( sn2.bv_val, ctx ); + + return rc; +} + + +static int +hexValidate( + Syntax *syntax, + struct berval *in ) +{ + ber_len_t i; + + assert( in != NULL ); + assert( !BER_BVISNULL( in ) ); + + for ( i = 0; i < in->bv_len; i++ ) { + if ( !ASCII_HEX( in->bv_val[ i ] ) ) { + return LDAP_INVALID_SYNTAX; + } + } + + return LDAP_SUCCESS; +} + +/* Normalize a SID as used inside a CSN: + * three-digit numeric string */ +static int +hexNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + ber_len_t i; + + assert( val != NULL ); + assert( normalized != NULL ); + + ber_dupbv_x( normalized, val, ctx ); + + for ( i = 0; i < normalized->bv_len; i++ ) { + if ( !ASCII_HEX( normalized->bv_val[ i ] ) ) { + ber_memfree_x( normalized->bv_val, ctx ); + BER_BVZERO( normalized ); + return LDAP_INVALID_SYNTAX; + } + + normalized->bv_val[ i ] = TOLOWER( normalized->bv_val[ i ] ); + } + + return LDAP_SUCCESS; +} + +static int +sidValidate ( + Syntax *syntax, + struct berval *in ) +{ + assert( in != NULL ); + assert( !BER_BVISNULL( in ) ); + + if ( in->bv_len != 3 ) { + return LDAP_INVALID_SYNTAX; + } + + return hexValidate( NULL, in ); +} + +/* Normalize a SID as used inside a CSN: + * three-digit numeric string */ +static int +sidNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + if ( val->bv_len != 3 ) { + return LDAP_INVALID_SYNTAX; + } + + return hexNormalize( 0, NULL, NULL, val, normalized, ctx ); +} + +static int +sidPretty( + Syntax *syntax, + struct berval *val, + struct berval *out, + void *ctx ) +{ + return sidNormalize( SLAP_MR_VALUE_OF_SYNTAX, NULL, NULL, val, out, ctx ); +} + +/* Normalize a SID as used inside a CSN, either as-is + * (assertion value) or extracted from the CSN + * (attribute value) */ +static int +csnSidNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + struct berval bv; + char *ptr, + buf[ 4 ]; + + + if ( BER_BVISEMPTY( val ) ) { + return LDAP_INVALID_SYNTAX; + } + + if ( SLAP_MR_IS_VALUE_OF_ASSERTION_SYNTAX(usage) ) { + return sidNormalize( 0, NULL, NULL, val, normalized, ctx ); + } + + assert( SLAP_MR_IS_VALUE_OF_ATTRIBUTE_SYNTAX(usage) != 0 ); + + ptr = ber_bvchr( val, '#' ); + if ( ptr == NULL || ptr == &val->bv_val[val->bv_len] ) { + return LDAP_INVALID_SYNTAX; + } + + bv.bv_val = ptr + 1; + bv.bv_len = val->bv_len - ( ptr + 1 - val->bv_val ); + + ptr = ber_bvchr( &bv, '#' ); + if ( ptr == NULL || ptr == &val->bv_val[val->bv_len] ) { + return LDAP_INVALID_SYNTAX; + } + + bv.bv_val = ptr + 1; + bv.bv_len = val->bv_len - ( ptr + 1 - val->bv_val ); + + ptr = ber_bvchr( &bv, '#' ); + if ( ptr == NULL || ptr == &val->bv_val[val->bv_len] ) { + return LDAP_INVALID_SYNTAX; + } + + bv.bv_len = ptr - bv.bv_val; + + if ( bv.bv_len == 2 ) { + /* OpenLDAP 2.3 SID */ + buf[ 0 ] = '0'; + buf[ 1 ] = bv.bv_val[ 0 ]; + buf[ 2 ] = bv.bv_val[ 1 ]; + buf[ 3 ] = '\0'; + + bv.bv_val = buf; + bv.bv_len = 3; + } + + return sidNormalize( 0, NULL, NULL, &bv, normalized, ctx ); +} + +static int +csnValidate( + Syntax *syntax, + struct berval *in ) +{ + struct berval bv; + char *ptr; + int rc; + + assert( in != NULL ); + + if ( BER_BVISNULL( in ) || BER_BVISEMPTY( in ) ) { + return LDAP_INVALID_SYNTAX; + } + + bv = *in; + + ptr = ber_bvchr( &bv, '#' ); + if ( ptr == NULL || ptr == &bv.bv_val[bv.bv_len] ) { + return LDAP_INVALID_SYNTAX; + } + + bv.bv_len = ptr - bv.bv_val; + if ( bv.bv_len != STRLENOF( "YYYYmmddHHMMSS.uuuuuuZ" ) && + bv.bv_len != STRLENOF( "YYYYmmddHHMMSSZ" ) ) + { + return LDAP_INVALID_SYNTAX; + } + + rc = generalizedTimeValidate( NULL, &bv ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + bv.bv_val = ptr + 1; + bv.bv_len = in->bv_len - ( bv.bv_val - in->bv_val ); + + ptr = ber_bvchr( &bv, '#' ); + if ( ptr == NULL || ptr == &in->bv_val[in->bv_len] ) { + return LDAP_INVALID_SYNTAX; + } + + bv.bv_len = ptr - bv.bv_val; + if ( bv.bv_len != 6 ) { + return LDAP_INVALID_SYNTAX; + } + + rc = hexValidate( NULL, &bv ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + bv.bv_val = ptr + 1; + bv.bv_len = in->bv_len - ( bv.bv_val - in->bv_val ); + + ptr = ber_bvchr( &bv, '#' ); + if ( ptr == NULL || ptr == &in->bv_val[in->bv_len] ) { + return LDAP_INVALID_SYNTAX; + } + + bv.bv_len = ptr - bv.bv_val; + if ( bv.bv_len == 2 ) { + /* tolerate old 2-digit replica-id */ + rc = hexValidate( NULL, &bv ); + + } else { + rc = sidValidate( NULL, &bv ); + } + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + bv.bv_val = ptr + 1; + bv.bv_len = in->bv_len - ( bv.bv_val - in->bv_val ); + + if ( bv.bv_len != 6 ) { + return LDAP_INVALID_SYNTAX; + } + + return hexValidate( NULL, &bv ); +} + +/* Normalize a CSN in OpenLDAP 2.1 format */ +static int +csnNormalize21( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + struct berval gt, cnt, sid, mod; + struct berval bv; + char buf[ STRLENOF( "YYYYmmddHHMMSS.uuuuuuZ#SSSSSS#SID#ssssss" ) + 1 ]; + char *ptr; + ber_len_t i; + + assert( SLAP_MR_IS_VALUE_OF_SYNTAX( usage ) != 0 ); + assert( !BER_BVISEMPTY( val ) ); + + gt = *val; + + ptr = ber_bvchr( >, '#' ); + if ( ptr == NULL || ptr == >.bv_val[gt.bv_len] ) { + return LDAP_INVALID_SYNTAX; + } + + gt.bv_len = ptr - gt.bv_val; + if ( gt.bv_len != STRLENOF( "YYYYmmddHH:MM:SSZ" ) ) { + return LDAP_INVALID_SYNTAX; + } + + if ( gt.bv_val[ 10 ] != ':' || gt.bv_val[ 13 ] != ':' ) { + return LDAP_INVALID_SYNTAX; + } + + cnt.bv_val = ptr + 1; + cnt.bv_len = val->bv_len - ( cnt.bv_val - val->bv_val ); + + ptr = ber_bvchr( &cnt, '#' ); + if ( ptr == NULL || ptr == &val->bv_val[val->bv_len] ) { + return LDAP_INVALID_SYNTAX; + } + + cnt.bv_len = ptr - cnt.bv_val; + if ( cnt.bv_len != STRLENOF( "0x0000" ) ) { + return LDAP_INVALID_SYNTAX; + } + + if ( strncmp( cnt.bv_val, "0x", STRLENOF( "0x" ) ) != 0 ) { + return LDAP_INVALID_SYNTAX; + } + + cnt.bv_val += STRLENOF( "0x" ); + cnt.bv_len -= STRLENOF( "0x" ); + + sid.bv_val = ptr + 1; + sid.bv_len = val->bv_len - ( sid.bv_val - val->bv_val ); + + ptr = ber_bvchr( &sid, '#' ); + if ( ptr == NULL || ptr == &val->bv_val[val->bv_len] ) { + return LDAP_INVALID_SYNTAX; + } + + sid.bv_len = ptr - sid.bv_val; + if ( sid.bv_len != STRLENOF( "0" ) ) { + return LDAP_INVALID_SYNTAX; + } + + mod.bv_val = ptr + 1; + mod.bv_len = val->bv_len - ( mod.bv_val - val->bv_val ); + if ( mod.bv_len != STRLENOF( "0000" ) ) { + return LDAP_INVALID_SYNTAX; + } + + bv.bv_len = STRLENOF( "YYYYmmddHHMMSS.uuuuuuZ#SSSSSS#SID#ssssss" ); + bv.bv_val = buf; + + ptr = bv.bv_val; + ptr = lutil_strncopy( ptr, gt.bv_val, STRLENOF( "YYYYmmddHH" ) ); + ptr = lutil_strncopy( ptr, >.bv_val[ STRLENOF( "YYYYmmddHH:" ) ], + STRLENOF( "MM" ) ); + ptr = lutil_strncopy( ptr, >.bv_val[ STRLENOF( "YYYYmmddHH:MM:" ) ], + STRLENOF( "SS" ) ); + ptr = lutil_strcopy( ptr, ".000000Z#00" ); + ptr = lutil_strbvcopy( ptr, &cnt ); + *ptr++ = '#'; + *ptr++ = '0'; + *ptr++ = '0'; + *ptr++ = sid.bv_val[ 0 ]; + *ptr++ = '#'; + *ptr++ = '0'; + *ptr++ = '0'; + for ( i = 0; i < mod.bv_len; i++ ) { + *ptr++ = TOLOWER( mod.bv_val[ i ] ); + } + *ptr = '\0'; + + assert( ptr == &bv.bv_val[bv.bv_len] ); + + if ( csnValidate( syntax, &bv ) != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + + ber_dupbv_x( normalized, &bv, ctx ); + + return LDAP_SUCCESS; +} + +/* Normalize a CSN in OpenLDAP 2.3 format */ +static int +csnNormalize23( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + struct berval gt, cnt, sid, mod; + struct berval bv; + char buf[ STRLENOF( "YYYYmmddHHMMSS.uuuuuuZ#SSSSSS#SID#ssssss" ) + 1 ]; + char *ptr; + ber_len_t i; + + assert( SLAP_MR_IS_VALUE_OF_SYNTAX( usage ) != 0 ); + assert( !BER_BVISEMPTY( val ) ); + + gt = *val; + + ptr = ber_bvchr( >, '#' ); + if ( ptr == NULL || ptr == >.bv_val[gt.bv_len] ) { + return LDAP_INVALID_SYNTAX; + } + + gt.bv_len = ptr - gt.bv_val; + if ( gt.bv_len != STRLENOF( "YYYYmmddHHMMSSZ" ) ) { + return LDAP_INVALID_SYNTAX; + } + + cnt.bv_val = ptr + 1; + cnt.bv_len = val->bv_len - ( cnt.bv_val - val->bv_val ); + + ptr = ber_bvchr( &cnt, '#' ); + if ( ptr == NULL || ptr == &val->bv_val[val->bv_len] ) { + return LDAP_INVALID_SYNTAX; + } + + cnt.bv_len = ptr - cnt.bv_val; + if ( cnt.bv_len != STRLENOF( "000000" ) ) { + return LDAP_INVALID_SYNTAX; + } + + sid.bv_val = ptr + 1; + sid.bv_len = val->bv_len - ( sid.bv_val - val->bv_val ); + + ptr = ber_bvchr( &sid, '#' ); + if ( ptr == NULL || ptr == &val->bv_val[val->bv_len] ) { + return LDAP_INVALID_SYNTAX; + } + + sid.bv_len = ptr - sid.bv_val; + if ( sid.bv_len != STRLENOF( "00" ) ) { + return LDAP_INVALID_SYNTAX; + } + + mod.bv_val = ptr + 1; + mod.bv_len = val->bv_len - ( mod.bv_val - val->bv_val ); + if ( mod.bv_len != STRLENOF( "000000" ) ) { + return LDAP_INVALID_SYNTAX; + } + + bv.bv_len = STRLENOF( "YYYYmmddHHMMSS.uuuuuuZ#SSSSSS#SID#ssssss" ); + bv.bv_val = buf; + + ptr = bv.bv_val; + ptr = lutil_strncopy( ptr, gt.bv_val, gt.bv_len - 1 ); + ptr = lutil_strcopy( ptr, ".000000Z#" ); + ptr = lutil_strbvcopy( ptr, &cnt ); + *ptr++ = '#'; + *ptr++ = '0'; + for ( i = 0; i < sid.bv_len; i++ ) { + *ptr++ = TOLOWER( sid.bv_val[ i ] ); + } + *ptr++ = '#'; + for ( i = 0; i < mod.bv_len; i++ ) { + *ptr++ = TOLOWER( mod.bv_val[ i ] ); + } + *ptr = '\0'; + + if ( ptr != &bv.bv_val[bv.bv_len] || + csnValidate( syntax, &bv ) != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + + ber_dupbv_x( normalized, &bv, ctx ); + + return LDAP_SUCCESS; +} + +/* Normalize a CSN */ +static int +csnNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + struct berval cnt, sid, mod; + char *ptr; + ber_len_t i; + + assert( val != NULL ); + assert( normalized != NULL ); + + assert( SLAP_MR_IS_VALUE_OF_SYNTAX( usage ) != 0 ); + + if ( BER_BVISEMPTY( val ) ) { + return LDAP_INVALID_SYNTAX; + } + + if ( val->bv_len == STRLENOF( "YYYYmmddHHMMSSZ#SSSSSS#ID#ssssss" ) ) { + /* Openldap <= 2.3 */ + + return csnNormalize23( usage, syntax, mr, val, normalized, ctx ); + } + + if ( val->bv_len == STRLENOF( "YYYYmmddHH:MM:SSZ#0xSSSS#I#ssss" ) ) { + /* Openldap 2.1 */ + + return csnNormalize21( usage, syntax, mr, val, normalized, ctx ); + } + + if ( val->bv_len != STRLENOF( "YYYYmmddHHMMSS.uuuuuuZ#SSSSSS#SID#ssssss" ) ) { + return LDAP_INVALID_SYNTAX; + } + + ptr = ber_bvchr( val, '#' ); + if ( ptr == NULL || ptr == &val->bv_val[val->bv_len] ) { + return LDAP_INVALID_SYNTAX; + } + + if ( ptr - val->bv_val != STRLENOF( "YYYYmmddHHMMSS.uuuuuuZ" ) ) { + return LDAP_INVALID_SYNTAX; + } + + cnt.bv_val = ptr + 1; + cnt.bv_len = val->bv_len - ( cnt.bv_val - val->bv_val ); + + ptr = ber_bvchr( &cnt, '#' ); + if ( ptr == NULL || ptr == &val->bv_val[val->bv_len] ) { + return LDAP_INVALID_SYNTAX; + } + + if ( ptr - cnt.bv_val != STRLENOF( "000000" ) ) { + return LDAP_INVALID_SYNTAX; + } + + sid.bv_val = ptr + 1; + sid.bv_len = val->bv_len - ( sid.bv_val - val->bv_val ); + + ptr = ber_bvchr( &sid, '#' ); + if ( ptr == NULL || ptr == &val->bv_val[val->bv_len] ) { + return LDAP_INVALID_SYNTAX; + } + + sid.bv_len = ptr - sid.bv_val; + if ( sid.bv_len != STRLENOF( "000" ) ) { + return LDAP_INVALID_SYNTAX; + } + + mod.bv_val = ptr + 1; + mod.bv_len = val->bv_len - ( mod.bv_val - val->bv_val ); + + if ( mod.bv_len != STRLENOF( "000000" ) ) { + return LDAP_INVALID_SYNTAX; + } + + ber_dupbv_x( normalized, val, ctx ); + + for ( i = STRLENOF( "YYYYmmddHHMMSS.uuuuuuZ#SSSSSS#" ); + i < normalized->bv_len; i++ ) + { + /* assume it's already validated that's all hex digits */ + normalized->bv_val[ i ] = TOLOWER( normalized->bv_val[ i ] ); + } + + return LDAP_SUCCESS; +} + +static int +csnPretty( + Syntax *syntax, + struct berval *val, + struct berval *out, + void *ctx ) +{ + return csnNormalize( SLAP_MR_VALUE_OF_SYNTAX, NULL, NULL, val, out, ctx ); +} + +#ifndef SUPPORT_OBSOLETE_UTC_SYNTAX +/* slight optimization - does not need the start parameter */ +#define check_time_syntax(v, start, p, f) (check_time_syntax)(v, p, f) +enum { start = 0 }; +#endif + +static int +check_time_syntax (struct berval *val, + int start, + int *parts, + struct berval *fraction) +{ + /* + * start=0 GeneralizedTime YYYYmmddHH[MM[SS]][(./,)d...](Z|(+/-)HH[MM]) + * start=1 UTCTime YYmmddHHMM[SS][Z|(+/-)HHMM] + * GeneralizedTime supports leap seconds, UTCTime does not. + */ + static const int ceiling[9] = { 100, 100, 12, 31, 24, 60, 60, 24, 60 }; + static const int mdays[2][12] = { + /* non-leap years */ + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + /* leap years */ + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } + }; + char *p, *e; + int part, c, c1, c2, tzoffset, leapyear = 0; + + p = val->bv_val; + e = p + val->bv_len; + +#ifdef SUPPORT_OBSOLETE_UTC_SYNTAX + parts[0] = 20; /* century - any multiple of 4 from 04 to 96 */ +#endif + for (part = start; part < 7 && p < e; part++) { + c1 = *p; + if (!ASCII_DIGIT(c1)) { + break; + } + p++; + if (p == e) { + return LDAP_INVALID_SYNTAX; + } + c = *p++; + if (!ASCII_DIGIT(c)) { + return LDAP_INVALID_SYNTAX; + } + c += c1 * 10 - '0' * 11; + if ((part | 1) == 3) { + --c; + if (c < 0) { + return LDAP_INVALID_SYNTAX; + } + } + if (c >= ceiling[part]) { + if (! (c == 60 && part == 6 && start == 0)) + return LDAP_INVALID_SYNTAX; + } + parts[part] = c; + } + if (part < 5 + start) { + return LDAP_INVALID_SYNTAX; + } + for (; part < 9; part++) { + parts[part] = 0; + } + + /* leapyear check for the Gregorian calendar (year>1581) */ + if (parts[parts[1] == 0 ? 0 : 1] % 4 == 0) { + leapyear = 1; + } + + if (parts[3] >= mdays[leapyear][parts[2]]) { + return LDAP_INVALID_SYNTAX; + } + + if (start == 0) { + fraction->bv_val = p; + fraction->bv_len = 0; + if (p < e && (*p == '.' || *p == ',')) { + char *end_num; + while (++p < e && ASCII_DIGIT(*p)) { + /* EMTPY */; + } + if (p - fraction->bv_val == 1) { + return LDAP_INVALID_SYNTAX; + } + for (end_num = p; end_num[-1] == '0'; --end_num) { + /* EMPTY */; + } + c = end_num - fraction->bv_val; + if (c != 1) fraction->bv_len = c; + } + } + + if (p == e) { + /* no time zone */ + return start == 0 ? LDAP_INVALID_SYNTAX : LDAP_SUCCESS; + } + + tzoffset = *p++; + switch (tzoffset) { + default: + return LDAP_INVALID_SYNTAX; + case 'Z': + /* UTC */ + break; + case '+': + case '-': + for (part = 7; part < 9 && p < e; part++) { + c1 = *p; + if (!ASCII_DIGIT(c1)) { + break; + } + p++; + if (p == e) { + return LDAP_INVALID_SYNTAX; + } + c2 = *p++; + if (!ASCII_DIGIT(c2)) { + return LDAP_INVALID_SYNTAX; + } + parts[part] = c1 * 10 + c2 - '0' * 11; + if (parts[part] >= ceiling[part]) { + return LDAP_INVALID_SYNTAX; + } + } + if (part < 8 + start) { + return LDAP_INVALID_SYNTAX; + } + + if (tzoffset == '-') { + /* negative offset to UTC, ie west of Greenwich */ + parts[4] += parts[7]; + parts[5] += parts[8]; + /* offset is just hhmm, no seconds */ + for (part = 6; --part >= 0; ) { + if (part != 3) { + c = ceiling[part]; + } else { + c = mdays[leapyear][parts[2]]; + } + if (parts[part] >= c) { + if (part == 0) { + return LDAP_INVALID_SYNTAX; + } + parts[part] -= c; + parts[part - 1]++; + continue; + } else if (part != 5) { + break; + } + } + } else { + /* positive offset to UTC, ie east of Greenwich */ + parts[4] -= parts[7]; + parts[5] -= parts[8]; + for (part = 6; --part >= 0; ) { + if (parts[part] < 0) { + if (part == 0) { + return LDAP_INVALID_SYNTAX; + } + if (part != 3) { + c = ceiling[part]; + } else { + /* make first arg to % non-negative */ + c = mdays[leapyear][(parts[2] - 1 + 12) % 12]; + } + parts[part] += c; + parts[part - 1]--; + continue; + } else if (part != 5) { + break; + } + } + } + } + + return p != e ? LDAP_INVALID_SYNTAX : LDAP_SUCCESS; +} + +#ifdef SUPPORT_OBSOLETE_UTC_SYNTAX + +#if 0 +static int +xutcTimeNormalize( + Syntax *syntax, + struct berval *val, + struct berval *normalized ) +{ + int parts[9], rc; + + rc = check_time_syntax(val, 1, parts, NULL); + if (rc != LDAP_SUCCESS) { + return rc; + } + + normalized->bv_val = ch_malloc( 14 ); + if ( normalized->bv_val == NULL ) { + return LBER_ERROR_MEMORY; + } + + sprintf( normalized->bv_val, "%02d%02d%02d%02d%02d%02dZ", + parts[1], parts[2] + 1, parts[3] + 1, + parts[4], parts[5], parts[6] ); + normalized->bv_len = 13; + + return LDAP_SUCCESS; +} +#endif /* 0 */ + +static int +utcTimeValidate( + Syntax *syntax, + struct berval *in ) +{ + int parts[9]; + return check_time_syntax(in, 1, parts, NULL); +} + +#endif /* SUPPORT_OBSOLETE_UTC_SYNTAX */ + +static int +generalizedTimeValidate( + Syntax *syntax, + struct berval *in ) +{ + int parts[9]; + struct berval fraction; + return check_time_syntax(in, 0, parts, &fraction); +} + +static int +generalizedTimeNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + int parts[9], rc; + unsigned int len; + struct berval fraction; + + rc = check_time_syntax(val, 0, parts, &fraction); + if (rc != LDAP_SUCCESS) { + return rc; + } + + len = STRLENOF("YYYYmmddHHMMSSZ") + fraction.bv_len; + normalized->bv_val = slap_sl_malloc( len + 1, ctx ); + if ( BER_BVISNULL( normalized ) ) { + return LBER_ERROR_MEMORY; + } + + sprintf( normalized->bv_val, "%02d%02d%02d%02d%02d%02d%02d", + parts[0], parts[1], parts[2] + 1, parts[3] + 1, + parts[4], parts[5], parts[6] ); + if ( !BER_BVISEMPTY( &fraction ) ) { + memcpy( normalized->bv_val + STRLENOF("YYYYmmddHHMMSSZ")-1, + fraction.bv_val, fraction.bv_len ); + normalized->bv_val[STRLENOF("YYYYmmddHHMMSSZ")-1] = '.'; + } + strcpy( normalized->bv_val + len-1, "Z" ); + normalized->bv_len = len; + + return LDAP_SUCCESS; +} + +static int +generalizedTimeOrderingMatch( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + struct berval *asserted = (struct berval *) assertedValue; + ber_len_t v_len = value->bv_len; + ber_len_t av_len = asserted->bv_len; + + /* ignore trailing 'Z' when comparing */ + int match = memcmp( value->bv_val, asserted->bv_val, + (v_len < av_len ? v_len : av_len) - 1 ); + if ( match == 0 ) match = v_len - av_len; + + /* If used in extensible match filter, match if value < asserted */ + if ( flags & SLAP_MR_EXT ) + match = (match >= 0); + + *matchp = match; + return LDAP_SUCCESS; +} + +/* Index generation function: Ordered index */ +int generalizedTimeIndexer( + slap_mask_t use, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *prefix, + BerVarray values, + BerVarray *keysp, + void *ctx ) +{ + int i, j; + BerVarray keys; + char tmp[5]; + BerValue bvtmp; /* 40 bit index */ + struct lutil_tm tm; + struct lutil_timet tt; + + bvtmp.bv_len = sizeof(tmp); + bvtmp.bv_val = tmp; + for( i=0; values[i].bv_val != NULL; i++ ) { + /* just count them */ + } + + /* we should have at least one value at this point */ + assert( i > 0 ); + + keys = slap_sl_malloc( sizeof( struct berval ) * (i+1), ctx ); + + /* GeneralizedTime YYYYmmddHH[MM[SS]][(./,)d...](Z|(+/-)HH[MM]) */ + for( i=0, j=0; values[i].bv_val != NULL; i++ ) { + assert(values[i].bv_val != NULL && values[i].bv_len >= 10); + /* Use 40 bits of time for key */ + if ( lutil_parsetime( values[i].bv_val, &tm ) == 0 ) { + lutil_tm2time( &tm, &tt ); + tmp[0] = tt.tt_gsec & 0xff; + tmp[4] = tt.tt_sec & 0xff; + tt.tt_sec >>= 8; + tmp[3] = tt.tt_sec & 0xff; + tt.tt_sec >>= 8; + tmp[2] = tt.tt_sec & 0xff; + tt.tt_sec >>= 8; + tmp[1] = tt.tt_sec & 0xff; + + ber_dupbv_x(&keys[j++], &bvtmp, ctx ); + } + } + + keys[j].bv_val = NULL; + keys[j].bv_len = 0; + + *keysp = keys; + + return LDAP_SUCCESS; +} + +/* Index generation function: Ordered index */ +int generalizedTimeFilter( + slap_mask_t use, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *prefix, + void * assertedValue, + BerVarray *keysp, + void *ctx ) +{ + BerVarray keys; + char tmp[5]; + BerValue bvtmp; /* 40 bit index */ + BerValue *value = (BerValue *) assertedValue; + struct lutil_tm tm; + struct lutil_timet tt; + + bvtmp.bv_len = sizeof(tmp); + bvtmp.bv_val = tmp; + /* GeneralizedTime YYYYmmddHH[MM[SS]][(./,)d...](Z|(+/-)HH[MM]) */ + /* Use 40 bits of time for key */ + if ( value->bv_val && value->bv_len >= 10 && + lutil_parsetime( value->bv_val, &tm ) == 0 ) { + + lutil_tm2time( &tm, &tt ); + tmp[0] = tt.tt_gsec & 0xff; + tmp[4] = tt.tt_sec & 0xff; + tt.tt_sec >>= 8; + tmp[3] = tt.tt_sec & 0xff; + tt.tt_sec >>= 8; + tmp[2] = tt.tt_sec & 0xff; + tt.tt_sec >>= 8; + tmp[1] = tt.tt_sec & 0xff; + + keys = slap_sl_malloc( sizeof( struct berval ) * 2, ctx ); + ber_dupbv_x(keys, &bvtmp, ctx ); + keys[1].bv_val = NULL; + keys[1].bv_len = 0; + } else { + keys = NULL; + } + + *keysp = keys; + + return LDAP_SUCCESS; +} + +static int +deliveryMethodValidate( + Syntax *syntax, + struct berval *val ) +{ +#undef LENOF +#define LENOF(s) (sizeof(s)-1) + struct berval tmp = *val; + /* + * DeliveryMethod = pdm *( WSP DOLLAR WSP DeliveryMethod ) + * pdm = "any" / "mhs" / "physical" / "telex" / "teletex" / + * "g3fax" / "g4fax" / "ia5" / "videotex" / "telephone" + */ +again: + if( tmp.bv_len < 3 ) return LDAP_INVALID_SYNTAX; + + switch( tmp.bv_val[0] ) { + case 'a': + case 'A': + if(( tmp.bv_len >= LENOF("any") ) && + ( strncasecmp(tmp.bv_val, "any", LENOF("any")) == 0 )) + { + tmp.bv_len -= LENOF("any"); + tmp.bv_val += LENOF("any"); + break; + } + return LDAP_INVALID_SYNTAX; + + case 'm': + case 'M': + if(( tmp.bv_len >= LENOF("mhs") ) && + ( strncasecmp(tmp.bv_val, "mhs", LENOF("mhs")) == 0 )) + { + tmp.bv_len -= LENOF("mhs"); + tmp.bv_val += LENOF("mhs"); + break; + } + return LDAP_INVALID_SYNTAX; + + case 'p': + case 'P': + if(( tmp.bv_len >= LENOF("physical") ) && + ( strncasecmp(tmp.bv_val, "physical", LENOF("physical")) == 0 )) + { + tmp.bv_len -= LENOF("physical"); + tmp.bv_val += LENOF("physical"); + break; + } + return LDAP_INVALID_SYNTAX; + + case 't': + case 'T': /* telex or teletex or telephone */ + if(( tmp.bv_len >= LENOF("telex") ) && + ( strncasecmp(tmp.bv_val, "telex", LENOF("telex")) == 0 )) + { + tmp.bv_len -= LENOF("telex"); + tmp.bv_val += LENOF("telex"); + break; + } + if(( tmp.bv_len >= LENOF("teletex") ) && + ( strncasecmp(tmp.bv_val, "teletex", LENOF("teletex")) == 0 )) + { + tmp.bv_len -= LENOF("teletex"); + tmp.bv_val += LENOF("teletex"); + break; + } + if(( tmp.bv_len >= LENOF("telephone") ) && + ( strncasecmp(tmp.bv_val, "telephone", LENOF("telephone")) == 0 )) + { + tmp.bv_len -= LENOF("telephone"); + tmp.bv_val += LENOF("telephone"); + break; + } + return LDAP_INVALID_SYNTAX; + + case 'g': + case 'G': /* g3fax or g4fax */ + if(( tmp.bv_len >= LENOF("g3fax") ) && ( + ( strncasecmp(tmp.bv_val, "g3fax", LENOF("g3fax")) == 0 ) || + ( strncasecmp(tmp.bv_val, "g4fax", LENOF("g4fax")) == 0 ))) + { + tmp.bv_len -= LENOF("g3fax"); + tmp.bv_val += LENOF("g3fax"); + break; + } + return LDAP_INVALID_SYNTAX; + + case 'i': + case 'I': + if(( tmp.bv_len >= LENOF("ia5") ) && + ( strncasecmp(tmp.bv_val, "ia5", LENOF("ia5")) == 0 )) + { + tmp.bv_len -= LENOF("ia5"); + tmp.bv_val += LENOF("ia5"); + break; + } + return LDAP_INVALID_SYNTAX; + + case 'v': + case 'V': + if(( tmp.bv_len >= LENOF("videotex") ) && + ( strncasecmp(tmp.bv_val, "videotex", LENOF("videotex")) == 0 )) + { + tmp.bv_len -= LENOF("videotex"); + tmp.bv_val += LENOF("videotex"); + break; + } + return LDAP_INVALID_SYNTAX; + + default: + return LDAP_INVALID_SYNTAX; + } + + if( BER_BVISEMPTY( &tmp ) ) return LDAP_SUCCESS; + + while( !BER_BVISEMPTY( &tmp ) && ( tmp.bv_val[0] == ' ' ) ) { + tmp.bv_len--; + tmp.bv_val++; + } + if( !BER_BVISEMPTY( &tmp ) && ( tmp.bv_val[0] == '$' ) ) { + tmp.bv_len--; + tmp.bv_val++; + } else { + return LDAP_INVALID_SYNTAX; + } + while( !BER_BVISEMPTY( &tmp ) && ( tmp.bv_val[0] == ' ' ) ) { + tmp.bv_len--; + tmp.bv_val++; + } + + goto again; +} + +static int +nisNetgroupTripleValidate( + Syntax *syntax, + struct berval *val ) +{ + char *p, *e; + int commas = 0; + + if ( BER_BVISEMPTY( val ) ) { + return LDAP_INVALID_SYNTAX; + } + + p = (char *)val->bv_val; + e = p + val->bv_len; + + if ( *p != '(' /*')'*/ ) { + return LDAP_INVALID_SYNTAX; + } + + for ( p++; ( p < e ) && ( *p != /*'('*/ ')' ); p++ ) { + if ( *p == ',' ) { + commas++; + if ( commas > 2 ) { + return LDAP_INVALID_SYNTAX; + } + + } else if ( !AD_CHAR( *p ) ) { + return LDAP_INVALID_SYNTAX; + } + } + + if ( ( commas != 2 ) || ( *p != /*'('*/ ')' ) ) { + return LDAP_INVALID_SYNTAX; + } + + p++; + + if (p != e) { + return LDAP_INVALID_SYNTAX; + } + + return LDAP_SUCCESS; +} + +static int +bootParameterValidate( + Syntax *syntax, + struct berval *val ) +{ + char *p, *e; + + if ( BER_BVISEMPTY( val ) ) { + return LDAP_INVALID_SYNTAX; + } + + p = (char *)val->bv_val; + e = p + val->bv_len; + + /* key */ + for (; ( p < e ) && ( *p != '=' ); p++ ) { + if ( !AD_CHAR( *p ) ) { + return LDAP_INVALID_SYNTAX; + } + } + + if ( *p != '=' ) { + return LDAP_INVALID_SYNTAX; + } + + /* server */ + for ( p++; ( p < e ) && ( *p != ':' ); p++ ) { + if ( !AD_CHAR( *p ) ) { + return LDAP_INVALID_SYNTAX; + } + } + + if ( *p != ':' ) { + return LDAP_INVALID_SYNTAX; + } + + /* path */ + for ( p++; p < e; p++ ) { + if ( !SLAP_PRINTABLE( *p ) ) { + return LDAP_INVALID_SYNTAX; + } + } + + return LDAP_SUCCESS; +} + +static int +firstComponentNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + int rc; + struct berval comp; + ber_len_t len; + + if( SLAP_MR_IS_VALUE_OF_ASSERTION_SYNTAX( usage )) { + ber_dupbv_x( normalized, val, ctx ); + return LDAP_SUCCESS; + } + + if( val->bv_len < 3 ) return LDAP_INVALID_SYNTAX; + + if( ! ( val->bv_val[0] == '(' /*')'*/ + && val->bv_val[val->bv_len - 1] == /*'('*/ ')' ) + && ! ( val->bv_val[0] == '{' /*'}'*/ + && val->bv_val[val->bv_len - 1] == /*'('*/ '}' ) ) + { + return LDAP_INVALID_SYNTAX; + } + + /* trim leading white space */ + for( len=1; + len < val->bv_len && ASCII_SPACE(val->bv_val[len]); + len++ ) + { + /* empty */ + } + + /* grab next word */ + comp.bv_val = &val->bv_val[len]; + len = val->bv_len - len - STRLENOF(/*"{"*/ "}"); + for( comp.bv_len = 0; + !ASCII_SPACE(comp.bv_val[comp.bv_len]) && comp.bv_len < len; + comp.bv_len++ ) + { + /* empty */ + } + + if( mr == slap_schema.si_mr_objectIdentifierFirstComponentMatch ) { + rc = numericoidValidate( NULL, &comp ); + } else if( mr == slap_schema.si_mr_integerFirstComponentMatch ) { + rc = integerValidate( NULL, &comp ); + } else { + rc = LDAP_INVALID_SYNTAX; + } + + + if( rc == LDAP_SUCCESS ) { + ber_dupbv_x( normalized, &comp, ctx ); + } + + return rc; +} + +static char *country_gen_syn[] = { + "1.3.6.1.4.1.1466.115.121.1.15", /* Directory String */ + "1.3.6.1.4.1.1466.115.121.1.26", /* IA5 String */ + "1.3.6.1.4.1.1466.115.121.1.44", /* Printable String */ + NULL +}; + +#define X_BINARY "X-BINARY-TRANSFER-REQUIRED 'TRUE' " +#define X_NOT_H_R "X-NOT-HUMAN-READABLE 'TRUE' " + +static slap_syntax_defs_rec syntax_defs[] = { + {"( 1.3.6.1.4.1.1466.115.121.1.1 DESC 'ACI Item' " + X_BINARY X_NOT_H_R ")", + SLAP_SYNTAX_BINARY|SLAP_SYNTAX_BER, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.2 DESC 'Access Point' " X_NOT_H_R ")", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.3 DESC 'Attribute Type Description' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.4 DESC 'Audio' " + X_NOT_H_R ")", + SLAP_SYNTAX_BLOB, NULL, blobValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.5 DESC 'Binary' " + X_NOT_H_R ")", + SLAP_SYNTAX_BER, NULL, berValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.6 DESC 'Bit String' )", + 0, NULL, bitStringValidate, NULL }, + {"( 1.3.6.1.4.1.1466.115.121.1.7 DESC 'Boolean' )", + 0, NULL, booleanValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.8 DESC 'Certificate' " + X_BINARY X_NOT_H_R ")", + SLAP_SYNTAX_BINARY|SLAP_SYNTAX_BER, + NULL, certificateValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.9 DESC 'Certificate List' " + X_BINARY X_NOT_H_R ")", + SLAP_SYNTAX_BINARY|SLAP_SYNTAX_BER, + NULL, certificateListValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.10 DESC 'Certificate Pair' " + X_BINARY X_NOT_H_R ")", + SLAP_SYNTAX_BINARY|SLAP_SYNTAX_BER, + NULL, sequenceValidate, NULL}, + {"( " attributeCertificateSyntaxOID " DESC 'X.509 AttributeCertificate' " + X_BINARY X_NOT_H_R ")", + SLAP_SYNTAX_BINARY|SLAP_SYNTAX_BER, + NULL, attributeCertificateValidate, NULL}, +#if 0 /* need to go __after__ printableString */ + {"( 1.3.6.1.4.1.1466.115.121.1.11 DESC 'Country String' )", + 0, "1.3.6.1.4.1.1466.115.121.1.44", + countryStringValidate, NULL}, +#endif + {"( 1.3.6.1.4.1.1466.115.121.1.12 DESC 'Distinguished Name' )", + SLAP_SYNTAX_DN, NULL, dnValidate, dnPretty}, + {"( 1.2.36.79672281.1.5.0 DESC 'RDN' )", + 0, NULL, rdnValidate, rdnPretty}, +#ifdef LDAP_COMP_MATCH + {"( 1.2.36.79672281.1.5.3 DESC 'allComponents' )", + 0, NULL, allComponentsValidate, NULL}, + {"( 1.2.36.79672281.1.5.2 DESC 'componentFilterMatch assertion') ", + 0, NULL, componentFilterValidate, NULL}, +#endif + {"( 1.3.6.1.4.1.1466.115.121.1.13 DESC 'Data Quality' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.14 DESC 'Delivery Method' )", + 0, NULL, deliveryMethodValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.15 DESC 'Directory String' )", + 0, NULL, UTF8StringValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.16 DESC 'DIT Content Rule Description' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.17 DESC 'DIT Structure Rule Description' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.19 DESC 'DSA Quality' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.20 DESC 'DSE Type' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.21 DESC 'Enhanced Guide' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.22 DESC 'Facsimile Telephone Number' )", + 0, NULL, printablesStringValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.23 DESC 'Fax' " X_NOT_H_R ")", + SLAP_SYNTAX_BLOB, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.24 DESC 'Generalized Time' )", + 0, NULL, generalizedTimeValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.25 DESC 'Guide' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.26 DESC 'IA5 String' )", + 0, NULL, IA5StringValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.27 DESC 'Integer' )", + 0, NULL, integerValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.28 DESC 'JPEG' " X_NOT_H_R ")", + SLAP_SYNTAX_BLOB, NULL, blobValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.29 DESC 'Master And Shadow Access Points' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.30 DESC 'Matching Rule Description' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.31 DESC 'Matching Rule Use Description' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.32 DESC 'Mail Preference' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.33 DESC 'MHS OR Address' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.34 DESC 'Name And Optional UID' )", + SLAP_SYNTAX_DN, NULL, nameUIDValidate, nameUIDPretty }, + {"( 1.3.6.1.4.1.1466.115.121.1.35 DESC 'Name Form Description' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.36 DESC 'Numeric String' )", + 0, NULL, numericStringValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.37 DESC 'Object Class Description' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.38 DESC 'OID' )", + 0, NULL, numericoidValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.39 DESC 'Other Mailbox' )", + 0, NULL, IA5StringValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.40 DESC 'Octet String' )", + 0, NULL, blobValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.41 DESC 'Postal Address' )", + 0, NULL, postalAddressValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.42 DESC 'Protocol Information' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.43 DESC 'Presentation Address' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.44 DESC 'Printable String' )", + 0, NULL, printableStringValidate, NULL}, + /* moved here because now depends on Directory String, IA5 String + * and Printable String */ + {"( 1.3.6.1.4.1.1466.115.121.1.11 DESC 'Country String' )", + 0, country_gen_syn, countryStringValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.45 DESC 'SubtreeSpecification' )", +#define subtreeSpecificationValidate UTF8StringValidate /* FIXME */ + 0, NULL, subtreeSpecificationValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.49 DESC 'Supported Algorithm' " + X_BINARY X_NOT_H_R ")", + SLAP_SYNTAX_BINARY|SLAP_SYNTAX_BER, NULL, berValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.50 DESC 'Telephone Number' )", + 0, NULL, printableStringValidate, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.51 DESC 'Teletex Terminal Identifier' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.52 DESC 'Telex Number' )", + 0, NULL, printablesStringValidate, NULL}, +#ifdef SUPPORT_OBSOLETE_UTC_SYNTAX + {"( 1.3.6.1.4.1.1466.115.121.1.53 DESC 'UTC Time' )", + 0, NULL, utcTimeValidate, NULL}, +#endif + {"( 1.3.6.1.4.1.1466.115.121.1.54 DESC 'LDAP Syntax Description' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.55 DESC 'Modify Rights' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.56 DESC 'LDAP Schema Definition' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.57 DESC 'LDAP Schema Description' )", + 0, NULL, NULL, NULL}, + {"( 1.3.6.1.4.1.1466.115.121.1.58 DESC 'Substring Assertion' )", + 0, NULL, NULL, NULL}, + + /* RFC 2307 NIS Syntaxes */ + {"( 1.3.6.1.1.1.0.0 DESC 'RFC2307 NIS Netgroup Triple' )", + 0, NULL, nisNetgroupTripleValidate, NULL}, + {"( 1.3.6.1.1.1.0.1 DESC 'RFC2307 Boot Parameter' )", + 0, NULL, bootParameterValidate, NULL}, + + /* draft-zeilenga-ldap-x509 */ + {"( 1.3.6.1.1.15.1 DESC 'Certificate Exact Assertion' )", + SLAP_SYNTAX_HIDE, NULL, + serialNumberAndIssuerValidate, + serialNumberAndIssuerPretty}, + {"( 1.3.6.1.1.15.2 DESC 'Certificate Assertion' )", + SLAP_SYNTAX_HIDE, NULL, NULL, NULL}, + {"( 1.3.6.1.1.15.3 DESC 'Certificate Pair Exact Assertion' )", + SLAP_SYNTAX_HIDE, NULL, NULL, NULL}, + {"( 1.3.6.1.1.15.4 DESC 'Certificate Pair Assertion' )", + SLAP_SYNTAX_HIDE, NULL, NULL, NULL}, + {"( 1.3.6.1.1.15.5 DESC 'Certificate List Exact Assertion' )", + SLAP_SYNTAX_HIDE, NULL, + issuerAndThisUpdateValidate, + issuerAndThisUpdatePretty}, + {"( 1.3.6.1.1.15.6 DESC 'Certificate List Assertion' )", + SLAP_SYNTAX_HIDE, NULL, NULL, NULL}, + {"( 1.3.6.1.1.15.7 DESC 'Algorithm Identifier' )", + SLAP_SYNTAX_HIDE, NULL, NULL, NULL}, + {"( " attributeCertificateExactAssertionSyntaxOID " DESC 'AttributeCertificate Exact Assertion' )", + SLAP_SYNTAX_HIDE, NULL, + serialNumberAndIssuerSerialValidate, + serialNumberAndIssuerSerialPretty}, + {"( " attributeCertificateAssertionSyntaxOID " DESC 'AttributeCertificate Assertion' )", + SLAP_SYNTAX_HIDE, NULL, NULL, NULL}, + +#ifdef SLAPD_AUTHPASSWD + /* needs updating */ + {"( 1.3.6.1.4.1.4203.666.2.2 DESC 'OpenLDAP authPassword' )", + SLAP_SYNTAX_HIDE, NULL, NULL, NULL}, +#endif + + {"( 1.3.6.1.1.16.1 DESC 'UUID' )", + 0, NULL, UUIDValidate, UUIDPretty}, + + {"( 1.3.6.1.4.1.4203.666.11.2.1 DESC 'CSN' )", + SLAP_SYNTAX_HIDE, NULL, csnValidate, csnPretty }, + + {"( 1.3.6.1.4.1.4203.666.11.2.4 DESC 'CSN SID' )", + SLAP_SYNTAX_HIDE, NULL, sidValidate, sidPretty }, + + /* OpenLDAP Void Syntax */ + {"( 1.3.6.1.4.1.4203.1.1.1 DESC 'OpenLDAP void' )" , + SLAP_SYNTAX_HIDE, NULL, inValidate, NULL}, + + /* FIXME: OID is unused, but not registered yet */ + {"( 1.3.6.1.4.1.4203.666.2.7 DESC 'OpenLDAP authz' )", + SLAP_SYNTAX_HIDE, NULL, authzValidate, authzPretty}, + + {NULL, 0, NULL, NULL, NULL} +}; + +char *csnSIDMatchSyntaxes[] = { + "1.3.6.1.4.1.4203.666.11.2.1" /* csn */, + NULL +}; +char *certificateExactMatchSyntaxes[] = { + "1.3.6.1.4.1.1466.115.121.1.8" /* certificate */, + NULL +}; +char *certificateListExactMatchSyntaxes[] = { + "1.3.6.1.4.1.1466.115.121.1.9" /* certificateList */, + NULL +}; +char *attributeCertificateExactMatchSyntaxes[] = { + attributeCertificateSyntaxOID /* attributeCertificate */, + NULL +}; + +#ifdef LDAP_COMP_MATCH +char *componentFilterMatchSyntaxes[] = { + "1.3.6.1.4.1.1466.115.121.1.8" /* certificate */, + "1.3.6.1.4.1.1466.115.121.1.9" /* certificateList */, + attributeCertificateSyntaxOID /* attributeCertificate */, + NULL +}; +#endif + +char *directoryStringSyntaxes[] = { + "1.3.6.1.4.1.1466.115.121.1.11" /* countryString */, + "1.3.6.1.4.1.1466.115.121.1.44" /* printableString */, + "1.3.6.1.4.1.1466.115.121.1.50" /* telephoneNumber */, + NULL +}; +char *integerFirstComponentMatchSyntaxes[] = { + "1.3.6.1.4.1.1466.115.121.1.27" /* INTEGER */, + "1.3.6.1.4.1.1466.115.121.1.17" /* dITStructureRuleDescription */, + NULL +}; +char *objectIdentifierFirstComponentMatchSyntaxes[] = { + "1.3.6.1.4.1.1466.115.121.1.38" /* OID */, + "1.3.6.1.4.1.1466.115.121.1.3" /* attributeTypeDescription */, + "1.3.6.1.4.1.1466.115.121.1.16" /* dITContentRuleDescription */, + "1.3.6.1.4.1.1466.115.121.1.54" /* ldapSyntaxDescription */, + "1.3.6.1.4.1.1466.115.121.1.30" /* matchingRuleDescription */, + "1.3.6.1.4.1.1466.115.121.1.31" /* matchingRuleUseDescription */, + "1.3.6.1.4.1.1466.115.121.1.35" /* nameFormDescription */, + "1.3.6.1.4.1.1466.115.121.1.37" /* objectClassDescription */, + NULL +}; + +/* + * Other matching rules in X.520 that we do not use (yet): + * + * 2.5.13.25 uTCTimeMatch + * 2.5.13.26 uTCTimeOrderingMatch + * 2.5.13.31* directoryStringFirstComponentMatch + * 2.5.13.32* wordMatch + * 2.5.13.33* keywordMatch + * 2.5.13.36+ certificatePairExactMatch + * 2.5.13.37+ certificatePairMatch + * 2.5.13.40+ algorithmIdentifierMatch + * 2.5.13.41* storedPrefixMatch + * 2.5.13.42 attributeCertificateMatch + * 2.5.13.43 readerAndKeyIDMatch + * 2.5.13.44 attributeIntegrityMatch + * + * (*) described in RFC 3698 (LDAP: Additional Matching Rules) + * (+) described in draft-zeilenga-ldap-x509 + */ +static slap_mrule_defs_rec mrule_defs[] = { + /* + * EQUALITY matching rules must be listed after associated APPROX + * matching rules. So, we list all APPROX matching rules first. + */ + {"( " directoryStringApproxMatchOID " NAME 'directoryStringApproxMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + SLAP_MR_HIDE | SLAP_MR_EQUALITY_APPROX | SLAP_MR_EXT, NULL, + NULL, NULL, directoryStringApproxMatch, + directoryStringApproxIndexer, directoryStringApproxFilter, + NULL}, + + {"( " IA5StringApproxMatchOID " NAME 'IA5StringApproxMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )", + SLAP_MR_HIDE | SLAP_MR_EQUALITY_APPROX | SLAP_MR_EXT, NULL, + NULL, NULL, IA5StringApproxMatch, + IA5StringApproxIndexer, IA5StringApproxFilter, + NULL}, + + /* + * Other matching rules + */ + + {"( 2.5.13.0 NAME 'objectIdentifierMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )", + SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL, + NULL, NULL, octetStringMatch, + octetStringIndexer, octetStringFilter, + NULL }, + + {"( 2.5.13.1 NAME 'distinguishedNameMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )", + SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL, + NULL, dnNormalize, dnMatch, + octetStringIndexer, octetStringFilter, + NULL }, + + {"( 1.3.6.1.4.1.4203.666.4.9 NAME 'dnSubtreeMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )", + SLAP_MR_HIDE | SLAP_MR_EXT, NULL, + NULL, dnNormalize, dnRelativeMatch, + NULL, NULL, + NULL }, + + {"( 1.3.6.1.4.1.4203.666.4.8 NAME 'dnOneLevelMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )", + SLAP_MR_HIDE | SLAP_MR_EXT, NULL, + NULL, dnNormalize, dnRelativeMatch, + NULL, NULL, + NULL }, + + {"( 1.3.6.1.4.1.4203.666.4.10 NAME 'dnSubordinateMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )", + SLAP_MR_HIDE | SLAP_MR_EXT, NULL, + NULL, dnNormalize, dnRelativeMatch, + NULL, NULL, + NULL }, + + {"( 1.3.6.1.4.1.4203.666.4.11 NAME 'dnSuperiorMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )", + SLAP_MR_HIDE | SLAP_MR_EXT, NULL, + NULL, dnNormalize, dnRelativeMatch, + NULL, NULL, + NULL }, + + {"( 1.2.36.79672281.1.13.3 NAME 'rdnMatch' " + "SYNTAX 1.2.36.79672281.1.5.0 )", + SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL, + NULL, rdnNormalize, rdnMatch, + octetStringIndexer, octetStringFilter, + NULL }, + +#ifdef LDAP_COMP_MATCH + {"( 1.2.36.79672281.1.13.2 NAME 'componentFilterMatch' " + "SYNTAX 1.2.36.79672281.1.5.2 )", /* componentFilterMatch assertion */ + SLAP_MR_EXT|SLAP_MR_COMPONENT, componentFilterMatchSyntaxes, + NULL, NULL , componentFilterMatch, + octetStringIndexer, octetStringFilter, + NULL }, + + {"( 1.2.36.79672281.1.13.6 NAME 'allComponentsMatch' " + "SYNTAX 1.2.36.79672281.1.5.3 )", /* allComponents */ + SLAP_MR_EQUALITY|SLAP_MR_EXT|SLAP_MR_COMPONENT, NULL, + NULL, NULL , allComponentsMatch, + octetStringIndexer, octetStringFilter, + NULL }, + + {"( 1.2.36.79672281.1.13.7 NAME 'directoryComponentsMatch' " + "SYNTAX 1.2.36.79672281.1.5.3 )", /* allComponents */ + SLAP_MR_EQUALITY|SLAP_MR_EXT|SLAP_MR_COMPONENT, NULL, + NULL, NULL , directoryComponentsMatch, + octetStringIndexer, octetStringFilter, + NULL }, +#endif + + {"( 2.5.13.2 NAME 'caseIgnoreMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + SLAP_MR_EQUALITY | SLAP_MR_EXT, directoryStringSyntaxes, + NULL, UTF8StringNormalize, octetStringMatch, + octetStringIndexer, octetStringFilter, + directoryStringApproxMatchOID }, + + {"( 2.5.13.3 NAME 'caseIgnoreOrderingMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + SLAP_MR_ORDERING | SLAP_MR_EXT, directoryStringSyntaxes, + NULL, UTF8StringNormalize, octetStringOrderingMatch, + NULL, NULL, + "caseIgnoreMatch" }, + + {"( 2.5.13.4 NAME 'caseIgnoreSubstringsMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 )", /* Substring Assertion */ + SLAP_MR_SUBSTR, directoryStringSyntaxes, + NULL, UTF8StringNormalize, directoryStringSubstringsMatch, + octetStringSubstringsIndexer, octetStringSubstringsFilter, + "caseIgnoreMatch" }, + + {"( 2.5.13.5 NAME 'caseExactMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + SLAP_MR_EQUALITY | SLAP_MR_EXT, directoryStringSyntaxes, + NULL, UTF8StringNormalize, octetStringMatch, + octetStringIndexer, octetStringFilter, + directoryStringApproxMatchOID }, + + {"( 2.5.13.6 NAME 'caseExactOrderingMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + SLAP_MR_ORDERING | SLAP_MR_EXT, directoryStringSyntaxes, + NULL, UTF8StringNormalize, octetStringOrderingMatch, + NULL, NULL, + "caseExactMatch" }, + + {"( 2.5.13.7 NAME 'caseExactSubstringsMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 )", /* Substring Assertion */ + SLAP_MR_SUBSTR, directoryStringSyntaxes, + NULL, UTF8StringNormalize, directoryStringSubstringsMatch, + octetStringSubstringsIndexer, octetStringSubstringsFilter, + "caseExactMatch" }, + + {"( 2.5.13.8 NAME 'numericStringMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 )", + SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL, + NULL, numericStringNormalize, octetStringMatch, + octetStringIndexer, octetStringFilter, + NULL }, + + {"( 2.5.13.9 NAME 'numericStringOrderingMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 )", + SLAP_MR_ORDERING | SLAP_MR_EXT, NULL, + NULL, numericStringNormalize, octetStringOrderingMatch, + NULL, NULL, + "numericStringMatch" }, + + {"( 2.5.13.10 NAME 'numericStringSubstringsMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 )", /* Substring Assertion */ + SLAP_MR_SUBSTR, NULL, + NULL, numericStringNormalize, octetStringSubstringsMatch, + octetStringSubstringsIndexer, octetStringSubstringsFilter, + "numericStringMatch" }, + + {"( 2.5.13.11 NAME 'caseIgnoreListMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )", /* Postal Address */ + SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL, + NULL, postalAddressNormalize, octetStringMatch, + octetStringIndexer, octetStringFilter, + NULL }, + + {"( 2.5.13.12 NAME 'caseIgnoreListSubstringsMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 )", /* Substring Assertion */ + SLAP_MR_SUBSTR, NULL, + NULL, NULL, NULL, NULL, NULL, + "caseIgnoreListMatch" }, + + {"( 2.5.13.13 NAME 'booleanMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 )", + SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL, + NULL, NULL, booleanMatch, + octetStringIndexer, octetStringFilter, + NULL }, + + {"( 2.5.13.14 NAME 'integerMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )", + SLAP_MR_EQUALITY | SLAP_MR_EXT | SLAP_MR_ORDERED_INDEX, NULL, + NULL, NULL, integerMatch, + integerIndexer, integerFilter, + NULL }, + + {"( 2.5.13.15 NAME 'integerOrderingMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )", + SLAP_MR_ORDERING | SLAP_MR_EXT | SLAP_MR_ORDERED_INDEX, NULL, + NULL, NULL, integerMatch, + NULL, NULL, + "integerMatch" }, + + {"( 2.5.13.16 NAME 'bitStringMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.6 )", + SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL, + NULL, NULL, octetStringMatch, + octetStringIndexer, octetStringFilter, + NULL }, + + {"( 2.5.13.17 NAME 'octetStringMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )", + SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL, + NULL, NULL, octetStringMatch, + octetStringIndexer, octetStringFilter, + NULL }, + + {"( 2.5.13.18 NAME 'octetStringOrderingMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )", + SLAP_MR_ORDERING | SLAP_MR_EXT, NULL, + NULL, NULL, octetStringOrderingMatch, + NULL, NULL, + "octetStringMatch" }, + + {"( 2.5.13.19 NAME 'octetStringSubstringsMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )", + SLAP_MR_SUBSTR, NULL, + NULL, NULL, octetStringSubstringsMatch, + octetStringSubstringsIndexer, octetStringSubstringsFilter, + "octetStringMatch" }, + + {"( 2.5.13.20 NAME 'telephoneNumberMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 )", + SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL, + NULL, + telephoneNumberNormalize, octetStringMatch, + octetStringIndexer, octetStringFilter, + NULL }, + + {"( 2.5.13.21 NAME 'telephoneNumberSubstringsMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 )", /* Substring Assertion */ + SLAP_MR_SUBSTR, NULL, + NULL, telephoneNumberNormalize, octetStringSubstringsMatch, + octetStringSubstringsIndexer, octetStringSubstringsFilter, + "telephoneNumberMatch" }, + + {"( 2.5.13.22 NAME 'presentationAddressMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.43 )", + SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL, + NULL, NULL, NULL, NULL, NULL, NULL }, + + {"( 2.5.13.23 NAME 'uniqueMemberMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.34 )", /* Name And Optional UID */ + SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL, + NULL, uniqueMemberNormalize, uniqueMemberMatch, + uniqueMemberIndexer, uniqueMemberFilter, + NULL }, + + {"( 2.5.13.24 NAME 'protocolInformationMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.42 )", + SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL, + NULL, NULL, NULL, NULL, NULL, NULL }, + + {"( 2.5.13.27 NAME 'generalizedTimeMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 )", + SLAP_MR_EQUALITY | SLAP_MR_EXT | SLAP_MR_ORDERED_INDEX, NULL, + NULL, generalizedTimeNormalize, octetStringMatch, + generalizedTimeIndexer, generalizedTimeFilter, + NULL }, + + {"( 2.5.13.28 NAME 'generalizedTimeOrderingMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 )", + SLAP_MR_ORDERING | SLAP_MR_EXT | SLAP_MR_ORDERED_INDEX, NULL, + NULL, generalizedTimeNormalize, generalizedTimeOrderingMatch, + NULL, NULL, + "generalizedTimeMatch" }, + + {"( 2.5.13.29 NAME 'integerFirstComponentMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )", /* Integer */ + SLAP_MR_EQUALITY | SLAP_MR_EXT, + integerFirstComponentMatchSyntaxes, + NULL, firstComponentNormalize, integerMatch, + octetStringIndexer, octetStringFilter, + NULL }, + + {"( 2.5.13.30 NAME 'objectIdentifierFirstComponentMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )", /* OID */ + SLAP_MR_EQUALITY | SLAP_MR_EXT, + objectIdentifierFirstComponentMatchSyntaxes, + NULL, firstComponentNormalize, octetStringMatch, + octetStringIndexer, octetStringFilter, + NULL }, + + {"( 2.5.13.34 NAME 'certificateExactMatch' " + "SYNTAX 1.3.6.1.1.15.1 )", /* Certificate Exact Assertion */ + SLAP_MR_EQUALITY | SLAP_MR_EXT, certificateExactMatchSyntaxes, + NULL, certificateExactNormalize, octetStringMatch, + octetStringIndexer, octetStringFilter, + NULL }, + + {"( 2.5.13.35 NAME 'certificateMatch' " + "SYNTAX 1.3.6.1.1.15.2 )", /* Certificate Assertion */ + SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL }, + + {"( 2.5.13.38 NAME 'certificateListExactMatch' " + "SYNTAX 1.3.6.1.1.15.5 )", /* Certificate List Exact Assertion */ + SLAP_MR_EQUALITY | SLAP_MR_EXT, certificateListExactMatchSyntaxes, + NULL, certificateListExactNormalize, octetStringMatch, + octetStringIndexer, octetStringFilter, + NULL }, + + {"( 2.5.13.39 NAME 'certificateListMatch' " + "SYNTAX 1.3.6.1.1.15.6 )", /* Certificate List Assertion */ + SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL }, + + {"( 2.5.13.45 NAME 'attributeCertificateExactMatch' " + "SYNTAX " attributeCertificateExactAssertionSyntaxOID " )", + SLAP_MR_EQUALITY | SLAP_MR_EXT | SLAP_MR_HIDE, attributeCertificateExactMatchSyntaxes, + NULL, attributeCertificateExactNormalize, octetStringMatch, + octetStringIndexer, octetStringFilter, + NULL }, + + {"( 2.5.13.46 NAME 'attributeCertificateMatch' " + "SYNTAX " attributeCertificateAssertionSyntaxOID " )", + SLAP_MR_EQUALITY | SLAP_MR_EXT | SLAP_MR_HIDE, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL }, + + {"( 1.3.6.1.4.1.1466.109.114.1 NAME 'caseExactIA5Match' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )", + SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL, + NULL, IA5StringNormalize, octetStringMatch, + octetStringIndexer, octetStringFilter, + IA5StringApproxMatchOID }, + + {"( 1.3.6.1.4.1.1466.109.114.2 NAME 'caseIgnoreIA5Match' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )", + SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL, + NULL, IA5StringNormalize, octetStringMatch, + octetStringIndexer, octetStringFilter, + IA5StringApproxMatchOID }, + + {"( 1.3.6.1.4.1.1466.109.114.3 NAME 'caseIgnoreIA5SubstringsMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )", + SLAP_MR_SUBSTR, NULL, + NULL, IA5StringNormalize, directoryStringSubstringsMatch, + octetStringSubstringsIndexer, octetStringSubstringsFilter, + "caseIgnoreIA5Match" }, + + {"( 1.3.6.1.4.1.4203.1.2.1 NAME 'caseExactIA5SubstringsMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )", + SLAP_MR_SUBSTR, NULL, + NULL, IA5StringNormalize, directoryStringSubstringsMatch, + octetStringSubstringsIndexer, octetStringSubstringsFilter, + "caseExactIA5Match" }, + +#ifdef SLAPD_AUTHPASSWD + /* needs updating */ + {"( 1.3.6.1.4.1.4203.666.4.1 NAME 'authPasswordMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )", /* Octet String */ + SLAP_MR_HIDE | SLAP_MR_EQUALITY, NULL, + NULL, NULL, authPasswordMatch, + NULL, NULL, + NULL}, +#endif + + {"( 1.2.840.113556.1.4.803 NAME 'integerBitAndMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )", /* Integer */ + SLAP_MR_EXT, NULL, + NULL, NULL, integerBitAndMatch, + NULL, NULL, + "integerMatch" }, + + {"( 1.2.840.113556.1.4.804 NAME 'integerBitOrMatch' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )", /* Integer */ + SLAP_MR_EXT, NULL, + NULL, NULL, integerBitOrMatch, + NULL, NULL, + "integerMatch" }, + + {"( 1.3.6.1.1.16.2 NAME 'UUIDMatch' " + "SYNTAX 1.3.6.1.1.16.1 )", + SLAP_MR_EQUALITY | SLAP_MR_MUTATION_NORMALIZER, NULL, + NULL, UUIDNormalize, octetStringMatch, + octetStringIndexer, octetStringFilter, + NULL}, + + {"( 1.3.6.1.1.16.3 NAME 'UUIDOrderingMatch' " + "SYNTAX 1.3.6.1.1.16.1 )", + SLAP_MR_ORDERING | SLAP_MR_MUTATION_NORMALIZER, NULL, + NULL, UUIDNormalize, octetStringOrderingMatch, + octetStringIndexer, octetStringFilter, + "UUIDMatch"}, + + {"( 1.3.6.1.4.1.4203.666.11.2.2 NAME 'CSNMatch' " + "SYNTAX 1.3.6.1.4.1.4203.666.11.2.1 )", + SLAP_MR_HIDE | SLAP_MR_EQUALITY | SLAP_MR_ORDERED_INDEX, NULL, + NULL, csnNormalize, csnMatch, + csnIndexer, csnFilter, + NULL}, + + {"( 1.3.6.1.4.1.4203.666.11.2.3 NAME 'CSNOrderingMatch' " + "SYNTAX 1.3.6.1.4.1.4203.666.11.2.1 )", + SLAP_MR_HIDE | SLAP_MR_ORDERING | SLAP_MR_EXT | SLAP_MR_ORDERED_INDEX, NULL, + NULL, csnNormalize, csnOrderingMatch, + NULL, NULL, + "CSNMatch" }, + + {"( 1.3.6.1.4.1.4203.666.11.2.5 NAME 'CSNSIDMatch' " + "SYNTAX 1.3.6.1.4.1.4203.666.11.2.4 )", + SLAP_MR_HIDE | SLAP_MR_EQUALITY | SLAP_MR_EXT, csnSIDMatchSyntaxes, + NULL, csnSidNormalize, octetStringMatch, + octetStringIndexer, octetStringFilter, + NULL }, + + /* FIXME: OID is unused, but not registered yet */ + {"( 1.3.6.1.4.1.4203.666.4.12 NAME 'authzMatch' " + "SYNTAX 1.3.6.1.4.1.4203.666.2.7 )", /* OpenLDAP authz */ + SLAP_MR_HIDE | SLAP_MR_EQUALITY, NULL, + NULL, authzNormalize, authzMatch, + NULL, NULL, + NULL}, + + {NULL, SLAP_MR_NONE, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL } +}; + +int +slap_schema_init( void ) +{ + int res; + int i; + + /* we should only be called once (from main) */ + assert( schema_init_done == 0 ); + + for ( i=0; syntax_defs[i].sd_desc != NULL; i++ ) { + res = register_syntax( &syntax_defs[i] ); + + if ( res ) { + fprintf( stderr, "slap_schema_init: Error registering syntax %s\n", + syntax_defs[i].sd_desc ); + return LDAP_OTHER; + } + } + + for ( i=0; mrule_defs[i].mrd_desc != NULL; i++ ) { + if( mrule_defs[i].mrd_usage == SLAP_MR_NONE && + mrule_defs[i].mrd_compat_syntaxes == NULL ) + { + fprintf( stderr, + "slap_schema_init: Ignoring unusable matching rule %s\n", + mrule_defs[i].mrd_desc ); + continue; + } + + res = register_matching_rule( &mrule_defs[i] ); + + if ( res ) { + fprintf( stderr, + "slap_schema_init: Error registering matching rule %s\n", + mrule_defs[i].mrd_desc ); + return LDAP_OTHER; + } + } + + res = slap_schema_load(); + schema_init_done = 1; + return res; +} + +void +schema_destroy( void ) +{ + oidm_destroy(); + oc_destroy(); + at_destroy(); + mr_destroy(); + mru_destroy(); + syn_destroy(); + + if( schema_init_done ) { + ldap_pvt_thread_mutex_destroy( &ad_index_mutex ); + ldap_pvt_thread_mutex_destroy( &ad_undef_mutex ); + ldap_pvt_thread_mutex_destroy( &oc_undef_mutex ); + } +} diff --git a/servers/slapd/schema_prep.c b/servers/slapd/schema_prep.c new file mode 100644 index 0000000..72a5ddf --- /dev/null +++ b/servers/slapd/schema_prep.c @@ -0,0 +1,1614 @@ +/* schema_prep.c - load builtin schema */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" + +#define OCDEBUG 0 + +int schema_init_done = 0; + +struct slap_internal_schema slap_schema; + +static int +oidValidate( + Syntax *syntax, + struct berval *in ) +{ + struct berval val = *in; + + if( val.bv_len == 0 ) { + /* disallow empty strings */ + return LDAP_INVALID_SYNTAX; + } + + if( DESC_LEADCHAR( val.bv_val[0] ) ) { + val.bv_val++; + val.bv_len--; + if ( val.bv_len == 0 ) return LDAP_SUCCESS; + + while( DESC_CHAR( val.bv_val[0] ) ) { + val.bv_val++; + val.bv_len--; + + if ( val.bv_len == 0 ) return LDAP_SUCCESS; + } + + } else { + int sep = 0; + while( OID_LEADCHAR( val.bv_val[0] ) ) { + val.bv_val++; + val.bv_len--; + + if ( val.bv_val[-1] != '0' ) { + while ( OID_LEADCHAR( val.bv_val[0] )) { + val.bv_val++; + val.bv_len--; + } + } + + if( val.bv_len == 0 ) { + if( sep == 0 ) break; + return LDAP_SUCCESS; + } + + if( !OID_SEPARATOR( val.bv_val[0] )) break; + + sep++; + val.bv_val++; + val.bv_len--; + } + } + + return LDAP_INVALID_SYNTAX; +} + + +static int objectClassPretty( + Syntax *syntax, + struct berval *in, + struct berval *out, + void *ctx ) +{ + ObjectClass *oc; + + if( oidValidate( NULL, in )) return LDAP_INVALID_SYNTAX; + + oc = oc_bvfind( in ); + if( oc == NULL ) return LDAP_INVALID_SYNTAX; + + ber_dupbv_x( out, &oc->soc_cname, ctx ); + return LDAP_SUCCESS; +} + +static int +attributeTypeMatch( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + struct berval *a = (struct berval *) assertedValue; + AttributeType *at = at_bvfind( value ); + AttributeType *asserted = at_bvfind( a ); + + if( asserted == NULL ) { + if( OID_LEADCHAR( *a->bv_val ) ) { + /* OID form, return FALSE */ + *matchp = 1; + return LDAP_SUCCESS; + } + + /* desc form, return undefined */ + return LDAP_INVALID_SYNTAX; + } + + if ( at == NULL ) { + /* unrecognized stored value */ + return LDAP_INVALID_SYNTAX; + } + + *matchp = ( asserted != at ); + return LDAP_SUCCESS; +} + +static int +matchingRuleMatch( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + struct berval *a = (struct berval *) assertedValue; + MatchingRule *mrv = mr_bvfind( value ); + MatchingRule *asserted = mr_bvfind( a ); + + if( asserted == NULL ) { + if( OID_LEADCHAR( *a->bv_val ) ) { + /* OID form, return FALSE */ + *matchp = 1; + return LDAP_SUCCESS; + } + + /* desc form, return undefined */ + return LDAP_INVALID_SYNTAX; + } + + if ( mrv == NULL ) { + /* unrecognized stored value */ + return LDAP_INVALID_SYNTAX; + } + + *matchp = ( asserted != mrv ); + return LDAP_SUCCESS; +} + +static int +objectClassMatch( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + struct berval *a = (struct berval *) assertedValue; + ObjectClass *oc = oc_bvfind( value ); + ObjectClass *asserted = oc_bvfind( a ); + + if( asserted == NULL ) { + if( OID_LEADCHAR( *a->bv_val ) ) { + /* OID form, return FALSE */ + *matchp = 1; + return LDAP_SUCCESS; + } + + /* desc form, return undefined */ + return LDAP_INVALID_SYNTAX; + } + + if ( oc == NULL ) { + /* unrecognized stored value */ + return LDAP_INVALID_SYNTAX; + } + + *matchp = ( asserted != oc ); + return LDAP_SUCCESS; +} + +static int +objectSubClassMatch( + int *matchp, + slap_mask_t flags, + Syntax *syntax, + MatchingRule *mr, + struct berval *value, + void *assertedValue ) +{ + struct berval *a = (struct berval *) assertedValue; + ObjectClass *oc = oc_bvfind( value ); + ObjectClass *asserted = oc_bvfind( a ); + + if( asserted == NULL ) { + if( OID_LEADCHAR( *a->bv_val ) ) { + /* OID form, return FALSE */ + *matchp = 1; + return LDAP_SUCCESS; + } + + /* desc form, return undefined */ + return LDAP_INVALID_SYNTAX; + } + + if ( oc == NULL ) { + /* unrecognized stored value */ + return LDAP_INVALID_SYNTAX; + } + + if( SLAP_MR_IS_VALUE_OF_ATTRIBUTE_SYNTAX( flags ) ) { + *matchp = ( asserted != oc ); + } else { + *matchp = !is_object_subclass( asserted, oc ); + } + + return LDAP_SUCCESS; +} + +static int objectSubClassIndexer( + slap_mask_t use, + slap_mask_t mask, + Syntax *syntax, + MatchingRule *mr, + struct berval *prefix, + BerVarray values, + BerVarray *keysp, + void *ctx ) +{ + int rc, noc, i; + BerVarray ocvalues; + ObjectClass **socs; + + for( noc=0; values[noc].bv_val != NULL; noc++ ) { + /* just count em */; + } + + /* over allocate */ + socs = slap_sl_malloc( (noc+16) * sizeof( ObjectClass * ), ctx ); + + /* initialize */ + for( i=0; i<noc; i++ ) { + socs[i] = oc_bvfind( &values[i] ); + } + + /* expand values */ + for( i=0; i<noc; i++ ) { + int j; + ObjectClass *oc = socs[i]; + if( oc == NULL || oc->soc_sups == NULL ) continue; + + for( j=0; oc->soc_sups[j] != NULL; j++ ) { + int found = 0; + ObjectClass *sup = oc->soc_sups[j]; + int k; + + for( k=0; k<noc; k++ ) { + if( sup == socs[k] ) { + found++; + break; + } + } + + if( !found ) { + socs = slap_sl_realloc( socs, + sizeof( ObjectClass * ) * (noc+2), ctx ); + + assert( k == noc ); + socs[noc++] = sup; + } + } + } + + ocvalues = slap_sl_malloc( sizeof( struct berval ) * (noc+1), ctx ); + /* copy values */ + for( i=0; i<noc; i++ ) { + if ( socs[i] ) + ocvalues[i] = socs[i]->soc_cname; + else + ocvalues[i] = values[i]; + } + BER_BVZERO( &ocvalues[i] ); + + rc = octetStringIndexer( use, mask, syntax, mr, + prefix, ocvalues, keysp, ctx ); + + slap_sl_free( ocvalues, ctx ); + slap_sl_free( socs, ctx ); + return rc; +} + +#define objectSubClassFilter octetStringFilter + +static ObjectClassSchemaCheckFN rootDseObjectClass; +static ObjectClassSchemaCheckFN aliasObjectClass; +static ObjectClassSchemaCheckFN referralObjectClass; +static ObjectClassSchemaCheckFN subentryObjectClass; +#ifdef LDAP_DYNAMIC_OBJECTS +static ObjectClassSchemaCheckFN dynamicObjectClass; +#endif + +static struct slap_schema_oc_map { + char *ssom_name; + char *ssom_defn; + ObjectClassSchemaCheckFN *ssom_check; + slap_mask_t ssom_flags; + size_t ssom_offset; +} oc_map[] = { + { "top", "( 2.5.6.0 NAME 'top' " + "DESC 'top of the superclass chain' " + "ABSTRACT MUST objectClass )", + 0, 0, offsetof(struct slap_internal_schema, si_oc_top) }, + { "extensibleObject", "( 1.3.6.1.4.1.1466.101.120.111 " + "NAME 'extensibleObject' " + "DESC 'RFC4512: extensible object' " + "SUP top AUXILIARY )", + 0, SLAP_OC_OPERATIONAL, + offsetof(struct slap_internal_schema, si_oc_extensibleObject) }, + { "alias", "( 2.5.6.1 NAME 'alias' " + "DESC 'RFC4512: an alias' " + "SUP top STRUCTURAL " + "MUST aliasedObjectName )", + aliasObjectClass, SLAP_OC_ALIAS|SLAP_OC_OPERATIONAL, + offsetof(struct slap_internal_schema, si_oc_alias) }, + { "referral", "( 2.16.840.1.113730.3.2.6 NAME 'referral' " + "DESC 'namedref: named subordinate referral' " + "SUP top STRUCTURAL MUST ref )", + referralObjectClass, SLAP_OC_REFERRAL|SLAP_OC_OPERATIONAL, + offsetof(struct slap_internal_schema, si_oc_referral) }, + { "LDAProotDSE", "( 1.3.6.1.4.1.4203.1.4.1 " + "NAME ( 'OpenLDAProotDSE' 'LDAProotDSE' ) " + "DESC 'OpenLDAP Root DSE object' " + "SUP top STRUCTURAL MAY cn )", + rootDseObjectClass, SLAP_OC_OPERATIONAL, + offsetof(struct slap_internal_schema, si_oc_rootdse) }, + { "subentry", "( 2.5.17.0 NAME 'subentry' " + "DESC 'RFC3672: subentry' " + "SUP top STRUCTURAL " + "MUST ( cn $ subtreeSpecification ) )", + subentryObjectClass, SLAP_OC_SUBENTRY|SLAP_OC_OPERATIONAL, + offsetof(struct slap_internal_schema, si_oc_subentry) }, + { "subschema", "( 2.5.20.1 NAME 'subschema' " + "DESC 'RFC4512: controlling subschema (sub)entry' " + "AUXILIARY " + "MAY ( dITStructureRules $ nameForms $ dITContentRules $ " + "objectClasses $ attributeTypes $ matchingRules $ " + "matchingRuleUse ) )", + subentryObjectClass, SLAP_OC_OPERATIONAL, + offsetof(struct slap_internal_schema, si_oc_subschema) }, +#ifdef LDAP_COLLECTIVE_ATTRIBUTES + { "collectiveAttributeSubentry", "( 2.5.17.2 " + "NAME 'collectiveAttributeSubentry' " + "DESC 'RFC3671: collective attribute subentry' " + "AUXILIARY )", + subentryObjectClass, + SLAP_OC_COLLECTIVEATTRIBUTESUBENTRY|SLAP_OC_OPERATIONAL|SLAP_OC_HIDE, + offsetof( struct slap_internal_schema, + si_oc_collectiveAttributeSubentry) }, +#endif +#ifdef LDAP_DYNAMIC_OBJECTS + { "dynamicObject", "( 1.3.6.1.4.1.1466.101.119.2 " + "NAME 'dynamicObject' " + "DESC 'RFC2589: Dynamic Object' " + "SUP top AUXILIARY )", + dynamicObjectClass, SLAP_OC_DYNAMICOBJECT, + offsetof(struct slap_internal_schema, si_oc_dynamicObject) }, +#endif + { "glue", "( 1.3.6.1.4.1.4203.666.3.4 " + "NAME 'glue' " + "DESC 'Glue Entry' " + "SUP top STRUCTURAL )", + 0, SLAP_OC_GLUE|SLAP_OC_OPERATIONAL|SLAP_OC_HIDE, + offsetof(struct slap_internal_schema, si_oc_glue) }, + { "syncConsumerSubentry", "( 1.3.6.1.4.1.4203.666.3.5 " + "NAME 'syncConsumerSubentry' " + "DESC 'Persistent Info for SyncRepl Consumer' " + "AUXILIARY " + "MAY syncreplCookie )", + 0, SLAP_OC_SYNCCONSUMERSUBENTRY|SLAP_OC_OPERATIONAL|SLAP_OC_HIDE, + offsetof(struct slap_internal_schema, si_oc_syncConsumerSubentry) }, + { "syncProviderSubentry", "( 1.3.6.1.4.1.4203.666.3.6 " + "NAME 'syncProviderSubentry' " + "DESC 'Persistent Info for SyncRepl Producer' " + "AUXILIARY " + "MAY contextCSN )", + 0, SLAP_OC_SYNCPROVIDERSUBENTRY|SLAP_OC_OPERATIONAL|SLAP_OC_HIDE, + offsetof(struct slap_internal_schema, si_oc_syncProviderSubentry) }, + + { NULL, NULL, NULL, 0, 0 } +}; + +static AttributeTypeSchemaCheckFN rootDseAttribute; +static AttributeTypeSchemaCheckFN aliasAttribute; +static AttributeTypeSchemaCheckFN referralAttribute; +static AttributeTypeSchemaCheckFN subentryAttribute; +static AttributeTypeSchemaCheckFN administrativeRoleAttribute; +#ifdef LDAP_DYNAMIC_OBJECTS +static AttributeTypeSchemaCheckFN dynamicAttribute; +#endif + +static struct slap_schema_ad_map { + char *ssam_name; + char *ssam_defn; + AttributeTypeSchemaCheckFN *ssam_check; + slap_mask_t ssam_flags; + slap_syntax_validate_func *ssam_syn_validate; + slap_syntax_transform_func *ssam_syn_pretty; + slap_mr_convert_func *ssam_mr_convert; + slap_mr_normalize_func *ssam_mr_normalize; + slap_mr_match_func *ssam_mr_match; + slap_mr_indexer_func *ssam_mr_indexer; + slap_mr_filter_func *ssam_mr_filter; + size_t ssam_offset; +} ad_map[] = { + { "objectClass", "( 2.5.4.0 NAME 'objectClass' " + "DESC 'RFC4512: object classes of the entity' " + "EQUALITY objectIdentifierMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )", + NULL, SLAP_AT_FINAL, + oidValidate, objectClassPretty, + NULL, NULL, objectSubClassMatch, + objectSubClassIndexer, objectSubClassFilter, + offsetof(struct slap_internal_schema, si_ad_objectClass) }, + + /* user entry operational attributes */ + { "structuralObjectClass", "( 2.5.21.9 NAME 'structuralObjectClass' " + "DESC 'RFC4512: structural object class of entry' " + "EQUALITY objectIdentifierMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 " + "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", + NULL, 0, + oidValidate, objectClassPretty, + NULL, NULL, objectSubClassMatch, + objectSubClassIndexer, objectSubClassFilter, + offsetof(struct slap_internal_schema, si_ad_structuralObjectClass) }, + { "createTimestamp", "( 2.5.18.1 NAME 'createTimestamp' " + "DESC 'RFC4512: time which object was created' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", + NULL, SLAP_AT_MANAGEABLE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_createTimestamp) }, + { "modifyTimestamp", "( 2.5.18.2 NAME 'modifyTimestamp' " + "DESC 'RFC4512: time which object was last modified' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", + NULL, SLAP_AT_MANAGEABLE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_modifyTimestamp) }, + { "creatorsName", "( 2.5.18.3 NAME 'creatorsName' " + "DESC 'RFC4512: name of creator' " + "EQUALITY distinguishedNameMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 " + "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", + NULL, SLAP_AT_MANAGEABLE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_creatorsName) }, + { "modifiersName", "( 2.5.18.4 NAME 'modifiersName' " + "DESC 'RFC4512: name of last modifier' " + "EQUALITY distinguishedNameMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 " + "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", + NULL, SLAP_AT_MANAGEABLE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_modifiersName) }, + { "hasSubordinates", "( 2.5.18.9 NAME 'hasSubordinates' " + "DESC 'X.501: entry has children' " + "EQUALITY booleanMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 " + "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", + NULL, SLAP_AT_DYNAMIC, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_hasSubordinates) }, + { "subschemaSubentry", "( 2.5.18.10 NAME 'subschemaSubentry' " + "DESC 'RFC4512: name of controlling subschema entry' " + "EQUALITY distinguishedNameMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE " + "NO-USER-MODIFICATION USAGE directoryOperation )", + NULL, SLAP_AT_DYNAMIC, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_subschemaSubentry) }, +#ifdef LDAP_COLLECTIVE_ATTRIBUTES + { "collectiveAttributeSubentries", "( 2.5.18.12 " + "NAME 'collectiveAttributeSubentries' " + "DESC 'RFC3671: collective attribute subentries' " + "EQUALITY distinguishedNameMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 " + "NO-USER-MODIFICATION USAGE directoryOperation )", + NULL, SLAP_AT_HIDE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_collectiveSubentries) }, + { "collectiveExclusions", "( 2.5.18.7 NAME 'collectiveExclusions' " + "DESC 'RFC3671: collective attribute exclusions' " + "EQUALITY objectIdentifierMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 " + "USAGE directoryOperation )", + NULL, SLAP_AT_HIDE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_collectiveExclusions) }, +#endif + + { "entryDN", "( 1.3.6.1.1.20 NAME 'entryDN' " + "DESC 'DN of the entry' " + "EQUALITY distinguishedNameMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 " + "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", + NULL, SLAP_AT_DYNAMIC, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_entryDN) }, + { "entryUUID", "( 1.3.6.1.1.16.4 NAME 'entryUUID' " + "DESC 'UUID of the entry' " + "EQUALITY UUIDMatch " + "ORDERING UUIDOrderingMatch " + "SYNTAX 1.3.6.1.1.16.1 " + "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", + NULL, SLAP_AT_MANAGEABLE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_entryUUID) }, + { "entryCSN", "( 1.3.6.1.4.1.4203.666.1.7 NAME 'entryCSN' " + "DESC 'change sequence number of the entry content' " + "EQUALITY CSNMatch " + "ORDERING CSNOrderingMatch " + "SYNTAX 1.3.6.1.4.1.4203.666.11.2.1{64} " + "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", + NULL, SLAP_AT_HIDE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_entryCSN) }, + { "namingCSN", "( 1.3.6.1.4.1.4203.666.1.13 NAME 'namingCSN' " + "DESC 'change sequence number of the entry naming (RDN)' " + "EQUALITY CSNMatch " + "ORDERING CSNOrderingMatch " + "SYNTAX 1.3.6.1.4.1.4203.666.11.2.1{64} " + "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", + NULL, SLAP_AT_HIDE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_namingCSN) }, + +#ifdef LDAP_SUPERIOR_UUID + { "superiorUUID", "( 1.3.6.1.4.1.4203.666.1.11 NAME 'superiorUUID' " + "DESC 'UUID of the superior entry' " + "EQUALITY UUIDMatch " + "ORDERING UUIDOrderingMatch " + "SYNTAX 1.3.6.1.1.16.1 " + "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", + NULL, SLAP_AT_HIDE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_superiorUUID) }, +#endif + + { "syncreplCookie", "( 1.3.6.1.4.1.4203.666.1.23 " + "NAME 'syncreplCookie' " + "DESC 'syncrepl Cookie for shadow copy' " + "EQUALITY octetStringMatch " + "ORDERING octetStringOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 " + "SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation )", + NULL, SLAP_AT_HIDE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_syncreplCookie) }, + + { "contextCSN", "( 1.3.6.1.4.1.4203.666.1.25 " + "NAME 'contextCSN' " + "DESC 'the largest committed CSN of a context' " + "EQUALITY CSNMatch " + "ORDERING CSNOrderingMatch " + "SYNTAX 1.3.6.1.4.1.4203.666.11.2.1{64} " + "NO-USER-MODIFICATION USAGE dSAOperation )", + NULL, SLAP_AT_HIDE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_contextCSN) }, + +#ifdef LDAP_SYNC_TIMESTAMP + { "syncTimestamp", "( 1.3.6.1.4.1.4203.666.1.26 NAME 'syncTimestamp' " + "DESC 'Time which object was replicated' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation )", + NULL, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_syncTimestamp) }, +#endif + + /* root DSE attributes */ + { "altServer", "( 1.3.6.1.4.1.1466.101.120.6 NAME 'altServer' " + "DESC 'RFC4512: alternative servers' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 USAGE dSAOperation )", + rootDseAttribute, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_altServer) }, + { "namingContexts", "( 1.3.6.1.4.1.1466.101.120.5 " + "NAME 'namingContexts' " + "DESC 'RFC4512: naming contexts' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 USAGE dSAOperation )", + rootDseAttribute, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_namingContexts) }, + { "supportedControl", "( 1.3.6.1.4.1.1466.101.120.13 " + "NAME 'supportedControl' " + "DESC 'RFC4512: supported controls' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 USAGE dSAOperation )", + rootDseAttribute, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_supportedControl) }, + { "supportedExtension", "( 1.3.6.1.4.1.1466.101.120.7 " + "NAME 'supportedExtension' " + "DESC 'RFC4512: supported extended operations' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 USAGE dSAOperation )", + rootDseAttribute, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_supportedExtension) }, + { "supportedLDAPVersion", "( 1.3.6.1.4.1.1466.101.120.15 " + "NAME 'supportedLDAPVersion' " + "DESC 'RFC4512: supported LDAP versions' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 USAGE dSAOperation )", + rootDseAttribute, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_supportedLDAPVersion) }, + { "supportedSASLMechanisms", "( 1.3.6.1.4.1.1466.101.120.14 " + "NAME 'supportedSASLMechanisms' " + "DESC 'RFC4512: supported SASL mechanisms'" + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 USAGE dSAOperation )", + rootDseAttribute, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_supportedSASLMechanisms) }, + { "supportedFeatures", "( 1.3.6.1.4.1.4203.1.3.5 " + "NAME 'supportedFeatures' " + "DESC 'RFC4512: features supported by the server' " + "EQUALITY objectIdentifierMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 " + "USAGE dSAOperation )", + rootDseAttribute, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_supportedFeatures) }, + { "monitorContext", "( 1.3.6.1.4.1.4203.666.1.10 " + "NAME 'monitorContext' " + "DESC 'monitor context' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 " + "EQUALITY distinguishedNameMatch " + "SINGLE-VALUE NO-USER-MODIFICATION " + "USAGE dSAOperation )", + rootDseAttribute, SLAP_AT_HIDE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_monitorContext) }, + { "configContext", "( 1.3.6.1.4.1.4203.1.12.2.1 " + "NAME 'configContext' " + "DESC 'config context' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 " + "EQUALITY distinguishedNameMatch " + "SINGLE-VALUE NO-USER-MODIFICATION " + "USAGE dSAOperation )", + rootDseAttribute, SLAP_AT_HIDE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_configContext) }, + { "vendorName", "( 1.3.6.1.1.4 NAME 'vendorName' " + "DESC 'RFC3045: name of implementation vendor' " + "EQUALITY caseExactMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 " + "SINGLE-VALUE NO-USER-MODIFICATION " + "USAGE dSAOperation )", + rootDseAttribute, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_vendorName) }, + { "vendorVersion", "( 1.3.6.1.1.5 NAME 'vendorVersion' " + "DESC 'RFC3045: version of implementation' " + "EQUALITY caseExactMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 " + "SINGLE-VALUE NO-USER-MODIFICATION " + "USAGE dSAOperation )", + rootDseAttribute, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_vendorVersion) }, + + /* subentry attributes */ + { "administrativeRole", "( 2.5.18.5 NAME 'administrativeRole' " + "DESC 'RFC3672: administrative role' " + "EQUALITY objectIdentifierMatch " + "USAGE directoryOperation " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )", + administrativeRoleAttribute, SLAP_AT_HIDE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_administrativeRole) }, + { "subtreeSpecification", "( 2.5.18.6 NAME 'subtreeSpecification' " + "DESC 'RFC3672: subtree specification' " + "SINGLE-VALUE " + "USAGE directoryOperation " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.45 )", + subentryAttribute, SLAP_AT_HIDE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_subtreeSpecification) }, + + /* subschema subentry attributes */ + { "dITStructureRules", "( 2.5.21.1 NAME 'dITStructureRules' " + "DESC 'RFC4512: DIT structure rules' " + "EQUALITY integerFirstComponentMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.17 " + "USAGE directoryOperation ) ", + subentryAttribute, SLAP_AT_HIDE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_ditStructureRules) }, + { "dITContentRules", "( 2.5.21.2 NAME 'dITContentRules' " + "DESC 'RFC4512: DIT content rules' " + "EQUALITY objectIdentifierFirstComponentMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.16 USAGE directoryOperation )", + subentryAttribute, SLAP_AT_HIDE, + oidValidate, NULL, + NULL, NULL, objectClassMatch, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_ditContentRules) }, + { "matchingRules", "( 2.5.21.4 NAME 'matchingRules' " + "DESC 'RFC4512: matching rules' " + "EQUALITY objectIdentifierFirstComponentMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.30 USAGE directoryOperation )", + subentryAttribute, 0, + oidValidate, NULL, + NULL, NULL, matchingRuleMatch, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_matchingRules) }, + { "attributeTypes", "( 2.5.21.5 NAME 'attributeTypes' " + "DESC 'RFC4512: attribute types' " + "EQUALITY objectIdentifierFirstComponentMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.3 USAGE directoryOperation )", + subentryAttribute, 0, + oidValidate, NULL, + NULL, NULL, attributeTypeMatch, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_attributeTypes) }, + { "objectClasses", "( 2.5.21.6 NAME 'objectClasses' " + "DESC 'RFC4512: object classes' " + "EQUALITY objectIdentifierFirstComponentMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.37 USAGE directoryOperation )", + subentryAttribute, 0, + oidValidate, NULL, + NULL, NULL, objectClassMatch, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_objectClasses) }, + { "nameForms", "( 2.5.21.7 NAME 'nameForms' " + "DESC 'RFC4512: name forms ' " + "EQUALITY objectIdentifierFirstComponentMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.35 USAGE directoryOperation )", + subentryAttribute, SLAP_AT_HIDE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_nameForms) }, + { "matchingRuleUse", "( 2.5.21.8 NAME 'matchingRuleUse' " + "DESC 'RFC4512: matching rule uses' " + "EQUALITY objectIdentifierFirstComponentMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.31 USAGE directoryOperation )", + subentryAttribute, 0, + oidValidate, NULL, + NULL, NULL, matchingRuleMatch, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_matchingRuleUse) }, + + { "ldapSyntaxes", "( 1.3.6.1.4.1.1466.101.120.16 NAME 'ldapSyntaxes' " + "DESC 'RFC4512: LDAP syntaxes' " + "EQUALITY objectIdentifierFirstComponentMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.54 USAGE directoryOperation )", + subentryAttribute, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_ldapSyntaxes) }, + + /* knowledge information */ + { "aliasedObjectName", "( 2.5.4.1 " + "NAME ( 'aliasedObjectName' 'aliasedEntryName' ) " + "DESC 'RFC4512: name of aliased object' " + "EQUALITY distinguishedNameMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )", + aliasAttribute, SLAP_AT_FINAL, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_aliasedObjectName) }, + { "ref", "( 2.16.840.1.113730.3.1.34 NAME 'ref' " + "DESC 'RFC3296: subordinate referral URL' " + "EQUALITY caseExactMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 " + "USAGE distributedOperation )", + referralAttribute, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_ref) }, + + /* access control internals */ + { "entry", "( 1.3.6.1.4.1.4203.1.3.1 " + "NAME 'entry' " + "DESC 'OpenLDAP ACL entry pseudo-attribute' " + "SYNTAX 1.3.6.1.4.1.4203.1.1.1 " + "SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation )", + NULL, SLAP_AT_HIDE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_entry) }, + { "children", "( 1.3.6.1.4.1.4203.1.3.2 " + "NAME 'children' " + "DESC 'OpenLDAP ACL children pseudo-attribute' " + "SYNTAX 1.3.6.1.4.1.4203.1.1.1 " + "SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation )", + NULL, SLAP_AT_HIDE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_children) }, + + /* access control externals */ + { "authzTo", "( 1.3.6.1.4.1.4203.666.1.8 " + "NAME ( 'authzTo' 'saslAuthzTo' ) " + "DESC 'proxy authorization targets' " + "EQUALITY authzMatch " + "SYNTAX 1.3.6.1.4.1.4203.666.2.7 " + "X-ORDERED 'VALUES' " + "USAGE distributedOperation )", + NULL, SLAP_AT_HIDE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_saslAuthzTo) }, + { "authzFrom", "( 1.3.6.1.4.1.4203.666.1.9 " + "NAME ( 'authzFrom' 'saslAuthzFrom' ) " + "DESC 'proxy authorization sources' " + "EQUALITY authzMatch " + "SYNTAX 1.3.6.1.4.1.4203.666.2.7 " + "X-ORDERED 'VALUES' " + "USAGE distributedOperation )", + NULL, SLAP_AT_HIDE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_saslAuthzFrom) }, + +#ifdef LDAP_DYNAMIC_OBJECTS + { "entryTtl", "( 1.3.6.1.4.1.1466.101.119.3 NAME 'entryTtl' " + "DESC 'RFC2589: entry time-to-live' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE " + "NO-USER-MODIFICATION USAGE dSAOperation )", + dynamicAttribute, SLAP_AT_MANAGEABLE, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_entryTtl) }, + { "dynamicSubtrees", "( 1.3.6.1.4.1.1466.101.119.4 " + "NAME 'dynamicSubtrees' " + "DESC 'RFC2589: dynamic subtrees' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 NO-USER-MODIFICATION " + "USAGE dSAOperation )", + rootDseAttribute, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_dynamicSubtrees) }, +#endif + + /* userApplication attributes (which system schema depends upon) */ + { "distinguishedName", "( 2.5.4.49 NAME 'distinguishedName' " + "DESC 'RFC4519: common supertype of DN attributes' " + "EQUALITY distinguishedNameMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )", + NULL, SLAP_AT_ABSTRACT, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_distinguishedName) }, + { "name", "( 2.5.4.41 NAME 'name' " + "DESC 'RFC4519: common supertype of name attributes' " + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768} )", + NULL, SLAP_AT_ABSTRACT, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_name) }, + { "cn", "( 2.5.4.3 NAME ( 'cn' 'commonName' ) " + "DESC 'RFC4519: common name(s) for which the entity is known by' " + "SUP name )", + NULL, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_cn) }, + { "uid", "( 0.9.2342.19200300.100.1.1 NAME ( 'uid' 'userid' ) " + "DESC 'RFC4519: user identifier' " + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )", + NULL, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_uid) }, + { "uidNumber", /* for ldapi:// */ + "( 1.3.6.1.1.1.1.0 NAME 'uidNumber' " + "DESC 'RFC2307: An integer uniquely identifying a user " + "in an administrative domain' " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )", + NULL, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_uidNumber) }, + { "gidNumber", /* for ldapi:// */ + "( 1.3.6.1.1.1.1.1 NAME 'gidNumber' " + "DESC 'RFC2307: An integer uniquely identifying a group " + "in an administrative domain' " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )", + NULL, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_gidNumber) }, + { "userPassword", "( 2.5.4.35 NAME 'userPassword' " + "DESC 'RFC4519/2307: password of user' " + "EQUALITY octetStringMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{128} )", + NULL, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_userPassword) }, + + { "labeledURI", "( 1.3.6.1.4.1.250.1.57 NAME 'labeledURI' " + "DESC 'RFC2079: Uniform Resource Identifier with optional label' " + "EQUALITY caseExactMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + NULL, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_labeledURI) }, + +#ifdef SLAPD_AUTHPASSWD + { "authPassword", "( 1.3.6.1.4.1.4203.1.3.4 " + "NAME 'authPassword' " + "DESC 'RFC3112: authentication password attribute' " + "EQUALITY 1.3.6.1.4.1.4203.1.2.2 " + "SYNTAX 1.3.6.1.4.1.4203.1.1.2 )", + NULL, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_authPassword) }, + { "supportedAuthPasswordSchemes", "( 1.3.6.1.4.1.4203.1.3.3 " + "NAME 'supportedAuthPasswordSchemes' " + "DESC 'RFC3112: supported authPassword schemes' " + "EQUALITY caseExactIA5Match " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} " + "USAGE dSAOperation )", + subschemaAttribute, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_authPasswordSchemes) }, +#endif + + { "description", "( 2.5.4.13 NAME 'description' " + "DESC 'RFC4519: descriptive information' " + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )", + NULL, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_description) }, + + { "seeAlso", "( 2.5.4.34 NAME 'seeAlso' " + "DESC 'RFC4519: DN of related object' " + "SUP distinguishedName )", + NULL, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_seeAlso) }, + + { NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0 } +}; + +static AttributeType slap_at_undefined = { + { "1.1.1", NULL, "Catchall for undefined attribute types", 1, NULL, + NULL, NULL, NULL, NULL, + 0, 0, 0, 1, LDAP_SCHEMA_DSA_OPERATION, NULL }, /* LDAPAttributeType */ + BER_BVC("UNDEFINED"), /* cname */ + NULL, /* sup */ + NULL, /* subtypes */ + NULL, NULL, NULL, NULL, /* matching rules routines */ + NULL, /* syntax (will be set later to "octetString") */ + NULL, /* schema check function */ + NULL, /* oidmacro */ + NULL, /* soidmacro */ + SLAP_AT_ABSTRACT|SLAP_AT_FINAL, /* mask */ + { NULL }, /* next */ + NULL /* attribute description */ + /* mutex (don't know how to initialize it :) */ +}; + +static AttributeType slap_at_proxied = { + { "1.1.1", NULL, "Catchall for undefined proxied attribute types", 1, NULL, + NULL, NULL, NULL, NULL, + 0, 0, 0, 0, LDAP_SCHEMA_USER_APPLICATIONS, NULL }, /* LDAPAttributeType */ + BER_BVC("PROXIED"), /* cname */ + NULL, /* sup */ + NULL, /* subtypes */ + NULL, NULL, NULL, NULL, /* matching rules routines (will be set later) */ + NULL, /* syntax (will be set later to "octetString") */ + NULL, /* schema check function */ + NULL, /* oidmacro */ + NULL, /* soidmacro */ + SLAP_AT_ABSTRACT|SLAP_AT_FINAL, /* mask */ + { NULL }, /* next */ + NULL /* attribute description */ + /* mutex (don't know how to initialize it :) */ +}; + +static struct slap_schema_mr_map { + char *ssmm_name; + size_t ssmm_offset; +} mr_map[] = { + { "caseExactIA5Match", + offsetof(struct slap_internal_schema, si_mr_caseExactIA5Match) }, + { "caseExactMatch", + offsetof(struct slap_internal_schema, si_mr_caseExactMatch) }, + { "caseExactSubstringsMatch", + offsetof(struct slap_internal_schema, si_mr_caseExactSubstringsMatch) }, + { "distinguishedNameMatch", + offsetof(struct slap_internal_schema, si_mr_distinguishedNameMatch) }, + { "dnSubtreeMatch", + offsetof(struct slap_internal_schema, si_mr_dnSubtreeMatch) }, + { "dnOneLevelMatch", + offsetof(struct slap_internal_schema, si_mr_dnOneLevelMatch) }, + { "dnSubordinateMatch", + offsetof(struct slap_internal_schema, si_mr_dnSubordinateMatch) }, + { "dnSuperiorMatch", + offsetof(struct slap_internal_schema, si_mr_dnSuperiorMatch) }, + { "integerMatch", + offsetof(struct slap_internal_schema, si_mr_integerMatch) }, + { "integerFirstComponentMatch", + offsetof(struct slap_internal_schema, + si_mr_integerFirstComponentMatch) }, + { "objectIdentifierFirstComponentMatch", + offsetof(struct slap_internal_schema, + si_mr_objectIdentifierFirstComponentMatch) }, + { "caseIgnoreMatch", + offsetof(struct slap_internal_schema, si_mr_caseIgnoreMatch) }, + { "caseIgnoreListMatch", + offsetof(struct slap_internal_schema, si_mr_caseIgnoreListMatch) }, + { NULL, 0 } +}; + +static struct slap_schema_syn_map { + char *sssm_name; + size_t sssm_offset; +} syn_map[] = { + { "1.3.6.1.4.1.1466.115.121.1.15", + offsetof(struct slap_internal_schema, si_syn_directoryString) }, + { "1.3.6.1.4.1.1466.115.121.1.12", + offsetof(struct slap_internal_schema, si_syn_distinguishedName) }, + { "1.3.6.1.4.1.1466.115.121.1.27", + offsetof(struct slap_internal_schema, si_syn_integer) }, + { "1.3.6.1.4.1.1466.115.121.1.40", + offsetof(struct slap_internal_schema, si_syn_octetString) }, + { "1.3.6.1.4.1.1466.115.121.1.3", + offsetof(struct slap_internal_schema, si_syn_attributeTypeDesc) }, + { "1.3.6.1.4.1.1466.115.121.1.16", + offsetof(struct slap_internal_schema, si_syn_ditContentRuleDesc) }, + { "1.3.6.1.4.1.1466.115.121.1.54", + offsetof(struct slap_internal_schema, si_syn_ldapSyntaxDesc) }, + { "1.3.6.1.4.1.1466.115.121.1.30", + offsetof(struct slap_internal_schema, si_syn_matchingRuleDesc) }, + { "1.3.6.1.4.1.1466.115.121.1.31", + offsetof(struct slap_internal_schema, si_syn_matchingRuleUseDesc) }, + { "1.3.6.1.4.1.1466.115.121.1.35", + offsetof(struct slap_internal_schema, si_syn_nameFormDesc) }, + { "1.3.6.1.4.1.1466.115.121.1.37", + offsetof(struct slap_internal_schema, si_syn_objectClassDesc) }, + { "1.3.6.1.4.1.1466.115.121.1.17", + offsetof(struct slap_internal_schema, si_syn_ditStructureRuleDesc) }, + { NULL, 0 } +}; + +int +slap_schema_load( void ) +{ + int i; + + for( i=0; syn_map[i].sssm_name; i++ ) { + Syntax ** synp = (Syntax **) + &(((char *) &slap_schema)[syn_map[i].sssm_offset]); + + assert( *synp == NULL ); + + *synp = syn_find( syn_map[i].sssm_name ); + + if( *synp == NULL ) { + fprintf( stderr, "slap_schema_load: Syntax: " + "No syntax \"%s\" defined in schema\n", + syn_map[i].sssm_name ); + return LDAP_INVALID_SYNTAX; + } + } + + for( i=0; mr_map[i].ssmm_name; i++ ) { + MatchingRule ** mrp = (MatchingRule **) + &(((char *) &slap_schema)[mr_map[i].ssmm_offset]); + + assert( *mrp == NULL ); + + *mrp = mr_find( mr_map[i].ssmm_name ); + + if( *mrp == NULL ) { + fprintf( stderr, "slap_schema_load: MatchingRule: " + "No matching rule \"%s\" defined in schema\n", + mr_map[i].ssmm_name ); + return LDAP_INAPPROPRIATE_MATCHING; + } + } + + slap_at_undefined.sat_syntax = slap_schema.si_syn_octetString; + slap_schema.si_at_undefined = &slap_at_undefined; + + slap_at_proxied.sat_equality = mr_find( "octetStringMatch" ); + slap_at_proxied.sat_approx = mr_find( "octetStringMatch" ); + slap_at_proxied.sat_ordering = mr_find( "octetStringOrderingMatch" ); + slap_at_proxied.sat_substr = mr_find( "octetStringSubstringsMatch" ); + slap_at_proxied.sat_syntax = slap_schema.si_syn_octetString; + slap_schema.si_at_proxied = &slap_at_proxied; + + ldap_pvt_thread_mutex_init( &ad_index_mutex ); + ldap_pvt_thread_mutex_init( &ad_undef_mutex ); + ldap_pvt_thread_mutex_init( &oc_undef_mutex ); + + for( i=0; ad_map[i].ssam_name; i++ ) { + assert( ad_map[i].ssam_defn != NULL ); + { + LDAPAttributeType *at; + int code; + const char *err; + + at = ldap_str2attributetype( ad_map[i].ssam_defn, + &code, &err, LDAP_SCHEMA_ALLOW_ALL ); + if ( !at ) { + fprintf( stderr, + "slap_schema_load: AttributeType \"%s\": %s before %s\n", + ad_map[i].ssam_name, ldap_scherr2str(code), err ); + return code; + } + + if ( at->at_oid == NULL ) { + fprintf( stderr, "slap_schema_load: " + "AttributeType \"%s\": no OID\n", + ad_map[i].ssam_name ); + ldap_attributetype_free( at ); + return LDAP_OTHER; + } + + code = at_add( at, 0, NULL, NULL, &err ); + if ( code ) { + ldap_attributetype_free( at ); + fprintf( stderr, "slap_schema_load: AttributeType " + "\"%s\": %s: \"%s\"\n", + ad_map[i].ssam_name, scherr2str(code), err ); + return code; + } + ldap_memfree( at ); + } + { + int rc; + const char *text; + Syntax *syntax = NULL; + + AttributeDescription ** adp = (AttributeDescription **) + &(((char *) &slap_schema)[ad_map[i].ssam_offset]); + + assert( *adp == NULL ); + + rc = slap_str2ad( ad_map[i].ssam_name, adp, &text ); + if( rc != LDAP_SUCCESS ) { + fprintf( stderr, "slap_schema_load: AttributeType \"%s\": " + "not defined in schema\n", + ad_map[i].ssam_name ); + return rc; + } + + if( ad_map[i].ssam_check ) { + /* install check routine */ + (*adp)->ad_type->sat_check = ad_map[i].ssam_check; + } + /* install flags */ + (*adp)->ad_type->sat_flags |= ad_map[i].ssam_flags; + + /* install custom syntax routines */ + if( ad_map[i].ssam_syn_validate || + ad_map[i].ssam_syn_pretty ) + { + Syntax *syn; + + syntax = (*adp)->ad_type->sat_syntax; + + syn = ch_malloc( sizeof( Syntax ) ); + *syn = *syntax; + + if( ad_map[i].ssam_syn_validate ) { + syn->ssyn_validate = ad_map[i].ssam_syn_validate; + } + if( ad_map[i].ssam_syn_pretty ) { + syn->ssyn_pretty = ad_map[i].ssam_syn_pretty; + } + + (*adp)->ad_type->sat_syntax = syn; + } + + /* install custom rule routines */ + if( syntax != NULL || + ad_map[i].ssam_mr_convert || + ad_map[i].ssam_mr_normalize || + ad_map[i].ssam_mr_match || + ad_map[i].ssam_mr_indexer || + ad_map[i].ssam_mr_filter ) + { + MatchingRule *mr = ch_malloc( sizeof( MatchingRule ) ); + *mr = *(*adp)->ad_type->sat_equality; + + if ( syntax != NULL ) { + mr->smr_syntax = (*adp)->ad_type->sat_syntax; + } + if ( ad_map[i].ssam_mr_convert ) { + mr->smr_convert = ad_map[i].ssam_mr_convert; + } + if ( ad_map[i].ssam_mr_normalize ) { + mr->smr_normalize = ad_map[i].ssam_mr_normalize; + } + if ( ad_map[i].ssam_mr_match ) { + mr->smr_match = ad_map[i].ssam_mr_match; + } + if ( ad_map[i].ssam_mr_indexer ) { + mr->smr_indexer = ad_map[i].ssam_mr_indexer; + } + if ( ad_map[i].ssam_mr_filter ) { + mr->smr_filter = ad_map[i].ssam_mr_filter; + } + + (*adp)->ad_type->sat_equality = mr; + } + } + } + + for( i=0; oc_map[i].ssom_name; i++ ) { + assert( oc_map[i].ssom_defn != NULL ); + { + LDAPObjectClass *oc; + int code; + const char *err; + + oc = ldap_str2objectclass( oc_map[i].ssom_defn, &code, &err, + LDAP_SCHEMA_ALLOW_ALL ); + if ( !oc ) { + fprintf( stderr, "slap_schema_load: ObjectClass " + "\"%s\": %s before %s\n", + oc_map[i].ssom_name, ldap_scherr2str(code), err ); + return code; + } + + if ( oc->oc_oid == NULL ) { + fprintf( stderr, "slap_schema_load: ObjectClass " + "\"%s\": no OID\n", + oc_map[i].ssom_name ); + ldap_objectclass_free( oc ); + return LDAP_OTHER; + } + + code = oc_add(oc,0,NULL,NULL,&err); + if ( code ) { + ldap_objectclass_free( oc ); + fprintf( stderr, "slap_schema_load: ObjectClass " + "\"%s\": %s: \"%s\"\n", + oc_map[i].ssom_name, scherr2str(code), err); + return code; + } + ldap_memfree(oc); + + } + { + ObjectClass ** ocp = (ObjectClass **) + &(((char *) &slap_schema)[oc_map[i].ssom_offset]); + + assert( *ocp == NULL ); + + *ocp = oc_find( oc_map[i].ssom_name ); + if( *ocp == NULL ) { + fprintf( stderr, "slap_schema_load: " + "ObjectClass \"%s\": not defined in schema\n", + oc_map[i].ssom_name ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + if( oc_map[i].ssom_check ) { + /* install check routine */ + (*ocp)->soc_check = oc_map[i].ssom_check; + } + /* install flags */ + (*ocp)->soc_flags |= oc_map[i].ssom_flags; + } + } + + return LDAP_SUCCESS; +} + +int +slap_schema_check( void ) +{ + /* we should only be called once after schema_init() was called */ + assert( schema_init_done == 1 ); + + /* + * cycle thru attributeTypes to build matchingRuleUse + */ + if ( matching_rule_use_init() ) { + return LDAP_OTHER; + } + + ++schema_init_done; + return LDAP_SUCCESS; +} + +static int rootDseObjectClass ( + Backend *be, + Entry *e, + ObjectClass *oc, + const char** text, + char *textbuf, size_t textlen ) +{ + *text = textbuf; + + if( e->e_nname.bv_len ) { + snprintf( textbuf, textlen, + "objectClass \"%s\" only allowed in the root DSE", + oc->soc_oid ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + /* we should not be called for the root DSE */ + assert( 0 ); + return LDAP_SUCCESS; +} + +static int aliasObjectClass ( + Backend *be, + Entry *e, + ObjectClass *oc, + const char** text, + char *textbuf, size_t textlen ) +{ + *text = textbuf; + + if( !SLAP_ALIASES(be) ) { + snprintf( textbuf, textlen, + "objectClass \"%s\" not supported in context", + oc->soc_oid ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + return LDAP_SUCCESS; +} + +static int referralObjectClass ( + Backend *be, + Entry *e, + ObjectClass *oc, + const char** text, + char *textbuf, size_t textlen ) +{ + *text = textbuf; + + if( !SLAP_REFERRALS(be) ) { + snprintf( textbuf, textlen, + "objectClass \"%s\" not supported in context", + oc->soc_oid ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + return LDAP_SUCCESS; +} + +static int subentryObjectClass ( + Backend *be, + Entry *e, + ObjectClass *oc, + const char** text, + char *textbuf, size_t textlen ) +{ + *text = textbuf; + + if( !SLAP_SUBENTRIES(be) ) { + snprintf( textbuf, textlen, + "objectClass \"%s\" not supported in context", + oc->soc_oid ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + if( oc != slap_schema.si_oc_subentry && !is_entry_subentry( e ) ) { + snprintf( textbuf, textlen, + "objectClass \"%s\" only allowed in subentries", + oc->soc_oid ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + return LDAP_SUCCESS; +} + +#ifdef LDAP_DYNAMIC_OBJECTS +static int dynamicObjectClass ( + Backend *be, + Entry *e, + ObjectClass *oc, + const char** text, + char *textbuf, size_t textlen ) +{ + *text = textbuf; + + if( !SLAP_DYNAMIC(be) ) { + snprintf( textbuf, textlen, + "objectClass \"%s\" not supported in context", + oc->soc_oid ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + return LDAP_SUCCESS; +} +#endif /* LDAP_DYNAMIC_OBJECTS */ + +static int rootDseAttribute ( + Backend *be, + Entry *e, + Attribute *attr, + const char** text, + char *textbuf, size_t textlen ) +{ + *text = textbuf; + + if( e->e_nname.bv_len ) { + snprintf( textbuf, textlen, + "attribute \"%s\" only allowed in the root DSE", + attr->a_desc->ad_cname.bv_val ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + /* we should not be called for the root DSE */ + assert( 0 ); + return LDAP_SUCCESS; +} + +static int aliasAttribute ( + Backend *be, + Entry *e, + Attribute *attr, + const char** text, + char *textbuf, size_t textlen ) +{ + *text = textbuf; + + if( !SLAP_ALIASES(be) ) { + snprintf( textbuf, textlen, + "attribute \"%s\" not supported in context", + attr->a_desc->ad_cname.bv_val ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + if( !is_entry_alias( e ) ) { + snprintf( textbuf, textlen, + "attribute \"%s\" only allowed in the alias", + attr->a_desc->ad_cname.bv_val ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + return LDAP_SUCCESS; +} + +static int referralAttribute ( + Backend *be, + Entry *e, + Attribute *attr, + const char** text, + char *textbuf, size_t textlen ) +{ + *text = textbuf; + + if( !SLAP_REFERRALS(be) ) { + snprintf( textbuf, textlen, + "attribute \"%s\" not supported in context", + attr->a_desc->ad_cname.bv_val ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + if( !is_entry_referral( e ) ) { + snprintf( textbuf, textlen, + "attribute \"%s\" only allowed in the referral", + attr->a_desc->ad_cname.bv_val ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + return LDAP_SUCCESS; +} + +static int subentryAttribute ( + Backend *be, + Entry *e, + Attribute *attr, + const char** text, + char *textbuf, size_t textlen ) +{ + *text = textbuf; + + if( !SLAP_SUBENTRIES(be) ) { + snprintf( textbuf, textlen, + "attribute \"%s\" not supported in context", + attr->a_desc->ad_cname.bv_val ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + if( !is_entry_subentry( e ) ) { + snprintf( textbuf, textlen, + "attribute \"%s\" only allowed in the subentry", + attr->a_desc->ad_cname.bv_val ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + return LDAP_SUCCESS; +} + +static int administrativeRoleAttribute ( + Backend *be, + Entry *e, + Attribute *attr, + const char** text, + char *textbuf, size_t textlen ) +{ + *text = textbuf; + + if( !SLAP_SUBENTRIES(be) ) { + snprintf( textbuf, textlen, + "attribute \"%s\" not supported in context", + attr->a_desc->ad_cname.bv_val ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + snprintf( textbuf, textlen, + "attribute \"%s\" not supported!", + attr->a_desc->ad_cname.bv_val ); + return LDAP_OBJECT_CLASS_VIOLATION; +} + +#ifdef LDAP_DYNAMIC_OBJECTS +static int dynamicAttribute ( + Backend *be, + Entry *e, + Attribute *attr, + const char** text, + char *textbuf, size_t textlen ) +{ + *text = textbuf; + + if( !SLAP_DYNAMIC(be) ) { + snprintf( textbuf, textlen, + "attribute \"%s\" not supported in context", + attr->a_desc->ad_cname.bv_val ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + if( !is_entry_dynamicObject( e ) ) { + snprintf( textbuf, textlen, + "attribute \"%s\" only allowed in dynamic object", + attr->a_desc->ad_cname.bv_val ); + return LDAP_OBJECT_CLASS_VIOLATION; + } + + return LDAP_SUCCESS; +} +#endif /* LDAP_DYNAMIC_OBJECTS */ diff --git a/servers/slapd/schemaparse.c b/servers/slapd/schemaparse.c new file mode 100644 index 0000000..f7bbcda --- /dev/null +++ b/servers/slapd/schemaparse.c @@ -0,0 +1,400 @@ +/* schemaparse.c - routines to parse config file objectclass definitions */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "ldap_schema.h" +#include "config.h" + +static void oc_usage(void); +static void at_usage(void); + +static char *const err2text[] = { + "Success", + "Out of memory", + "ObjectClass not found", + "user-defined ObjectClass includes operational attributes", + "user-defined ObjectClass has inappropriate SUPerior", + "Duplicate objectClass", + "Inconsistent duplicate objectClass", + "AttributeType not found", + "AttributeType inappropriate matching rule", + "AttributeType inappropriate USAGE", + "AttributeType inappropriate SUPerior", + "AttributeType SYNTAX or SUPerior required", + "Duplicate attributeType", + "Inconsistent duplicate attributeType", + "MatchingRule not found", + "MatchingRule incomplete", + "Duplicate matchingRule", + "Syntax not found", + "Duplicate ldapSyntax", + "Superior syntax not found", + "Substitute syntax not specified", + "Substitute syntax not found", + "OID or name required", + "Qualifier not supported", + "Invalid NAME", + "OID could not be expanded", + "Duplicate Content Rule", + "Content Rule not for STRUCTURAL object class", + "Content Rule AUX contains inappropriate object class", + "Content Rule attribute type list contains duplicate", + NULL +}; + +char * +scherr2str(int code) +{ + if ( code < 0 || SLAP_SCHERR_LAST <= code ) { + return "Unknown error"; + } else { + return err2text[code]; + } +} + +/* check schema descr validity */ +int slap_valid_descr( const char *descr ) +{ + int i=0; + + if( !DESC_LEADCHAR( descr[i] ) ) { + return 0; + } + + while( descr[++i] ) { + if( !DESC_CHAR( descr[i] ) ) { + return 0; + } + } + + return 1; +} + + +/* OID Macros */ + +/* String compare with delimiter check. Return 0 if not + * matched, otherwise return length matched. + */ +int +dscompare(const char *s1, const char *s2, char delim) +{ + const char *orig = s1; + while (*s1++ == *s2++) + if (!s1[-1]) break; + --s1; + --s2; + if (!*s1 && (!*s2 || *s2 == delim)) + return s1 - orig; + return 0; +} + +static void +cr_usage( void ) +{ + fprintf( stderr, + "DITContentRuleDescription = \"(\" whsp\n" + " numericoid whsp ; StructuralObjectClass identifier\n" + " [ \"NAME\" qdescrs ]\n" + " [ \"DESC\" qdstring ]\n" + " [ \"OBSOLETE\" whsp ]\n" + " [ \"AUX\" oids ] ; Auxiliary ObjectClasses\n" + " [ \"MUST\" oids ] ; AttributeTypes\n" + " [ \"MAY\" oids ] ; AttributeTypes\n" + " [ \"NOT\" oids ] ; AttributeTypes\n" + " whsp \")\"\n" ); +} + +int +parse_cr( + struct config_args_s *c, + ContentRule **scr ) +{ + LDAPContentRule *cr; + int code; + const char *err; + char *line = strchr( c->line, '(' ); + + cr = ldap_str2contentrule( line, &code, &err, LDAP_SCHEMA_ALLOW_ALL ); + if ( !cr ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: %s before %s", + c->argv[0], ldap_scherr2str( code ), err ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s %s\n", c->log, c->cr_msg, 0 ); + cr_usage(); + return 1; + } + + if ( cr->cr_oid == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: OID is missing", + c->argv[0] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s %s\n", c->log, c->cr_msg, 0 ); + cr_usage(); + code = 1; + goto done; + } + + code = cr_add( cr, 1, scr, &err ); + if ( code ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: %s: \"%s\"", + c->argv[0], scherr2str(code), err); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s %s\n", c->log, c->cr_msg, 0 ); + code = 1; + goto done; + } + +done:; + if ( code ) { + ldap_contentrule_free( cr ); + + } else { + ldap_memfree( cr ); + } + + return code; +} + +int +parse_oc( + struct config_args_s *c, + ObjectClass **soc, + ObjectClass *prev ) +{ + LDAPObjectClass *oc; + int code; + const char *err; + char *line = strchr( c->line, '(' ); + + oc = ldap_str2objectclass(line, &code, &err, LDAP_SCHEMA_ALLOW_ALL ); + if ( !oc ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: %s before %s", + c->argv[0], ldap_scherr2str( code ), err ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s %s\n", c->log, c->cr_msg, 0 ); + oc_usage(); + return 1; + } + + if ( oc->oc_oid == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: OID is missing", + c->argv[0] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s %s\n", c->log, c->cr_msg, 0 ); + oc_usage(); + code = 1; + goto done; + } + + code = oc_add( oc, 1, soc, prev, &err ); + if ( code ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: %s: \"%s\"", + c->argv[0], scherr2str(code), err); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s %s\n", c->log, c->cr_msg, 0 ); + code = 1; + goto done; + } + +done:; + if ( code ) { + ldap_objectclass_free( oc ); + + } else { + ldap_memfree( oc ); + } + + return code; +} + +static void +oc_usage( void ) +{ + fprintf( stderr, + "ObjectClassDescription = \"(\" whsp\n" + " numericoid whsp ; ObjectClass identifier\n" + " [ \"NAME\" qdescrs ]\n" + " [ \"DESC\" qdstring ]\n" + " [ \"OBSOLETE\" whsp ]\n" + " [ \"SUP\" oids ] ; Superior ObjectClasses\n" + " [ ( \"ABSTRACT\" / \"STRUCTURAL\" / \"AUXILIARY\" ) whsp ]\n" + " ; default structural\n" + " [ \"MUST\" oids ] ; AttributeTypes\n" + " [ \"MAY\" oids ] ; AttributeTypes\n" + " whsp \")\"\n" ); +} + +static void +at_usage( void ) +{ + fprintf( stderr, "%s%s%s", + "AttributeTypeDescription = \"(\" whsp\n" + " numericoid whsp ; AttributeType identifier\n" + " [ \"NAME\" qdescrs ] ; name used in AttributeType\n" + " [ \"DESC\" qdstring ] ; description\n" + " [ \"OBSOLETE\" whsp ]\n" + " [ \"SUP\" woid ] ; derived from this other\n" + " ; AttributeType\n", + " [ \"EQUALITY\" woid ] ; Matching Rule name\n" + " [ \"ORDERING\" woid ] ; Matching Rule name\n" + " [ \"SUBSTR\" woid ] ; Matching Rule name\n" + " [ \"SYNTAX\" whsp noidlen whsp ] ; see section 4.3\n" + " [ \"SINGLE-VALUE\" whsp ] ; default multi-valued\n" + " [ \"COLLECTIVE\" whsp ] ; default not collective\n", + " [ \"NO-USER-MODIFICATION\" whsp ]; default user modifiable\n" + " [ \"USAGE\" whsp AttributeUsage ]; default userApplications\n" + " ; userApplications\n" + " ; directoryOperation\n" + " ; distributedOperation\n" + " ; dSAOperation\n" + " whsp \")\"\n"); +} + +int +parse_at( + struct config_args_s *c, + AttributeType **sat, + AttributeType *prev ) +{ + LDAPAttributeType *at; + int code; + const char *err; + char *line = strchr( c->line, '(' ); + + at = ldap_str2attributetype( line, &code, &err, LDAP_SCHEMA_ALLOW_ALL ); + if ( !at ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: %s before %s", + c->argv[0], ldap_scherr2str(code), err ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s %s\n", c->log, c->cr_msg, 0 ); + at_usage(); + return 1; + } + + if ( at->at_oid == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: OID is missing", + c->argv[0] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s %s\n", c->log, c->cr_msg, 0 ); + at_usage(); + code = 1; + goto done; + } + + /* operational attributes should be defined internally */ + if ( at->at_usage ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: \"%s\" is operational", + c->argv[0], at->at_oid ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s %s\n", c->log, c->cr_msg, 0 ); + code = 1; + goto done; + } + + code = at_add( at, 1, sat, prev, &err); + if ( code ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: %s: \"%s\"", + c->argv[0], scherr2str(code), err); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s %s\n", c->log, c->cr_msg, 0 ); + code = 1; + goto done; + } + +done:; + if ( code ) { + ldap_attributetype_free( at ); + + } else { + ldap_memfree( at ); + } + + return code; +} + +static void +syn_usage( void ) +{ + fprintf( stderr, "%s", + "SyntaxDescription = \"(\" whsp\n" + " numericoid whsp ; object identifier\n" + " [ whsp \"DESC\" whsp qdstring ] ; description\n" + " extensions whsp \")\" ; extensions\n" + " whsp \")\"\n"); +} + +int +parse_syn( + struct config_args_s *c, + Syntax **ssyn, + Syntax *prev ) +{ + LDAPSyntax *syn; + slap_syntax_defs_rec def = { 0 }; + int code; + const char *err; + char *line = strchr( c->line, '(' ); + + syn = ldap_str2syntax( line, &code, &err, LDAP_SCHEMA_ALLOW_ALL ); + if ( !syn ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: %s before %s", + c->argv[0], ldap_scherr2str(code), err ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s %s\n", c->log, c->cr_msg, 0 ); + syn_usage(); + return 1; + } + + if ( syn->syn_oid == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: OID is missing", + c->argv[0] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s %s\n", c->log, c->cr_msg, 0 ); + syn_usage(); + code = 1; + goto done; + } + + code = syn_add( syn, 1, &def, ssyn, prev, &err ); + if ( code ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: %s: \"%s\"", + c->argv[0], scherr2str(code), err); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s %s\n", c->log, c->cr_msg, 0 ); + code = 1; + goto done; + } + +done:; + if ( code ) { + ldap_syntax_free( syn ); + + } else { + ldap_memfree( syn ); + } + + return code; +} + diff --git a/servers/slapd/search.c b/servers/slapd/search.c new file mode 100644 index 0000000..57b87a6 --- /dev/null +++ b/servers/slapd/search.c @@ -0,0 +1,415 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "lutil.h" +#include "slap.h" + +int +do_search( + Operation *op, /* info about the op to which we're responding */ + SlapReply *rs /* all the response data we'll send */ ) +{ + struct berval base = BER_BVNULL; + ber_len_t siz, off, i; + + Debug( LDAP_DEBUG_TRACE, "%s do_search\n", + op->o_log_prefix, 0, 0 ); + /* + * Parse the search request. It looks like this: + * + * SearchRequest := [APPLICATION 3] SEQUENCE { + * baseObject DistinguishedName, + * scope ENUMERATED { + * baseObject (0), + * singleLevel (1), + * wholeSubtree (2), + * subordinate (3) -- OpenLDAP extension + * }, + * derefAliases ENUMERATED { + * neverDerefaliases (0), + * derefInSearching (1), + * derefFindingBaseObj (2), + * alwaysDerefAliases (3) + * }, + * sizelimit INTEGER (0 .. 65535), + * timelimit INTEGER (0 .. 65535), + * attrsOnly BOOLEAN, + * filter Filter, + * attributes SEQUENCE OF AttributeType + * } + */ + + /* baseObject, scope, derefAliases, sizelimit, timelimit, attrsOnly */ + if ( ber_scanf( op->o_ber, "{miiiib" /*}*/, + &base, &op->ors_scope, &op->ors_deref, &op->ors_slimit, + &op->ors_tlimit, &op->ors_attrsonly ) == LBER_ERROR ) + { + send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" ); + rs->sr_err = SLAPD_DISCONNECT; + goto return_results; + } + + if ( op->ors_tlimit < 0 || op->ors_tlimit > SLAP_MAX_LIMIT ) { + send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR, "invalid time limit" ); + goto return_results; + } + + if ( op->ors_slimit < 0 || op->ors_slimit > SLAP_MAX_LIMIT ) { + send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR, "invalid size limit" ); + goto return_results; + } + + switch( op->ors_scope ) { + case LDAP_SCOPE_BASE: + case LDAP_SCOPE_ONELEVEL: + case LDAP_SCOPE_SUBTREE: + case LDAP_SCOPE_SUBORDINATE: + break; + default: + send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR, "invalid scope" ); + goto return_results; + } + + switch( op->ors_deref ) { + case LDAP_DEREF_NEVER: + case LDAP_DEREF_FINDING: + case LDAP_DEREF_SEARCHING: + case LDAP_DEREF_ALWAYS: + break; + default: + send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR, "invalid deref" ); + goto return_results; + } + + rs->sr_err = dnPrettyNormal( NULL, &base, &op->o_req_dn, &op->o_req_ndn, op->o_tmpmemctx ); + if( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s do_search: invalid dn: \"%s\"\n", + op->o_log_prefix, base.bv_val, 0 ); + send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid DN" ); + goto return_results; + } + + Debug( LDAP_DEBUG_ARGS, "SRCH \"%s\" %d %d", + base.bv_val, op->ors_scope, op->ors_deref ); + Debug( LDAP_DEBUG_ARGS, " %d %d %d\n", + op->ors_slimit, op->ors_tlimit, op->ors_attrsonly); + + /* filter - returns a "normalized" version */ + rs->sr_err = get_filter( op, op->o_ber, &op->ors_filter, &rs->sr_text ); + if( rs->sr_err != LDAP_SUCCESS ) { + if( rs->sr_err == SLAPD_DISCONNECT ) { + rs->sr_err = LDAP_PROTOCOL_ERROR; + send_ldap_disconnect( op, rs ); + rs->sr_err = SLAPD_DISCONNECT; + } else { + send_ldap_result( op, rs ); + } + goto return_results; + } + filter2bv_x( op, op->ors_filter, &op->ors_filterstr ); + + Debug( LDAP_DEBUG_ARGS, " filter: %s\n", + !BER_BVISEMPTY( &op->ors_filterstr ) ? op->ors_filterstr.bv_val : "empty", 0, 0 ); + + /* attributes */ + siz = sizeof(AttributeName); + off = offsetof(AttributeName,an_name); + if ( ber_scanf( op->o_ber, "{M}}", &op->ors_attrs, &siz, off ) == LBER_ERROR ) { + send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding attrs error" ); + rs->sr_err = SLAPD_DISCONNECT; + goto return_results; + } + for ( i=0; i<siz; i++ ) { + const char *dummy; /* ignore msgs from bv2ad */ + op->ors_attrs[i].an_desc = NULL; + op->ors_attrs[i].an_oc = NULL; + op->ors_attrs[i].an_flags = 0; + if ( slap_bv2ad( &op->ors_attrs[i].an_name, + &op->ors_attrs[i].an_desc, &dummy ) != LDAP_SUCCESS ) + { + if ( slap_bv2undef_ad( &op->ors_attrs[i].an_name, + &op->ors_attrs[i].an_desc, &dummy, + SLAP_AD_PROXIED|SLAP_AD_NOINSERT ) ) + { + struct berval *bv = &op->ors_attrs[i].an_name; + + /* RFC 4511 LDAPv3: All User Attributes */ + if ( bvmatch( bv, slap_bv_all_user_attrs ) ) { + continue; + } + + /* RFC 3673 LDAPv3: All Operational Attributes */ + if ( bvmatch( bv, slap_bv_all_operational_attrs ) ) { + continue; + } + + /* RFC 4529 LDAP: Requesting Attributes by Object Class */ + if ( bv->bv_len > 1 && bv->bv_val[0] == '@' ) { + /* FIXME: check if remaining is valid oc name? */ + continue; + } + + /* add more "exceptions" to RFC 4511 4.5.1.8. */ + + /* invalid attribute description? remove */ + if ( ad_keystring( bv ) ) { + /* NOTE: parsed in-place, don't modify; + * rather add "1.1", which must be ignored */ + BER_BVSTR( &op->ors_attrs[i].an_name, LDAP_NO_ATTRS ); + } + + /* otherwise leave in place... */ + } + } + } + + if( get_ctrls( op, rs, 1 ) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s do_search: get_ctrls failed\n", + op->o_log_prefix, 0, 0 ); + goto return_results; + } + + Debug( LDAP_DEBUG_ARGS, " attrs:", 0, 0, 0 ); + + if ( siz != 0 ) { + for ( i = 0; i<siz; i++ ) { + Debug( LDAP_DEBUG_ARGS, " %s", op->ors_attrs[i].an_name.bv_val, 0, 0 ); + } + } + + Debug( LDAP_DEBUG_ARGS, "\n", 0, 0, 0 ); + + if ( StatslogTest( LDAP_DEBUG_STATS ) ) { + char abuf[BUFSIZ/2], *ptr = abuf; + unsigned len = 0, alen; + + sprintf(abuf, "scope=%d deref=%d", op->ors_scope, op->ors_deref); + Statslog( LDAP_DEBUG_STATS, + "%s SRCH base=\"%s\" %s filter=\"%s\"\n", + op->o_log_prefix, op->o_req_dn.bv_val, abuf, + op->ors_filterstr.bv_val, 0 ); + + for ( i = 0; i<siz; i++ ) { + alen = op->ors_attrs[i].an_name.bv_len; + if (alen >= sizeof(abuf)) { + alen = sizeof(abuf)-1; + } + if (len && (len + 1 + alen >= sizeof(abuf))) { + Statslog( LDAP_DEBUG_STATS, "%s SRCH attr=%s\n", + op->o_log_prefix, abuf, 0, 0, 0 ); + len = 0; + ptr = abuf; + } + if (len) { + *ptr++ = ' '; + len++; + } + ptr = lutil_strncopy(ptr, op->ors_attrs[i].an_name.bv_val, alen); + len += alen; + *ptr = '\0'; + } + if (len) { + Statslog( LDAP_DEBUG_STATS, "%s SRCH attr=%s\n", + op->o_log_prefix, abuf, 0, 0, 0 ); + } + } + + op->o_bd = frontendDB; + rs->sr_err = frontendDB->be_search( op, rs ); + +return_results:; + if ( !BER_BVISNULL( &op->o_req_dn ) ) { + slap_sl_free( op->o_req_dn.bv_val, op->o_tmpmemctx ); + } + if ( !BER_BVISNULL( &op->o_req_ndn ) ) { + slap_sl_free( op->o_req_ndn.bv_val, op->o_tmpmemctx ); + } + if ( !BER_BVISNULL( &op->ors_filterstr ) ) { + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + } + if ( op->ors_filter != NULL) { + filter_free_x( op, op->ors_filter, 1 ); + } + if ( op->ors_attrs != NULL ) { + op->o_tmpfree( op->ors_attrs, op->o_tmpmemctx ); + } + + return rs->sr_err; +} + +int +fe_op_search( Operation *op, SlapReply *rs ) +{ + BackendDB *bd = op->o_bd; + + if ( op->ors_scope == LDAP_SCOPE_BASE ) { + Entry *entry = NULL; + + if ( BER_BVISEMPTY( &op->o_req_ndn ) ) { +#ifdef LDAP_CONNECTIONLESS + /* Ignore LDAPv2 CLDAP Root DSE queries */ + if (op->o_protocol == LDAP_VERSION2 && op->o_conn->c_is_udp) { + goto return_results; + } +#endif + /* check restrictions */ + if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto return_results; + } + + rs->sr_err = root_dse_info( op->o_conn, &entry, &rs->sr_text ); + + } else if ( bvmatch( &op->o_req_ndn, &frontendDB->be_schemandn ) ) { + /* check restrictions */ + if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto return_results; + } + + rs->sr_err = schema_info( &entry, &rs->sr_text ); + } + + if( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto return_results; + + } else if ( entry != NULL ) { + if ( get_assert( op ) && + ( test_filter( op, entry, get_assertion( op )) != LDAP_COMPARE_TRUE )) { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto fail1; + } + + rs->sr_err = test_filter( op, entry, op->ors_filter ); + + if( rs->sr_err == LDAP_COMPARE_TRUE ) { + /* note: we set no limits because either + * no limit is specified, or at least 1 + * is specified, and we're going to return + * at most one entry */ + op->ors_slimit = SLAP_NO_LIMIT; + op->ors_tlimit = SLAP_NO_LIMIT; + + rs->sr_entry = entry; + rs->sr_attrs = op->ors_attrs; + rs->sr_operational_attrs = NULL; + rs->sr_flags = 0; + send_search_entry( op, rs ); + rs->sr_entry = NULL; + rs->sr_operational_attrs = NULL; + } + rs->sr_err = LDAP_SUCCESS; +fail1: + entry_free( entry ); + send_ldap_result( op, rs ); + goto return_results; + } + } + + if( BER_BVISEMPTY( &op->o_req_ndn ) && !BER_BVISEMPTY( &default_search_nbase ) ) { + slap_sl_free( op->o_req_dn.bv_val, op->o_tmpmemctx ); + slap_sl_free( op->o_req_ndn.bv_val, op->o_tmpmemctx ); + + ber_dupbv_x( &op->o_req_dn, &default_search_base, op->o_tmpmemctx ); + ber_dupbv_x( &op->o_req_ndn, &default_search_nbase, op->o_tmpmemctx ); + } + + /* + * We could be serving multiple database backends. Select the + * appropriate one, or send a referral to our "referral server" + * if we don't hold it. + */ + + op->o_bd = select_backend( &op->o_req_ndn, 1 ); + if ( op->o_bd == NULL ) { + rs->sr_ref = referral_rewrite( default_referral, + NULL, &op->o_req_dn, op->ors_scope ); + + if (!rs->sr_ref) rs->sr_ref = default_referral; + rs->sr_err = LDAP_REFERRAL; + op->o_bd = bd; + send_ldap_result( op, rs ); + + if (rs->sr_ref != default_referral) + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + goto return_results; + } + + /* check restrictions */ + if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto return_results; + } + + /* check for referrals */ + if( backend_check_referrals( op, rs ) != LDAP_SUCCESS ) { + goto return_results; + } + + if ( SLAP_SHADOW(op->o_bd) && get_dontUseCopy(op) ) { + /* don't use shadow copy */ + BerVarray defref = op->o_bd->be_update_refs + ? op->o_bd->be_update_refs : default_referral; + + if( defref != NULL ) { + rs->sr_ref = referral_rewrite( defref, + NULL, &op->o_req_dn, op->ors_scope ); + if( !rs->sr_ref) rs->sr_ref = defref; + rs->sr_err = LDAP_REFERRAL; + send_ldap_result( op, rs ); + + if (rs->sr_ref != defref) ber_bvarray_free( rs->sr_ref ); + + } else { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "copy not used; no referral information available" ); + } + + } else if ( op->o_bd->be_search ) { + if ( limits_check( op, rs ) == 0 ) { + /* actually do the search and send the result(s) */ + (op->o_bd->be_search)( op, rs ); + } + /* else limits_check() sends error */ + + } else { + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "operation not supported within namingContext" ); + } + +return_results:; + op->o_bd = bd; + return rs->sr_err; +} + diff --git a/servers/slapd/sets.c b/servers/slapd/sets.c new file mode 100644 index 0000000..6723aa8 --- /dev/null +++ b/servers/slapd/sets.c @@ -0,0 +1,832 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "slap.h" +#include "sets.h" + +static BerVarray set_chase( SLAP_SET_GATHER gatherer, + SetCookie *cookie, BerVarray set, AttributeDescription *desc, int closure ); + +/* Count the array members */ +static long +slap_set_size( BerVarray set ) +{ + long i = 0; + + if ( set != NULL ) { + while ( !BER_BVISNULL( &set[ i ] ) ) { + i++; + } + } + + return i; +} + +/* Return 0 if there is at least one array member, non-zero otherwise */ +static int +slap_set_isempty( BerVarray set ) +{ + if ( set == NULL ) { + return 1; + } + + if ( !BER_BVISNULL( &set[ 0 ] ) ) { + return 0; + } + + return 1; +} + +/* Dispose of the contents of the array and the array itself according + * to the flags value. If SLAP_SET_REFVAL, don't dispose of values; + * if SLAP_SET_REFARR, don't dispose of the array itself. In case of + * binary operators, there are LEFT flags and RIGHT flags, referring to + * the first and the second operator arguments, respectively. In this + * case, flags must be transformed using macros SLAP_SET_LREF2REF() and + * SLAP_SET_RREF2REF() before calling this function. + */ +static void +slap_set_dispose( SetCookie *cp, BerVarray set, unsigned flags ) +{ + if ( flags & SLAP_SET_REFVAL ) { + if ( ! ( flags & SLAP_SET_REFARR ) ) { + cp->set_op->o_tmpfree( set, cp->set_op->o_tmpmemctx ); + } + + } else { + ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx ); + } +} + +/* Duplicate a set. If SLAP_SET_REFARR, is not set, the original array + * with the original values is returned, otherwise the array is duplicated; + * if SLAP_SET_REFVAL is set, also the values are duplicated. + */ +static BerVarray +set_dup( SetCookie *cp, BerVarray set, unsigned flags ) +{ + BerVarray newset = NULL; + + if ( set == NULL ) { + return NULL; + } + + if ( flags & SLAP_SET_REFARR ) { + int i; + + for ( i = 0; !BER_BVISNULL( &set[ i ] ); i++ ) + ; + newset = cp->set_op->o_tmpcalloc( i + 1, + sizeof( struct berval ), + cp->set_op->o_tmpmemctx ); + if ( newset == NULL ) { + return NULL; + } + + if ( flags & SLAP_SET_REFVAL ) { + for ( i = 0; !BER_BVISNULL( &set[ i ] ); i++ ) { + ber_dupbv_x( &newset[ i ], &set[ i ], + cp->set_op->o_tmpmemctx ); + } + + } else { + AC_MEMCPY( newset, set, ( i + 1 ) * sizeof( struct berval ) ); + } + + } else { + newset = set; + } + + return newset; +} + +/* Join two sets according to operator op and flags op_flags. + * op can be: + * '|' (or): the union between the two sets is returned, + * eliminating duplicates + * '&' (and): the intersection between the two sets + * is returned + * '+' (add): the inner product of the two sets is returned, + * namely a set containing the concatenation of + * all combinations of the two sets members, + * except for duplicates. + * The two sets are disposed of according to the flags as described + * for slap_set_dispose(). + */ +BerVarray +slap_set_join( + SetCookie *cp, + BerVarray lset, + unsigned op_flags, + BerVarray rset ) +{ + BerVarray set; + long i, j, last, rlast; + unsigned op = ( op_flags & SLAP_SET_OPMASK ); + + set = NULL; + switch ( op ) { + case '|': /* union */ + if ( lset == NULL || BER_BVISNULL( &lset[ 0 ] ) ) { + if ( rset == NULL ) { + if ( lset == NULL ) { + set = cp->set_op->o_tmpcalloc( 1, + sizeof( struct berval ), + cp->set_op->o_tmpmemctx ); + BER_BVZERO( &set[ 0 ] ); + goto done2; + } + set = set_dup( cp, lset, SLAP_SET_LREF2REF( op_flags ) ); + goto done2; + } + slap_set_dispose( cp, lset, SLAP_SET_LREF2REF( op_flags ) ); + set = set_dup( cp, rset, SLAP_SET_RREF2REF( op_flags ) ); + goto done2; + } + if ( rset == NULL || BER_BVISNULL( &rset[ 0 ] ) ) { + slap_set_dispose( cp, rset, SLAP_SET_RREF2REF( op_flags ) ); + set = set_dup( cp, lset, SLAP_SET_LREF2REF( op_flags ) ); + goto done2; + } + + /* worst scenario: no duplicates */ + rlast = slap_set_size( rset ); + i = slap_set_size( lset ) + rlast + 1; + set = cp->set_op->o_tmpcalloc( i, sizeof( struct berval ), cp->set_op->o_tmpmemctx ); + if ( set != NULL ) { + /* set_chase() depends on this routine to + * keep the first elements of the result + * set the same (and in the same order) + * as the left-set. + */ + for ( i = 0; !BER_BVISNULL( &lset[ i ] ); i++ ) { + if ( op_flags & SLAP_SET_LREFVAL ) { + ber_dupbv_x( &set[ i ], &lset[ i ], cp->set_op->o_tmpmemctx ); + + } else { + set[ i ] = lset[ i ]; + } + } + + /* pointers to values have been used in set - don't free twice */ + op_flags |= SLAP_SET_LREFVAL; + + last = i; + + for ( i = 0; !BER_BVISNULL( &rset[ i ] ); i++ ) { + int exists = 0; + + for ( j = 0; !BER_BVISNULL( &set[ j ] ); j++ ) { + if ( bvmatch( &rset[ i ], &set[ j ] ) ) + { + if ( !( op_flags & SLAP_SET_RREFVAL ) ) { + cp->set_op->o_tmpfree( rset[ i ].bv_val, cp->set_op->o_tmpmemctx ); + rset[ i ] = rset[ --rlast ]; + BER_BVZERO( &rset[ rlast ] ); + i--; + } + exists = 1; + break; + } + } + + if ( !exists ) { + if ( op_flags & SLAP_SET_RREFVAL ) { + ber_dupbv_x( &set[ last ], &rset[ i ], cp->set_op->o_tmpmemctx ); + + } else { + set[ last ] = rset[ i ]; + } + last++; + } + } + + /* pointers to values have been used in set - don't free twice */ + op_flags |= SLAP_SET_RREFVAL; + + BER_BVZERO( &set[ last ] ); + } + break; + + case '&': /* intersection */ + if ( lset == NULL || BER_BVISNULL( &lset[ 0 ] ) + || rset == NULL || BER_BVISNULL( &rset[ 0 ] ) ) + { + set = cp->set_op->o_tmpcalloc( 1, sizeof( struct berval ), + cp->set_op->o_tmpmemctx ); + BER_BVZERO( &set[ 0 ] ); + break; + + } else { + long llen, rlen; + BerVarray sset; + + llen = slap_set_size( lset ); + rlen = slap_set_size( rset ); + + /* dup the shortest */ + if ( llen < rlen ) { + last = llen; + set = set_dup( cp, lset, SLAP_SET_LREF2REF( op_flags ) ); + lset = NULL; + sset = rset; + + } else { + last = rlen; + set = set_dup( cp, rset, SLAP_SET_RREF2REF( op_flags ) ); + rset = NULL; + sset = lset; + } + + if ( set == NULL ) { + break; + } + + for ( i = 0; !BER_BVISNULL( &set[ i ] ); i++ ) { + for ( j = 0; !BER_BVISNULL( &sset[ j ] ); j++ ) { + if ( bvmatch( &set[ i ], &sset[ j ] ) ) { + break; + } + } + + if ( BER_BVISNULL( &sset[ j ] ) ) { + cp->set_op->o_tmpfree( set[ i ].bv_val, cp->set_op->o_tmpmemctx ); + set[ i ] = set[ --last ]; + BER_BVZERO( &set[ last ] ); + i--; + } + } + } + break; + + case '+': /* string concatenation */ + i = slap_set_size( rset ); + j = slap_set_size( lset ); + + /* handle empty set cases */ + if ( i == 0 || j == 0 ) { + set = cp->set_op->o_tmpcalloc( 1, sizeof( struct berval ), + cp->set_op->o_tmpmemctx ); + if ( set == NULL ) { + break; + } + BER_BVZERO( &set[ 0 ] ); + break; + } + + set = cp->set_op->o_tmpcalloc( i * j + 1, sizeof( struct berval ), + cp->set_op->o_tmpmemctx ); + if ( set == NULL ) { + break; + } + + for ( last = 0, i = 0; !BER_BVISNULL( &lset[ i ] ); i++ ) { + for ( j = 0; !BER_BVISNULL( &rset[ j ] ); j++ ) { + struct berval bv; + long k; + + /* don't concatenate with the empty string */ + if ( BER_BVISEMPTY( &lset[ i ] ) ) { + ber_dupbv_x( &bv, &rset[ j ], cp->set_op->o_tmpmemctx ); + if ( bv.bv_val == NULL ) { + ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx ); + set = NULL; + goto done; + } + + } else if ( BER_BVISEMPTY( &rset[ j ] ) ) { + ber_dupbv_x( &bv, &lset[ i ], cp->set_op->o_tmpmemctx ); + if ( bv.bv_val == NULL ) { + ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx ); + set = NULL; + goto done; + } + + } else { + bv.bv_len = lset[ i ].bv_len + rset[ j ].bv_len; + bv.bv_val = cp->set_op->o_tmpalloc( bv.bv_len + 1, + cp->set_op->o_tmpmemctx ); + if ( bv.bv_val == NULL ) { + ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx ); + set = NULL; + goto done; + } + AC_MEMCPY( bv.bv_val, lset[ i ].bv_val, lset[ i ].bv_len ); + AC_MEMCPY( &bv.bv_val[ lset[ i ].bv_len ], rset[ j ].bv_val, rset[ j ].bv_len ); + bv.bv_val[ bv.bv_len ] = '\0'; + } + + for ( k = 0; k < last; k++ ) { + if ( bvmatch( &set[ k ], &bv ) ) { + cp->set_op->o_tmpfree( bv.bv_val, cp->set_op->o_tmpmemctx ); + break; + } + } + + if ( k == last ) { + set[ last++ ] = bv; + } + } + } + BER_BVZERO( &set[ last ] ); + break; + + default: + break; + } + +done:; + if ( lset ) slap_set_dispose( cp, lset, SLAP_SET_LREF2REF( op_flags ) ); + if ( rset ) slap_set_dispose( cp, rset, SLAP_SET_RREF2REF( op_flags ) ); + +done2:; + if ( LogTest( LDAP_DEBUG_ACL ) ) { + if ( !set || BER_BVISNULL( set ) ) { + Debug( LDAP_DEBUG_ACL, " ACL set: empty\n", 0, 0, 0 ); + + } else { + for ( i = 0; !BER_BVISNULL( &set[ i ] ); i++ ) { + Debug( LDAP_DEBUG_ACL, " ACL set[%ld]=%s\n", i, set[i].bv_val, 0 ); + } + } + } + + return set; +} + +static BerVarray +set_chase( SLAP_SET_GATHER gatherer, + SetCookie *cp, BerVarray set, AttributeDescription *desc, int closure ) +{ + BerVarray vals, nset; + int i; + + if ( set == NULL ) { + set = cp->set_op->o_tmpcalloc( 1, sizeof( struct berval ), + cp->set_op->o_tmpmemctx ); + if ( set != NULL ) { + BER_BVZERO( &set[ 0 ] ); + } + return set; + } + + if ( BER_BVISNULL( set ) ) { + return set; + } + + nset = cp->set_op->o_tmpcalloc( 1, sizeof( struct berval ), cp->set_op->o_tmpmemctx ); + if ( nset == NULL ) { + ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx ); + return NULL; + } + for ( i = 0; !BER_BVISNULL( &set[ i ] ); i++ ) { + vals = gatherer( cp, &set[ i ], desc ); + if ( vals != NULL ) { + nset = slap_set_join( cp, nset, '|', vals ); + } + } + ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx ); + + if ( closure ) { + for ( i = 0; !BER_BVISNULL( &nset[ i ] ); i++ ) { + vals = gatherer( cp, &nset[ i ], desc ); + if ( vals != NULL ) { + nset = slap_set_join( cp, nset, '|', vals ); + if ( nset == NULL ) { + break; + } + } + } + } + + return nset; +} + + +static BerVarray +set_parents( SetCookie *cp, BerVarray set ) +{ + int i, j, last; + struct berval bv, pbv; + BerVarray nset, vals; + + if ( set == NULL ) { + set = cp->set_op->o_tmpcalloc( 1, sizeof( struct berval ), + cp->set_op->o_tmpmemctx ); + if ( set != NULL ) { + BER_BVZERO( &set[ 0 ] ); + } + return set; + } + + if ( BER_BVISNULL( &set[ 0 ] ) ) { + return set; + } + + nset = cp->set_op->o_tmpcalloc( 1, sizeof( struct berval ), cp->set_op->o_tmpmemctx ); + if ( nset == NULL ) { + ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx ); + return NULL; + } + + BER_BVZERO( &nset[ 0 ] ); + + for ( i = 0; !BER_BVISNULL( &set[ i ] ); i++ ) { + int level = 1; + + pbv = bv = set[ i ]; + for ( ; !BER_BVISEMPTY( &pbv ); dnParent( &bv, &pbv ) ) { + level++; + bv = pbv; + } + + vals = cp->set_op->o_tmpcalloc( level + 1, sizeof( struct berval ), cp->set_op->o_tmpmemctx ); + if ( vals == NULL ) { + ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx ); + ber_bvarray_free_x( nset, cp->set_op->o_tmpmemctx ); + return NULL; + } + BER_BVZERO( &vals[ 0 ] ); + last = 0; + + bv = set[ i ]; + for ( j = 0 ; j < level ; j++ ) { + ber_dupbv_x( &vals[ last ], &bv, cp->set_op->o_tmpmemctx ); + last++; + dnParent( &bv, &bv ); + } + BER_BVZERO( &vals[ last ] ); + + nset = slap_set_join( cp, nset, '|', vals ); + } + + ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx ); + + return nset; +} + + + +static BerVarray +set_parent( SetCookie *cp, BerVarray set, int level ) +{ + int i, j, last; + struct berval bv; + BerVarray nset; + + if ( set == NULL ) { + set = cp->set_op->o_tmpcalloc( 1, sizeof( struct berval ), + cp->set_op->o_tmpmemctx ); + if ( set != NULL ) { + BER_BVZERO( &set[ 0 ] ); + } + return set; + } + + if ( BER_BVISNULL( &set[ 0 ] ) ) { + return set; + } + + nset = cp->set_op->o_tmpcalloc( slap_set_size( set ) + 1, sizeof( struct berval ), cp->set_op->o_tmpmemctx ); + if ( nset == NULL ) { + ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx ); + return NULL; + } + + BER_BVZERO( &nset[ 0 ] ); + last = 0; + + for ( i = 0; !BER_BVISNULL( &set[ i ] ); i++ ) { + bv = set[ i ]; + + for ( j = 0 ; j < level ; j++ ) { + dnParent( &bv, &bv ); + } + + for ( j = 0; !BER_BVISNULL( &nset[ j ] ); j++ ) { + if ( bvmatch( &bv, &nset[ j ] ) ) + { + break; + } + } + + if ( BER_BVISNULL( &nset[ j ] ) ) { + ber_dupbv_x( &nset[ last ], &bv, cp->set_op->o_tmpmemctx ); + last++; + } + } + + BER_BVZERO( &nset[ last ] ); + + ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx ); + + return nset; +} + +int +slap_set_filter( SLAP_SET_GATHER gatherer, + SetCookie *cp, struct berval *fbv, + struct berval *user, struct berval *target, BerVarray *results ) +{ +#define STACK_SIZE 64 +#define IS_SET(x) ( (unsigned long)(x) >= 256 ) +#define IS_OP(x) ( (unsigned long)(x) < 256 ) +#define SF_ERROR(x) do { rc = -1; goto _error; } while ( 0 ) +#define SF_TOP() ( (BerVarray)( ( stp < 0 ) ? 0 : stack[ stp ] ) ) +#define SF_POP() ( (BerVarray)( ( stp < 0 ) ? 0 : stack[ stp-- ] ) ) +#define SF_PUSH(x) do { \ + if ( stp >= ( STACK_SIZE - 1 ) ) SF_ERROR( overflow ); \ + stack[ ++stp ] = (BerVarray)(long)(x); \ + } while ( 0 ) + + BerVarray set, lset; + BerVarray stack[ STACK_SIZE ] = { 0 }; + int len, rc, stp; + unsigned long op; + char c, *filter = fbv->bv_val; + + if ( results ) { + *results = NULL; + } + + stp = -1; + while ( ( c = *filter++ ) ) { + set = NULL; + switch ( c ) { + case ' ': + case '\t': + case '\x0A': + case '\x0D': + break; + + case '(' /* ) */ : + if ( IS_SET( SF_TOP() ) ) { + SF_ERROR( syntax ); + } + SF_PUSH( c ); + break; + + case /* ( */ ')': + set = SF_POP(); + if ( IS_OP( set ) ) { + SF_ERROR( syntax ); + } + if ( SF_TOP() == (void *)'(' /* ) */ ) { + SF_POP(); + SF_PUSH( set ); + set = NULL; + + } else if ( IS_OP( SF_TOP() ) ) { + op = (unsigned long)SF_POP(); + lset = SF_POP(); + SF_POP(); + set = slap_set_join( cp, lset, op, set ); + if ( set == NULL ) { + SF_ERROR( memory ); + } + SF_PUSH( set ); + set = NULL; + + } else { + SF_ERROR( syntax ); + } + break; + + case '|': /* union */ + case '&': /* intersection */ + case '+': /* string concatenation */ + set = SF_POP(); + if ( IS_OP( set ) ) { + SF_ERROR( syntax ); + } + if ( SF_TOP() == 0 || SF_TOP() == (void *)'(' /* ) */ ) { + SF_PUSH( set ); + set = NULL; + + } else if ( IS_OP( SF_TOP() ) ) { + op = (unsigned long)SF_POP(); + lset = SF_POP(); + set = slap_set_join( cp, lset, op, set ); + if ( set == NULL ) { + SF_ERROR( memory ); + } + SF_PUSH( set ); + set = NULL; + + } else { + SF_ERROR( syntax ); + } + SF_PUSH( c ); + break; + + case '[' /* ] */: + if ( ( SF_TOP() == (void *)'/' ) || IS_SET( SF_TOP() ) ) { + SF_ERROR( syntax ); + } + for ( len = 0; ( c = *filter++ ) && ( c != /* [ */ ']' ); len++ ) + ; + if ( c == 0 ) { + SF_ERROR( syntax ); + } + + set = cp->set_op->o_tmpcalloc( 2, sizeof( struct berval ), + cp->set_op->o_tmpmemctx ); + if ( set == NULL ) { + SF_ERROR( memory ); + } + set->bv_val = cp->set_op->o_tmpcalloc( len + 1, sizeof( char ), + cp->set_op->o_tmpmemctx ); + if ( BER_BVISNULL( set ) ) { + SF_ERROR( memory ); + } + AC_MEMCPY( set->bv_val, &filter[ - len - 1 ], len ); + set->bv_len = len; + SF_PUSH( set ); + set = NULL; + break; + + case '-': + if ( ( SF_TOP() == (void *)'/' ) + && ( *filter == '*' || ASCII_DIGIT( *filter ) ) ) + { + SF_POP(); + + if ( *filter == '*' ) { + set = set_parents( cp, SF_POP() ); + filter++; + + } else { + char *next = NULL; + long parent = strtol( filter, &next, 10 ); + + if ( next == filter ) { + SF_ERROR( syntax ); + } + + set = SF_POP(); + if ( parent != 0 ) { + set = set_parent( cp, set, parent ); + } + filter = next; + } + + if ( set == NULL ) { + SF_ERROR( memory ); + } + + SF_PUSH( set ); + set = NULL; + break; + } else { + c = *filter++; + if ( c != '>' ) { + SF_ERROR( syntax ); + } + /* fall through to next case */ + } + + case '/': + if ( IS_OP( SF_TOP() ) ) { + SF_ERROR( syntax ); + } + SF_PUSH( '/' ); + break; + + default: + if ( !AD_LEADCHAR( c ) ) { + SF_ERROR( syntax ); + } + filter--; + for ( len = 1; + ( c = filter[ len ] ) && AD_CHAR( c ); + len++ ) + { + /* count */ + if ( c == '-' && !AD_CHAR( filter[ len + 1 ] ) ) { + break; + } + } + if ( len == 4 + && memcmp( "this", filter, len ) == 0 ) + { + assert( !BER_BVISNULL( target ) ); + if ( ( SF_TOP() == (void *)'/' ) || IS_SET( SF_TOP() ) ) { + SF_ERROR( syntax ); + } + set = cp->set_op->o_tmpcalloc( 2, sizeof( struct berval ), + cp->set_op->o_tmpmemctx ); + if ( set == NULL ) { + SF_ERROR( memory ); + } + ber_dupbv_x( set, target, cp->set_op->o_tmpmemctx ); + if ( BER_BVISNULL( set ) ) { + SF_ERROR( memory ); + } + BER_BVZERO( &set[ 1 ] ); + + } else if ( len == 4 + && memcmp( "user", filter, len ) == 0 ) + { + if ( ( SF_TOP() == (void *)'/' ) || IS_SET( SF_TOP() ) ) { + SF_ERROR( syntax ); + } + if ( BER_BVISNULL( user ) ) { + SF_ERROR( memory ); + } + set = cp->set_op->o_tmpcalloc( 2, sizeof( struct berval ), + cp->set_op->o_tmpmemctx ); + if ( set == NULL ) { + SF_ERROR( memory ); + } + ber_dupbv_x( set, user, cp->set_op->o_tmpmemctx ); + BER_BVZERO( &set[ 1 ] ); + + } else if ( SF_TOP() != (void *)'/' ) { + SF_ERROR( syntax ); + + } else { + struct berval fb2; + AttributeDescription *ad = NULL; + const char *text = NULL; + + SF_POP(); + fb2.bv_val = filter; + fb2.bv_len = len; + + if ( slap_bv2ad( &fb2, &ad, &text ) != LDAP_SUCCESS ) { + SF_ERROR( syntax ); + } + + /* NOTE: ad must have distinguishedName syntax + * or expand in an LDAP URI if c == '*' + */ + + set = set_chase( gatherer, + cp, SF_POP(), ad, c == '*' ); + if ( set == NULL ) { + SF_ERROR( memory ); + } + if ( c == '*' ) { + len++; + } + } + filter += len; + SF_PUSH( set ); + set = NULL; + break; + } + } + + set = SF_POP(); + if ( IS_OP( set ) ) { + SF_ERROR( syntax ); + } + if ( SF_TOP() == 0 ) { + /* FIXME: ok ? */ ; + + } else if ( IS_OP( SF_TOP() ) ) { + op = (unsigned long)SF_POP(); + lset = SF_POP(); + set = slap_set_join( cp, lset, op, set ); + if ( set == NULL ) { + SF_ERROR( memory ); + } + + } else { + SF_ERROR( syntax ); + } + + rc = slap_set_isempty( set ) ? 0 : 1; + if ( results ) { + *results = set; + set = NULL; + } + +_error: + if ( IS_SET( set ) ) { + ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx ); + } + while ( ( set = SF_POP() ) ) { + if ( IS_SET( set ) ) { + ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx ); + } + } + return rc; +} diff --git a/servers/slapd/sets.h b/servers/slapd/sets.h new file mode 100644 index 0000000..ec16dbf --- /dev/null +++ b/servers/slapd/sets.h @@ -0,0 +1,75 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#ifndef SLAP_SETS_H_ +#define SLAP_SETS_H_ + +#include <ldap_cdefs.h> + +LDAP_BEGIN_DECL + +typedef struct slap_set_cookie { + Operation *set_op; +} SetCookie; + +/* this routine needs to return the bervals instead of + * plain strings, since syntax is not known. It should + * also return the syntax or some "comparison cookie" + * that is used by set_filter. + */ +typedef BerVarray (SLAP_SET_GATHER)( SetCookie *cookie, + struct berval *name, AttributeDescription *ad); + +LDAP_SLAPD_F (int) slap_set_filter( + SLAP_SET_GATHER gatherer, + SetCookie *cookie, struct berval *filter, + struct berval *user, struct berval *target, BerVarray *results); + +LDAP_SLAPD_F (BerVarray) slap_set_join(SetCookie *cp, + BerVarray lset, unsigned op, BerVarray rset); + +#define SLAP_SET_OPMASK 0x00FF + +#define SLAP_SET_REFARR 0x0100 +#define SLAP_SET_REFVAL 0x0200 +#define SLAP_SET_REF (SLAP_SET_REFARR|SLAP_SET_REFVAL) + +/* The unsigned "op" can be ORed with the flags below; + * - if the rset's values must not be freed, or must be copied if kept, + * it is ORed with SLAP_SET_RREFVAL + * - if the rset array must not be freed, or must be copied if kept, + * it is ORed with SLAP_SET_RREFARR + * - the same applies to the lset with SLAP_SET_LREFVAL and SLAP_SET_LREFARR + * - it is assumed that SLAP_SET_REFVAL implies SLAP_SET_REFARR, + * i.e. the former is checked only if the latter is set. + */ + +#define SLAP_SET_RREFARR SLAP_SET_REFARR +#define SLAP_SET_RREFVAL SLAP_SET_REFVAL +#define SLAP_SET_RREF SLAP_SET_REF +#define SLAP_SET_RREFMASK 0x0F00 + +#define SLAP_SET_RREF2REF(r) ((r) & SLAP_SET_RREFMASK) + +#define SLAP_SET_LREFARR 0x1000 +#define SLAP_SET_LREFVAL 0x2000 +#define SLAP_SET_LREF (SLAP_SET_LREFARR|SLAP_SET_LREFVAL) +#define SLAP_SET_LREFMASK 0xF000 + +#define SLAP_SET_LREF2REF(r) (((r) & SLAP_SET_LREFMASK) >> 4) + +LDAP_END_DECL + +#endif diff --git a/servers/slapd/shell-backends/Makefile.in b/servers/slapd/shell-backends/Makefile.in new file mode 100644 index 0000000..d032ead --- /dev/null +++ b/servers/slapd/shell-backends/Makefile.in @@ -0,0 +1,40 @@ +# Makefile.in for shell-backends +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +PROGRAMS = passwd-shell + +SRCS = passwd-shell.c shellutil.c +XSRCS = pwd-version.c +OBJS = passwd-shell.o shellutil.o + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-shell" +BUILD_SRV = @BUILD_SHELL@ + +all-local-srv: $(PROGRAMS) + +# create programs also when using modules +depend-mod: depend-yes +all-mod: all-yes +install-mod: install-yes + +passwd-shell: pwd-version.o + $(CC) $(LDFLAGS) -o $@ $(OBJS) pwd-version.o $(LIBS) + +pwd-version.c: $(OBJS) $(LDAP_LIBDEPEND) + @-$(RM) $@ + $(MKVERSION) passwd-shell > $@ diff --git a/servers/slapd/shell-backends/passwd-shell.c b/servers/slapd/shell-backends/passwd-shell.c new file mode 100644 index 0000000..2414431 --- /dev/null +++ b/servers/slapd/shell-backends/passwd-shell.c @@ -0,0 +1,207 @@ +/* passwd-shell.c - passwd(5) shell-based backend for slapd(8) */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by the University of Michigan + * (as part of U-MICH LDAP). + */ + + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/string.h> +#include <ac/unistd.h> + +#include <pwd.h> + +#include <lber.h> +#include <ldap.h> + +#include "shellutil.h" + +static void pwdfile_search LDAP_P(( struct ldop *op, FILE *ofp )); +static struct ldentry *pw2entry LDAP_P(( struct ldop *op, struct passwd *pw )); + +static char tmpbuf[ MAXLINELEN * 2 ]; + + +int +main( int argc, char **argv ) +{ + int c, errflg; + struct ldop op; + + if (( progname = strrchr( argv[ 0 ], '/' )) == NULL ) { + progname = estrdup( argv[ 0 ] ); + } else { + progname = estrdup( progname + 1 ); + } + + errflg = debugflg = 0; + + while (( c = getopt( argc, argv, "d" )) != EOF ) { + switch( c ) { + case 'd': +#ifdef LDAP_DEBUG + ++debugflg; +#else /* LDAP_DEBUG */ + fprintf( stderr, "%s: compile with -DLDAP_DEBUG for debugging\n", + progname ); +#endif /* LDAP_DEBUG */ + break; + default: + ++errflg; + } + } + + if ( errflg || optind < argc ) { + fprintf( stderr, "usage: %s [-d]\n", progname ); + exit( EXIT_FAILURE ); + } + + debug_printf( "started\n" ); + + (void) memset( (char *)&op, '\0', sizeof( op )); + + if ( parse_input( stdin, stdout, &op ) < 0 ) { + exit( EXIT_SUCCESS ); + } + + if ( op.ldop_op != LDOP_SEARCH ) { + write_result( stdout, LDAP_UNWILLING_TO_PERFORM, NULL, + "Command Not Implemented" ); + exit( EXIT_SUCCESS ); + } + +#ifdef LDAP_DEBUG + dump_ldop( &op ); +#endif /* LDAP_DEBUG */ + + pwdfile_search( &op, stdout ); + + exit( EXIT_SUCCESS ); +} + + +static void +pwdfile_search( struct ldop *op, FILE *ofp ) +{ + struct passwd *pw; + struct ldentry *entry; + int oneentry; + + oneentry = ( strchr( op->ldop_dn, '@' ) != NULL ); + + for ( pw = getpwent(); pw != NULL; pw = getpwent()) { + if (( entry = pw2entry( op, pw )) != NULL ) { + if ( oneentry ) { + if ( strcasecmp( op->ldop_dn, entry->lde_dn ) == 0 ) { + write_entry( op, entry, ofp ); + break; + } + } else if ( test_filter( op, entry ) == LDAP_COMPARE_TRUE ) { + write_entry( op, entry, ofp ); + } + free_entry( entry ); + } + } + endpwent(); + + write_result( ofp, LDAP_SUCCESS, NULL, NULL ); +} + + +static struct ldentry * +pw2entry( struct ldop *op, struct passwd *pw ) +{ + struct ldentry *entry; + struct ldattr *attr; + int i; + + /* + * construct the DN from pw_name + */ + if ( strchr( op->ldop_suffixes[ 0 ], '=' ) != NULL ) { + /* + * X.500 style DN + */ + i = snprintf( tmpbuf, sizeof( tmpbuf ), "cn=%s, %s", pw->pw_name, op->ldop_suffixes[ 0 ] ); + } else { + /* + * RFC-822 style DN + */ + i = snprintf( tmpbuf, sizeof( tmpbuf ), "%s@%s", pw->pw_name, op->ldop_suffixes[ 0 ] ); + } + + if ( i < 0 || i >= sizeof( tmpbuf ) ) { + return NULL; + } + + entry = (struct ldentry *) ecalloc( 1, sizeof( struct ldentry )); + entry->lde_dn = estrdup( tmpbuf ); + + /* + * for now, we simply derive the LDAP attribute values as follows: + * objectClass = person + * uid = pw_name + * sn = pw_name + * cn = pw_name + * cn = pw_gecos (second common name) + */ + entry->lde_attrs = (struct ldattr **)ecalloc( 5, sizeof( struct ldattr * )); + i = 0; + attr = (struct ldattr *)ecalloc( 1, sizeof( struct ldattr )); + attr->lda_name = estrdup( "objectClass" ); + attr->lda_values = (char **)ecalloc( 2, sizeof( char * )); + attr->lda_values[ 0 ] = estrdup( "person" ); + entry->lde_attrs[ i++ ] = attr; + + attr = (struct ldattr *)ecalloc( 1, sizeof( struct ldattr )); + attr->lda_name = estrdup( "uid" ); + attr->lda_values = (char **)ecalloc( 2, sizeof( char * )); + attr->lda_values[ 0 ] = estrdup( pw->pw_name ); + entry->lde_attrs[ i++ ] = attr; + + attr = (struct ldattr *)ecalloc( 1, sizeof( struct ldattr )); + attr->lda_name = estrdup( "sn" ); + attr->lda_values = (char **)ecalloc( 2, sizeof( char * )); + attr->lda_values[ 0 ] = estrdup( pw->pw_name ); + entry->lde_attrs[ i++ ] = attr; + + attr = (struct ldattr *)ecalloc( 1, sizeof( struct ldattr )); + attr->lda_name = estrdup( "cn" ); + attr->lda_values = (char **)ecalloc( 3, sizeof( char * )); + attr->lda_values[ 0 ] = estrdup( pw->pw_name ); + if ( pw->pw_gecos != NULL && *pw->pw_gecos != '\0' ) { + attr->lda_values[ 1 ] = estrdup( pw->pw_gecos ); + } + entry->lde_attrs[ i++ ] = attr; + + return( entry ); +} diff --git a/servers/slapd/shell-backends/shellutil.c b/servers/slapd/shell-backends/shellutil.c new file mode 100644 index 0000000..605114e --- /dev/null +++ b/servers/slapd/shell-backends/shellutil.c @@ -0,0 +1,396 @@ +/* shellutil.c - common routines useful when building shell-based backends */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by the University of Michigan + * (as part of U-MICH LDAP). + */ + + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> +#include <ac/stdarg.h> + +#include <pwd.h> + +#include <ac/ctype.h> +#include <ac/string.h> + +#include <lber.h> +#include <ldap.h> +#include "shellutil.h" + + +int debugflg; +char *progname; + +static struct inputparams ips[] = { + IP_TYPE_SUFFIX, "suffix", + IP_TYPE_BASE, "base", + IP_TYPE_SCOPE, "scope", + IP_TYPE_ALIASDEREF, "deref", + IP_TYPE_SIZELIMIT, "sizelimit", + IP_TYPE_TIMELIMIT, "timelimit", + IP_TYPE_FILTER, "filter", + IP_TYPE_ATTRS, "attrs", + IP_TYPE_ATTRSONLY, "attrsonly", + 0, NULL +}; + + +void +write_result( FILE *fp, int code, char *matched, char *info ) +{ + fprintf( fp, "RESULT\ncode: %d\n", code ); + debug_printf( ">> RESULT\n" ); + debug_printf( ">> code: %d\n", code ); + + if ( matched != NULL ) { + fprintf( fp, "matched: %s\n", matched ); + debug_printf( ">> matched: %s\n", matched ); + } + + if ( info != NULL ) { + fprintf( fp, "info: %s\n", info ); + debug_printf( ">> info: %s\n", info ); + } +} + + +void +write_entry( struct ldop *op, struct ldentry *entry, FILE *ofp ) +{ + struct ldattr **app; + char **valp; + + fprintf( ofp, "dn: %s\n", entry->lde_dn ); + for ( app = entry->lde_attrs; *app != NULL; ++app ) { + if ( attr_requested( (*app)->lda_name, op )) { + for ( valp = (*app)->lda_values; *valp != NULL; ++valp ) { + fprintf( ofp, "%s: %s\n", (*app)->lda_name, *valp ); + } + } + } + fputc( '\n', ofp ); +} + + +int +test_filter( struct ldop *op, struct ldentry *entry ) +{ + return ((random() & 0x07 ) == 0x07) /* XXX random for now */ + ? LDAP_COMPARE_TRUE : LDAP_COMPARE_FALSE; +} + + +int +attr_requested( char *name, struct ldop *op ) +{ + char **ap; + + if ( op->ldop_srch.ldsp_attrs == NULL ) { /* special case */ + return( 1 ); + } + + for ( ap = op->ldop_srch.ldsp_attrs; *ap != NULL; ++ap ) { + if ( strcasecmp( name, *ap ) == 0 ) { + return( 1 ); + } + } + + return( 0 ); +} + + +void +free_entry( struct ldentry *entry ) +{ + struct ldattr **app; + char **valp; + + free( entry->lde_dn ); + + for ( app = entry->lde_attrs; *app != NULL; ++app ) { + for ( valp = (*app)->lda_values; *valp != NULL; ++valp ) { + free( *valp ); + } + free( (*app)->lda_values ); + free( (*app)->lda_name ); + } + + free( entry->lde_attrs ); + free( entry ); +} + + +int +parse_input( FILE *ifp, FILE *ofp, struct ldop *op ) +{ + char *p, *args, line[ MAXLINELEN + 1 ]; + struct inputparams *ip; + + if ( fgets( line, MAXLINELEN, ifp ) == NULL ) { + write_result( ofp, LDAP_OTHER, NULL, "Empty Input" ); + } + line[ strlen( line ) - 1 ] = '\0'; + if ( strncasecmp( line, STR_OP_SEARCH, sizeof( STR_OP_SEARCH ) - 1 ) + != 0 ) { + write_result( ofp, LDAP_UNWILLING_TO_PERFORM, NULL, + "Operation Not Supported" ); + return( -1 ); + } + + op->ldop_op = LDOP_SEARCH; + + while ( fgets( line, MAXLINELEN, ifp ) != NULL ) { + line[ strlen( line ) - 1 ] = '\0'; + debug_printf( "<< %s\n", line ); + + args = line; + if (( ip = find_input_tag( &args )) == NULL ) { + debug_printf( "ignoring %s\n", line ); + continue; + } + + switch( ip->ip_type ) { + case IP_TYPE_SUFFIX: + add_strval( &op->ldop_suffixes, args ); + break; + case IP_TYPE_BASE: + op->ldop_dn = estrdup( args ); + break; + case IP_TYPE_SCOPE: + if ( lutil_atoi( &op->ldop_srch.ldsp_scope, args ) != 0 || + ( op->ldop_srch.ldsp_scope != LDAP_SCOPE_BASE && + op->ldop_srch.ldsp_scope != LDAP_SCOPE_ONELEVEL && + op->ldop_srch.ldsp_scope != LDAP_SCOPE_SUBTREE ) ) + { + write_result( ofp, LDAP_OTHER, NULL, "Bad scope" ); + return( -1 ); + } + break; + case IP_TYPE_ALIASDEREF: + if ( lutil_atoi( &op->ldop_srch.ldsp_aliasderef, args ) != 0 ) { + write_result( ofp, LDAP_OTHER, NULL, "Bad alias deref" ); + return( -1 ); + } + break; + case IP_TYPE_SIZELIMIT: + if ( lutil_atoi( &op->ldop_srch.ldsp_sizelimit, args ) != 0 ) { + write_result( ofp, LDAP_OTHER, NULL, "Bad size limit" ); + return( -1 ); + } + break; + case IP_TYPE_TIMELIMIT: + if ( lutil_atoi( &op->ldop_srch.ldsp_timelimit, args ) != 0 ) { + write_result( ofp, LDAP_OTHER, NULL, "Bad time limit" ); + return( -1 ); + } + break; + case IP_TYPE_FILTER: + op->ldop_srch.ldsp_filter = estrdup( args ); + break; + case IP_TYPE_ATTRSONLY: + op->ldop_srch.ldsp_attrsonly = ( *args != '0' ); + break; + case IP_TYPE_ATTRS: + if ( strcmp( args, "all" ) == 0 ) { + op->ldop_srch.ldsp_attrs = NULL; + } else { + while ( args != NULL ) { + if (( p = strchr( args, ' ' )) != NULL ) { + *p++ = '\0'; + while ( isspace( (unsigned char) *p )) { + ++p; + } + } + add_strval( &op->ldop_srch.ldsp_attrs, args ); + args = p; + } + } + break; + } + } + + if ( op->ldop_suffixes == NULL || op->ldop_dn == NULL || + op->ldop_srch.ldsp_filter == NULL ) { + write_result( ofp, LDAP_OTHER, NULL, + "Required suffix:, base:, or filter: missing" ); + return( -1 ); + } + + return( 0 ); +} + + +struct inputparams * +find_input_tag( char **linep ) /* linep is set to start of args */ +{ + int i; + char *p; + + if (( p = strchr( *linep, ':' )) == NULL || p == *linep ) { + return( NULL ); + } + + for ( i = 0; ips[ i ].ip_type != 0; ++i ) { + if ( strncasecmp( *linep, ips[ i ].ip_tag, p - *linep ) == 0 ) { + while ( isspace( (unsigned char) *(++p) )) { + ; + } + *linep = p; + return( &ips[ i ] ); + } + } + + return( NULL ); +} + + +void +add_strval( char ***sp, char *val ) +{ + int i; + char **vallist; + + vallist = *sp; + + if ( vallist == NULL ) { + i = 0; + } else { + for ( i = 0; vallist[ i ] != NULL; ++i ) { + ; + } + } + + vallist = (char **)erealloc( vallist, ( i + 2 ) * sizeof( char * )); + vallist[ i ] = estrdup( val ); + vallist[ ++i ] = NULL; + *sp = vallist; +} + + +char * +estrdup( char *s ) +{ + char *p; + + if (( p = strdup( s )) == NULL ) { + debug_printf( "strdup failed\n" ); + exit( EXIT_FAILURE ); + } + + return( p ); +} + + +void * +erealloc( void *s, unsigned size ) +{ + char *p; + + if ( s == NULL ) { + p = malloc( size ); + } else { + p = realloc( s, size ); + } + + if ( p == NULL ) { + debug_printf( "realloc( p, %d ) failed\n", size ); + exit( EXIT_FAILURE ); + } + + return( p ); +} + + +char * +ecalloc( unsigned nelem, unsigned elsize ) +{ + char *p; + + if (( p = calloc( nelem, elsize )) == NULL ) { + debug_printf( "calloc( %d, %d ) failed\n", nelem, elsize ); + exit( EXIT_FAILURE ); + } + + return( p ); +} + + +#ifdef LDAP_DEBUG + +/* VARARGS */ +void +debug_printf( const char *fmt, ... ) +{ + va_list ap; + + if ( debugflg ) { + va_start( ap, fmt ); + fprintf( stderr, "%s: ", progname ); + vfprintf( stderr, fmt, ap ); + va_end( ap ); + } +} + + +void +dump_ldop( struct ldop *op ) +{ + if ( !debugflg ) { + return; + } + + debug_printf( "SEARCH operation\n" ); + if ( op->ldop_suffixes == NULL ) { + debug_printf( " suffix: NONE\n" ); + } else { + int i; + for ( i = 0; op->ldop_suffixes[ i ] != NULL; ++i ) { + debug_printf( " suffix: <%s>\n", op->ldop_suffixes[ i ] ); + } + } + debug_printf( " dn: <%s>\n", op->ldop_dn ); + debug_printf( " scope: <%d>\n", op->ldop_srch.ldsp_scope ); + debug_printf( " filter: <%s>\n", op->ldop_srch.ldsp_filter ); + debug_printf( "aliasderef: <%d>\n", op->ldop_srch.ldsp_aliasderef ); + debug_printf( " sizelimit: <%d>\n", op->ldop_srch.ldsp_sizelimit ); + debug_printf( " timelimit: <%d>\n", op->ldop_srch.ldsp_timelimit ); + debug_printf( " attrsonly: <%d>\n", op->ldop_srch.ldsp_attrsonly ); + if ( op->ldop_srch.ldsp_attrs == NULL ) { + debug_printf( " attrs: ALL\n" ); + } else { + int i; + + for ( i = 0; op->ldop_srch.ldsp_attrs[ i ] != NULL; ++i ) { + debug_printf( " attrs: <%s>\n", op->ldop_srch.ldsp_attrs[ i ] ); + } + } +} +#endif /* LDAP_DEBUG */ diff --git a/servers/slapd/shell-backends/shellutil.h b/servers/slapd/shell-backends/shellutil.h new file mode 100644 index 0000000..a359aa2 --- /dev/null +++ b/servers/slapd/shell-backends/shellutil.h @@ -0,0 +1,123 @@ +/* shellutil.h */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ +/* ACKNOWLEDGEMENTS: + * This work was originally developed by the University of Michigan + * (as part of U-MICH LDAP). + */ + +#ifndef SHELLUTIL_H +#define SHELLUTIL_H + +#include <ldap_cdefs.h> + +LDAP_BEGIN_DECL + +#define MAXLINELEN 512 + +#define STR_OP_SEARCH "SEARCH" + + +struct inputparams { + int ip_type; +#define IP_TYPE_SUFFIX 0x01 +#define IP_TYPE_BASE 0x02 +#define IP_TYPE_SCOPE 0x03 +#define IP_TYPE_ALIASDEREF 0x04 +#define IP_TYPE_SIZELIMIT 0x05 +#define IP_TYPE_TIMELIMIT 0x06 +#define IP_TYPE_FILTER 0x07 +#define IP_TYPE_ATTRSONLY 0x08 +#define IP_TYPE_ATTRS 0x09 + char *ip_tag; +}; + + +struct ldsrchparms { + int ldsp_scope; + int ldsp_aliasderef; + int ldsp_sizelimit; + int ldsp_timelimit; + int ldsp_attrsonly; + char *ldsp_filter; + char **ldsp_attrs; +}; + + +struct ldop { + int ldop_op; +#define LDOP_SEARCH 0x01 + char **ldop_suffixes; + char *ldop_dn; + union ldapop_params_u { + struct ldsrchparms LDsrchparams; + } ldop_params; +#define ldop_srch ldop_params.LDsrchparams +}; + + +struct ldattr { + char *lda_name; + char **lda_values; +}; + + +struct ldentry { + char *lde_dn; + struct ldattr **lde_attrs; +}; + + +#ifdef LDAP_DEBUG +void debug_printf(const char *, ...) LDAP_GCCATTR((format(printf, 1, 2))); +#else /* LDAP_DEBUG */ +#define debug_printf (void) /* Ignore "arguments" */ +#endif /* LDAP_DEBUG */ + +/* + * function prototypes + */ +void write_result( FILE *fp, int code, char *matched, char *info ); +void write_entry( struct ldop *op, struct ldentry *entry, FILE *ofp ); +int test_filter( struct ldop *op, struct ldentry *entry ); +void free_entry( struct ldentry *entry ); +int attr_requested( char *name, struct ldop *op ); +int parse_input( FILE *ifp, FILE *ofp, struct ldop *op ); +struct inputparams *find_input_tag( char **linep ); +void add_strval( char ***sp, char *val ); +char *ecalloc( unsigned nelem, unsigned elsize ); +void *erealloc( void *s, unsigned size ); +char *estrdup( char *s ); +extern void dump_ldop (struct ldop *op); + + +/* + * global variables + */ +extern int debugflg; +extern char *progname; + +LDAP_END_DECL +#endif diff --git a/servers/slapd/sl_malloc.c b/servers/slapd/sl_malloc.c new file mode 100644 index 0000000..91ef43c --- /dev/null +++ b/servers/slapd/sl_malloc.c @@ -0,0 +1,730 @@ +/* sl_malloc.c - malloc routines using a per-thread slab */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "slap.h" + +#ifdef USE_VALGRIND +/* Get debugging help from Valgrind */ +#include <valgrind/memcheck.h> +#define VGMEMP_MARK(m,s) VALGRIND_MAKE_MEM_NOACCESS(m,s) +#define VGMEMP_CREATE(h,r,z) VALGRIND_CREATE_MEMPOOL(h,r,z) +#define VGMEMP_TRIM(h,a,s) VALGRIND_MEMPOOL_TRIM(h,a,s) +#define VGMEMP_ALLOC(h,a,s) VALGRIND_MEMPOOL_ALLOC(h,a,s) +#define VGMEMP_CHANGE(h,a,b,s) VALGRIND_MEMPOOL_CHANGE(h,a,b,s) +#else +#define VGMEMP_MARK(m,s) +#define VGMEMP_CREATE(h,r,z) +#define VGMEMP_TRIM(h,a,s) +#define VGMEMP_ALLOC(h,a,s) +#define VGMEMP_CHANGE(h,a,b,s) +#endif + +/* + * This allocator returns temporary memory from a slab in a given memory + * context, aligned on a 2-int boundary. It cannot be used for data + * which will outlive the task allocating it. + * + * A new memory context attaches to the creator's thread context, if any. + * Threads cannot use other threads' memory contexts; there are no locks. + * + * The caller of slap_sl_malloc, usually a thread pool task, must + * slap_sl_free the memory before finishing: New tasks reuse the context + * and normally reset it, reclaiming memory left over from last task. + * + * The allocator helps memory fragmentation, speed and memory leaks. + * It is not (yet) reliable as a garbage collector: + * + * It falls back to context NULL - plain ber_memalloc() - when the + * context's slab is full. A reset does not reclaim such memory. + * Conversely, free/realloc of data not from the given context assumes + * context NULL. The data must not belong to another memory context. + * + * Code which has lost track of the current memory context can try + * slap_sl_context() or ch_malloc.c:ch_free/ch_realloc(). + * + * Allocations cannot yet return failure. Like ch_malloc, they succeed + * or abort slapd. This will change, do fix code which assumes success. + */ + +/* + * The stack-based allocator stores (ber_len_t)sizeof(head+block) at + * allocated blocks' head - and in freed blocks also at the tail, marked + * by ORing *next* block's head with 1. Freed blocks are only reclaimed + * from the last block forward. This is fast, but when a block is never + * freed, older blocks will not be reclaimed until the slab is reset... + */ + +#ifdef SLAP_NO_SL_MALLOC /* Useful with memory debuggers like Valgrind */ +enum { No_sl_malloc = 1 }; +#else +enum { No_sl_malloc = 0 }; +#endif + +#define SLAP_SLAB_SOBLOCK 64 + +struct slab_object { + void *so_ptr; + int so_blockhead; + LDAP_LIST_ENTRY(slab_object) so_link; +}; + +struct slab_heap { + void *sh_base; + void *sh_last; + void *sh_end; + int sh_stack; + int sh_maxorder; + unsigned char **sh_map; + LDAP_LIST_HEAD(sh_freelist, slab_object) *sh_free; + LDAP_LIST_HEAD(sh_so, slab_object) sh_sopool; +}; + +enum { + Align = sizeof(ber_len_t) > 2*sizeof(int) + ? sizeof(ber_len_t) : 2*sizeof(int), + Align_log2 = 1 + (Align>2) + (Align>4) + (Align>8) + (Align>16), + order_start = Align_log2 - 1, + pad = Align - 1 +}; + +static struct slab_object * slap_replenish_sopool(struct slab_heap* sh); +#ifdef SLAPD_UNUSED +static void print_slheap(int level, void *ctx); +#endif + +/* Keep memory context in a thread-local var, or in a global when no threads */ +#ifdef NO_THREADS +static struct slab_heap *slheap; +# define SET_MEMCTX(thrctx, memctx, sfree) ((void) (slheap = (memctx))) +# define GET_MEMCTX(thrctx, memctxp) (*(memctxp) = slheap) +#else +# define memctx_key ((void *) slap_sl_mem_init) +# define SET_MEMCTX(thrctx, memctx, kfree) \ + ldap_pvt_thread_pool_setkey(thrctx,memctx_key, memctx,kfree, NULL,NULL) +# define GET_MEMCTX(thrctx, memctxp) \ + ((void) (*(memctxp) = NULL), \ + (void) ldap_pvt_thread_pool_getkey(thrctx,memctx_key, memctxp,NULL), \ + *(memctxp)) +#endif /* NO_THREADS */ + + +/* Destroy the context, or if key==NULL clean it up for reuse. */ +void +slap_sl_mem_destroy( + void *key, + void *data +) +{ + struct slab_heap *sh = data; + struct slab_object *so; + int i; + + if (!sh->sh_stack) { + for (i = 0; i <= sh->sh_maxorder - order_start; i++) { + so = LDAP_LIST_FIRST(&sh->sh_free[i]); + while (so) { + struct slab_object *so_tmp = so; + so = LDAP_LIST_NEXT(so, so_link); + LDAP_LIST_INSERT_HEAD(&sh->sh_sopool, so_tmp, so_link); + } + ch_free(sh->sh_map[i]); + } + ch_free(sh->sh_free); + ch_free(sh->sh_map); + + so = LDAP_LIST_FIRST(&sh->sh_sopool); + while (so) { + struct slab_object *so_tmp = so; + so = LDAP_LIST_NEXT(so, so_link); + if (!so_tmp->so_blockhead) { + LDAP_LIST_REMOVE(so_tmp, so_link); + } + } + so = LDAP_LIST_FIRST(&sh->sh_sopool); + while (so) { + struct slab_object *so_tmp = so; + so = LDAP_LIST_NEXT(so, so_link); + ch_free(so_tmp); + } + } + + if (key != NULL) { + ber_memfree_x(sh->sh_base, NULL); + ber_memfree_x(sh, NULL); + } +} + +BerMemoryFunctions slap_sl_mfuncs = + { slap_sl_malloc, slap_sl_calloc, slap_sl_realloc, slap_sl_free }; + +void +slap_sl_mem_init() +{ + assert( Align == 1 << Align_log2 ); + + ber_set_option( NULL, LBER_OPT_MEMORY_FNS, &slap_sl_mfuncs ); +} + +/* Create, reset or just return the memory context of the current thread. */ +void * +slap_sl_mem_create( + ber_len_t size, + int stack, + void *thrctx, + int new +) +{ + void *memctx; + struct slab_heap *sh; + ber_len_t size_shift; + struct slab_object *so; + char *base, *newptr; + enum { Base_offset = (unsigned) -sizeof(ber_len_t) % Align }; + + sh = GET_MEMCTX(thrctx, &memctx); + if ( sh && !new ) + return sh; + + /* Round up to doubleword boundary, then make room for initial + * padding, preserving expected available size for pool version */ + size = ((size + Align-1) & -Align) + Base_offset; + + if (!sh) { + sh = ch_malloc(sizeof(struct slab_heap)); + base = ch_malloc(size); + SET_MEMCTX(thrctx, sh, slap_sl_mem_destroy); + VGMEMP_MARK(base, size); + VGMEMP_CREATE(sh, 0, 0); + } else { + slap_sl_mem_destroy(NULL, sh); + base = sh->sh_base; + if (size > (ber_len_t) ((char *) sh->sh_end - base)) { + newptr = ch_realloc(base, size); + if ( newptr == NULL ) return NULL; + VGMEMP_CHANGE(sh, base, newptr, size); + base = newptr; + } + VGMEMP_TRIM(sh, base, 0); + } + sh->sh_base = base; + sh->sh_end = base + size; + + /* Align (base + head of first block) == first returned block */ + base += Base_offset; + size -= Base_offset; + + sh->sh_stack = stack; + if (stack) { + sh->sh_last = base; + + } else { + int i, order = -1, order_end = -1; + + size_shift = size - 1; + do { + order_end++; + } while (size_shift >>= 1); + order = order_end - order_start + 1; + sh->sh_maxorder = order_end; + + sh->sh_free = (struct sh_freelist *) + ch_malloc(order * sizeof(struct sh_freelist)); + for (i = 0; i < order; i++) { + LDAP_LIST_INIT(&sh->sh_free[i]); + } + + LDAP_LIST_INIT(&sh->sh_sopool); + + if (LDAP_LIST_EMPTY(&sh->sh_sopool)) { + slap_replenish_sopool(sh); + } + so = LDAP_LIST_FIRST(&sh->sh_sopool); + LDAP_LIST_REMOVE(so, so_link); + so->so_ptr = base; + + LDAP_LIST_INSERT_HEAD(&sh->sh_free[order-1], so, so_link); + + sh->sh_map = (unsigned char **) + ch_malloc(order * sizeof(unsigned char *)); + for (i = 0; i < order; i++) { + int shiftamt = order_start + 1 + i; + int nummaps = size >> shiftamt; + assert(nummaps); + nummaps >>= 3; + if (!nummaps) nummaps = 1; + sh->sh_map[i] = (unsigned char *) ch_malloc(nummaps); + memset(sh->sh_map[i], 0, nummaps); + } + } + + return sh; +} + +/* + * Assign memory context to thread context. Use NULL to detach + * current memory context from thread. Future users must + * know the context, since ch_free/slap_sl_context() cannot find it. + */ +void +slap_sl_mem_setctx( + void *thrctx, + void *memctx +) +{ + SET_MEMCTX(thrctx, memctx, slap_sl_mem_destroy); +} + +void * +slap_sl_malloc( + ber_len_t size, + void *ctx +) +{ + struct slab_heap *sh = ctx; + ber_len_t *ptr, *newptr; + + /* ber_set_option calls us like this */ + if (No_sl_malloc || !ctx) { + newptr = ber_memalloc_x( size, NULL ); + if ( newptr ) return newptr; + Debug(LDAP_DEBUG_ANY, "slap_sl_malloc of %lu bytes failed\n", + (unsigned long) size, 0, 0); + assert( 0 ); + exit( EXIT_FAILURE ); + } + + /* Add room for head, ensure room for tail when freed, and + * round up to doubleword boundary. */ + size = (size + sizeof(ber_len_t) + Align-1 + !size) & -Align; + + if (sh->sh_stack) { + if (size < (ber_len_t) ((char *) sh->sh_end - (char *) sh->sh_last)) { + newptr = sh->sh_last; + sh->sh_last = (char *) sh->sh_last + size; + VGMEMP_ALLOC(sh, newptr, size); + *newptr++ = size; + return( (void *)newptr ); + } + + size -= sizeof(ber_len_t); + + } else { + struct slab_object *so_new, *so_left, *so_right; + ber_len_t size_shift; + unsigned long diff; + int i, j, order = -1; + + size_shift = size - 1; + do { + order++; + } while (size_shift >>= 1); + + size -= sizeof(ber_len_t); + + for (i = order; i <= sh->sh_maxorder && + LDAP_LIST_EMPTY(&sh->sh_free[i-order_start]); i++); + + if (i == order) { + so_new = LDAP_LIST_FIRST(&sh->sh_free[i-order_start]); + LDAP_LIST_REMOVE(so_new, so_link); + ptr = so_new->so_ptr; + diff = (unsigned long)((char*)ptr - + (char*)sh->sh_base) >> (order + 1); + sh->sh_map[order-order_start][diff>>3] |= (1 << (diff & 0x7)); + *ptr++ = size; + LDAP_LIST_INSERT_HEAD(&sh->sh_sopool, so_new, so_link); + return((void*)ptr); + } else if (i <= sh->sh_maxorder) { + for (j = i; j > order; j--) { + so_left = LDAP_LIST_FIRST(&sh->sh_free[j-order_start]); + LDAP_LIST_REMOVE(so_left, so_link); + if (LDAP_LIST_EMPTY(&sh->sh_sopool)) { + slap_replenish_sopool(sh); + } + so_right = LDAP_LIST_FIRST(&sh->sh_sopool); + LDAP_LIST_REMOVE(so_right, so_link); + so_right->so_ptr = (void *)((char *)so_left->so_ptr + (1 << j)); + if (j == order + 1) { + ptr = so_left->so_ptr; + diff = (unsigned long)((char*)ptr - + (char*)sh->sh_base) >> (order+1); + sh->sh_map[order-order_start][diff>>3] |= + (1 << (diff & 0x7)); + *ptr++ = size; + LDAP_LIST_INSERT_HEAD( + &sh->sh_free[j-1-order_start], so_right, so_link); + LDAP_LIST_INSERT_HEAD(&sh->sh_sopool, so_left, so_link); + return((void*)ptr); + } else { + LDAP_LIST_INSERT_HEAD( + &sh->sh_free[j-1-order_start], so_right, so_link); + LDAP_LIST_INSERT_HEAD( + &sh->sh_free[j-1-order_start], so_left, so_link); + } + } + } + /* FIXME: missing return; guessing we failed... */ + } + + Debug(LDAP_DEBUG_TRACE, + "sl_malloc %lu: ch_malloc\n", + (unsigned long) size, 0, 0); + return ch_malloc(size); +} + +#define LIM_SQRT(t) /* some value < sqrt(max value of unsigned type t) */ \ + ((0UL|(t)-1) >>31>>31 > 1 ? ((t)1 <<32) - 1 : \ + (0UL|(t)-1) >>31 ? 65535U : (0UL|(t)-1) >>15 ? 255U : 15U) + +void * +slap_sl_calloc( ber_len_t n, ber_len_t size, void *ctx ) +{ + void *newptr; + ber_len_t total = n * size; + + /* The sqrt test is a slight optimization: often avoids the division */ + if ((n | size) <= LIM_SQRT(ber_len_t) || n == 0 || total/n == size) { + newptr = slap_sl_malloc( total, ctx ); + memset( newptr, 0, n*size ); + } else { + Debug(LDAP_DEBUG_ANY, "slap_sl_calloc(%lu,%lu) out of range\n", + (unsigned long) n, (unsigned long) size, 0); + assert(0); + exit(EXIT_FAILURE); + } + return newptr; +} + +void * +slap_sl_realloc(void *ptr, ber_len_t size, void *ctx) +{ + struct slab_heap *sh = ctx; + ber_len_t oldsize, *p = (ber_len_t *) ptr, *nextp; + void *newptr; + + if (ptr == NULL) + return slap_sl_malloc(size, ctx); + + /* Not our memory? */ + if (No_sl_malloc || !sh || ptr < sh->sh_base || ptr >= sh->sh_end) { + /* Like ch_realloc(), except not trying a new context */ + newptr = ber_memrealloc_x(ptr, size, NULL); + if (newptr) { + return newptr; + } + Debug(LDAP_DEBUG_ANY, "slap_sl_realloc of %lu bytes failed\n", + (unsigned long) size, 0, 0); + assert(0); + exit( EXIT_FAILURE ); + } + + if (size == 0) { + slap_sl_free(ptr, ctx); + return NULL; + } + + oldsize = p[-1]; + + if (sh->sh_stack) { + /* Add room for head, round up to doubleword boundary */ + size = (size + sizeof(ber_len_t) + Align-1) & -Align; + + p--; + + /* Never shrink blocks */ + if (size <= oldsize) { + return ptr; + } + + oldsize &= -2; + nextp = (ber_len_t *) ((char *) p + oldsize); + + /* If reallocing the last block, try to grow it */ + if (nextp == sh->sh_last) { + if (size < (ber_len_t) ((char *) sh->sh_end - (char *) p)) { + sh->sh_last = (char *) p + size; + p[0] = (p[0] & 1) | size; + return ptr; + } + + /* Nowhere to grow, need to alloc and copy */ + } else { + /* Slight optimization of the final realloc variant */ + newptr = slap_sl_malloc(size-sizeof(ber_len_t), ctx); + AC_MEMCPY(newptr, ptr, oldsize-sizeof(ber_len_t)); + /* Not last block, can just mark old region as free */ + nextp[-1] = oldsize; + nextp[0] |= 1; + return newptr; + } + + size -= sizeof(ber_len_t); + oldsize -= sizeof(ber_len_t); + + } else if (oldsize > size) { + oldsize = size; + } + + newptr = slap_sl_malloc(size, ctx); + AC_MEMCPY(newptr, ptr, oldsize); + slap_sl_free(ptr, ctx); + return newptr; +} + +void +slap_sl_free(void *ptr, void *ctx) +{ + struct slab_heap *sh = ctx; + ber_len_t size; + ber_len_t *p = ptr, *nextp, *tmpp; + + if (!ptr) + return; + + if (No_sl_malloc || !sh || ptr < sh->sh_base || ptr >= sh->sh_end) { + ber_memfree_x(ptr, NULL); + return; + } + + size = *(--p); + + if (sh->sh_stack) { + size &= -2; + nextp = (ber_len_t *) ((char *) p + size); + if (sh->sh_last != nextp) { + /* Mark it free: tail = size, head of next block |= 1 */ + nextp[-1] = size; + nextp[0] |= 1; + /* We can't tell Valgrind about it yet, because we + * still need read/write access to this block for + * when we eventually get to reclaim it. + */ + } else { + /* Reclaim freed block(s) off tail */ + while (*p & 1) { + p = (ber_len_t *) ((char *) p - p[-1]); + } + sh->sh_last = p; + VGMEMP_TRIM(sh, sh->sh_base, + (char *) sh->sh_last - (char *) sh->sh_base); + } + + } else { + int size_shift, order_size; + struct slab_object *so; + unsigned long diff; + int i, inserted = 0, order = -1; + + size_shift = size + sizeof(ber_len_t) - 1; + do { + order++; + } while (size_shift >>= 1); + + for (i = order, tmpp = p; i <= sh->sh_maxorder; i++) { + order_size = 1 << (i+1); + diff = (unsigned long)((char*)tmpp - (char*)sh->sh_base) >> (i+1); + sh->sh_map[i-order_start][diff>>3] &= (~(1 << (diff & 0x7))); + if (diff == ((diff>>1)<<1)) { + if (!(sh->sh_map[i-order_start][(diff+1)>>3] & + (1<<((diff+1)&0x7)))) { + so = LDAP_LIST_FIRST(&sh->sh_free[i-order_start]); + while (so) { + if ((char*)so->so_ptr == (char*)tmpp) { + LDAP_LIST_REMOVE( so, so_link ); + } else if ((char*)so->so_ptr == + (char*)tmpp + order_size) { + LDAP_LIST_REMOVE(so, so_link); + break; + } + so = LDAP_LIST_NEXT(so, so_link); + } + if (so) { + if (i < sh->sh_maxorder) { + inserted = 1; + so->so_ptr = tmpp; + LDAP_LIST_INSERT_HEAD(&sh->sh_free[i-order_start+1], + so, so_link); + } + continue; + } else { + if (LDAP_LIST_EMPTY(&sh->sh_sopool)) { + slap_replenish_sopool(sh); + } + so = LDAP_LIST_FIRST(&sh->sh_sopool); + LDAP_LIST_REMOVE(so, so_link); + so->so_ptr = tmpp; + LDAP_LIST_INSERT_HEAD(&sh->sh_free[i-order_start], + so, so_link); + break; + + Debug(LDAP_DEBUG_TRACE, "slap_sl_free: " + "free object not found while bit is clear.\n", + 0, 0, 0); + assert(so != NULL); + + } + } else { + if (!inserted) { + if (LDAP_LIST_EMPTY(&sh->sh_sopool)) { + slap_replenish_sopool(sh); + } + so = LDAP_LIST_FIRST(&sh->sh_sopool); + LDAP_LIST_REMOVE(so, so_link); + so->so_ptr = tmpp; + LDAP_LIST_INSERT_HEAD(&sh->sh_free[i-order_start], + so, so_link); + } + break; + } + } else { + if (!(sh->sh_map[i-order_start][(diff-1)>>3] & + (1<<((diff-1)&0x7)))) { + so = LDAP_LIST_FIRST(&sh->sh_free[i-order_start]); + while (so) { + if ((char*)so->so_ptr == (char*)tmpp) { + LDAP_LIST_REMOVE(so, so_link); + } else if ((char*)tmpp == (char *)so->so_ptr + order_size) { + LDAP_LIST_REMOVE(so, so_link); + tmpp = so->so_ptr; + break; + } + so = LDAP_LIST_NEXT(so, so_link); + } + if (so) { + if (i < sh->sh_maxorder) { + inserted = 1; + LDAP_LIST_INSERT_HEAD(&sh->sh_free[i-order_start+1], so, so_link); + continue; + } + } else { + if (LDAP_LIST_EMPTY(&sh->sh_sopool)) { + slap_replenish_sopool(sh); + } + so = LDAP_LIST_FIRST(&sh->sh_sopool); + LDAP_LIST_REMOVE(so, so_link); + so->so_ptr = tmpp; + LDAP_LIST_INSERT_HEAD(&sh->sh_free[i-order_start], + so, so_link); + break; + + Debug(LDAP_DEBUG_TRACE, "slap_sl_free: " + "free object not found while bit is clear.\n", + 0, 0, 0 ); + assert(so != NULL); + + } + } else { + if ( !inserted ) { + if (LDAP_LIST_EMPTY(&sh->sh_sopool)) { + slap_replenish_sopool(sh); + } + so = LDAP_LIST_FIRST(&sh->sh_sopool); + LDAP_LIST_REMOVE(so, so_link); + so->so_ptr = tmpp; + LDAP_LIST_INSERT_HEAD(&sh->sh_free[i-order_start], + so, so_link); + } + break; + } + } + } + } +} + +/* + * Return the memory context of the current thread if the given block of + * memory belongs to it, otherwise return NULL. + */ +void * +slap_sl_context( void *ptr ) +{ + void *memctx; + struct slab_heap *sh; + + if ( slapMode & SLAP_TOOL_MODE ) return NULL; + + sh = GET_MEMCTX(ldap_pvt_thread_pool_context(), &memctx); + if (sh && ptr >= sh->sh_base && ptr <= sh->sh_end) { + return sh; + } + return NULL; +} + +static struct slab_object * +slap_replenish_sopool( + struct slab_heap* sh +) +{ + struct slab_object *so_block; + int i; + + so_block = (struct slab_object *)ch_malloc( + SLAP_SLAB_SOBLOCK * sizeof(struct slab_object)); + + if ( so_block == NULL ) { + return NULL; + } + + so_block[0].so_blockhead = 1; + LDAP_LIST_INSERT_HEAD(&sh->sh_sopool, &so_block[0], so_link); + for (i = 1; i < SLAP_SLAB_SOBLOCK; i++) { + so_block[i].so_blockhead = 0; + LDAP_LIST_INSERT_HEAD(&sh->sh_sopool, &so_block[i], so_link ); + } + + return so_block; +} + +#ifdef SLAPD_UNUSED +static void +print_slheap(int level, void *ctx) +{ + struct slab_heap *sh = ctx; + struct slab_object *so; + int i, j, once = 0; + + if (!ctx) { + Debug(level, "NULL memctx\n", 0, 0, 0); + return; + } + + Debug(level, "sh->sh_maxorder=%d\n", sh->sh_maxorder, 0, 0); + + for (i = order_start; i <= sh->sh_maxorder; i++) { + once = 0; + Debug(level, "order=%d\n", i, 0, 0); + for (j = 0; j < (1<<(sh->sh_maxorder-i))/8; j++) { + Debug(level, "%02x ", sh->sh_map[i-order_start][j], 0, 0); + once = 1; + } + if (!once) { + Debug(level, "%02x ", sh->sh_map[i-order_start][0], 0, 0); + } + Debug(level, "\n", 0, 0, 0); + Debug(level, "free list:\n", 0, 0, 0); + so = LDAP_LIST_FIRST(&sh->sh_free[i-order_start]); + while (so) { + Debug(level, "%p\n", so->so_ptr, 0, 0); + so = LDAP_LIST_NEXT(so, so_link); + } + } +} +#endif diff --git a/servers/slapd/slap.h b/servers/slapd/slap.h new file mode 100644 index 0000000..7581967 --- /dev/null +++ b/servers/slapd/slap.h @@ -0,0 +1,3347 @@ +/* slap.h - stand alone ldap server include file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#ifndef _SLAP_H_ +#define _SLAP_H_ + +#include "ldap_defaults.h" + +#include <stdio.h> +#include <ac/stdlib.h> + +#include <sys/types.h> +#include <ac/syslog.h> +#include <ac/regex.h> +#include <ac/signal.h> +#include <ac/socket.h> +#include <ac/time.h> +#include <ac/param.h> + +#include "avl.h" + +#ifndef ldap_debug +#define ldap_debug slap_debug +#endif + +#include "ldap_log.h" + +#include <ldap.h> +#include <ldap_schema.h> + +#include "lber_pvt.h" +#include "ldap_pvt.h" +#include "ldap_pvt_thread.h" +#include "ldap_queue.h" + +LDAP_BEGIN_DECL + +#ifdef LDAP_DEVEL +#define LDAP_COLLECTIVE_ATTRIBUTES +#define LDAP_COMP_MATCH +#define LDAP_SYNC_TIMESTAMP +#define SLAP_CONTROL_X_WHATFAILED +#define SLAP_CONFIG_DELETE +#ifndef SLAP_SCHEMA_EXPOSE +#define SLAP_SCHEMA_EXPOSE +#endif +#endif + +#define LDAP_DYNAMIC_OBJECTS +#define SLAP_CONTROL_X_TREE_DELETE LDAP_CONTROL_X_TREE_DELETE +#define SLAP_CONTROL_X_SESSION_TRACKING +#define SLAP_DISTPROC + +#ifdef ENABLE_REWRITE +#define SLAP_AUTH_REWRITE 1 /* use librewrite for sasl-regexp */ +#endif + +/* + * SLAPD Memory allocation macros + * + * Unlike ch_*() routines, these routines do not assert() upon + * allocation error. They are intended to be used instead of + * ch_*() routines where the caller has implemented proper + * checking for and handling of allocation errors. + * + * Patches to convert ch_*() calls to SLAP_*() calls welcomed. + */ +#define SLAP_MALLOC(s) ber_memalloc((s)) +#define SLAP_CALLOC(n,s) ber_memcalloc((n),(s)) +#define SLAP_REALLOC(p,s) ber_memrealloc((p),(s)) +#define SLAP_FREE(p) ber_memfree((p)) +#define SLAP_VFREE(v) ber_memvfree((void**)(v)) +#define SLAP_STRDUP(s) ber_strdup((s)) +#define SLAP_STRNDUP(s,l) ber_strndup((s),(l)) + +#ifdef f_next +#undef f_next /* name conflict between sys/file.h on SCO and struct filter */ +#endif + +#define SERVICE_NAME OPENLDAP_PACKAGE "-slapd" +#define SLAPD_ANONYMOUS "" + +#ifdef HAVE_TCPD +# include <tcpd.h> +# define SLAP_STRING_UNKNOWN STRING_UNKNOWN +#else /* ! TCP Wrappers */ +# define SLAP_STRING_UNKNOWN "unknown" +#endif /* ! TCP Wrappers */ + +/* LDAPMod.mod_op value ===> Must be kept in sync with ldap.h! */ +/* These values are used internally by the backends. */ +/* SLAP_MOD_SOFTADD allows adding values that already exist without getting + * an error as required by modrdn when the new rdn was already an attribute + * value itself. + */ +#define SLAP_MOD_SOFTADD 0x1000 +/* SLAP_MOD_SOFTDEL allows deleting values if they exist without getting + * an error otherwise. + */ +#define SLAP_MOD_SOFTDEL 0x1001 +/* SLAP_MOD_ADD_IF_NOT_PRESENT allows adding values unless the attribute + * is already present without getting an error. + */ +#define SLAP_MOD_ADD_IF_NOT_PRESENT 0x1002 +/* SLAP_MOD_DEL_IF_PRESENT allows deleting values if the attribute + * is present, without getting an error otherwise. + * The semantics can be obtained using SLAP_MOD_SOFTDEL with NULL values. + */ + +#define MAXREMATCHES (100) + +#define SLAP_MAX_WORKER_THREADS (16) + +#define SLAP_SB_MAX_INCOMING_DEFAULT ((1<<18) - 1) +#define SLAP_SB_MAX_INCOMING_AUTH ((1<<24) - 1) + +#define SLAP_CONN_MAX_PENDING_DEFAULT 100 +#define SLAP_CONN_MAX_PENDING_AUTH 1000 + +#define SLAP_TEXT_BUFLEN (256) + +/* pseudo error code indicating abandoned operation */ +#define SLAPD_ABANDON (-1024) + +/* pseudo error code indicating disconnect */ +#define SLAPD_DISCONNECT (-1025) + +/* unknown config file directive */ +#define SLAP_CONF_UNKNOWN (-1026) + +/* We assume "C" locale, that is US-ASCII */ +#define ASCII_SPACE(c) ( (c) == ' ' ) +#define ASCII_LOWER(c) ( (c) >= 'a' && (c) <= 'z' ) +#define ASCII_UPPER(c) ( (c) >= 'A' && (c) <= 'Z' ) +#define ASCII_ALPHA(c) ( ASCII_LOWER(c) || ASCII_UPPER(c) ) +#define ASCII_DIGIT(c) ( (c) >= '0' && (c) <= '9' ) +#define ASCII_HEXLOWER(c) ( (c) >= 'a' && (c) <= 'f' ) +#define ASCII_HEXUPPER(c) ( (c) >= 'A' && (c) <= 'F' ) +#define ASCII_HEX(c) ( ASCII_DIGIT(c) || \ + ASCII_HEXLOWER(c) || ASCII_HEXUPPER(c) ) +#define ASCII_ALNUM(c) ( ASCII_ALPHA(c) || ASCII_DIGIT(c) ) +#define ASCII_PRINTABLE(c) ( (c) >= ' ' && (c) <= '~' ) + +#define SLAP_NIBBLE(c) ((c)&0x0f) +#define SLAP_ESCAPE_CHAR ('\\') +#define SLAP_ESCAPE_LO(c) ( "0123456789ABCDEF"[SLAP_NIBBLE(c)] ) +#define SLAP_ESCAPE_HI(c) ( SLAP_ESCAPE_LO((c)>>4) ) + +#define FILTER_ESCAPE(c) ( (c) == '*' || (c) == '\\' \ + || (c) == '(' || (c) == ')' || !ASCII_PRINTABLE(c) ) + +#define DN_ESCAPE(c) ((c) == SLAP_ESCAPE_CHAR) +/* NOTE: for consistency, this macro must only operate + * on normalized/pretty DN, such that ';' is never used + * as RDN separator, and all occurrences of ';' must be escaped */ +#define DN_SEPARATOR(c) ((c) == ',') +#define RDN_ATTRTYPEANDVALUE_SEPARATOR(c) ((c) == '+') /* RFC 4514 */ +#define RDN_SEPARATOR(c) (DN_SEPARATOR(c) || RDN_ATTRTYPEANDVALUE_SEPARATOR(c)) +#define RDN_NEEDSESCAPE(c) ((c) == '\\' || (c) == '"') + +#define DESC_LEADCHAR(c) ( ASCII_ALPHA(c) ) +#define DESC_CHAR(c) ( ASCII_ALNUM(c) || (c) == '-' ) +#define OID_LEADCHAR(c) ( ASCII_DIGIT(c) ) +#define OID_SEPARATOR(c) ( (c) == '.' ) +#define OID_CHAR(c) ( OID_LEADCHAR(c) || OID_SEPARATOR(c) ) + +#define ATTR_LEADCHAR(c) ( DESC_LEADCHAR(c) || OID_LEADCHAR(c) ) +#define ATTR_CHAR(c) ( DESC_CHAR((c)) || OID_SEPARATOR(c) ) + +#define AD_LEADCHAR(c) ( ATTR_LEADCHAR(c) ) +#define AD_CHAR(c) ( ATTR_CHAR(c) || (c) == ';' ) + +#define SLAP_NUMERIC(c) ( ASCII_DIGIT(c) || ASCII_SPACE(c) ) + +#define SLAP_PRINTABLE(c) ( ASCII_ALNUM(c) || (c) == '\'' || \ + (c) == '(' || (c) == ')' || (c) == '+' || (c) == ',' || \ + (c) == '-' || (c) == '.' || (c) == '/' || (c) == ':' || \ + (c) == '?' || (c) == ' ' || (c) == '=' ) +#define SLAP_PRINTABLES(c) ( SLAP_PRINTABLE(c) || (c) == '$' ) + +/* must match in schema_init.c */ +#define SLAPD_DN_SYNTAX "1.3.6.1.4.1.1466.115.121.1.12" +#define SLAPD_NAMEUID_SYNTAX "1.3.6.1.4.1.1466.115.121.1.34" +#define SLAPD_INTEGER_SYNTAX "1.3.6.1.4.1.1466.115.121.1.27" +#define SLAPD_GROUP_ATTR "member" +#define SLAPD_GROUP_CLASS "groupOfNames" +#define SLAPD_ROLE_ATTR "roleOccupant" +#define SLAPD_ROLE_CLASS "organizationalRole" + +#define SLAPD_TOP_OID "2.5.6.0" + +LDAP_SLAPD_V (int) slap_debug; + +typedef unsigned long slap_mask_t; + +/* Security Strength Factor */ +typedef unsigned slap_ssf_t; + +typedef struct slap_ssf_set { + slap_ssf_t sss_ssf; + slap_ssf_t sss_transport; + slap_ssf_t sss_tls; + slap_ssf_t sss_sasl; + slap_ssf_t sss_update_ssf; + slap_ssf_t sss_update_transport; + slap_ssf_t sss_update_tls; + slap_ssf_t sss_update_sasl; + slap_ssf_t sss_simple_bind; +} slap_ssf_set_t; + +/* Flags for telling slap_sasl_getdn() what type of identity is being passed */ +#define SLAP_GETDN_AUTHCID 2 +#define SLAP_GETDN_AUTHZID 4 + +/* + * Index types + */ +#define SLAP_INDEX_TYPE 0x00FFUL +#define SLAP_INDEX_UNDEFINED 0x0001UL +#define SLAP_INDEX_PRESENT 0x0002UL +#define SLAP_INDEX_EQUALITY 0x0004UL +#define SLAP_INDEX_APPROX 0x0008UL +#define SLAP_INDEX_SUBSTR 0x0010UL +#define SLAP_INDEX_EXTENDED 0x0020UL + +#define SLAP_INDEX_DEFAULT SLAP_INDEX_EQUALITY + +#define IS_SLAP_INDEX(mask, type) (((mask) & (type)) == (type)) + +#define SLAP_INDEX_SUBSTR_TYPE 0x0F00UL + +#define SLAP_INDEX_SUBSTR_INITIAL ( SLAP_INDEX_SUBSTR | 0x0100UL ) +#define SLAP_INDEX_SUBSTR_ANY ( SLAP_INDEX_SUBSTR | 0x0200UL ) +#define SLAP_INDEX_SUBSTR_FINAL ( SLAP_INDEX_SUBSTR | 0x0400UL ) +#define SLAP_INDEX_SUBSTR_DEFAULT \ + ( SLAP_INDEX_SUBSTR \ + | SLAP_INDEX_SUBSTR_INITIAL \ + | SLAP_INDEX_SUBSTR_ANY \ + | SLAP_INDEX_SUBSTR_FINAL ) + +/* defaults for initial/final substring indices */ +#define SLAP_INDEX_SUBSTR_IF_MINLEN_DEFAULT 2 +#define SLAP_INDEX_SUBSTR_IF_MAXLEN_DEFAULT 4 + +/* defaults for any substring indices */ +#define SLAP_INDEX_SUBSTR_ANY_LEN_DEFAULT 4 +#define SLAP_INDEX_SUBSTR_ANY_STEP_DEFAULT 2 + +/* default for ordered integer index keys */ +#define SLAP_INDEX_INTLEN_DEFAULT 4 + +#define SLAP_INDEX_FLAGS 0xF000UL +#define SLAP_INDEX_NOSUBTYPES 0x1000UL /* don't use index w/ subtypes */ +#define SLAP_INDEX_NOTAGS 0x2000UL /* don't use index w/ tags */ + +/* + * there is a single index for each attribute. these prefixes ensure + * that there is no collision among keys. + */ +#define SLAP_INDEX_EQUALITY_PREFIX '=' /* prefix for equality keys */ +#define SLAP_INDEX_APPROX_PREFIX '~' /* prefix for approx keys */ +#define SLAP_INDEX_SUBSTR_PREFIX '*' /* prefix for substring keys */ +#define SLAP_INDEX_SUBSTR_INITIAL_PREFIX '^' +#define SLAP_INDEX_SUBSTR_FINAL_PREFIX '$' +#define SLAP_INDEX_CONT_PREFIX '.' /* prefix for continuation keys */ + +#define SLAP_SYNTAX_MATCHINGRULES_OID "1.3.6.1.4.1.1466.115.121.1.30" +#define SLAP_SYNTAX_ATTRIBUTETYPES_OID "1.3.6.1.4.1.1466.115.121.1.3" +#define SLAP_SYNTAX_OBJECTCLASSES_OID "1.3.6.1.4.1.1466.115.121.1.37" +#define SLAP_SYNTAX_MATCHINGRULEUSES_OID "1.3.6.1.4.1.1466.115.121.1.31" +#define SLAP_SYNTAX_CONTENTRULE_OID "1.3.6.1.4.1.1466.115.121.1.16" + +/* + * represents schema information for a database + */ +enum { + SLAP_SCHERR_OUTOFMEM = 1, + SLAP_SCHERR_CLASS_NOT_FOUND, + SLAP_SCHERR_CLASS_BAD_USAGE, + SLAP_SCHERR_CLASS_BAD_SUP, + SLAP_SCHERR_CLASS_DUP, + SLAP_SCHERR_CLASS_INCONSISTENT, + SLAP_SCHERR_ATTR_NOT_FOUND, + SLAP_SCHERR_ATTR_BAD_MR, + SLAP_SCHERR_ATTR_BAD_USAGE, + SLAP_SCHERR_ATTR_BAD_SUP, + SLAP_SCHERR_ATTR_INCOMPLETE, + SLAP_SCHERR_ATTR_DUP, + SLAP_SCHERR_ATTR_INCONSISTENT, + SLAP_SCHERR_MR_NOT_FOUND, + SLAP_SCHERR_MR_INCOMPLETE, + SLAP_SCHERR_MR_DUP, + SLAP_SCHERR_SYN_NOT_FOUND, + SLAP_SCHERR_SYN_DUP, + SLAP_SCHERR_SYN_SUP_NOT_FOUND, + SLAP_SCHERR_SYN_SUBST_NOT_SPECIFIED, + SLAP_SCHERR_SYN_SUBST_NOT_FOUND, + SLAP_SCHERR_NO_NAME, + SLAP_SCHERR_NOT_SUPPORTED, + SLAP_SCHERR_BAD_DESCR, + SLAP_SCHERR_OIDM, + SLAP_SCHERR_CR_DUP, + SLAP_SCHERR_CR_BAD_STRUCT, + SLAP_SCHERR_CR_BAD_AUX, + SLAP_SCHERR_CR_BAD_AT, + + SLAP_SCHERR_LAST +}; + +/* forward declarations */ +typedef struct Syntax Syntax; +typedef struct MatchingRule MatchingRule; +typedef struct MatchingRuleUse MatchingRuleUse; +typedef struct MatchingRuleAssertion MatchingRuleAssertion; +typedef struct OidMacro OidMacro; +typedef struct ObjectClass ObjectClass; +typedef struct AttributeType AttributeType; +typedef struct AttributeDescription AttributeDescription; +typedef struct AttributeName AttributeName; +typedef struct ContentRule ContentRule; + +typedef struct AttributeAssertion AttributeAssertion; +typedef struct SubstringsAssertion SubstringsAssertion; +typedef struct Filter Filter; +typedef struct ValuesReturnFilter ValuesReturnFilter; +typedef struct Attribute Attribute; +#ifdef LDAP_COMP_MATCH +typedef struct ComponentData ComponentData; +typedef struct ComponentFilter ComponentFilter; +#endif + +typedef struct Entry Entry; +typedef struct Modification Modification; +typedef struct Modifications Modifications; +typedef struct LDAPModList LDAPModList; + +typedef struct BackendInfo BackendInfo; /* per backend type */ +typedef struct BackendDB BackendDB; /* per backend database */ + +typedef struct Connection Connection; +typedef struct Operation Operation; +typedef struct SlapReply SlapReply; +/* end of forward declarations */ + +typedef union Sockaddr { + struct sockaddr sa_addr; + struct sockaddr_in sa_in_addr; +#ifdef LDAP_PF_INET6 + struct sockaddr_storage sa_storage; + struct sockaddr_in6 sa_in6_addr; +#endif +#ifdef LDAP_PF_LOCAL + struct sockaddr_un sa_un_addr; +#endif +} Sockaddr; + +#ifdef LDAP_PF_INET6 +extern int slap_inet4or6; +#endif + +struct OidMacro { + struct berval som_oid; + BerVarray som_names; + BerVarray som_subs; +#define SLAP_OM_HARDCODE 0x10000U /* This is hardcoded schema */ + int som_flags; + LDAP_STAILQ_ENTRY(OidMacro) som_next; +}; + +typedef int slap_syntax_validate_func LDAP_P(( + Syntax *syntax, + struct berval * in)); + +typedef int slap_syntax_transform_func LDAP_P(( + Syntax *syntax, + struct berval * in, + struct berval * out, + void *memctx)); + +#ifdef LDAP_COMP_MATCH +typedef void* slap_component_transform_func LDAP_P(( + struct berval * in )); +struct ComponentDesc; +#endif + +struct Syntax { + LDAPSyntax ssyn_syn; +#define ssyn_oid ssyn_syn.syn_oid +#define ssyn_desc ssyn_syn.syn_desc +#define ssyn_extensions ssyn_syn.syn_extensions + /* + * Note: the former + ber_len_t ssyn_oidlen; + * has been replaced by a struct berval that uses the value + * provided by ssyn_syn.syn_oid; a macro that expands to + * the bv_len field of the berval is provided for backward + * compatibility. CAUTION: NEVER FREE THE BERVAL + */ + struct berval ssyn_bvoid; +#define ssyn_oidlen ssyn_bvoid.bv_len + + unsigned int ssyn_flags; + +#define SLAP_SYNTAX_NONE 0x0000U +#define SLAP_SYNTAX_BLOB 0x0001U /* syntax treated as blob (audio) */ +#define SLAP_SYNTAX_BINARY 0x0002U /* binary transfer required (certificate) */ +#define SLAP_SYNTAX_BER 0x0004U /* stored in BER encoding (certificate) */ +#ifdef SLAP_SCHEMA_EXPOSE +#define SLAP_SYNTAX_HIDE 0x0000U /* publish everything */ +#else +#define SLAP_SYNTAX_HIDE 0x8000U /* hide (do not publish) */ +#endif +#define SLAP_SYNTAX_HARDCODE 0x10000U /* This is hardcoded schema */ +#define SLAP_SYNTAX_DN 0x20000U /* Treat like a DN */ + + Syntax **ssyn_sups; + + slap_syntax_validate_func *ssyn_validate; + slap_syntax_transform_func *ssyn_pretty; + +#ifdef SLAPD_BINARY_CONVERSION + /* convert to and from binary */ + slap_syntax_transform_func *ssyn_ber2str; + slap_syntax_transform_func *ssyn_str2ber; +#endif +#ifdef LDAP_COMP_MATCH + slap_component_transform_func *ssyn_attr2comp; + struct ComponentDesc* ssync_comp_syntax; +#endif + + LDAP_STAILQ_ENTRY(Syntax) ssyn_next; +}; + +#define slap_syntax_is_flag(s,flag) ((int)((s)->ssyn_flags & (flag)) ? 1 : 0) +#define slap_syntax_is_blob(s) slap_syntax_is_flag((s),SLAP_SYNTAX_BLOB) +#define slap_syntax_is_binary(s) slap_syntax_is_flag((s),SLAP_SYNTAX_BINARY) +#define slap_syntax_is_ber(s) slap_syntax_is_flag((s),SLAP_SYNTAX_BER) +#define slap_syntax_is_hidden(s) slap_syntax_is_flag((s),SLAP_SYNTAX_HIDE) + +typedef struct slap_syntax_defs_rec { + char *sd_desc; + int sd_flags; + char **sd_sups; + slap_syntax_validate_func *sd_validate; + slap_syntax_transform_func *sd_pretty; +#ifdef SLAPD_BINARY_CONVERSION + slap_syntax_transform_func *sd_ber2str; + slap_syntax_transform_func *sd_str2ber; +#endif +} slap_syntax_defs_rec; + +/* X -> Y Converter */ +typedef int slap_mr_convert_func LDAP_P(( + struct berval * in, + struct berval * out, + void *memctx )); + +/* Normalizer */ +typedef int slap_mr_normalize_func LDAP_P(( + slap_mask_t use, + Syntax *syntax, /* NULL if in is asserted value */ + MatchingRule *mr, + struct berval *in, + struct berval *out, + void *memctx )); + +/* Match (compare) function */ +typedef int slap_mr_match_func LDAP_P(( + int *match, + slap_mask_t use, + Syntax *syntax, /* syntax of stored value */ + MatchingRule *mr, + struct berval *value, + void *assertValue )); + +/* Index generation function */ +typedef int slap_mr_indexer_func LDAP_P(( + slap_mask_t use, + slap_mask_t mask, + Syntax *syntax, /* syntax of stored value */ + MatchingRule *mr, + struct berval *prefix, + BerVarray values, + BerVarray *keys, + void *memctx )); + +/* Filter index function */ +typedef int slap_mr_filter_func LDAP_P(( + slap_mask_t use, + slap_mask_t mask, + Syntax *syntax, /* syntax of stored value */ + MatchingRule *mr, + struct berval *prefix, + void *assertValue, + BerVarray *keys, + void *memctx )); + +struct MatchingRule { + LDAPMatchingRule smr_mrule; + MatchingRuleUse *smr_mru; + /* RFC 4512 string representation */ + struct berval smr_str; + /* + * Note: the former + * ber_len_t smr_oidlen; + * has been replaced by a struct berval that uses the value + * provided by smr_mrule.mr_oid; a macro that expands to + * the bv_len field of the berval is provided for backward + * compatibility. CAUTION: NEVER FREE THE BERVAL + */ + struct berval smr_bvoid; +#define smr_oidlen smr_bvoid.bv_len + + slap_mask_t smr_usage; + +#ifdef SLAP_SCHEMA_EXPOSE +#define SLAP_MR_HIDE 0x0000U +#else +#define SLAP_MR_HIDE 0x8000U +#endif + +#define SLAP_MR_MUTATION_NORMALIZER 0x4000U + +#define SLAP_MR_TYPE_MASK 0x0F00U +#define SLAP_MR_SUBTYPE_MASK 0x00F0U +#define SLAP_MR_USAGE 0x000FU + +#define SLAP_MR_NONE 0x0000U +#define SLAP_MR_EQUALITY 0x0100U +#define SLAP_MR_ORDERING 0x0200U +#define SLAP_MR_SUBSTR 0x0400U +#define SLAP_MR_EXT 0x0800U /* implicitly extensible */ +#define SLAP_MR_ORDERED_INDEX 0x1000U +#ifdef LDAP_COMP_MATCH +#define SLAP_MR_COMPONENT 0x2000U +#endif + +#define SLAP_MR_EQUALITY_APPROX ( SLAP_MR_EQUALITY | 0x0010U ) + +#define SLAP_MR_SUBSTR_INITIAL ( SLAP_MR_SUBSTR | 0x0010U ) +#define SLAP_MR_SUBSTR_ANY ( SLAP_MR_SUBSTR | 0x0020U ) +#define SLAP_MR_SUBSTR_FINAL ( SLAP_MR_SUBSTR | 0x0040U ) + + +/* + * The asserted value, depending on the particular usage, + * is expected to conform to either the assertion syntax + * or the attribute syntax. In some cases, the syntax of + * the value is known. If so, these flags indicate which + * syntax the value is expected to conform to. If not, + * neither of these flags is set (until the syntax of the + * provided value is determined). If the value is of the + * attribute syntax, the flag is changed once a value of + * the assertion syntax is derived from the provided value. + */ +#define SLAP_MR_VALUE_OF_ASSERTION_SYNTAX 0x0001U +#define SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX 0x0002U +#define SLAP_MR_VALUE_OF_SYNTAX (SLAP_MR_VALUE_OF_ASSERTION_SYNTAX|SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX) +#define SLAP_MR_DENORMALIZE (SLAP_MR_MUTATION_NORMALIZER) + +#define SLAP_MR_IS_VALUE_OF_ATTRIBUTE_SYNTAX( usage ) \ + ((usage) & SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX ) +#define SLAP_MR_IS_VALUE_OF_ASSERTION_SYNTAX( usage ) \ + ((usage) & SLAP_MR_VALUE_OF_ASSERTION_SYNTAX ) +#ifdef LDAP_DEBUG +#define SLAP_MR_IS_VALUE_OF_SYNTAX( usage ) \ + ((usage) & SLAP_MR_VALUE_OF_SYNTAX) +#else +#define SLAP_MR_IS_VALUE_OF_SYNTAX( usage ) (1) +#endif +#define SLAP_MR_IS_DENORMALIZE( usage ) \ + ((usage) & SLAP_MR_DENORMALIZE ) + +/* either or both the asserted value or attribute value + * may be provided in normalized form + */ +#define SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH 0x0004U +#define SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH 0x0008U + +#define SLAP_IS_MR_ASSERTION_SYNTAX_MATCH( usage ) \ + (!((usage) & SLAP_MR_ATTRIBUTE_SYNTAX_MATCH)) +#define SLAP_IS_MR_ATTRIBUTE_SYNTAX_MATCH( usage ) \ + ((usage) & SLAP_MR_ATTRIBUTE_SYNTAX_MATCH) + +#define SLAP_IS_MR_ATTRIBUTE_SYNTAX_CONVERTED_MATCH( usage ) \ + (((usage) & SLAP_MR_ATTRIBUTE_SYNTAX_CONVERTED_MATCH) \ + == SLAP_MR_ATTRIBUTE_SYNTAX_CONVERTED_MATCH) +#define SLAP_IS_MR_ATTRIBUTE_SYNTAX_NONCONVERTED_MATCH( usage ) \ + (((usage) & SLAP_MR_ATTRIBUTE_SYNTAX_CONVERTED_MATCH) \ + == SLAP_MR_ATTRIBUTE_SYNTAX_MATCH) + +#define SLAP_IS_MR_ASSERTED_VALUE_NORMALIZED_MATCH( usage ) \ + ((usage) & SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH ) +#define SLAP_IS_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH( usage ) \ + ((usage) & SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH ) + + Syntax *smr_syntax; + slap_mr_convert_func *smr_convert; + slap_mr_normalize_func *smr_normalize; + slap_mr_match_func *smr_match; + slap_mr_indexer_func *smr_indexer; + slap_mr_filter_func *smr_filter; + + /* + * null terminated array of syntaxes compatible with this syntax + * note: when MS_EXT is set, this MUST NOT contain the assertion + * syntax of the rule. When MS_EXT is not set, it MAY. + */ + Syntax **smr_compat_syntaxes; + + /* + * For equality rules, refers to an associated approximate rule. + * For non-equality rules, refers to an associated equality rule. + */ + MatchingRule *smr_associated; + +#define SLAP_MR_ASSOCIATED(mr,amr) \ + (((mr) == (amr)) || ((mr)->smr_associated == (amr))) + + LDAP_SLIST_ENTRY(MatchingRule) smr_next; + +#define smr_oid smr_mrule.mr_oid +#define smr_names smr_mrule.mr_names +#define smr_desc smr_mrule.mr_desc +#define smr_obsolete smr_mrule.mr_obsolete +#define smr_syntax_oid smr_mrule.mr_syntax_oid +#define smr_extensions smr_mrule.mr_extensions +}; + +struct MatchingRuleUse { + LDAPMatchingRuleUse smru_mruleuse; + MatchingRule *smru_mr; + /* RFC 4512 string representation */ + struct berval smru_str; + + LDAP_SLIST_ENTRY(MatchingRuleUse) smru_next; + +#define smru_oid smru_mruleuse.mru_oid +#define smru_names smru_mruleuse.mru_names +#define smru_desc smru_mruleuse.mru_desc +#define smru_obsolete smru_mruleuse.mru_obsolete +#define smru_applies_oids smru_mruleuse.mru_applies_oids + +#define smru_usage smru_mr->smr_usage +} /* MatchingRuleUse */ ; + +typedef struct slap_mrule_defs_rec { + char * mrd_desc; + slap_mask_t mrd_usage; + char ** mrd_compat_syntaxes; + slap_mr_convert_func * mrd_convert; + slap_mr_normalize_func * mrd_normalize; + slap_mr_match_func * mrd_match; + slap_mr_indexer_func * mrd_indexer; + slap_mr_filter_func * mrd_filter; + + /* For equality rule, this may refer to an associated approximate rule */ + /* For non-equality rule, this may refer to an associated equality rule */ + char * mrd_associated; +} slap_mrule_defs_rec; + +typedef int (AttributeTypeSchemaCheckFN)( + BackendDB *be, + Entry *e, + Attribute *attr, + const char** text, + char *textbuf, size_t textlen ); + +struct AttributeType { + LDAPAttributeType sat_atype; + struct berval sat_cname; + AttributeType *sat_sup; + AttributeType **sat_subtypes; + MatchingRule *sat_equality; + MatchingRule *sat_approx; + MatchingRule *sat_ordering; + MatchingRule *sat_substr; + Syntax *sat_syntax; + + AttributeTypeSchemaCheckFN *sat_check; + char *sat_oidmacro; /* attribute OID */ + char *sat_soidmacro; /* syntax OID */ + +#define SLAP_AT_NONE 0x0000U +#define SLAP_AT_ABSTRACT 0x0100U /* cannot be instantiated */ +#define SLAP_AT_FINAL 0x0200U /* cannot be subtyped */ +#ifdef SLAP_SCHEMA_EXPOSE +#define SLAP_AT_HIDE 0x0000U /* publish everything */ +#else +#define SLAP_AT_HIDE 0x8000U /* hide attribute */ +#endif +#define SLAP_AT_DYNAMIC 0x0400U /* dynamically generated */ + +#define SLAP_AT_MANAGEABLE 0x0800U /* no-user-mod can be by-passed */ + +/* Note: ORDERED values have an ordering specifically set by the + * user, denoted by the {x} ordering prefix on the values. + * + * SORTED values are simply sorted by memcmp. SORTED values can + * be efficiently located by binary search. ORDERED values have no + * such advantage. An attribute cannot have both properties. + */ +#define SLAP_AT_ORDERED_VAL 0x0001U /* values are ordered */ +#define SLAP_AT_ORDERED_SIB 0x0002U /* siblings are ordered */ +#define SLAP_AT_ORDERED 0x0003U /* value has order index */ + +#define SLAP_AT_SORTED_VAL 0x0010U /* values should be sorted */ + +#define SLAP_AT_HARDCODE 0x10000U /* hardcoded schema */ +#define SLAP_AT_DELETED 0x20000U + + slap_mask_t sat_flags; + + LDAP_STAILQ_ENTRY(AttributeType) sat_next; + +#define sat_oid sat_atype.at_oid +#define sat_names sat_atype.at_names +#define sat_desc sat_atype.at_desc +#define sat_obsolete sat_atype.at_obsolete +#define sat_sup_oid sat_atype.at_sup_oid +#define sat_equality_oid sat_atype.at_equality_oid +#define sat_ordering_oid sat_atype.at_ordering_oid +#define sat_substr_oid sat_atype.at_substr_oid +#define sat_syntax_oid sat_atype.at_syntax_oid +#define sat_single_value sat_atype.at_single_value +#define sat_collective sat_atype.at_collective +#define sat_no_user_mod sat_atype.at_no_user_mod +#define sat_usage sat_atype.at_usage +#define sat_extensions sat_atype.at_extensions + + AttributeDescription *sat_ad; + ldap_pvt_thread_mutex_t sat_ad_mutex; +}; + +#define is_at_operational(at) ((at)->sat_usage) +#define is_at_single_value(at) ((at)->sat_single_value) +#define is_at_collective(at) ((at)->sat_collective) +#define is_at_obsolete(at) ((at)->sat_obsolete) +#define is_at_no_user_mod(at) ((at)->sat_no_user_mod) + +typedef int (ObjectClassSchemaCheckFN)( + BackendDB *be, + Entry *e, + ObjectClass *oc, + const char** text, + char *textbuf, size_t textlen ); + +struct ObjectClass { + LDAPObjectClass soc_oclass; + struct berval soc_cname; + ObjectClass **soc_sups; + AttributeType **soc_required; + AttributeType **soc_allowed; + ObjectClassSchemaCheckFN *soc_check; + char *soc_oidmacro; + slap_mask_t soc_flags; +#define soc_oid soc_oclass.oc_oid +#define soc_names soc_oclass.oc_names +#define soc_desc soc_oclass.oc_desc +#define soc_obsolete soc_oclass.oc_obsolete +#define soc_sup_oids soc_oclass.oc_sup_oids +#define soc_kind soc_oclass.oc_kind +#define soc_at_oids_must soc_oclass.oc_at_oids_must +#define soc_at_oids_may soc_oclass.oc_at_oids_may +#define soc_extensions soc_oclass.oc_extensions + + LDAP_STAILQ_ENTRY(ObjectClass) soc_next; +}; + +#define SLAP_OCF_SET_FLAGS 0x1 +#define SLAP_OCF_CHECK_SUP 0x2 +#define SLAP_OCF_MASK (SLAP_OCF_SET_FLAGS|SLAP_OCF_CHECK_SUP) + +#define SLAP_OC_ALIAS 0x0001 +#define SLAP_OC_REFERRAL 0x0002 +#define SLAP_OC_SUBENTRY 0x0004 +#define SLAP_OC_DYNAMICOBJECT 0x0008 +#define SLAP_OC_COLLECTIVEATTRIBUTESUBENTRY 0x0010 +#define SLAP_OC_GLUE 0x0020 +#define SLAP_OC_SYNCPROVIDERSUBENTRY 0x0040 +#define SLAP_OC_SYNCCONSUMERSUBENTRY 0x0080 +#define SLAP_OC__MASK 0x00FF +#define SLAP_OC__END 0x0100 +#define SLAP_OC_OPERATIONAL 0x4000 +#ifdef SLAP_SCHEMA_EXPOSE +#define SLAP_OC_HIDE 0x0000 +#else +#define SLAP_OC_HIDE 0x8000 +#endif +#define SLAP_OC_HARDCODE 0x10000U /* This is hardcoded schema */ +#define SLAP_OC_DELETED 0x20000U + +/* + * DIT content rule + */ +struct ContentRule { + LDAPContentRule scr_crule; + ObjectClass *scr_sclass; + ObjectClass **scr_auxiliaries; /* optional */ + AttributeType **scr_required; /* optional */ + AttributeType **scr_allowed; /* optional */ + AttributeType **scr_precluded; /* optional */ +#define scr_oid scr_crule.cr_oid +#define scr_names scr_crule.cr_names +#define scr_desc scr_crule.cr_desc +#define scr_obsolete scr_crule.cr_obsolete +#define scr_oc_oids_aux scr_crule.cr_oc_oids_aux +#define scr_at_oids_must scr_crule.cr_at_oids_must +#define scr_at_oids_may scr_crule.cr_at_oids_may +#define scr_at_oids_not scr_crule.cr_at_oids_not + + char *scr_oidmacro; +#define SLAP_CR_HARDCODE 0x10000U + int scr_flags; + + LDAP_STAILQ_ENTRY( ContentRule ) scr_next; +}; + +/* Represents a recognized attribute description ( type + options ). */ +struct AttributeDescription { + AttributeDescription *ad_next; + AttributeType *ad_type; /* attribute type, must be specified */ + struct berval ad_cname; /* canonical name, must be specified */ + struct berval ad_tags; /* empty if no tagging options */ + unsigned ad_flags; +#define SLAP_DESC_NONE 0x00U +#define SLAP_DESC_BINARY 0x01U +#define SLAP_DESC_TAG_RANGE 0x80U +#define SLAP_DESC_TEMPORARY 0x1000U + unsigned ad_index; +}; + +/* flags to slap_*2undef_ad to register undefined (0, the default) + * or proxied (SLAP_AD_PROXIED) AttributeDescriptions; the additional + * SLAP_AD_NOINSERT is to lookup without insert */ +#define SLAP_AD_UNDEF 0x00U +#define SLAP_AD_PROXIED 0x01U +#define SLAP_AD_NOINSERT 0x02U + +#define SLAP_AN_OCEXCLUDE 0x01 +#define SLAP_AN_OCINITED 0x02 + +struct AttributeName { + struct berval an_name; + AttributeDescription *an_desc; + int an_flags; + ObjectClass *an_oc; +}; + +#define slap_ad_is_tagged(ad) ( (ad)->ad_tags.bv_len != 0 ) +#define slap_ad_is_tag_range(ad) \ + ( ((ad)->ad_flags & SLAP_DESC_TAG_RANGE) ? 1 : 0 ) +#define slap_ad_is_binary(ad) \ + ( ((ad)->ad_flags & SLAP_DESC_BINARY) ? 1 : 0 ) + +/* + * pointers to schema elements used internally + */ +struct slap_internal_schema { + /* objectClass */ + ObjectClass *si_oc_top; + ObjectClass *si_oc_extensibleObject; + ObjectClass *si_oc_alias; + ObjectClass *si_oc_referral; + ObjectClass *si_oc_rootdse; + ObjectClass *si_oc_subentry; + ObjectClass *si_oc_subschema; + ObjectClass *si_oc_collectiveAttributeSubentry; + ObjectClass *si_oc_dynamicObject; + + ObjectClass *si_oc_glue; + ObjectClass *si_oc_syncConsumerSubentry; + ObjectClass *si_oc_syncProviderSubentry; + + /* objectClass attribute descriptions */ + AttributeDescription *si_ad_objectClass; + + /* operational attribute descriptions */ + AttributeDescription *si_ad_structuralObjectClass; + AttributeDescription *si_ad_creatorsName; + AttributeDescription *si_ad_createTimestamp; + AttributeDescription *si_ad_modifiersName; + AttributeDescription *si_ad_modifyTimestamp; + AttributeDescription *si_ad_hasSubordinates; + AttributeDescription *si_ad_subschemaSubentry; + AttributeDescription *si_ad_collectiveSubentries; + AttributeDescription *si_ad_collectiveExclusions; + AttributeDescription *si_ad_entryDN; + AttributeDescription *si_ad_entryUUID; + AttributeDescription *si_ad_entryCSN; + AttributeDescription *si_ad_namingCSN; + + AttributeDescription *si_ad_dseType; + AttributeDescription *si_ad_syncreplCookie; + AttributeDescription *si_ad_syncTimestamp; + AttributeDescription *si_ad_contextCSN; + + /* root DSE attribute descriptions */ + AttributeDescription *si_ad_altServer; + AttributeDescription *si_ad_namingContexts; + AttributeDescription *si_ad_supportedControl; + AttributeDescription *si_ad_supportedExtension; + AttributeDescription *si_ad_supportedLDAPVersion; + AttributeDescription *si_ad_supportedSASLMechanisms; + AttributeDescription *si_ad_supportedFeatures; + AttributeDescription *si_ad_monitorContext; + AttributeDescription *si_ad_vendorName; + AttributeDescription *si_ad_vendorVersion; + AttributeDescription *si_ad_configContext; + + /* subentry attribute descriptions */ + AttributeDescription *si_ad_administrativeRole; + AttributeDescription *si_ad_subtreeSpecification; + + /* subschema subentry attribute descriptions */ + AttributeDescription *si_ad_attributeTypes; + AttributeDescription *si_ad_ditContentRules; + AttributeDescription *si_ad_ditStructureRules; + AttributeDescription *si_ad_ldapSyntaxes; + AttributeDescription *si_ad_matchingRules; + AttributeDescription *si_ad_matchingRuleUse; + AttributeDescription *si_ad_nameForms; + AttributeDescription *si_ad_objectClasses; + + /* Aliases & Referrals */ + AttributeDescription *si_ad_aliasedObjectName; + AttributeDescription *si_ad_ref; + + /* Access Control Internals */ + AttributeDescription *si_ad_entry; + AttributeDescription *si_ad_children; + AttributeDescription *si_ad_saslAuthzTo; + AttributeDescription *si_ad_saslAuthzFrom; + + /* dynamic entries */ + AttributeDescription *si_ad_entryTtl; + AttributeDescription *si_ad_dynamicSubtrees; + + /* Other attributes descriptions */ + AttributeDescription *si_ad_distinguishedName; + AttributeDescription *si_ad_name; + AttributeDescription *si_ad_cn; + AttributeDescription *si_ad_uid; + AttributeDescription *si_ad_uidNumber; + AttributeDescription *si_ad_gidNumber; + AttributeDescription *si_ad_userPassword; + AttributeDescription *si_ad_labeledURI; +#ifdef SLAPD_AUTHPASSWD + AttributeDescription *si_ad_authPassword; + AttributeDescription *si_ad_authPasswordSchemes; +#endif + AttributeDescription *si_ad_description; + AttributeDescription *si_ad_seeAlso; + + /* Undefined Attribute Type */ + AttributeType *si_at_undefined; + + /* "Proxied" Attribute Type */ + AttributeType *si_at_proxied; + + /* Matching Rules */ + MatchingRule *si_mr_distinguishedNameMatch; + MatchingRule *si_mr_dnSubtreeMatch; + MatchingRule *si_mr_dnOneLevelMatch; + MatchingRule *si_mr_dnSubordinateMatch; + MatchingRule *si_mr_dnSuperiorMatch; + MatchingRule *si_mr_caseExactMatch; + MatchingRule *si_mr_caseExactSubstringsMatch; + MatchingRule *si_mr_caseExactIA5Match; + MatchingRule *si_mr_integerMatch; + MatchingRule *si_mr_integerFirstComponentMatch; + MatchingRule *si_mr_objectIdentifierFirstComponentMatch; + MatchingRule *si_mr_caseIgnoreMatch; + MatchingRule *si_mr_caseIgnoreListMatch; + + /* Syntaxes */ + Syntax *si_syn_directoryString; + Syntax *si_syn_distinguishedName; + Syntax *si_syn_integer; + Syntax *si_syn_octetString; + + /* Schema Syntaxes */ + Syntax *si_syn_attributeTypeDesc; + Syntax *si_syn_ditContentRuleDesc; + Syntax *si_syn_ditStructureRuleDesc; + Syntax *si_syn_ldapSyntaxDesc; + Syntax *si_syn_matchingRuleDesc; + Syntax *si_syn_matchingRuleUseDesc; + Syntax *si_syn_nameFormDesc; + Syntax *si_syn_objectClassDesc; +}; + +struct AttributeAssertion { + AttributeDescription *aa_desc; + struct berval aa_value; +#ifdef LDAP_COMP_MATCH + ComponentFilter *aa_cf; /* for attribute aliasing */ +#endif +}; +#ifdef LDAP_COMP_MATCH +#define ATTRIBUTEASSERTION_INIT { NULL, BER_BVNULL, NULL } +#else +#define ATTRIBUTEASSERTION_INIT { NULL, BER_BVNULL } +#endif + +struct SubstringsAssertion { + AttributeDescription *sa_desc; + struct berval sa_initial; + struct berval *sa_any; + struct berval sa_final; +}; + +struct MatchingRuleAssertion { + AttributeDescription *ma_desc; /* optional */ + struct berval ma_value; /* required */ + MatchingRule *ma_rule; /* optional */ + struct berval ma_rule_text; /* optional */ + int ma_dnattrs; /* boolean */ +#ifdef LDAP_COMP_MATCH + ComponentFilter *ma_cf; /* component filter */ +#endif +}; + +/* + * represents a search filter + */ +struct Filter { + ber_tag_t f_choice; /* values taken from ldap.h, plus: */ +#define SLAPD_FILTER_COMPUTED 0 +#define SLAPD_FILTER_MASK 0x7fff +#define SLAPD_FILTER_UNDEFINED 0x8000 + + union f_un_u { + /* precomputed result */ + ber_int_t f_un_result; + + /* present */ + AttributeDescription *f_un_desc; + + /* simple value assertion */ + AttributeAssertion *f_un_ava; + + /* substring assertion */ + SubstringsAssertion *f_un_ssa; + + /* matching rule assertion */ + MatchingRuleAssertion *f_un_mra; + +#define f_desc f_un.f_un_desc +#define f_ava f_un.f_un_ava +#define f_av_desc f_un.f_un_ava->aa_desc +#define f_av_value f_un.f_un_ava->aa_value +#define f_sub f_un.f_un_ssa +#define f_sub_desc f_un.f_un_ssa->sa_desc +#define f_sub_initial f_un.f_un_ssa->sa_initial +#define f_sub_any f_un.f_un_ssa->sa_any +#define f_sub_final f_un.f_un_ssa->sa_final +#define f_mra f_un.f_un_mra +#define f_mr_rule f_un.f_un_mra->ma_rule +#define f_mr_rule_text f_un.f_un_mra->ma_rule_text +#define f_mr_desc f_un.f_un_mra->ma_desc +#define f_mr_value f_un.f_un_mra->ma_value +#define f_mr_dnattrs f_un.f_un_mra->ma_dnattrs + + /* and, or, not */ + Filter *f_un_complex; + } f_un; + +#define f_result f_un.f_un_result +#define f_and f_un.f_un_complex +#define f_or f_un.f_un_complex +#define f_not f_un.f_un_complex +#define f_list f_un.f_un_complex + + Filter *f_next; +}; + +/* compare routines can return undefined */ +#define SLAPD_COMPARE_UNDEFINED ((ber_int_t) -1) + +struct ValuesReturnFilter { + ber_tag_t vrf_choice; + + union vrf_un_u { + /* precomputed result */ + ber_int_t vrf_un_result; + + /* DN */ + char *vrf_un_dn; + + /* present */ + AttributeDescription *vrf_un_desc; + + /* simple value assertion */ + AttributeAssertion *vrf_un_ava; + + /* substring assertion */ + SubstringsAssertion *vrf_un_ssa; + + /* matching rule assertion */ + MatchingRuleAssertion *vrf_un_mra; + +#define vrf_result vrf_un.vrf_un_result +#define vrf_dn vrf_un.vrf_un_dn +#define vrf_desc vrf_un.vrf_un_desc +#define vrf_ava vrf_un.vrf_un_ava +#define vrf_av_desc vrf_un.vrf_un_ava->aa_desc +#define vrf_av_value vrf_un.vrf_un_ava->aa_value +#define vrf_ssa vrf_un.vrf_un_ssa +#define vrf_sub vrf_un.vrf_un_ssa +#define vrf_sub_desc vrf_un.vrf_un_ssa->sa_desc +#define vrf_sub_initial vrf_un.vrf_un_ssa->sa_initial +#define vrf_sub_any vrf_un.vrf_un_ssa->sa_any +#define vrf_sub_final vrf_un.vrf_un_ssa->sa_final +#define vrf_mra vrf_un.vrf_un_mra +#define vrf_mr_rule vrf_un.vrf_un_mra->ma_rule +#define vrf_mr_rule_text vrf_un.vrf_un_mra->ma_rule_text +#define vrf_mr_desc vrf_un.vrf_un_mra->ma_desc +#define vrf_mr_value vrf_un.vrf_un_mra->ma_value +#define vrf_mr_dnattrs vrf_un.vrf_un_mra->ma_dnattrs + + + } vrf_un; + + ValuesReturnFilter *vrf_next; +}; + +/* + * represents an attribute (description + values) + * desc, vals, nvals, numvals fields must align with Modification + */ +struct Attribute { + AttributeDescription *a_desc; + BerVarray a_vals; /* preserved values */ + BerVarray a_nvals; /* normalized values */ + unsigned a_numvals; /* number of vals */ + unsigned a_flags; +#define SLAP_ATTR_IXADD 0x1U +#define SLAP_ATTR_IXDEL 0x2U +#define SLAP_ATTR_DONT_FREE_DATA 0x4U +#define SLAP_ATTR_DONT_FREE_VALS 0x8U +#define SLAP_ATTR_SORTED_VALS 0x10U /* values are sorted */ + +/* These flags persist across an attr_dup() */ +#define SLAP_ATTR_PERSISTENT_FLAGS \ + SLAP_ATTR_SORTED_VALS + + Attribute *a_next; +#ifdef LDAP_COMP_MATCH + ComponentData *a_comp_data; /* component values */ +#endif +}; + + +/* + * the id used in the indexes to refer to an entry + */ +typedef unsigned long ID; +#define NOID ((ID)~0) + +typedef struct EntryHeader { + struct berval bv; + char *data; + int nattrs; + int nvals; +} EntryHeader; + +/* + * represents an entry in core + */ +struct Entry { + /* + * The ID field should only be changed before entry is + * inserted into a cache. The ID value is backend + * specific. + */ + ID e_id; + + struct berval e_name; /* name (DN) of this entry */ + struct berval e_nname; /* normalized name (DN) of this entry */ + + /* for migration purposes */ +#define e_dn e_name.bv_val +#define e_ndn e_nname.bv_val + + Attribute *e_attrs; /* list of attributes + values */ + + slap_mask_t e_ocflags; + + struct berval e_bv; /* For entry_encode/entry_decode */ + + /* for use by the backend for any purpose */ + void* e_private; +}; + +/* + * A list of LDAPMods + * desc, values, nvalues, numvals must align with Attribute + */ +struct Modification { + AttributeDescription *sm_desc; + BerVarray sm_values; + BerVarray sm_nvalues; + unsigned sm_numvals; + short sm_op; + short sm_flags; +/* Set for internal mods, will bypass ACL checks. Only needed when + * running as non-root user, for user modifiable attributes. + */ +#define SLAP_MOD_INTERNAL 0x01 +#define SLAP_MOD_MANAGING 0x02 + struct berval sm_type; +}; + +struct Modifications { + Modification sml_mod; +#define sml_op sml_mod.sm_op +#define sml_flags sml_mod.sm_flags +#define sml_desc sml_mod.sm_desc +#define sml_type sml_mod.sm_type +#define sml_values sml_mod.sm_values +#define sml_nvalues sml_mod.sm_nvalues +#define sml_numvals sml_mod.sm_numvals + Modifications *sml_next; +}; + +/* + * represents an access control list + */ +typedef enum slap_access_t { + ACL_INVALID_ACCESS = -1, + ACL_NONE = 0, + ACL_DISCLOSE, + ACL_AUTH, + ACL_COMPARE, + ACL_SEARCH, + ACL_READ, + ACL_WRITE_, + ACL_MANAGE, + + /* always leave at end of levels but not greater than ACL_LEVEL_MASK */ + ACL_LAST, + + /* ACL level mask and modifiers */ + ACL_LEVEL_MASK = 0x000f, + ACL_QUALIFIER1 = 0x0100, + ACL_QUALIFIER2 = 0x0200, + ACL_QUALIFIER3 = 0x0400, + ACL_QUALIFIER4 = 0x0800, + ACL_QUALIFIER_MASK = 0x0f00, + + /* write granularity */ + ACL_WADD = ACL_WRITE_|ACL_QUALIFIER1, + ACL_WDEL = ACL_WRITE_|ACL_QUALIFIER2, + + ACL_WRITE = ACL_WADD|ACL_WDEL +} slap_access_t; + +typedef enum slap_control_e { + ACL_INVALID_CONTROL = 0, + ACL_STOP, + ACL_CONTINUE, + ACL_BREAK +} slap_control_t; + +typedef enum slap_style_e { + ACL_STYLE_REGEX = 0, + ACL_STYLE_EXPAND, + ACL_STYLE_BASE, + ACL_STYLE_ONE, + ACL_STYLE_SUBTREE, + ACL_STYLE_CHILDREN, + ACL_STYLE_LEVEL, + ACL_STYLE_ATTROF, + ACL_STYLE_ANONYMOUS, + ACL_STYLE_USERS, + ACL_STYLE_SELF, + ACL_STYLE_IP, + ACL_STYLE_IPV6, + ACL_STYLE_PATH, + + ACL_STYLE_NONE +} slap_style_t; + +typedef struct AuthorizationInformation { + ber_tag_t sai_method; /* LDAP_AUTH_* from <ldap.h> */ + struct berval sai_mech; /* SASL Mechanism */ + struct berval sai_dn; /* DN for reporting purposes */ + struct berval sai_ndn; /* Normalized DN */ + + /* Security Strength Factors */ + slap_ssf_t sai_ssf; /* Overall SSF */ + slap_ssf_t sai_transport_ssf; /* Transport SSF */ + slap_ssf_t sai_tls_ssf; /* TLS SSF */ + slap_ssf_t sai_sasl_ssf; /* SASL SSF */ +} AuthorizationInformation; + +#ifdef SLAP_DYNACL + +/* + * "dynamic" ACL infrastructure (for ACIs and more) + */ +typedef int (slap_dynacl_parse) LDAP_P(( const char *fname, int lineno, + const char *opts, slap_style_t, const char *, void **privp )); +typedef int (slap_dynacl_unparse) LDAP_P(( void *priv, struct berval *bv )); +typedef int (slap_dynacl_mask) LDAP_P(( + void *priv, + Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + int nmatch, + regmatch_t *matches, + slap_access_t *grant, + slap_access_t *deny )); +typedef int (slap_dynacl_destroy) LDAP_P(( void *priv )); + +typedef struct slap_dynacl_t { + char *da_name; + slap_dynacl_parse *da_parse; + slap_dynacl_unparse *da_unparse; + slap_dynacl_mask *da_mask; + slap_dynacl_destroy *da_destroy; + + void *da_private; + struct slap_dynacl_t *da_next; +} slap_dynacl_t; +#endif /* SLAP_DYNACL */ + +/* the DN portion of the "by" part */ +typedef struct slap_dn_access { + /* DN pattern */ + AuthorizationInformation a_dnauthz; +#define a_pat a_dnauthz.sai_dn + + slap_style_t a_style; + int a_level; + int a_self_level; + AttributeDescription *a_at; + int a_self; + int a_expand; +} slap_dn_access; + +/* the "by" part */ +typedef struct Access { + slap_control_t a_type; + +/* strip qualifiers */ +#define ACL_LEVEL(p) ((p) & ACL_LEVEL_MASK) +#define ACL_QUALIFIERS(p) ((p) & ~ACL_LEVEL_MASK) + +#define ACL_ACCESS2PRIV(access) ((0x01U << ACL_LEVEL((access))) | ACL_QUALIFIERS((access))) + +#define ACL_PRIV_NONE ACL_ACCESS2PRIV( ACL_NONE ) +#define ACL_PRIV_DISCLOSE ACL_ACCESS2PRIV( ACL_DISCLOSE ) +#define ACL_PRIV_AUTH ACL_ACCESS2PRIV( ACL_AUTH ) +#define ACL_PRIV_COMPARE ACL_ACCESS2PRIV( ACL_COMPARE ) +#define ACL_PRIV_SEARCH ACL_ACCESS2PRIV( ACL_SEARCH ) +#define ACL_PRIV_READ ACL_ACCESS2PRIV( ACL_READ ) +#define ACL_PRIV_WADD ACL_ACCESS2PRIV( ACL_WADD ) +#define ACL_PRIV_WDEL ACL_ACCESS2PRIV( ACL_WDEL ) +#define ACL_PRIV_WRITE ( ACL_PRIV_WADD | ACL_PRIV_WDEL ) +#define ACL_PRIV_MANAGE ACL_ACCESS2PRIV( ACL_MANAGE ) + +/* NOTE: always use the highest level; current: 0x00ffUL */ +#define ACL_PRIV_MASK ((ACL_ACCESS2PRIV(ACL_LAST) - 1) | ACL_QUALIFIER_MASK) + +/* priv flags */ +#define ACL_PRIV_LEVEL 0x1000UL +#define ACL_PRIV_ADDITIVE 0x2000UL +#define ACL_PRIV_SUBSTRACTIVE 0x4000UL + +/* invalid privs */ +#define ACL_PRIV_INVALID 0x0UL + +#define ACL_PRIV_ISSET(m,p) (((m) & (p)) == (p)) +#define ACL_PRIV_ASSIGN(m,p) do { (m) = (p); } while(0) +#define ACL_PRIV_SET(m,p) do { (m) |= (p); } while(0) +#define ACL_PRIV_CLR(m,p) do { (m) &= ~(p); } while(0) + +#define ACL_INIT(m) ACL_PRIV_ASSIGN((m), ACL_PRIV_NONE) +#define ACL_INVALIDATE(m) ACL_PRIV_ASSIGN((m), ACL_PRIV_INVALID) + +#define ACL_GRANT(m,a) ACL_PRIV_ISSET((m),ACL_ACCESS2PRIV(a)) + +#define ACL_IS_INVALID(m) ((m) == ACL_PRIV_INVALID) + +#define ACL_IS_LEVEL(m) ACL_PRIV_ISSET((m),ACL_PRIV_LEVEL) +#define ACL_IS_ADDITIVE(m) ACL_PRIV_ISSET((m),ACL_PRIV_ADDITIVE) +#define ACL_IS_SUBTRACTIVE(m) ACL_PRIV_ISSET((m),ACL_PRIV_SUBSTRACTIVE) + +#define ACL_LVL_NONE (ACL_PRIV_NONE|ACL_PRIV_LEVEL) +#define ACL_LVL_DISCLOSE (ACL_PRIV_DISCLOSE|ACL_LVL_NONE) +#define ACL_LVL_AUTH (ACL_PRIV_AUTH|ACL_LVL_DISCLOSE) +#define ACL_LVL_COMPARE (ACL_PRIV_COMPARE|ACL_LVL_AUTH) +#define ACL_LVL_SEARCH (ACL_PRIV_SEARCH|ACL_LVL_COMPARE) +#define ACL_LVL_READ (ACL_PRIV_READ|ACL_LVL_SEARCH) +#define ACL_LVL_WADD (ACL_PRIV_WADD|ACL_LVL_READ) +#define ACL_LVL_WDEL (ACL_PRIV_WDEL|ACL_LVL_READ) +#define ACL_LVL_WRITE (ACL_PRIV_WRITE|ACL_LVL_READ) +#define ACL_LVL_MANAGE (ACL_PRIV_MANAGE|ACL_LVL_WRITE) + +#define ACL_LVL(m,l) (((m)&ACL_PRIV_MASK) == ((l)&ACL_PRIV_MASK)) +#define ACL_LVL_IS_NONE(m) ACL_LVL((m),ACL_LVL_NONE) +#define ACL_LVL_IS_DISCLOSE(m) ACL_LVL((m),ACL_LVL_DISCLOSE) +#define ACL_LVL_IS_AUTH(m) ACL_LVL((m),ACL_LVL_AUTH) +#define ACL_LVL_IS_COMPARE(m) ACL_LVL((m),ACL_LVL_COMPARE) +#define ACL_LVL_IS_SEARCH(m) ACL_LVL((m),ACL_LVL_SEARCH) +#define ACL_LVL_IS_READ(m) ACL_LVL((m),ACL_LVL_READ) +#define ACL_LVL_IS_WADD(m) ACL_LVL((m),ACL_LVL_WADD) +#define ACL_LVL_IS_WDEL(m) ACL_LVL((m),ACL_LVL_WDEL) +#define ACL_LVL_IS_WRITE(m) ACL_LVL((m),ACL_LVL_WRITE) +#define ACL_LVL_IS_MANAGE(m) ACL_LVL((m),ACL_LVL_MANAGE) + +#define ACL_LVL_ASSIGN_NONE(m) ACL_PRIV_ASSIGN((m),ACL_LVL_NONE) +#define ACL_LVL_ASSIGN_DISCLOSE(m) ACL_PRIV_ASSIGN((m),ACL_LVL_DISCLOSE) +#define ACL_LVL_ASSIGN_AUTH(m) ACL_PRIV_ASSIGN((m),ACL_LVL_AUTH) +#define ACL_LVL_ASSIGN_COMPARE(m) ACL_PRIV_ASSIGN((m),ACL_LVL_COMPARE) +#define ACL_LVL_ASSIGN_SEARCH(m) ACL_PRIV_ASSIGN((m),ACL_LVL_SEARCH) +#define ACL_LVL_ASSIGN_READ(m) ACL_PRIV_ASSIGN((m),ACL_LVL_READ) +#define ACL_LVL_ASSIGN_WADD(m) ACL_PRIV_ASSIGN((m),ACL_LVL_WADD) +#define ACL_LVL_ASSIGN_WDEL(m) ACL_PRIV_ASSIGN((m),ACL_LVL_WDEL) +#define ACL_LVL_ASSIGN_WRITE(m) ACL_PRIV_ASSIGN((m),ACL_LVL_WRITE) +#define ACL_LVL_ASSIGN_MANAGE(m) ACL_PRIV_ASSIGN((m),ACL_LVL_MANAGE) + + slap_mask_t a_access_mask; + + /* DN pattern */ + slap_dn_access a_dn; +#define a_dn_pat a_dn.a_dnauthz.sai_dn +#define a_dn_at a_dn.a_at +#define a_dn_self a_dn.a_self + + /* real DN pattern */ + slap_dn_access a_realdn; +#define a_realdn_pat a_realdn.a_dnauthz.sai_dn +#define a_realdn_at a_realdn.a_at +#define a_realdn_self a_realdn.a_self + + /* used for ssf stuff + * NOTE: the ssf stuff in a_realdn is ignored */ +#define a_authz a_dn.a_dnauthz + + /* connection related stuff */ + slap_style_t a_peername_style; + struct berval a_peername_pat; +#ifdef LDAP_PF_INET6 + union { + struct in6_addr ax6; + unsigned long ax; + } ax_peername_addr, + ax_peername_mask; +#define a_peername_addr6 ax_peername_addr.ax6 +#define a_peername_addr ax_peername_addr.ax +#define a_peername_mask6 ax_peername_mask.ax6 +#define a_peername_mask ax_peername_mask.ax +/* apparently, only s6_addr is portable; + * define a portable address mask comparison */ +#define slap_addr6_mask(val, msk, asr) ( \ + (((val)->s6_addr[0] & (msk)->s6_addr[0]) == (asr)->s6_addr[0]) \ + && (((val)->s6_addr[1] & (msk)->s6_addr[1]) == (asr)->s6_addr[1]) \ + && (((val)->s6_addr[2] & (msk)->s6_addr[2]) == (asr)->s6_addr[2]) \ + && (((val)->s6_addr[3] & (msk)->s6_addr[3]) == (asr)->s6_addr[3]) \ + && (((val)->s6_addr[4] & (msk)->s6_addr[4]) == (asr)->s6_addr[4]) \ + && (((val)->s6_addr[5] & (msk)->s6_addr[5]) == (asr)->s6_addr[5]) \ + && (((val)->s6_addr[6] & (msk)->s6_addr[6]) == (asr)->s6_addr[6]) \ + && (((val)->s6_addr[7] & (msk)->s6_addr[7]) == (asr)->s6_addr[7]) \ + && (((val)->s6_addr[8] & (msk)->s6_addr[8]) == (asr)->s6_addr[8]) \ + && (((val)->s6_addr[9] & (msk)->s6_addr[9]) == (asr)->s6_addr[9]) \ + && (((val)->s6_addr[10] & (msk)->s6_addr[10]) == (asr)->s6_addr[10]) \ + && (((val)->s6_addr[11] & (msk)->s6_addr[11]) == (asr)->s6_addr[11]) \ + && (((val)->s6_addr[12] & (msk)->s6_addr[12]) == (asr)->s6_addr[12]) \ + && (((val)->s6_addr[13] & (msk)->s6_addr[13]) == (asr)->s6_addr[13]) \ + && (((val)->s6_addr[14] & (msk)->s6_addr[14]) == (asr)->s6_addr[14]) \ + && (((val)->s6_addr[15] & (msk)->s6_addr[15]) == (asr)->s6_addr[15]) \ + ) +#else /* ! LDAP_PF_INET6 */ + unsigned long a_peername_addr, + a_peername_mask; +#endif /* ! LDAP_PF_INET6 */ + int a_peername_port; + + slap_style_t a_sockname_style; + struct berval a_sockname_pat; + + slap_style_t a_domain_style; + struct berval a_domain_pat; + int a_domain_expand; + + slap_style_t a_sockurl_style; + struct berval a_sockurl_pat; + slap_style_t a_set_style; + struct berval a_set_pat; + +#ifdef SLAP_DYNACL + slap_dynacl_t *a_dynacl; +#endif /* SLAP_DYNACL */ + + /* ACL Groups */ + slap_style_t a_group_style; + struct berval a_group_pat; + ObjectClass *a_group_oc; + AttributeDescription *a_group_at; + + struct Access *a_next; +} Access; + +/* the "to" part */ +typedef struct AccessControl { + /* "to" part: the entries this acl applies to */ + Filter *acl_filter; + slap_style_t acl_dn_style; + regex_t acl_dn_re; + struct berval acl_dn_pat; + AttributeName *acl_attrs; + MatchingRule *acl_attrval_mr; + slap_style_t acl_attrval_style; + regex_t acl_attrval_re; + struct berval acl_attrval; + + /* "by" part: list of who has what access to the entries */ + Access *acl_access; + + struct AccessControl *acl_next; +} AccessControl; + +typedef struct AccessControlState { + /* Access state */ + + /* The stored state is valid when requesting as_access access + * to the as_desc attributes. */ + AttributeDescription *as_desc; + slap_access_t as_access; + + /* Value dependent acl where processing can restart */ + AccessControl *as_vd_acl; + int as_vd_acl_present; + int as_vd_acl_count; + slap_mask_t as_vd_mask; + + /* The cached result after evaluating a value independent attr. + * Only valid when != -1 and as_vd_acl == NULL */ + int as_result; + + /* True if started to process frontend ACLs */ + int as_fe_done; +} AccessControlState; +#define ACL_STATE_INIT { NULL, ACL_NONE, NULL, 0, 0, ACL_PRIV_NONE, -1, 0 } + +typedef struct AclRegexMatches { + int dn_count; + regmatch_t dn_data[MAXREMATCHES]; + int val_count; + regmatch_t val_data[MAXREMATCHES]; +} AclRegexMatches; + +/* + * Backend-info + * represents a backend + */ + +typedef LDAP_STAILQ_HEAD(BeI, BackendInfo) slap_bi_head; +typedef LDAP_STAILQ_HEAD(BeDB, BackendDB) slap_be_head; + +LDAP_SLAPD_V (int) nBackendInfo; +LDAP_SLAPD_V (int) nBackendDB; +LDAP_SLAPD_V (slap_bi_head) backendInfo; +LDAP_SLAPD_V (slap_be_head) backendDB; +LDAP_SLAPD_V (BackendDB *) frontendDB; + +LDAP_SLAPD_V (int) slapMode; +#define SLAP_UNDEFINED_MODE 0x0000 +#define SLAP_SERVER_MODE 0x0001 +#define SLAP_TOOL_MODE 0x0002 +#define SLAP_MODE 0x0003 + +#define SLAP_TRUNCATE_MODE 0x0100 +#define SLAP_TOOL_READMAIN 0x0200 +#define SLAP_TOOL_READONLY 0x0400 +#define SLAP_TOOL_QUICK 0x0800 +#define SLAP_TOOL_NO_SCHEMA_CHECK 0x1000 +#define SLAP_TOOL_VALUE_CHECK 0x2000 + +#define SLAP_SERVER_RUNNING 0x8000 + +#define SB_TLS_DEFAULT (-1) +#define SB_TLS_OFF 0 +#define SB_TLS_ON 1 +#define SB_TLS_CRITICAL 2 + +typedef struct slap_keepalive { + int sk_idle; + int sk_probes; + int sk_interval; +} slap_keepalive; + +typedef struct slap_bindconf { + struct berval sb_uri; + int sb_version; + int sb_tls; + int sb_method; + int sb_timeout_api; + int sb_timeout_net; + struct berval sb_binddn; + struct berval sb_cred; + struct berval sb_saslmech; + char *sb_secprops; + struct berval sb_realm; + struct berval sb_authcId; + struct berval sb_authzId; + slap_keepalive sb_keepalive; +#ifdef HAVE_TLS + void *sb_tls_ctx; + char *sb_tls_cert; + char *sb_tls_key; + char *sb_tls_cacert; + char *sb_tls_cacertdir; + char *sb_tls_reqcert; + char *sb_tls_reqsan; + char *sb_tls_cipher_suite; + char *sb_tls_protocol_min; + char *sb_tls_ecname; +#ifdef HAVE_OPENSSL_CRL + char *sb_tls_crlcheck; +#endif + int sb_tls_do_init; +#endif +} slap_bindconf; + +typedef struct slap_verbmasks { + struct berval word; + const slap_mask_t mask; +} slap_verbmasks; + +typedef struct slap_cf_aux_table { + struct berval key; + int off; + char type; + char quote; + void *aux; +} slap_cf_aux_table; + +typedef int +slap_cf_aux_table_parse_x LDAP_P(( + struct berval *val, + void *bc, + slap_cf_aux_table *tab0, + const char *tabmsg, + int unparse )); + +#define SLAP_LIMIT_TIME 1 +#define SLAP_LIMIT_SIZE 2 + +struct slap_limits_set { + /* time limits */ + int lms_t_soft; + int lms_t_hard; + + /* size limits */ + int lms_s_soft; + int lms_s_hard; + int lms_s_unchecked; + int lms_s_pr; + int lms_s_pr_hide; + int lms_s_pr_total; +}; + +/* Note: this is different from LDAP_NO_LIMIT (0); slapd internal use only */ +#define SLAP_NO_LIMIT -1 +#define SLAP_MAX_LIMIT 2147483647 + +struct slap_limits { + unsigned lm_flags; /* type of pattern */ + /* Values must match lmpats[] in limits.c */ +#define SLAP_LIMITS_UNDEFINED 0x0000U +#define SLAP_LIMITS_EXACT 0x0001U +#define SLAP_LIMITS_BASE SLAP_LIMITS_EXACT +#define SLAP_LIMITS_ONE 0x0002U +#define SLAP_LIMITS_SUBTREE 0x0003U +#define SLAP_LIMITS_CHILDREN 0x0004U +#define SLAP_LIMITS_REGEX 0x0005U +#define SLAP_LIMITS_ANONYMOUS 0x0006U +#define SLAP_LIMITS_USERS 0x0007U +#define SLAP_LIMITS_ANY 0x0008U +#define SLAP_LIMITS_MASK 0x000FU + +#define SLAP_LIMITS_TYPE_SELF 0x0000U +#define SLAP_LIMITS_TYPE_DN SLAP_LIMITS_TYPE_SELF +#define SLAP_LIMITS_TYPE_GROUP 0x0010U +#define SLAP_LIMITS_TYPE_THIS 0x0020U +#define SLAP_LIMITS_TYPE_MASK 0x00F0U + + regex_t lm_regex; /* regex data for REGEX */ + + /* + * normalized DN for EXACT, BASE, ONE, SUBTREE, CHILDREN; + * pattern for REGEX; NULL for ANONYMOUS, USERS + */ + struct berval lm_pat; + + /* if lm_flags & SLAP_LIMITS_TYPE_MASK == SLAP_LIMITS_GROUP, + * lm_group_oc is objectClass and lm_group_at is attributeType + * of member in oc for match; then lm_flags & SLAP_LIMITS_MASK + * can only be SLAP_LIMITS_EXACT */ + ObjectClass *lm_group_oc; + AttributeDescription *lm_group_ad; + + struct slap_limits_set lm_limits; +}; + +/* temporary aliases */ +typedef BackendDB Backend; +#define nbackends nBackendDB +#define backends backendDB + +/* + * syncinfo structure for syncrepl + */ + +struct syncinfo_s; + +#define SLAP_SYNC_RID_MAX 999 +#define SLAP_SYNC_SID_MAX 4095 /* based on liblutil/csn.c field width */ + +/* fake conn connid constructed as rid; real connids start + * at SLAPD_SYNC_CONN_OFFSET */ +#define SLAPD_SYNC_SYNCCONN_OFFSET (SLAP_SYNC_RID_MAX + 1) +#define SLAPD_SYNC_IS_SYNCCONN(connid) ((connid) < SLAPD_SYNC_SYNCCONN_OFFSET) +#define SLAPD_SYNC_RID2SYNCCONN(rid) (rid) + +#define SLAP_SYNCUUID_SET_SIZE 256 + +struct sync_cookie { + BerVarray ctxcsn; + int *sids; + int numcsns; + int rid; + struct berval octet_str; + int sid; + LDAP_STAILQ_ENTRY(sync_cookie) sc_next; +}; + +LDAP_STAILQ_HEAD( slap_sync_cookie_s, sync_cookie ); + +LDAP_TAILQ_HEAD( be_pcl, slap_csn_entry ); + +#ifndef SLAP_MAX_CIDS +#define SLAP_MAX_CIDS 32 /* Maximum number of supported controls */ +#endif + +struct ConfigOCs; /* config.h */ + +struct BackendDB { + BackendInfo *bd_info; /* pointer to shared backend info */ + BackendDB *bd_self; /* pointer to this struct */ + + /* fields in this structure (and routines acting on this structure) + should be renamed from be_ to bd_ */ + + /* BackendInfo accessors */ +#define be_config bd_info->bi_db_config +#define be_type bd_info->bi_type + +#define be_bind bd_info->bi_op_bind +#define be_unbind bd_info->bi_op_unbind +#define be_add bd_info->bi_op_add +#define be_compare bd_info->bi_op_compare +#define be_delete bd_info->bi_op_delete +#define be_modify bd_info->bi_op_modify +#define be_modrdn bd_info->bi_op_modrdn +#define be_search bd_info->bi_op_search +#define be_abandon bd_info->bi_op_abandon + +#define be_extended bd_info->bi_extended +#define be_cancel bd_info->bi_op_cancel + +#define be_chk_referrals bd_info->bi_chk_referrals +#define be_chk_controls bd_info->bi_chk_controls +#define be_fetch bd_info->bi_entry_get_rw +#define be_release bd_info->bi_entry_release_rw +#define be_group bd_info->bi_acl_group +#define be_attribute bd_info->bi_acl_attribute +#define be_operational bd_info->bi_operational + +/* + * define to honor hasSubordinates operational attribute in search filters + * (in previous use there was a flaw with back-bdb; now it is fixed). + */ +#define be_has_subordinates bd_info->bi_has_subordinates + +#define be_connection_init bd_info->bi_connection_init +#define be_connection_destroy bd_info->bi_connection_destroy + +#ifdef SLAPD_TOOLS +#define be_entry_open bd_info->bi_tool_entry_open +#define be_entry_close bd_info->bi_tool_entry_close +#define be_entry_first bd_info->bi_tool_entry_first +#define be_entry_first_x bd_info->bi_tool_entry_first_x +#define be_entry_next bd_info->bi_tool_entry_next +#define be_entry_reindex bd_info->bi_tool_entry_reindex +#define be_entry_get bd_info->bi_tool_entry_get +#define be_entry_put bd_info->bi_tool_entry_put +#define be_sync bd_info->bi_tool_sync +#define be_dn2id_get bd_info->bi_tool_dn2id_get +#define be_entry_modify bd_info->bi_tool_entry_modify +#endif + + /* supported controls */ + /* note: set to 0 if the database does not support the control; + * be_ctrls[SLAP_MAX_CIDS] is set to 1 if initialized */ + char be_ctrls[SLAP_MAX_CIDS + 1]; + +/* Database flags */ +#define SLAP_DBFLAG_NOLASTMOD 0x0001U +#define SLAP_DBFLAG_NO_SCHEMA_CHECK 0x0002U +#define SLAP_DBFLAG_HIDDEN 0x0004U +#define SLAP_DBFLAG_ONE_SUFFIX 0x0008U +#define SLAP_DBFLAG_GLUE_INSTANCE 0x0010U /* a glue backend */ +#define SLAP_DBFLAG_GLUE_SUBORDINATE 0x0020U /* child of a glue hierarchy */ +#define SLAP_DBFLAG_GLUE_LINKED 0x0040U /* child is connected to parent */ +#define SLAP_DBFLAG_GLUE_ADVERTISE 0x0080U /* advertise in rootDSE */ +#define SLAP_DBFLAG_OVERLAY 0x0100U /* this db struct is an overlay */ +#define SLAP_DBFLAG_GLOBAL_OVERLAY 0x0200U /* this db struct is a global overlay */ +#define SLAP_DBFLAG_DYNAMIC 0x0400U /* this db allows dynamicObjects */ +#define SLAP_DBFLAG_MONITORING 0x0800U /* custom monitoring enabled */ +#define SLAP_DBFLAG_SHADOW 0x8000U /* a shadow */ +#define SLAP_DBFLAG_SINGLE_SHADOW 0x4000U /* a single-provider shadow */ +#define SLAP_DBFLAG_SYNC_SHADOW 0x1000U /* a sync shadow */ +#define SLAP_DBFLAG_SLURP_SHADOW 0x2000U /* a slurp shadow */ +#define SLAP_DBFLAG_SHADOW_MASK (SLAP_DBFLAG_SHADOW|SLAP_DBFLAG_SINGLE_SHADOW|SLAP_DBFLAG_SYNC_SHADOW|SLAP_DBFLAG_SLURP_SHADOW) +#define SLAP_DBFLAG_CLEAN 0x10000U /* was cleanly shutdown */ +#define SLAP_DBFLAG_ACL_ADD 0x20000U /* check attr ACLs on adds */ +#define SLAP_DBFLAG_SYNC_SUBENTRY 0x40000U /* use subentry for context */ +#define SLAP_DBFLAG_MULTI_SHADOW 0x80000U /* uses multi-provider */ + slap_mask_t be_flags; +#define SLAP_DBFLAGS(be) ((be)->be_flags) +#define SLAP_NOLASTMOD(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_NOLASTMOD) +#define SLAP_LASTMOD(be) (!SLAP_NOLASTMOD(be)) +#define SLAP_DBHIDDEN(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_HIDDEN) +#define SLAP_DB_ONE_SUFFIX(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_ONE_SUFFIX) +#define SLAP_ISOVERLAY(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_OVERLAY) +#define SLAP_ISGLOBALOVERLAY(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_GLOBAL_OVERLAY) +#define SLAP_DBMONITORING(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_MONITORING) +#define SLAP_NO_SCHEMA_CHECK(be) \ + (SLAP_DBFLAGS(be) & SLAP_DBFLAG_NO_SCHEMA_CHECK) +#define SLAP_GLUE_INSTANCE(be) \ + (SLAP_DBFLAGS(be) & SLAP_DBFLAG_GLUE_INSTANCE) +#define SLAP_GLUE_SUBORDINATE(be) \ + (SLAP_DBFLAGS(be) & SLAP_DBFLAG_GLUE_SUBORDINATE) +#define SLAP_GLUE_LINKED(be) \ + (SLAP_DBFLAGS(be) & SLAP_DBFLAG_GLUE_LINKED) +#define SLAP_GLUE_ADVERTISE(be) \ + (SLAP_DBFLAGS(be) & SLAP_DBFLAG_GLUE_ADVERTISE) +#define SLAP_SHADOW(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_SHADOW) +#define SLAP_SYNC_SHADOW(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_SYNC_SHADOW) +#define SLAP_SLURP_SHADOW(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_SLURP_SHADOW) +#define SLAP_SINGLE_SHADOW(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_SINGLE_SHADOW) +#define SLAP_MULTIMASTER(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_MULTI_SHADOW) +#define SLAP_DBCLEAN(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_CLEAN) +#define SLAP_DBACL_ADD(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_ACL_ADD) +#define SLAP_SYNC_SUBENTRY(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_SYNC_SUBENTRY) + + slap_mask_t be_restrictops; /* restriction operations */ +#define SLAP_RESTRICT_OP_ADD 0x0001U +#define SLAP_RESTRICT_OP_BIND 0x0002U +#define SLAP_RESTRICT_OP_COMPARE 0x0004U +#define SLAP_RESTRICT_OP_DELETE 0x0008U +#define SLAP_RESTRICT_OP_EXTENDED 0x0010U +#define SLAP_RESTRICT_OP_MODIFY 0x0020U +#define SLAP_RESTRICT_OP_RENAME 0x0040U +#define SLAP_RESTRICT_OP_SEARCH 0x0080U +#define SLAP_RESTRICT_OP_MASK 0x00FFU + +#define SLAP_RESTRICT_READONLY 0x80000000U + +#define SLAP_RESTRICT_EXOP_START_TLS 0x0100U +#define SLAP_RESTRICT_EXOP_MODIFY_PASSWD 0x0200U +#define SLAP_RESTRICT_EXOP_WHOAMI 0x0400U +#define SLAP_RESTRICT_EXOP_CANCEL 0x0800U +#define SLAP_RESTRICT_EXOP_MASK 0xFF00U + +#define SLAP_RESTRICT_OP_READS \ + ( SLAP_RESTRICT_OP_COMPARE \ + | SLAP_RESTRICT_OP_SEARCH ) +#define SLAP_RESTRICT_OP_WRITES \ + ( SLAP_RESTRICT_OP_ADD \ + | SLAP_RESTRICT_OP_DELETE \ + | SLAP_RESTRICT_OP_MODIFY \ + | SLAP_RESTRICT_OP_RENAME ) +#define SLAP_RESTRICT_OP_ALL \ + ( SLAP_RESTRICT_OP_READS \ + | SLAP_RESTRICT_OP_WRITES \ + | SLAP_RESTRICT_OP_BIND \ + | SLAP_RESTRICT_OP_EXTENDED ) + +#define SLAP_ALLOW_BIND_V2 0x0001U /* LDAPv2 bind */ +#define SLAP_ALLOW_BIND_ANON_CRED 0x0002U /* cred should be empty */ +#define SLAP_ALLOW_BIND_ANON_DN 0x0004U /* dn should be empty */ + +#define SLAP_ALLOW_UPDATE_ANON 0x0008U /* allow anonymous updates */ +#define SLAP_ALLOW_PROXY_AUTHZ_ANON 0x0010U /* allow anonymous proxyAuthz */ + +#define SLAP_DISALLOW_BIND_ANON 0x0001U /* no anonymous */ +#define SLAP_DISALLOW_BIND_SIMPLE 0x0002U /* simple authentication */ + +#define SLAP_DISALLOW_TLS_2_ANON 0x0010U /* StartTLS -> Anonymous */ +#define SLAP_DISALLOW_TLS_AUTHC 0x0020U /* TLS while authenticated */ + +#define SLAP_DISALLOW_PROXY_AUTHZ_N_CRIT 0x0100U +#define SLAP_DISALLOW_DONTUSECOPY_N_CRIT 0x0200U + +#define SLAP_DISALLOW_AUX_WO_CR 0x4000U + + slap_mask_t be_requires; /* pre-operation requirements */ +#define SLAP_REQUIRE_BIND 0x0001U /* bind before op */ +#define SLAP_REQUIRE_LDAP_V3 0x0002U /* LDAPv3 before op */ +#define SLAP_REQUIRE_AUTHC 0x0004U /* authentication before op */ +#define SLAP_REQUIRE_SASL 0x0008U /* SASL before op */ +#define SLAP_REQUIRE_STRONG 0x0010U /* strong authentication before op */ + + /* Required Security Strength Factor */ + slap_ssf_set_t be_ssf_set; + + BerVarray be_suffix; /* the DN suffixes of data in this backend */ + BerVarray be_nsuffix; /* the normalized DN suffixes in this backend */ + struct berval be_schemadn; /* per-backend subschema subentry DN */ + struct berval be_schemandn; /* normalized subschema DN */ + struct berval be_rootdn; /* the magic "root" name (DN) for this db */ + struct berval be_rootndn; /* the magic "root" normalized name (DN) for this db */ + struct berval be_rootpw; /* the magic "root" password for this db */ + unsigned int be_max_deref_depth; /* limit for depth of an alias deref */ +#define be_sizelimit be_def_limit.lms_s_soft +#define be_timelimit be_def_limit.lms_t_soft + struct slap_limits_set be_def_limit; /* default limits */ + struct slap_limits **be_limits; /* regex-based size and time limits */ + AccessControl *be_acl; /* access control list for this backend */ + slap_access_t be_dfltaccess; /* access given if no acl matches */ + AttributeName *be_extra_anlist; /* attributes that need to be added to search requests (ITS#6513) */ + + /* Consumer Information */ + struct berval be_update_ndn; /* allowed to make changes (in replicas) */ + BerVarray be_update_refs; /* where to refer modifying clients to */ + struct be_pcl *be_pending_csn_list; + ldap_pvt_thread_mutex_t be_pcl_mutex; + struct syncinfo_s *be_syncinfo; /* For syncrepl */ + + void *be_pb; /* Netscape plugin */ + struct ConfigOCs *be_cf_ocs; + + void *be_private; /* anything the backend database needs */ + LDAP_STAILQ_ENTRY(BackendDB) be_next; +}; + +/* Backend function typedefs */ +typedef int (BI_bi_func) LDAP_P((BackendInfo *bi)); +typedef BI_bi_func BI_init; +typedef BI_bi_func BI_open; +typedef BI_bi_func BI_close; +typedef BI_bi_func BI_destroy; +typedef int (BI_config) LDAP_P((BackendInfo *bi, + const char *fname, int lineno, + int argc, char **argv)); + +typedef struct config_reply_s ConfigReply; /* config.h */ +typedef int (BI_db_func) LDAP_P((Backend *bd, ConfigReply *cr)); +typedef BI_db_func BI_db_init; +typedef BI_db_func BI_db_open; +typedef BI_db_func BI_db_close; +typedef BI_db_func BI_db_destroy; +typedef int (BI_db_config) LDAP_P((Backend *bd, + const char *fname, int lineno, + int argc, char **argv)); + +typedef struct req_bind_s { + int rb_method; + struct berval rb_cred; + struct berval rb_edn; + slap_ssf_t rb_ssf; + struct berval rb_mech; +} req_bind_s; + +typedef struct req_search_s { + int rs_scope; + int rs_deref; + int rs_slimit; + int rs_tlimit; + /* NULL means be_isroot evaluated to TRUE */ + struct slap_limits_set *rs_limit; + int rs_attrsonly; + AttributeName *rs_attrs; + Filter *rs_filter; + struct berval rs_filterstr; +} req_search_s; + +typedef struct req_compare_s { + AttributeAssertion *rs_ava; +} req_compare_s; + +typedef struct req_modifications_s { + Modifications *rs_modlist; + char rs_no_opattrs; /* don't att modify operational attrs */ +} req_modifications_s; + +typedef struct req_modify_s { + req_modifications_s rs_mods; /* NOTE: must be first in req_modify_s & req_modrdn_s */ + int rs_increment; +} req_modify_s; + +typedef struct req_modrdn_s { + req_modifications_s rs_mods; /* NOTE: must be first in req_modify_s & req_modrdn_s */ + int rs_deleteoldrdn; + struct berval rs_newrdn; + struct berval rs_nnewrdn; + struct berval *rs_newSup; + struct berval *rs_nnewSup; +} req_modrdn_s; + +typedef struct req_add_s { + Modifications *rs_modlist; + Entry *rs_e; +} req_add_s; + +typedef struct req_abandon_s { + ber_int_t rs_msgid; +} req_abandon_s; + +#ifdef SLAP_SCHEMA_EXPOSE +#define SLAP_EXOP_HIDE 0x0000 +#else +#define SLAP_EXOP_HIDE 0x8000 +#endif +#define SLAP_EXOP_WRITES 0x0001 /* Exop does writes */ + +typedef struct req_extended_s { + struct berval rs_reqoid; + int rs_flags; + struct berval *rs_reqdata; +} req_extended_s; + +typedef struct req_pwdexop_s { + struct req_extended_s rs_extended; + struct berval rs_old; + struct berval rs_new; + Modifications *rs_mods; + Modifications **rs_modtail; +} req_pwdexop_s; + +typedef enum slap_reply_e { + REP_RESULT, + REP_SASL, + REP_EXTENDED, + REP_SEARCH, + REP_SEARCHREF, + REP_INTERMEDIATE, + REP_GLUE_RESULT +} slap_reply_t; + +typedef struct rep_sasl_s { + struct berval *r_sasldata; +} rep_sasl_s; + +typedef struct rep_extended_s { + const char *r_rspoid; + struct berval *r_rspdata; +} rep_extended_s; + +typedef struct rep_search_s { + Entry *r_entry; + slap_mask_t r_attr_flags; +#define SLAP_ATTRS_UNDEFINED (0x00U) +#define SLAP_OPATTRS_NO (0x01U) +#define SLAP_OPATTRS_YES (0x02U) +#define SLAP_USERATTRS_NO (0x10U) +#define SLAP_USERATTRS_YES (0x20U) +#define SLAP_OPATTRS_MASK(f) ((f) & (SLAP_OPATTRS_NO|SLAP_OPATTRS_YES)) +#define SLAP_OPATTRS(f) (((f) & SLAP_OPATTRS_YES) == SLAP_OPATTRS_YES) +#define SLAP_USERATTRS_MASK(f) ((f) & (SLAP_USERATTRS_NO|SLAP_USERATTRS_YES)) +#define SLAP_USERATTRS(f) \ + (((f) & SLAP_USERATTRS_YES) == SLAP_USERATTRS_YES) + + Attribute *r_operational_attrs; + AttributeName *r_attrs; + int r_nentries; + BerVarray r_v2ref; +} rep_search_s; + +struct SlapReply { + slap_reply_t sr_type; + ber_tag_t sr_tag; + ber_int_t sr_msgid; + ber_int_t sr_err; + const char *sr_matched; + const char *sr_text; + BerVarray sr_ref; + LDAPControl **sr_ctrls; + union sr_u { + rep_search_s sru_search; + rep_sasl_s sru_sasl; + rep_extended_s sru_extended; + } sr_un; + slap_mask_t sr_flags; +#define REP_ENTRY_MODIFIABLE ((slap_mask_t) 0x0001U) +#define REP_ENTRY_MUSTBEFREED ((slap_mask_t) 0x0002U) +#define REP_ENTRY_MUSTRELEASE ((slap_mask_t) 0x0004U) +#define REP_ENTRY_MASK (REP_ENTRY_MODIFIABLE|REP_ENTRY_MUSTFLUSH) +#define REP_ENTRY_MUSTFLUSH (REP_ENTRY_MUSTBEFREED|REP_ENTRY_MUSTRELEASE) + +#define REP_MATCHED_MUSTBEFREED ((slap_mask_t) 0x0010U) +#define REP_MATCHED_MASK (REP_MATCHED_MUSTBEFREED) + +#define REP_REF_MUSTBEFREED ((slap_mask_t) 0x0020U) +#define REP_REF_MASK (REP_REF_MUSTBEFREED) + +#define REP_CTRLS_MUSTBEFREED ((slap_mask_t) 0x0040U) +#define REP_CTRLS_MASK (REP_CTRLS_MUSTBEFREED) + +#define REP_NO_ENTRYDN ((slap_mask_t) 0x1000U) +#define REP_NO_SUBSCHEMA ((slap_mask_t) 0x2000U) +#define REP_NO_OPERATIONALS (REP_NO_ENTRYDN|REP_NO_SUBSCHEMA) +}; + +/* short hands for response members */ +#define sr_attrs sr_un.sru_search.r_attrs +#define sr_entry sr_un.sru_search.r_entry +#define sr_operational_attrs sr_un.sru_search.r_operational_attrs +#define sr_attr_flags sr_un.sru_search.r_attr_flags +#define sr_v2ref sr_un.sru_search.r_v2ref +#define sr_nentries sr_un.sru_search.r_nentries +#define sr_rspoid sr_un.sru_extended.r_rspoid +#define sr_rspdata sr_un.sru_extended.r_rspdata +#define sr_sasldata sr_un.sru_sasl.r_sasldata + +typedef int (BI_op_func) LDAP_P(( Operation *op, SlapReply *rs )); +typedef BI_op_func BI_op_bind; +typedef BI_op_func BI_op_unbind; +typedef BI_op_func BI_op_search; +typedef BI_op_func BI_op_compare; +typedef BI_op_func BI_op_modify; +typedef BI_op_func BI_op_modrdn; +typedef BI_op_func BI_op_add; +typedef BI_op_func BI_op_delete; +typedef BI_op_func BI_op_abandon; +typedef BI_op_func BI_op_extended; +typedef BI_op_func BI_op_cancel; +typedef BI_op_func BI_chk_referrals; +typedef BI_op_func BI_chk_controls; +typedef int (BI_entry_release_rw) + LDAP_P(( Operation *op, Entry *e, int rw )); +typedef int (BI_entry_get_rw) LDAP_P(( Operation *op, struct berval *ndn, + ObjectClass *oc, AttributeDescription *at, int rw, Entry **e )); +typedef int (BI_operational) LDAP_P(( Operation *op, SlapReply *rs )); +typedef int (BI_has_subordinates) LDAP_P(( Operation *op, + Entry *e, int *hasSubs )); +typedef int (BI_access_allowed) LDAP_P(( Operation *op, Entry *e, + AttributeDescription *desc, struct berval *val, slap_access_t access, + AccessControlState *state, slap_mask_t *maskp )); +typedef int (BI_acl_group) LDAP_P(( Operation *op, Entry *target, + struct berval *gr_ndn, struct berval *op_ndn, + ObjectClass *group_oc, AttributeDescription *group_at )); +typedef int (BI_acl_attribute) LDAP_P(( Operation *op, Entry *target, + struct berval *entry_ndn, AttributeDescription *entry_at, + BerVarray *vals, slap_access_t access )); + +typedef int (BI_conn_func) LDAP_P(( BackendDB *bd, Connection *c )); +typedef BI_conn_func BI_connection_init; +typedef BI_conn_func BI_connection_destroy; + +typedef int (BI_tool_entry_open) LDAP_P(( BackendDB *be, int mode )); +typedef int (BI_tool_entry_close) LDAP_P(( BackendDB *be )); +typedef ID (BI_tool_entry_first) LDAP_P(( BackendDB *be )); +typedef ID (BI_tool_entry_first_x) LDAP_P(( BackendDB *be, struct berval *base, int scope, Filter *f )); +typedef ID (BI_tool_entry_next) LDAP_P(( BackendDB *be )); +typedef Entry* (BI_tool_entry_get) LDAP_P(( BackendDB *be, ID id )); +typedef ID (BI_tool_entry_put) LDAP_P(( BackendDB *be, Entry *e, + struct berval *text )); +typedef int (BI_tool_entry_reindex) LDAP_P(( BackendDB *be, ID id, AttributeDescription **adv )); +typedef int (BI_tool_sync) LDAP_P(( BackendDB *be )); +typedef ID (BI_tool_dn2id_get) LDAP_P(( BackendDB *be, struct berval *dn )); +typedef ID (BI_tool_entry_modify) LDAP_P(( BackendDB *be, Entry *e, + struct berval *text )); + +struct BackendInfo { + char *bi_type; /* type of backend */ + + /* + * per backend type routines: + * bi_init: called to allocate a backend_info structure, + * called once BEFORE configuration file is read. + * bi_init() initializes this structure hence is + * called directly from be_initialize() + * bi_config: called per 'backend' specific option + * all such options must before any 'database' options + * bi_config() is called only from read_config() + * bi_open: called to open each database, called + * once AFTER configuration file is read but + * BEFORE any bi_db_open() calls. + * bi_open() is called from backend_startup() + * bi_close: called to close each database, called + * once during shutdown after all bi_db_close calls. + * bi_close() is called from backend_shutdown() + * bi_destroy: called to destroy each database, called + * once during shutdown after all bi_db_destroy calls. + * bi_destory() is called from backend_destroy() + */ + BI_init *bi_init; + BI_config *bi_config; + BI_open *bi_open; + BI_close *bi_close; + BI_destroy *bi_destroy; + + /* + * per database routines: + * bi_db_init: called to initialize each database, + * called upon reading 'database <type>' + * called only from backend_db_init() + * bi_db_config: called to configure each database, + * called per database to handle per database options + * called only from read_config() + * bi_db_open: called to open each database + * called once per database immediately AFTER bi_open() + * calls but before daemon startup. + * called only by backend_startup() + * bi_db_close: called to close each database + * called once per database during shutdown but BEFORE + * any bi_close call. + * called only by backend_shutdown() + * bi_db_destroy: called to destroy each database + * called once per database during shutdown AFTER all + * bi_close calls but before bi_destory calls. + * called only by backend_destory() + */ + BI_db_init *bi_db_init; + BI_db_config *bi_db_config; + BI_db_open *bi_db_open; + BI_db_close *bi_db_close; + BI_db_destroy *bi_db_destroy; + + /* LDAP Operations Handling Routines */ + BI_op_bind *bi_op_bind; + BI_op_unbind *bi_op_unbind; + BI_op_search *bi_op_search; + BI_op_compare *bi_op_compare; + BI_op_modify *bi_op_modify; + BI_op_modrdn *bi_op_modrdn; + BI_op_add *bi_op_add; + BI_op_delete *bi_op_delete; + BI_op_abandon *bi_op_abandon; + + /* Extended Operations Helper */ + BI_op_extended *bi_extended; + BI_op_cancel *bi_op_cancel; + + /* Auxilary Functions */ + BI_operational *bi_operational; + BI_chk_referrals *bi_chk_referrals; + BI_chk_controls *bi_chk_controls; + BI_entry_get_rw *bi_entry_get_rw; + BI_entry_release_rw *bi_entry_release_rw; + + BI_has_subordinates *bi_has_subordinates; + BI_access_allowed *bi_access_allowed; + BI_acl_group *bi_acl_group; + BI_acl_attribute *bi_acl_attribute; + + BI_connection_init *bi_connection_init; + BI_connection_destroy *bi_connection_destroy; + + /* hooks for slap tools */ + BI_tool_entry_open *bi_tool_entry_open; + BI_tool_entry_close *bi_tool_entry_close; + BI_tool_entry_first *bi_tool_entry_first; /* deprecated */ + BI_tool_entry_first_x *bi_tool_entry_first_x; + BI_tool_entry_next *bi_tool_entry_next; + BI_tool_entry_get *bi_tool_entry_get; + BI_tool_entry_put *bi_tool_entry_put; + BI_tool_entry_reindex *bi_tool_entry_reindex; + BI_tool_sync *bi_tool_sync; + BI_tool_dn2id_get *bi_tool_dn2id_get; + BI_tool_entry_modify *bi_tool_entry_modify; + +#define SLAP_INDEX_ADD_OP 0x0001 +#define SLAP_INDEX_DELETE_OP 0x0002 + + slap_mask_t bi_flags; /* backend flags */ +#define SLAP_BFLAG_MONITOR 0x0001U /* a monitor backend */ +#define SLAP_BFLAG_CONFIG 0x0002U /* a config backend */ +#define SLAP_BFLAG_FRONTEND 0x0004U /* the frontendDB */ +#define SLAP_BFLAG_NOLASTMODCMD 0x0010U +#define SLAP_BFLAG_INCREMENT 0x0100U +#define SLAP_BFLAG_ALIASES 0x1000U +#define SLAP_BFLAG_REFERRALS 0x2000U +#define SLAP_BFLAG_SUBENTRIES 0x4000U +#define SLAP_BFLAG_DYNAMIC 0x8000U + +/* overlay specific */ +#define SLAPO_BFLAG_SINGLE 0x01000000U +#define SLAPO_BFLAG_DBONLY 0x02000000U +#define SLAPO_BFLAG_GLOBONLY 0x04000000U +#define SLAPO_BFLAG_MASK 0xFF000000U + +#define SLAP_BFLAGS(be) ((be)->bd_info->bi_flags) +#define SLAP_MONITOR(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_MONITOR) +#define SLAP_CONFIG(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_CONFIG) +#define SLAP_FRONTEND(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_FRONTEND) +#define SLAP_INCREMENT(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_INCREMENT) +#define SLAP_ALIASES(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_ALIASES) +#define SLAP_REFERRALS(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_REFERRALS) +#define SLAP_SUBENTRIES(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_SUBENTRIES) +#define SLAP_DYNAMIC(be) ((SLAP_BFLAGS(be) & SLAP_BFLAG_DYNAMIC) || (SLAP_DBFLAGS(be) & SLAP_DBFLAG_DYNAMIC)) +#define SLAP_NOLASTMODCMD(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_NOLASTMODCMD) +#define SLAP_LASTMODCMD(be) (!SLAP_NOLASTMODCMD(be)) + +/* overlay specific */ +#define SLAPO_SINGLE(be) (SLAP_BFLAGS(be) & SLAPO_BFLAG_SINGLE) +#define SLAPO_DBONLY(be) (SLAP_BFLAGS(be) & SLAPO_BFLAG_DBONLY) +#define SLAPO_GLOBONLY(be) (SLAP_BFLAGS(be) & SLAPO_BFLAG_GLOBONLY) + + char **bi_controls; /* supported controls */ + char bi_ctrls[SLAP_MAX_CIDS + 1]; + + unsigned int bi_nDB; /* number of databases of this type */ + struct ConfigOCs *bi_cf_ocs; + char **bi_obsolete_names; + void *bi_extra; /* backend type-specific APIs */ + void *bi_private; /* backend type-specific config data */ + LDAP_STAILQ_ENTRY(BackendInfo) bi_next ; +}; + +#define c_authtype c_authz.sai_method +#define c_authmech c_authz.sai_mech +#define c_dn c_authz.sai_dn +#define c_ndn c_authz.sai_ndn +#define c_ssf c_authz.sai_ssf +#define c_transport_ssf c_authz.sai_transport_ssf +#define c_tls_ssf c_authz.sai_tls_ssf +#define c_sasl_ssf c_authz.sai_sasl_ssf + +#define o_authtype o_authz.sai_method +#define o_authmech o_authz.sai_mech +#define o_dn o_authz.sai_dn +#define o_ndn o_authz.sai_ndn +#define o_ssf o_authz.sai_ssf +#define o_transport_ssf o_authz.sai_transport_ssf +#define o_tls_ssf o_authz.sai_tls_ssf +#define o_sasl_ssf o_authz.sai_sasl_ssf + +typedef int (slap_response)( Operation *, SlapReply * ); + +struct slap_callback; +typedef void (slap_writewait)( Operation *, struct slap_callback * ); + +typedef struct slap_callback { + struct slap_callback *sc_next; + slap_response *sc_response; + slap_response *sc_cleanup; + void *sc_private; + slap_writewait *sc_writewait; +} slap_callback; + +struct slap_overinfo; + +typedef enum slap_operation_e { + op_bind = 0, + op_unbind, + op_search, + op_compare, + op_modify, + op_modrdn, + op_add, + op_delete, + op_abandon, + op_extended, + op_cancel, + op_aux_operational, + op_aux_chk_referrals, + op_aux_chk_controls, + op_last +} slap_operation_t; + +typedef struct slap_overinst { + BackendInfo on_bi; + slap_response *on_response; + struct slap_overinfo *on_info; + struct slap_overinst *on_next; +} slap_overinst; + +typedef struct slap_overinfo { + BackendInfo oi_bi; + BackendInfo *oi_orig; + BackendDB *oi_origdb; + struct slap_overinst *oi_list; +} slap_overinfo; + +/* Should successive callbacks in a chain be processed? */ +#define SLAP_CB_BYPASS 0x08800 +#define SLAP_CB_CONTINUE 0x08000 + +/* + * Paged Results state + */ +typedef unsigned long PagedResultsCookie; +typedef struct PagedResultsState { + Backend *ps_be; + ber_int_t ps_size; + int ps_count; + PagedResultsCookie ps_cookie; + struct berval ps_cookieval; +} PagedResultsState; + +struct slap_csn_entry { + Operation *ce_op; + struct berval ce_csn; + int ce_sid; +#define SLAP_CSN_PENDING 1 +#define SLAP_CSN_COMMIT 2 + long ce_state; + LDAP_TAILQ_ENTRY (slap_csn_entry) ce_csn_link; +}; + +/* + * Caches the result of a backend_group check for ACL evaluation + */ +typedef struct GroupAssertion { + struct GroupAssertion *ga_next; + Backend *ga_be; + ObjectClass *ga_oc; + AttributeDescription *ga_at; + int ga_res; + ber_len_t ga_len; + char ga_ndn[1]; +} GroupAssertion; + +struct slap_control_ids { + int sc_LDAPsync; + int sc_assert; + int sc_domainScope; + int sc_dontUseCopy; + int sc_manageDSAit; + int sc_modifyIncrement; + int sc_noOp; + int sc_pagedResults; + int sc_permissiveModify; + int sc_postRead; + int sc_preRead; + int sc_proxyAuthz; + int sc_relax; + int sc_searchOptions; +#ifdef SLAP_CONTROL_X_SORTEDRESULTS + int sc_sortedResults; +#endif + int sc_subentries; +#ifdef SLAP_CONTROL_X_TREE_DELETE + int sc_treeDelete; +#endif +#ifdef LDAP_X_TXN + int sc_txnSpec; +#endif +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + int sc_sessionTracking; +#endif + int sc_valuesReturnFilter; +#ifdef SLAP_CONTROL_X_WHATFAILED + int sc_whatFailed; +#endif +}; + +/* + * Operation indices + */ +typedef enum { + SLAP_OP_BIND = 0, + SLAP_OP_UNBIND, + SLAP_OP_SEARCH, + SLAP_OP_COMPARE, + SLAP_OP_MODIFY, + SLAP_OP_MODRDN, + SLAP_OP_ADD, + SLAP_OP_DELETE, + SLAP_OP_ABANDON, + SLAP_OP_EXTENDED, + SLAP_OP_LAST +} slap_op_t; + +typedef struct slap_counters_t { + struct slap_counters_t *sc_next; + ldap_pvt_thread_mutex_t sc_mutex; + ldap_pvt_mp_t sc_bytes; + ldap_pvt_mp_t sc_pdu; + ldap_pvt_mp_t sc_entries; + ldap_pvt_mp_t sc_refs; + + ldap_pvt_mp_t sc_ops_completed; + ldap_pvt_mp_t sc_ops_initiated; +#ifdef SLAPD_MONITOR + ldap_pvt_mp_t sc_ops_completed_[SLAP_OP_LAST]; + ldap_pvt_mp_t sc_ops_initiated_[SLAP_OP_LAST]; +#endif /* SLAPD_MONITOR */ +} slap_counters_t; + +/* + * represents an operation pending from an ldap client + */ +typedef struct Opheader { + unsigned long oh_opid; /* id of this operation */ + unsigned long oh_connid; /* id of conn initiating this op */ + Connection *oh_conn; /* connection spawning this op */ + + ber_int_t oh_msgid; /* msgid of the request */ + ber_int_t oh_protocol; /* version of the LDAP protocol used by client */ + + ldap_pvt_thread_t oh_tid; /* thread handling this op */ + + void *oh_threadctx; /* thread pool thread context */ + void *oh_tmpmemctx; /* slab malloc context */ + BerMemoryFunctions *oh_tmpmfuncs; + + slap_counters_t *oh_counters; + + char oh_log_prefix[ /* sizeof("conn= op=") + 2*LDAP_PVT_INTTYPE_CHARS(unsigned long) */ SLAP_TEXT_BUFLEN ]; + +#ifdef LDAP_SLAPI + void *oh_extensions; /* NS-SLAPI plugin */ +#endif +} Opheader; + +typedef union OpRequest { + req_add_s oq_add; + req_bind_s oq_bind; + req_compare_s oq_compare; + req_modify_s oq_modify; + req_modrdn_s oq_modrdn; + req_search_s oq_search; + req_abandon_s oq_abandon; + req_abandon_s oq_cancel; + req_extended_s oq_extended; + req_pwdexop_s oq_pwdexop; +} OpRequest; + +/* This is only a header. Actual users should define their own + * structs with the oe_next / oe_key fields at the top and + * whatever else they need following. + */ +typedef struct OpExtra { + LDAP_SLIST_ENTRY(OpExtra) oe_next; + void *oe_key; +} OpExtra; + +typedef struct OpExtraDB { + OpExtra oe; + BackendDB *oe_db; +} OpExtraDB; + +struct Operation { + Opheader *o_hdr; + +#define o_opid o_hdr->oh_opid +#define o_connid o_hdr->oh_connid +#define o_conn o_hdr->oh_conn +#define o_msgid o_hdr->oh_msgid +#define o_protocol o_hdr->oh_protocol +#define o_tid o_hdr->oh_tid +#define o_threadctx o_hdr->oh_threadctx +#define o_tmpmemctx o_hdr->oh_tmpmemctx +#define o_tmpmfuncs o_hdr->oh_tmpmfuncs +#define o_counters o_hdr->oh_counters + +#define o_tmpalloc o_tmpmfuncs->bmf_malloc +#define o_tmpcalloc o_tmpmfuncs->bmf_calloc +#define o_tmprealloc o_tmpmfuncs->bmf_realloc +#define o_tmpfree o_tmpmfuncs->bmf_free + +#define o_log_prefix o_hdr->oh_log_prefix + + ber_tag_t o_tag; /* tag of the request */ + time_t o_time; /* time op was initiated */ + int o_tincr; /* counter for multiple ops with same o_time */ + + BackendDB *o_bd; /* backend DB processing this op */ + struct berval o_req_dn; /* DN of target of request */ + struct berval o_req_ndn; + + OpRequest o_request; + +/* short hands for union members */ +#define oq_add o_request.oq_add +#define oq_bind o_request.oq_bind +#define oq_compare o_request.oq_compare +#define oq_modify o_request.oq_modify +#define oq_modrdn o_request.oq_modrdn +#define oq_search o_request.oq_search +#define oq_abandon o_request.oq_abandon +#define oq_cancel o_request.oq_cancel +#define oq_extended o_request.oq_extended +#define oq_pwdexop o_request.oq_pwdexop + +/* short hands for inner request members */ +#define orb_method oq_bind.rb_method +#define orb_cred oq_bind.rb_cred +#define orb_edn oq_bind.rb_edn +#define orb_ssf oq_bind.rb_ssf +#define orb_mech oq_bind.rb_mech + +#define ors_scope oq_search.rs_scope +#define ors_deref oq_search.rs_deref +#define ors_slimit oq_search.rs_slimit +#define ors_tlimit oq_search.rs_tlimit +#define ors_limit oq_search.rs_limit +#define ors_attrsonly oq_search.rs_attrsonly +#define ors_attrs oq_search.rs_attrs +#define ors_filter oq_search.rs_filter +#define ors_filterstr oq_search.rs_filterstr + +#define orr_modlist oq_modrdn.rs_mods.rs_modlist +#define orr_no_opattrs oq_modrdn.rs_mods.rs_no_opattrs +#define orr_deleteoldrdn oq_modrdn.rs_deleteoldrdn +#define orr_newrdn oq_modrdn.rs_newrdn +#define orr_nnewrdn oq_modrdn.rs_nnewrdn +#define orr_newSup oq_modrdn.rs_newSup +#define orr_nnewSup oq_modrdn.rs_nnewSup + +#define orc_ava oq_compare.rs_ava + +#define ora_e oq_add.rs_e +#define ora_modlist oq_add.rs_modlist + +#define orn_msgid oq_abandon.rs_msgid + +#define orm_modlist oq_modify.rs_mods.rs_modlist +#define orm_no_opattrs oq_modify.rs_mods.rs_no_opattrs +#define orm_increment oq_modify.rs_increment + +#define ore_reqoid oq_extended.rs_reqoid +#define ore_flags oq_extended.rs_flags +#define ore_reqdata oq_extended.rs_reqdata + volatile sig_atomic_t o_abandon; /* abandon flag */ + volatile sig_atomic_t o_cancel; /* cancel flag */ +#define SLAP_CANCEL_NONE 0x00 +#define SLAP_CANCEL_REQ 0x01 +#define SLAP_CANCEL_ACK 0x02 +#define SLAP_CANCEL_DONE 0x03 + + GroupAssertion *o_groups; + char o_do_not_cache; /* don't cache groups from this op */ + char o_is_auth_check; /* authorization in progress */ + char o_dont_replicate; + slap_access_t o_acl_priv; + + char o_nocaching; + char o_delete_glue_parent; + char o_no_schema_check; +#define get_no_schema_check(op) ((op)->o_no_schema_check) + char o_no_subordinate_glue; +#define get_no_subordinate_glue(op) ((op)->o_no_subordinate_glue) + +#define SLAP_CONTROL_NONE 0 +#define SLAP_CONTROL_IGNORED 1 +#define SLAP_CONTROL_NONCRITICAL 2 +#define SLAP_CONTROL_CRITICAL 3 +#define SLAP_CONTROL_MASK 3 + +/* spare bits for simple flags */ +#define SLAP_CONTROL_SHIFT 4 /* shift to reach data bits */ +#define SLAP_CONTROL_DATA0 0x10 +#define SLAP_CONTROL_DATA1 0x20 +#define SLAP_CONTROL_DATA2 0x40 +#define SLAP_CONTROL_DATA3 0x80 + +#define _SCM(x) ((x) & SLAP_CONTROL_MASK) + + char o_ctrlflag[SLAP_MAX_CIDS]; /* per-control flags */ + void **o_controls; /* per-control state */ + +#define o_dontUseCopy o_ctrlflag[slap_cids.sc_dontUseCopy] +#define get_dontUseCopy(op) _SCM((op)->o_dontUseCopy) + +#define o_relax o_ctrlflag[slap_cids.sc_relax] +#define get_relax(op) _SCM((op)->o_relax) + +#define o_managedsait o_ctrlflag[slap_cids.sc_manageDSAit] +#define get_manageDSAit(op) _SCM((op)->o_managedsait) + +#define o_noop o_ctrlflag[slap_cids.sc_noOp] +#define o_proxy_authz o_ctrlflag[slap_cids.sc_proxyAuthz] +#define o_subentries o_ctrlflag[slap_cids.sc_subentries] + +#define get_subentries(op) _SCM((op)->o_subentries) +#define o_subentries_visibility o_ctrlflag[slap_cids.sc_subentries] + +#define set_subentries_visibility(op) ((op)->o_subentries |= SLAP_CONTROL_DATA0) +#define get_subentries_visibility(op) (((op)->o_subentries & SLAP_CONTROL_DATA0) != 0) + +#define o_assert o_ctrlflag[slap_cids.sc_assert] +#define get_assert(op) ((int)(op)->o_assert) +#define o_assertion o_controls[slap_cids.sc_assert] +#define get_assertion(op) ((op)->o_assertion) + +#define o_valuesreturnfilter o_ctrlflag[slap_cids.sc_valuesReturnFilter] +#define o_vrFilter o_controls[slap_cids.sc_valuesReturnFilter] + +#define o_permissive_modify o_ctrlflag[slap_cids.sc_permissiveModify] +#define get_permissiveModify(op) ((int)(op)->o_permissive_modify) + +#define o_domain_scope o_ctrlflag[slap_cids.sc_domainScope] +#define get_domainScope(op) ((int)(op)->o_domain_scope) + +#ifdef SLAP_CONTROL_X_TREE_DELETE +#define o_tree_delete o_ctrlflag[slap_cids.sc_treeDelete] +#define get_treeDelete(op) ((int)(op)->o_tree_delete) +#endif + +#define o_preread o_ctrlflag[slap_cids.sc_preRead] +#define o_postread o_ctrlflag[slap_cids.sc_postRead] + +#define o_preread_attrs o_controls[slap_cids.sc_preRead] +#define o_postread_attrs o_controls[slap_cids.sc_postRead] + +#define o_pagedresults o_ctrlflag[slap_cids.sc_pagedResults] +#define o_pagedresults_state o_controls[slap_cids.sc_pagedResults] +#define get_pagedresults(op) ((int)(op)->o_pagedresults) + +#ifdef SLAP_CONTROL_X_SORTEDRESULTS +#define o_sortedresults o_ctrlflag[slap_cids.sc_sortedResults] +#endif + +#ifdef LDAP_X_TXN +#define o_txnSpec o_ctrlflag[slap_cids.sc_txnSpec] +#endif + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING +#define o_session_tracking o_ctrlflag[slap_cids.sc_sessionTracking] +#define o_tracked_sessions o_controls[slap_cids.sc_sessionTracking] +#define get_sessionTracking(op) ((int)(op)->o_session_tracking) +#endif + +#ifdef SLAP_CONTROL_X_WHATFAILED +#define o_whatFailed o_ctrlflag[slap_cids.sc_whatFailed] +#define get_whatFailed(op) _SCM((op)->o_whatFailed) +#endif + +#define o_sync o_ctrlflag[slap_cids.sc_LDAPsync] + + AuthorizationInformation o_authz; + + BerElement *o_ber; /* ber of the request */ + BerElement *o_res_ber; /* ber of the CLDAP reply or readback control */ + slap_callback *o_callback; /* callback pointers */ + LDAPControl **o_ctrls; /* controls */ + struct berval o_csn; + + /* DEPRECATE o_private - use o_extra instead */ + void *o_private; /* anything the backend needs */ + LDAP_SLIST_HEAD(o_e, OpExtra) o_extra; /* anything the backend needs */ + + LDAP_STAILQ_ENTRY(Operation) o_next; /* next operation in list */ +}; + +typedef struct OperationBuffer { + Operation ob_op; + Opheader ob_hdr; + void *ob_controls[SLAP_MAX_CIDS]; +} OperationBuffer; + +#define send_ldap_error( op, rs, err, text ) do { \ + (rs)->sr_err = err; (rs)->sr_text = text; \ + ((op)->o_conn->c_send_ldap_result)( op, rs ); \ + } while (0) +#define send_ldap_discon( op, rs, err, text ) do { \ + (rs)->sr_err = err; (rs)->sr_text = text; \ + send_ldap_disconnect( op, rs ); \ + } while (0) + +typedef void (SEND_LDAP_RESULT)( + Operation *op, SlapReply *rs); +typedef int (SEND_SEARCH_ENTRY)( + Operation *op, SlapReply *rs); +typedef int (SEND_SEARCH_REFERENCE)( + Operation *op, SlapReply *rs); +typedef void (SEND_LDAP_EXTENDED)( + Operation *op, SlapReply *rs); +typedef void (SEND_LDAP_INTERMEDIATE)( + Operation *op, SlapReply *rs); + +#define send_ldap_result( op, rs ) \ + ((op)->o_conn->c_send_ldap_result)( op, rs ) +#define send_search_entry( op, rs ) \ + ((op)->o_conn->c_send_search_entry)( op, rs ) +#define send_search_reference( op, rs ) \ + ((op)->o_conn->c_send_search_reference)( op, rs ) +#define send_ldap_extended( op, rs ) \ + ((op)->o_conn->c_send_ldap_extended)( op, rs ) +#define send_ldap_intermediate( op, rs ) \ + ((op)->o_conn->c_send_ldap_intermediate)( op, rs ) + +typedef struct Listener Listener; + +/* + * represents a connection from an ldap client + */ +/* structure state (protected by connections_mutex) */ +enum sc_struct_state { + SLAP_C_UNINITIALIZED = 0, /* MUST BE ZERO (0) */ + SLAP_C_UNUSED, + SLAP_C_USED, + SLAP_C_PENDING +}; + +/* connection state (protected by c_mutex ) */ +enum sc_conn_state { + SLAP_C_INVALID = 0, /* MUST BE ZERO (0) */ + SLAP_C_INACTIVE, /* zero threads */ + SLAP_C_CLOSING, /* closing */ + SLAP_C_ACTIVE, /* one or more threads */ + SLAP_C_BINDING, /* binding */ + SLAP_C_CLIENT /* outbound client conn */ +}; +struct Connection { + enum sc_struct_state c_struct_state; /* structure management state */ + enum sc_conn_state c_conn_state; /* connection state */ + int c_conn_idx; /* slot in connections array */ + ber_socket_t c_sd; + const char *c_close_reason; /* why connection is closing */ + + ldap_pvt_thread_mutex_t c_mutex; /* protect the connection */ + Sockbuf *c_sb; /* ber connection stuff */ + + /* only can be changed by connect_init */ + time_t c_starttime; /* when the connection was opened */ + time_t c_activitytime; /* when the connection was last used */ + unsigned long c_connid; /* id of this connection for stats*/ + + struct berval c_peer_domain; /* DNS name of client */ + struct berval c_peer_name; /* peer name (trans=addr:port) */ + Listener *c_listener; +#define c_listener_url c_listener->sl_url /* listener URL */ +#define c_sock_name c_listener->sl_name /* sock name (trans=addr:port) */ + + /* only can be changed by binding thread */ + struct berval c_sasl_bind_mech; /* mech in progress */ + struct berval c_sasl_dn; /* temporary storage */ + struct berval c_sasl_authz_dn; /* SASL proxy authz */ + + /* authorization backend */ + Backend *c_authz_backend; + void *c_authz_cookie; +#define SLAP_IS_AUTHZ_BACKEND( op ) \ + ( (op)->o_bd != NULL \ + && (op)->o_bd->be_private != NULL \ + && (op)->o_conn != NULL \ + && (op)->o_conn->c_authz_backend != NULL \ + && ( (op)->o_bd->be_private == (op)->o_conn->c_authz_backend->be_private \ + || (op)->o_bd->be_private == (op)->o_conn->c_authz_cookie ) ) + + AuthorizationInformation c_authz; + + ber_int_t c_protocol; /* version of the LDAP protocol used by client */ + + LDAP_STAILQ_HEAD(c_o, Operation) c_ops; /* list of operations being processed */ + LDAP_STAILQ_HEAD(c_po, Operation) c_pending_ops; /* list of pending operations */ + + ldap_pvt_thread_mutex_t c_write1_mutex; /* only one pdu written at a time */ + ldap_pvt_thread_cond_t c_write1_cv; /* only one pdu written at a time */ + ldap_pvt_thread_mutex_t c_write2_mutex; /* used to wait for sd write-ready */ + ldap_pvt_thread_cond_t c_write2_cv; /* used to wait for sd write-ready*/ + + BerElement *c_currentber; /* ber we're attempting to read */ + int c_writers; /* number of writers waiting */ + char c_writing; /* someone is writing */ + + char c_sasl_bind_in_progress; /* multi-op bind in progress */ + char c_writewaiter; /* true if blocked on write */ + + +#define CONN_IS_TLS 1 +#define CONN_IS_UDP 2 +#define CONN_IS_CLIENT 4 +#define CONN_IS_IPC 8 + +#ifdef LDAP_CONNECTIONLESS + char c_is_udp; /* true if this is (C)LDAP over UDP */ +#endif +#ifdef HAVE_TLS + char c_is_tls; /* true if this LDAP over raw TLS */ + char c_needs_tls_accept; /* true if SSL_accept should be called */ +#endif + char c_sasl_layers; /* true if we need to install SASL i/o handlers */ + char c_sasl_done; /* SASL completed once */ + void *c_sasl_authctx; /* SASL authentication context */ + void *c_sasl_sockctx; /* SASL security layer context */ + void *c_sasl_extra; /* SASL session extra stuff */ + Operation *c_sasl_bindop; /* set to current op if it's a bind */ + +#ifdef LDAP_X_TXN +#define CONN_TXN_INACTIVE 0 +#define CONN_TXN_SPECIFY 1 +#define CONN_TXN_SETTLE -1 + int c_txn; + + Backend *c_txn_backend; + LDAP_STAILQ_HEAD(c_to, Operation) c_txn_ops; /* list of operations in txn */ +#endif + + PagedResultsState c_pagedresults_state; /* paged result state */ + + long c_n_ops_received; /* num of ops received (next op_id) */ + long c_n_ops_executing; /* num of ops currently executing */ + long c_n_ops_pending; /* num of ops pending execution */ + long c_n_ops_completed; /* num of ops completed */ + + long c_n_get; /* num of get calls */ + long c_n_read; /* num of read calls */ + long c_n_write; /* num of write calls */ + + void *c_extensions; /* Netscape plugin */ + + /* + * Client connection handling + */ + ldap_pvt_thread_start_t *c_clientfunc; + void *c_clientarg; + + /* + * These are the "callbacks" that are available for back-ends to + * supply data back to connected clients that are connected + * through the "front-end". + */ + SEND_LDAP_RESULT *c_send_ldap_result; + SEND_SEARCH_ENTRY *c_send_search_entry; + SEND_SEARCH_REFERENCE *c_send_search_reference; + SEND_LDAP_EXTENDED *c_send_ldap_extended; + SEND_LDAP_INTERMEDIATE *c_send_ldap_intermediate; +}; + +#ifdef LDAP_DEBUG +#ifdef LDAP_SYSLOG +#ifdef LOG_LOCAL4 +#define SLAP_DEFAULT_SYSLOG_USER LOG_LOCAL4 +#endif /* LOG_LOCAL4 */ + +#define Statslog( level, fmt, connid, opid, arg1, arg2, arg3 ) \ + Log5( (level), ldap_syslog_level, (fmt), (connid), (opid), (arg1), (arg2), (arg3) ) +#define StatslogTest( level ) ((ldap_debug | ldap_syslog) & (level)) +#else /* !LDAP_SYSLOG */ +#define Statslog( level, fmt, connid, opid, arg1, arg2, arg3 ) \ + do { \ + if ( ldap_debug & (level) ) \ + lutil_debug( ldap_debug, (level), (fmt), (connid), (opid), (arg1), (arg2), (arg3) );\ + } while (0) +#define StatslogTest( level ) (ldap_debug & (level)) +#endif /* !LDAP_SYSLOG */ +#else /* !LDAP_DEBUG */ +#define Statslog( level, fmt, connid, opid, arg1, arg2, arg3 ) ((void) 0) +#define StatslogTest( level ) (0) +#endif /* !LDAP_DEBUG */ + +/* + * listener; need to access it from monitor backend + */ +struct Listener { + struct berval sl_url; + struct berval sl_name; + mode_t sl_perms; +#ifdef HAVE_TLS + int sl_is_tls; +#endif +#ifdef LDAP_CONNECTIONLESS + int sl_is_udp; /* UDP listener is also data port */ +#endif + int sl_mute; /* Listener is temporarily disabled due to emfile */ + int sl_busy; /* Listener is busy (accept thread activated) */ + ber_socket_t sl_sd; + Sockaddr sl_sa; +#define sl_addr sl_sa.sa_in_addr +#ifdef LDAP_DEVEL +#define LDAP_TCP_BUFFER +#endif +#ifdef LDAP_TCP_BUFFER + int sl_tcp_rmem; /* custom TCP read buffer size */ + int sl_tcp_wmem; /* custom TCP write buffer size */ +#endif +}; + +/* + * Better know these all around slapd + */ +#define SLAP_LDAPDN_PRETTY 0x1 +#define SLAP_LDAPDN_MAXLEN 8192 + +/* number of response controls supported */ +#define SLAP_MAX_RESPONSE_CONTROLS 6 + +#ifdef SLAP_SCHEMA_EXPOSE +#define SLAP_CTRL_HIDE 0x00000000U +#else +#define SLAP_CTRL_HIDE 0x80000000U +#endif + +#define SLAP_CTRL_REQUIRES_ROOT 0x40000000U /* for Relax */ + +#define SLAP_CTRL_GLOBAL 0x00800000U +#define SLAP_CTRL_GLOBAL_SEARCH 0x00010000U /* for NOOP */ + +#define SLAP_CTRL_OPFLAGS 0x0000FFFFU +#define SLAP_CTRL_ABANDON 0x00000001U +#define SLAP_CTRL_ADD 0x00002002U +#define SLAP_CTRL_BIND 0x00000004U +#define SLAP_CTRL_COMPARE 0x00001008U +#define SLAP_CTRL_DELETE 0x00002010U +#define SLAP_CTRL_MODIFY 0x00002020U +#define SLAP_CTRL_RENAME 0x00002040U +#define SLAP_CTRL_SEARCH 0x00001080U +#define SLAP_CTRL_UNBIND 0x00000100U + +#define SLAP_CTRL_INTROGATE (SLAP_CTRL_COMPARE|SLAP_CTRL_SEARCH) +#define SLAP_CTRL_UPDATE \ + (SLAP_CTRL_ADD|SLAP_CTRL_DELETE|SLAP_CTRL_MODIFY|SLAP_CTRL_RENAME) +#define SLAP_CTRL_ACCESS (SLAP_CTRL_INTROGATE|SLAP_CTRL_UPDATE) + +typedef int (SLAP_CTRL_PARSE_FN) LDAP_P(( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl )); + +typedef int (*SLAP_ENTRY_INFO_FN) LDAP_P(( void *arg, Entry *e )); + +#define SLAP_SLAB_SIZE (1024*1024) +#define SLAP_SLAB_STACK 1 + +#define SLAP_ZONE_ALLOC 1 +#undef SLAP_ZONE_ALLOC + +#ifdef LDAP_COMP_MATCH +/* + * Extensible Filter Definition + * + * MatchingRuleAssertion := SEQUENCE { + * matchingRule [1] MatchingRuleId OPTIONAL, + * type [2] AttributeDescription OPTIONAL, + * matchValue [3] AssertionValue, + * dnAttributes [4] BOOLEAN DEFAULT FALSE } + * + * Following ComponentFilter is contained in matchValue + * + * ComponentAssertion ::= SEQUENCE { + * component ComponentReference (SIZE(1..MAX)) OPTIONAL + * useDefaultValues BOOLEAN DEFAULT TRUE, + * rule MATCHING-RULE.&id, + * value MATCHING-RULE.&AssertionType } + * + * ComponentFilter ::= CHOICE { + * item [0] ComponentAssertion, + * and [1] SEQUENCE OF ComponentFilter, + * or [2] SEQUENCE OF ComponentFilter, + * not [3] ComponentFilter } + */ + +#define LDAP_COMPREF_IDENTIFIER ((ber_tag_t) 0x80U) +#define LDAP_COMPREF_FROM_BEGINNING ((ber_tag_t) 0x81U) +#define LDAP_COMPREF_COUNT ((ber_tag_t) 0x82U) +#define LDAP_COMPREF_FROM_END ((ber_tag_t) 0x83U) +#define LDAP_COMPREF_CONTENT ((ber_tag_t) 0x84U) +#define LDAP_COMPREF_SELECT ((ber_tag_t) 0x85U) +#define LDAP_COMPREF_ALL ((ber_tag_t) 0x86U) +#define LDAP_COMPREF_DEFINED ((ber_tag_t) 0x87U) +#define LDAP_COMPREF_UNDEFINED ((ber_tag_t) 0x88U) + +#define LDAP_COMP_FILTER_AND ((ber_tag_t) 0xa0U) +#define LDAP_COMP_FILTER_OR ((ber_tag_t) 0xa1U) +#define LDAP_COMP_FILTER_NOT ((ber_tag_t) 0xa2U) +#define LDAP_COMP_FILTER_ITEM ((ber_tag_t) 0xa3U) +#define LDAP_COMP_FILTER_UNDEFINED ((ber_tag_t) 0xa4U) + +typedef struct ComponentId ComponentId; +typedef struct ComponentReference ComponentReference; +typedef struct ComponentAssertion ComponentAssertion; +typedef struct ComponentAssertionValue ComponentAssertionValue; +typedef struct ComponentSyntaxInfo ComponentSyntaxInfo; +typedef struct ComponentDesc ComponentDesc; + +struct ComponentData { + void *cd_mem_op; /* nibble memory handler */ + ComponentSyntaxInfo** cd_tree; /* component tree */ +}; + +struct ComponentId { + int ci_type; + ComponentId *ci_next; + + union comp_id_value{ + BerValue ci_identifier; + ber_int_t ci_from_beginning; + ber_int_t ci_count; + ber_int_t ci_from_end; + ber_int_t ci_content; + BerValue ci_select_value; + char ci_all; + } ci_val; +}; + +struct ComponentReference { + ComponentId *cr_list; + ComponentId *cr_curr; + struct berval cr_string; + int cr_len; + /* Component Indexing */ + int cr_asn_type_id; + slap_mask_t cr_indexmask; + AttributeDescription* cr_ad; + BerVarray cr_nvals; + ComponentReference* cr_next; +}; + +struct ComponentAssertion { + ComponentReference *ca_comp_ref; + ber_int_t ca_use_def; + MatchingRule *ca_ma_rule; + struct berval ca_ma_value; + ComponentData ca_comp_data; /* componentized assertion */ + ComponentFilter *ca_cf; + MatchingRuleAssertion *ca_mra; +}; + +struct ComponentFilter { + ber_tag_t cf_choice; + union cf_un_u { + ber_int_t cf_un_result; + ComponentAssertion *cf_un_ca; + ComponentFilter *cf_un_complex; + } cf_un; + +#define cf_ca cf_un.cf_un_ca +#define cf_result cf_un.cf_un_result +#define cf_and cf_un.cf_un_complex +#define cf_or cf_un.cf_un_complex +#define cf_not cf_un.cf_un_complex +#define cf_any cf_un.cf_un_complex + + ComponentFilter *cf_next; +}; + +struct ComponentAssertionValue { + char* cav_buf; + char* cav_ptr; + char* cav_end; +}; + +typedef int encoder_func LDAP_P(( + void* b, + void* comp)); + +typedef int gser_decoder_func LDAP_P(( + void* mem_op, + void* b, + ComponentSyntaxInfo** comp_syn_info, + int* len, + int mode)); + +typedef int comp_free_func LDAP_P(( + void* b)); + +typedef int ber_decoder_func LDAP_P(( + void* mem_op, + void* b, + int tag, + int elmtLen, + ComponentSyntaxInfo* comp_syn_info, + int* len, + int mode)); + +typedef int ber_tag_decoder_func LDAP_P(( + void* mem_op, + void* b, + ComponentSyntaxInfo* comp_syn_info, + int* len, + int mode)); + +typedef void* extract_component_from_id_func LDAP_P(( + void* mem_op, + ComponentReference* cr, + void* comp )); + +typedef void* convert_attr_to_comp_func LDAP_P (( + Attribute* a, + Syntax* syn, + struct berval* bv )); + +typedef void* alloc_nibble_func LDAP_P (( + int initial_size, + int increment_size )); + +typedef void free_nibble_func LDAP_P (( + void* nm )); + +typedef void convert_assert_to_comp_func LDAP_P (( + void *mem_op, + ComponentSyntaxInfo* csi_attr, + struct berval* bv, + ComponentSyntaxInfo** csi, + int* len, + int mode )); + +typedef int convert_asn_to_ldap_func LDAP_P (( + ComponentSyntaxInfo* csi, + struct berval *bv )); + +typedef void free_component_func LDAP_P (( + void* mem_op)); + +typedef int test_component_func LDAP_P (( + void* attr_mem_op, + void* assert_mem_op, + ComponentSyntaxInfo* csi, + ComponentAssertion* ca)); + +typedef void* test_membership_func LDAP_P (( + void* in )); + +typedef void* get_component_info_func LDAP_P (( + int in )); + +typedef int component_encoder_func LDAP_P (( + void* mem_op, + ComponentSyntaxInfo* csi, + struct berval* nvals )); + +typedef int allcomponent_matching_func LDAP_P(( + char* oid, + ComponentSyntaxInfo* comp1, + ComponentSyntaxInfo* comp)); + +struct ComponentDesc { + /* Don't change the order of following four fields */ + int cd_tag; + AttributeType *cd_comp_type; + struct berval cd_ad_type; /* ad_type, ad_cname */ + struct berval cd_ad_cname; /* ad_type, ad_cname */ + unsigned cd_flags; /* ad_flags */ + int cd_type; + int cd_type_id; + encoder_func *cd_ldap_encoder; + encoder_func *cd_gser_encoder; + encoder_func *cd_ber_encoder; + gser_decoder_func *cd_gser_decoder; + ber_decoder_func *cd_ber_decoder; + comp_free_func *cd_free; + extract_component_from_id_func* cd_extract_i; + allcomponent_matching_func *cd_all_match; +}; + +struct ComponentSyntaxInfo { + Syntax *csi_syntax; + ComponentDesc *csi_comp_desc; +}; + +#endif /* LDAP_COMP_MATCH */ + +#ifdef SLAP_ZONE_ALLOC +#define SLAP_ZONE_SIZE 0x80000 /* 512KB */ +#define SLAP_ZONE_SHIFT 19 +#define SLAP_ZONE_INITSIZE 0x800000 /* 8MB */ +#define SLAP_ZONE_MAXSIZE 0x80000000/* 2GB */ +#define SLAP_ZONE_DELTA 0x800000 /* 8MB */ +#define SLAP_ZONE_ZOBLOCK 256 + +struct zone_object { + void *zo_ptr; + int zo_siz; + int zo_idx; + int zo_blockhead; + LDAP_LIST_ENTRY(zone_object) zo_link; +}; + +struct zone_latency_history { + double zlh_latency; + LDAP_STAILQ_ENTRY(zone_latency_history) zlh_next; +}; + +struct zone_heap { + int zh_fd; + int zh_zonesize; + int zh_zoneorder; + int zh_numzones; + int zh_maxzones; + int zh_deltazones; + void **zh_zones; + ldap_pvt_thread_rdwr_t *zh_znlock; + Avlnode *zh_zonetree; + unsigned char ***zh_maps; + int *zh_seqno; + LDAP_LIST_HEAD( zh_freelist, zone_object ) *zh_free; + LDAP_LIST_HEAD( zh_so, zone_object ) zh_zopool; + ldap_pvt_thread_mutex_t zh_mutex; + ldap_pvt_thread_rdwr_t zh_lock; + double zh_ema_latency; + unsigned long zh_ema_samples; + LDAP_STAILQ_HEAD( zh_latency_history, zone_latency_history ) + zh_latency_history_queue; + int zh_latency_history_qlen; + int zh_latency_jump; + int zh_swapping; +}; +#endif + +#define SLAP_BACKEND_INIT_MODULE(b) \ + static BackendInfo bi; \ + int \ + init_module( int argc, char *argv[] ) \ + { \ + bi.bi_type = #b ; \ + bi.bi_init = b ## _back_initialize; \ + backend_add( &bi ); \ + return 0; \ + } + +typedef int (OV_init)(void); +typedef struct slap_oinit_t { + const char *ov_type; + OV_init *ov_init; +} OverlayInit; + +LDAP_END_DECL + +#include "proto-slap.h" + +#endif /* _SLAP_H_ */ diff --git a/servers/slapd/slapacl.c b/servers/slapd/slapacl.c new file mode 100644 index 0000000..32c5e7e --- /dev/null +++ b/servers/slapd/slapacl.c @@ -0,0 +1,411 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2021 The OpenLDAP Foundation. + * Portions Copyright 2004 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/socket.h> +#include <ac/unistd.h> + +#include <lber.h> +#include <ldif.h> +#include <lutil.h> + +#include "slapcommon.h" + +static int +print_access( + Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + struct berval *nval ) +{ + int rc; + slap_mask_t mask; + char accessmaskbuf[ACCESSMASK_MAXLEN]; + + rc = access_allowed_mask( op, e, desc, nval, ACL_AUTH, NULL, &mask ); + + fprintf( stderr, "%s%s%s: %s\n", + desc->ad_cname.bv_val, + ( val && !BER_BVISNULL( val ) ) ? "=" : "", + ( val && !BER_BVISNULL( val ) ) ? + ( desc == slap_schema.si_ad_userPassword ? + "****" : val->bv_val ) : "", + accessmask2str( mask, accessmaskbuf, 1 ) ); + + return rc; +} + +int +slapacl( int argc, char **argv ) +{ + int rc = EXIT_SUCCESS; + const char *progname = "slapacl"; + Connection conn = { 0 }; + Listener listener; + OperationBuffer opbuf; + Operation *op = NULL; + Entry e = { 0 }, *ep = &e; + char *attr = NULL; + int doclose = 0; + BackendDB *bd; + void *thrctx; + + slap_tool_init( progname, SLAPACL, argc, argv ); + + if ( !dryrun ) { + int i = 0; + + LDAP_STAILQ_FOREACH( bd, &backendDB, be_next ) { + if ( bd != be && backend_startup( bd ) ) { + fprintf( stderr, "backend_startup(#%d%s%s) failed\n", + i, + bd->be_suffix ? ": " : "", + bd->be_suffix ? bd->be_suffix[0].bv_val : "" ); + rc = 1; + goto destroy; + } + + i++; + } + } + + argv = &argv[ optind ]; + argc -= optind; + + thrctx = ldap_pvt_thread_pool_context(); + connection_fake_init( &conn, &opbuf, thrctx ); + op = &opbuf.ob_op; + op->o_tmpmemctx = NULL; + + conn.c_listener = &listener; + conn.c_listener_url = listener_url; + conn.c_peer_domain = peer_domain; + conn.c_peer_name = peer_name; + conn.c_sock_name = sock_name; + op->o_ssf = ssf; + op->o_transport_ssf = transport_ssf; + op->o_tls_ssf = tls_ssf; + op->o_sasl_ssf = sasl_ssf; + + if ( !BER_BVISNULL( &authcID ) ) { + if ( !BER_BVISNULL( &authcDN ) ) { + fprintf( stderr, "both authcID=\"%s\" " + "and authcDN=\"%s\" provided\n", + authcID.bv_val, authcDN.bv_val ); + rc = 1; + goto destroy; + } + + rc = slap_sasl_getdn( &conn, op, &authcID, NULL, + &authcDN, SLAP_GETDN_AUTHCID ); + if ( rc != LDAP_SUCCESS ) { + fprintf( stderr, "authcID: <%s> check failed %d (%s)\n", + authcID.bv_val, rc, + ldap_err2string( rc ) ); + rc = 1; + goto destroy; + } + + } else if ( !BER_BVISNULL( &authcDN ) ) { + struct berval ndn; + + rc = dnNormalize( 0, NULL, NULL, &authcDN, &ndn, NULL ); + if ( rc != LDAP_SUCCESS ) { + fprintf( stderr, "autchDN=\"%s\" normalization failed %d (%s)\n", + authcDN.bv_val, rc, + ldap_err2string( rc ) ); + rc = 1; + goto destroy; + } + ch_free( authcDN.bv_val ); + authcDN = ndn; + } + + if ( !BER_BVISNULL( &authzID ) ) { + if ( !BER_BVISNULL( &authzDN ) ) { + fprintf( stderr, "both authzID=\"%s\" " + "and authzDN=\"%s\" provided\n", + authzID.bv_val, authzDN.bv_val ); + rc = 1; + goto destroy; + } + + rc = slap_sasl_getdn( &conn, op, &authzID, NULL, + &authzDN, SLAP_GETDN_AUTHZID ); + if ( rc != LDAP_SUCCESS ) { + fprintf( stderr, "authzID: <%s> check failed %d (%s)\n", + authzID.bv_val, rc, + ldap_err2string( rc ) ); + rc = 1; + goto destroy; + } + + } else if ( !BER_BVISNULL( &authzDN ) ) { + struct berval ndn; + + rc = dnNormalize( 0, NULL, NULL, &authzDN, &ndn, NULL ); + if ( rc != LDAP_SUCCESS ) { + fprintf( stderr, "autchDN=\"%s\" normalization failed %d (%s)\n", + authzDN.bv_val, rc, + ldap_err2string( rc ) ); + rc = 1; + goto destroy; + } + ch_free( authzDN.bv_val ); + authzDN = ndn; + } + + + if ( !BER_BVISNULL( &authcDN ) ) { + fprintf( stderr, "authcDN: \"%s\"\n", authcDN.bv_val ); + } + + if ( !BER_BVISNULL( &authzDN ) ) { + fprintf( stderr, "authzDN: \"%s\"\n", authzDN.bv_val ); + } + + if ( !BER_BVISNULL( &authzDN ) ) { + op->o_dn = authzDN; + op->o_ndn = authzDN; + + if ( !BER_BVISNULL( &authcDN ) ) { + op->o_conn->c_dn = authcDN; + op->o_conn->c_ndn = authcDN; + + } else { + op->o_conn->c_dn = authzDN; + op->o_conn->c_ndn = authzDN; + } + + } else if ( !BER_BVISNULL( &authcDN ) ) { + op->o_conn->c_dn = authcDN; + op->o_conn->c_ndn = authcDN; + op->o_dn = authcDN; + op->o_ndn = authcDN; + } + + assert( !BER_BVISNULL( &baseDN ) ); + rc = dnPrettyNormal( NULL, &baseDN, &e.e_name, &e.e_nname, NULL ); + if ( rc != LDAP_SUCCESS ) { + fprintf( stderr, "base=\"%s\" normalization failed %d (%s)\n", + baseDN.bv_val, rc, + ldap_err2string( rc ) ); + rc = 1; + goto destroy; + } + + op->o_bd = be; + if ( op->o_bd == NULL ) { + /* NOTE: if no database could be found (e.g. because + * accessing the rootDSE or so), use the frontendDB + * rules; might need work */ + op->o_bd = frontendDB; + } + + if ( !dryrun ) { + ID id; + + if ( be == NULL ) { + fprintf( stderr, "%s: no target database " + "has been found for baseDN=\"%s\"; " + "you may try with \"-u\" (dry run).\n", + baseDN.bv_val, progname ); + rc = 1; + goto destroy; + } + + if ( !be->be_entry_open || + !be->be_entry_close || + !be->be_dn2id_get || + !be->be_entry_get ) + { + fprintf( stderr, "%s: target database " + "doesn't support necessary operations; " + "you may try with \"-u\" (dry run).\n", + progname ); + rc = 1; + goto destroy; + } + + if ( be->be_entry_open( be, 0 ) != 0 ) { + fprintf( stderr, "%s: could not open database.\n", + progname ); + rc = 1; + goto destroy; + } + + doclose = 1; + + id = be->be_dn2id_get( be, &e.e_nname ); + if ( id == NOID ) { + fprintf( stderr, "%s: unable to fetch ID of DN \"%s\"\n", + progname, e.e_nname.bv_val ); + rc = 1; + goto destroy; + } + ep = be->be_entry_get( be, id ); + if ( ep == NULL ) { + fprintf( stderr, "%s: unable to fetch entry \"%s\" (%lu)\n", + progname, e.e_nname.bv_val, id ); + rc = 1; + goto destroy; + + } + + if ( argc == 0 ) { + Attribute *a; + + (void)print_access( op, ep, slap_schema.si_ad_entry, NULL, NULL ); + (void)print_access( op, ep, slap_schema.si_ad_children, NULL, NULL ); + + for ( a = ep->e_attrs; a; a = a->a_next ) { + int i; + + for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) { + (void)print_access( op, ep, a->a_desc, + &a->a_vals[ i ], + &a->a_nvals[ i ] ); + } + } + } + } + + for ( ; argc--; argv++ ) { + slap_mask_t mask; + AttributeDescription *desc = NULL; + struct berval val = BER_BVNULL, + *valp = NULL; + const char *text; + char accessmaskbuf[ACCESSMASK_MAXLEN]; + char *accessstr; + slap_access_t access = ACL_AUTH; + + if ( attr == NULL ) { + attr = argv[ 0 ]; + } + + val.bv_val = strchr( attr, ':' ); + if ( val.bv_val != NULL ) { + val.bv_val[0] = '\0'; + val.bv_val++; + val.bv_len = strlen( val.bv_val ); + valp = &val; + } + + accessstr = strchr( attr, '/' ); + if ( accessstr != NULL ) { + int invalid = 0; + + accessstr[0] = '\0'; + accessstr++; + access = str2access( accessstr ); + switch ( access ) { + case ACL_INVALID_ACCESS: + fprintf( stderr, "unknown access \"%s\" for attribute \"%s\"\n", + accessstr, attr ); + invalid = 1; + break; + + case ACL_NONE: + fprintf( stderr, "\"none\" not allowed for attribute \"%s\"\n", + attr ); + invalid = 1; + break; + + default: + break; + } + + if ( invalid ) { + if ( continuemode ) { + continue; + } + break; + } + } + + rc = slap_str2ad( attr, &desc, &text ); + if ( rc != LDAP_SUCCESS ) { + fprintf( stderr, "slap_str2ad(%s) failed %d (%s)\n", + attr, rc, ldap_err2string( rc ) ); + if ( continuemode ) { + continue; + } + break; + } + + rc = access_allowed_mask( op, ep, desc, valp, access, + NULL, &mask ); + + if ( accessstr ) { + fprintf( stderr, "%s access to %s%s%s: %s\n", + accessstr, + desc->ad_cname.bv_val, + val.bv_val ? "=" : "", + val.bv_val ? val.bv_val : "", + rc ? "ALLOWED" : "DENIED" ); + + } else { + fprintf( stderr, "%s%s%s: %s\n", + desc->ad_cname.bv_val, + val.bv_val ? "=" : "", + val.bv_val ? val.bv_val : "", + accessmask2str( mask, accessmaskbuf, 1 ) ); + } + rc = 0; + attr = NULL; + } + +destroy:; + if ( !BER_BVISNULL( &e.e_name ) ) { + ber_memfree( e.e_name.bv_val ); + } + if ( !BER_BVISNULL( &e.e_nname ) ) { + ber_memfree( e.e_nname.bv_val ); + } + if ( !dryrun && be ) { + if ( ep && ep != &e ) { + be_entry_release_r( op, ep ); + } + if ( doclose ) { + be->be_entry_close( be ); + } + + LDAP_STAILQ_FOREACH( bd, &backendDB, be_next ) { + if ( bd != be ) { + backend_shutdown( bd ); + } + } + } + + if ( slap_tool_destroy()) + rc = EXIT_FAILURE; + + return rc; +} + diff --git a/servers/slapd/slapadd.c b/servers/slapd/slapadd.c new file mode 100644 index 0000000..d8ed964 --- /dev/null +++ b/servers/slapd/slapadd.c @@ -0,0 +1,531 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * Portions Copyright 1998-2003 Kurt D. Zeilenga. + * Portions Copyright 2003 IBM Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Kurt Zeilenga for inclusion + * in OpenLDAP Software. Additional signficant contributors include + * Jong Hyuk Choi + * Pierangelo Masarati + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/socket.h> +#include <ac/unistd.h> + +#include <lber.h> +#include <ldif.h> +#include <lutil.h> +#include <lutil_meter.h> +#include <sys/stat.h> + +#include "slapcommon.h" + +#ifdef _WIN32 +# ifdef __WIN64__ +# define ftello(fp) _ftelli64(fp) +# else +/* Ideally we would use _ftelli64 but that was only available + * starting in MSVCR80.DLL. The approach used here is inaccurate + * because returning the underlying file handle's file pointer + * doesn't take the stdio buffer offset into account. But, it + * works with all versions of MSVCRT. + */ +# define ftello(fp) _telli64(fileno(fp)) +# endif +#endif + +extern int slap_DN_strict; /* dn.c */ + +static char csnbuf[ LDAP_PVT_CSNSTR_BUFSIZE ]; + +typedef struct Erec { + Entry *e; + unsigned long lineno; + unsigned long nextline; +} Erec; + +typedef struct Trec { + Entry *e; + unsigned long lineno; + unsigned long nextline; + int rc; + int ready; +} Trec; + +static Trec trec; +static unsigned long sid = SLAP_SYNC_SID_MAX + 1; +static int checkvals; +static int enable_meter; +static lutil_meter_t meter; +static const char *progname = "slapadd"; +static OperationBuffer opbuf; +static char *buf; +static int lmax; + +static ldap_pvt_thread_mutex_t add_mutex; +static ldap_pvt_thread_cond_t add_cond; +static int add_stop; + +/* returns: + * 1: got a record + * 0: EOF + * -1: read failure + * -2: parse failure + */ +static int +getrec0(Erec *erec) +{ + const char *text; + int ldifrc; + char textbuf[SLAP_TEXT_BUFLEN] = { '\0' }; + size_t textlen = sizeof textbuf; + struct berval csn; + Operation *op = &opbuf.ob_op; + op->o_hdr = &opbuf.ob_hdr; + +again: + erec->lineno = erec->nextline+1; + /* nextline is the line number of the end of the current entry */ + ldifrc = ldif_read_record( ldiffp, &erec->nextline, &buf, &lmax ); + if (ldifrc < 1) + return ldifrc < 0 ? -1 : 0; + { + BackendDB *bd; + Entry *e; + int prev_DN_strict; + + if ( erec->lineno < jumpline ) + goto again; + + if ( !dbnum ) { + prev_DN_strict = slap_DN_strict; + slap_DN_strict = 0; + } + e = str2entry2( buf, checkvals ); + if ( !dbnum ) { + slap_DN_strict = prev_DN_strict; + } + + if ( enable_meter ) + lutil_meter_update( &meter, + ftello( ldiffp->fp ), + 0); + + if( e == NULL ) { + fprintf( stderr, "%s: could not parse entry (line=%lu)\n", + progname, erec->lineno ); + return -2; + } + + /* make sure the DN is not empty */ + if( BER_BVISEMPTY( &e->e_nname ) && + !BER_BVISEMPTY( be->be_nsuffix )) + { + fprintf( stderr, "%s: line %lu: " + "cannot add entry with empty dn=\"%s\"", + progname, erec->lineno, e->e_dn ); + bd = select_backend( &e->e_nname, nosubordinates ); + if ( bd ) { + BackendDB *bdtmp; + int dbidx = 0; + LDAP_STAILQ_FOREACH( bdtmp, &backendDB, be_next ) { + if ( bdtmp == bd ) break; + dbidx++; + } + + assert( bdtmp != NULL ); + + fprintf( stderr, "; did you mean to use database #%d (%s)?", + dbidx, + bd->be_suffix[0].bv_val ); + + } + fprintf( stderr, "\n" ); + entry_free( e ); + return -2; + } + + /* check backend */ + bd = select_backend( &e->e_nname, nosubordinates ); + if ( bd != be ) { + fprintf( stderr, "%s: line %lu: " + "database #%d (%s) not configured to hold \"%s\"", + progname, erec->lineno, + dbnum, + be->be_suffix[0].bv_val, + e->e_dn ); + if ( bd ) { + BackendDB *bdtmp; + int dbidx = 0; + LDAP_STAILQ_FOREACH( bdtmp, &backendDB, be_next ) { + if ( bdtmp == bd ) break; + dbidx++; + } + + assert( bdtmp != NULL ); + + fprintf( stderr, "; did you mean to use database #%d (%s)?", + dbidx, + bd->be_suffix[0].bv_val ); + + } else { + fprintf( stderr, "; no database configured for that naming context" ); + } + fprintf( stderr, "\n" ); + entry_free( e ); + return -2; + } + + if ( slap_tool_entry_check( progname, op, e, erec->lineno, &text, textbuf, textlen ) != + LDAP_SUCCESS ) { + entry_free( e ); + return -2; + } + + if ( SLAP_LASTMOD(be) ) { + time_t now = slap_get_time(); + char uuidbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE ]; + struct berval vals[ 2 ]; + + struct berval name, timestamp; + + struct berval nvals[ 2 ]; + struct berval nname; + char timebuf[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + + enum { + GOT_NONE = 0x0, + GOT_CSN = 0x1, + GOT_UUID = 0x2, + GOT_ALL = (GOT_CSN|GOT_UUID) + } got = GOT_ALL; + + vals[1].bv_len = 0; + vals[1].bv_val = NULL; + + nvals[1].bv_len = 0; + nvals[1].bv_val = NULL; + + csn.bv_len = ldap_pvt_csnstr( csnbuf, sizeof( csnbuf ), csnsid, 0 ); + csn.bv_val = csnbuf; + + timestamp.bv_val = timebuf; + timestamp.bv_len = sizeof(timebuf); + + slap_timestamp( &now, ×tamp ); + + if ( BER_BVISEMPTY( &be->be_rootndn ) ) { + BER_BVSTR( &name, SLAPD_ANONYMOUS ); + nname = name; + } else { + name = be->be_rootdn; + nname = be->be_rootndn; + } + + if( attr_find( e->e_attrs, slap_schema.si_ad_entryUUID ) + == NULL ) + { + got &= ~GOT_UUID; + vals[0].bv_len = lutil_uuidstr( uuidbuf, sizeof( uuidbuf ) ); + vals[0].bv_val = uuidbuf; + attr_merge_normalize_one( e, slap_schema.si_ad_entryUUID, vals, NULL ); + } + + if( attr_find( e->e_attrs, slap_schema.si_ad_creatorsName ) + == NULL ) + { + vals[0] = name; + nvals[0] = nname; + attr_merge( e, slap_schema.si_ad_creatorsName, vals, nvals ); + } + + if( attr_find( e->e_attrs, slap_schema.si_ad_createTimestamp ) + == NULL ) + { + vals[0] = timestamp; + attr_merge( e, slap_schema.si_ad_createTimestamp, vals, NULL ); + } + + if( attr_find( e->e_attrs, slap_schema.si_ad_entryCSN ) + == NULL ) + { + got &= ~GOT_CSN; + vals[0] = csn; + attr_merge( e, slap_schema.si_ad_entryCSN, vals, NULL ); + } + + if( attr_find( e->e_attrs, slap_schema.si_ad_modifiersName ) + == NULL ) + { + vals[0] = name; + nvals[0] = nname; + attr_merge( e, slap_schema.si_ad_modifiersName, vals, nvals ); + } + + if( attr_find( e->e_attrs, slap_schema.si_ad_modifyTimestamp ) + == NULL ) + { + vals[0] = timestamp; + attr_merge( e, slap_schema.si_ad_modifyTimestamp, vals, NULL ); + } + + if ( SLAP_SINGLE_SHADOW(be) && got != GOT_ALL ) { + char buf[SLAP_TEXT_BUFLEN]; + + snprintf( buf, sizeof(buf), + "%s%s%s", + ( !(got & GOT_UUID) ? slap_schema.si_ad_entryUUID->ad_cname.bv_val : "" ), + ( !(got & GOT_CSN) ? "," : "" ), + ( !(got & GOT_CSN) ? slap_schema.si_ad_entryCSN->ad_cname.bv_val : "" ) ); + + Debug( LDAP_DEBUG_ANY, "%s: warning, missing attrs %s from entry dn=\"%s\"\n", + progname, buf, e->e_name.bv_val ); + } + + sid = slap_tool_update_ctxcsn_check( progname, e ); + } + erec->e = e; + } + return 1; +} + +static void * +getrec_thr(void *ctx) +{ + ldap_pvt_thread_mutex_lock( &add_mutex ); + while (!add_stop) { + trec.rc = getrec0((Erec *)&trec); + trec.ready = 1; + while (trec.ready) + ldap_pvt_thread_cond_wait( &add_cond, &add_mutex ); + /* eof or read failure */ + if ( trec.rc == 0 || trec.rc == -1 ) + break; + } + ldap_pvt_thread_mutex_unlock( &add_mutex ); + return NULL; +} + +static int ldif_threaded; + +static int +getrec(Erec *erec) +{ + int rc; + if ( !ldif_threaded ) + return getrec0(erec); + + while (!trec.ready) + ldap_pvt_thread_yield(); + erec->e = trec.e; + erec->lineno = trec.lineno; + erec->nextline = trec.nextline; + trec.ready = 0; + rc = trec.rc; + ldap_pvt_thread_mutex_lock( &add_mutex ); + ldap_pvt_thread_mutex_unlock( &add_mutex ); + ldap_pvt_thread_cond_signal( &add_cond ); + return rc; +} + +int +slapadd( int argc, char **argv ) +{ + char textbuf[SLAP_TEXT_BUFLEN] = { '\0' }; + size_t textlen = sizeof textbuf; + Erec erec; + struct berval bvtext; + ldap_pvt_thread_t thr; + ID id; + Entry *prev = NULL; + + int ldifrc; + int rc = EXIT_SUCCESS; + + struct stat stat_buf; + + /* default "000" */ + csnsid = 0; + + if ( isatty (2) ) enable_meter = 1; + slap_tool_init( progname, SLAPADD, argc, argv ); + + if( !be->be_entry_open || + !be->be_entry_close || + !be->be_entry_put || + (update_ctxcsn && + (!be->be_dn2id_get || + !be->be_entry_get || + !be->be_entry_modify)) ) + { + fprintf( stderr, "%s: database doesn't support necessary operations.\n", + progname ); + if ( dryrun ) { + fprintf( stderr, "\t(dry) continuing...\n" ); + + } else { + exit( EXIT_FAILURE ); + } + } + + checkvals = (slapMode & SLAP_TOOL_QUICK) ? 0 : 1; + + /* do not check values in quick mode */ + if ( slapMode & SLAP_TOOL_QUICK ) { + if ( slapMode & SLAP_TOOL_VALUE_CHECK ) { + fprintf( stderr, "%s: value-check incompatible with quick mode; disabled.\n", progname ); + slapMode &= ~SLAP_TOOL_VALUE_CHECK; + } + } + + /* enforce schema checking unless not disabled */ + if ( (slapMode & SLAP_TOOL_NO_SCHEMA_CHECK) == 0) { + SLAP_DBFLAGS(be) &= ~(SLAP_DBFLAG_NO_SCHEMA_CHECK); + } + + if( !dryrun && be->be_entry_open( be, 1 ) != 0 ) { + fprintf( stderr, "%s: could not open database.\n", + progname ); + exit( EXIT_FAILURE ); + } + + (void)slap_tool_update_ctxcsn_init(); + + if ( enable_meter +#ifdef LDAP_DEBUG + /* tools default to "none" */ + && slap_debug == LDAP_DEBUG_NONE +#endif + && !fstat ( fileno ( ldiffp->fp ), &stat_buf ) + && S_ISREG(stat_buf.st_mode) ) { + enable_meter = !lutil_meter_open( + &meter, + &lutil_meter_text_display, + &lutil_meter_linear_estimator, + stat_buf.st_size); + } else { + enable_meter = 0; + } + + if ( slap_tool_thread_max > 1 ) { + ldap_pvt_thread_mutex_init( &add_mutex ); + ldap_pvt_thread_cond_init( &add_cond ); + ldap_pvt_thread_create( &thr, 0, getrec_thr, NULL ); + ldif_threaded = 1; + } + + erec.nextline = 0; + erec.e = NULL; + + for (;;) { + ldifrc = getrec( &erec ); + if ( ldifrc < 1 ) { + if ( ldifrc == -2 && continuemode ) + continue; + break; + } + + if ( !dryrun ) { + /* + * Initialize text buffer + */ + bvtext.bv_len = textlen; + bvtext.bv_val = textbuf; + bvtext.bv_val[0] = '\0'; + + id = be->be_entry_put( be, erec.e, &bvtext ); + if( id == NOID ) { + fprintf( stderr, "%s: could not add entry dn=\"%s\" " + "(line=%lu): %s\n", progname, erec.e->e_dn, + erec.lineno, bvtext.bv_val ); + rc = EXIT_FAILURE; + if( continuemode ) { + if ( prev ) entry_free( prev ); + prev = erec.e; + continue; + } + break; + } + if ( verbose ) + fprintf( stderr, "added: \"%s\" (%08lx)\n", + erec.e->e_dn, (long) id ); + } else { + if ( verbose ) + fprintf( stderr, "added: \"%s\"\n", + erec.e->e_dn ); + } + + if ( prev ) entry_free( prev ); + prev = erec.e; + } + + if ( ldif_threaded ) { + ldap_pvt_thread_mutex_lock( &add_mutex ); + add_stop = 1; + trec.ready = 0; + ldap_pvt_thread_cond_signal( &add_cond ); + ldap_pvt_thread_mutex_unlock( &add_mutex ); + ldap_pvt_thread_join( thr, NULL ); + } + if ( erec.e ) entry_free( erec.e ); + + if ( ldifrc < 0 ) + rc = EXIT_FAILURE; + + bvtext.bv_len = textlen; + bvtext.bv_val = textbuf; + bvtext.bv_val[0] = '\0'; + + if ( enable_meter ) { + lutil_meter_update( &meter, ftello( ldiffp->fp ), 1); + lutil_meter_close( &meter ); + } + + if ( rc == EXIT_SUCCESS ) { + rc = slap_tool_update_ctxcsn( progname, sid, &bvtext ); + } + + ch_free( buf ); + + if ( !dryrun ) { + if ( enable_meter ) { + fprintf( stderr, "Closing DB..." ); + } + if( be->be_entry_close( be ) ) { + rc = EXIT_FAILURE; + } + + if( be->be_sync ) { + be->be_sync( be ); + } + if ( enable_meter ) { + fprintf( stderr, "\n" ); + } + } + + if ( slap_tool_destroy()) + rc = EXIT_FAILURE; + + return rc; +} + diff --git a/servers/slapd/slapauth.c b/servers/slapd/slapauth.c new file mode 100644 index 0000000..1f28cd5 --- /dev/null +++ b/servers/slapd/slapauth.c @@ -0,0 +1,177 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2021 The OpenLDAP Foundation. + * Portions Copyright 2004 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/socket.h> +#include <ac/unistd.h> + +#include <lber.h> +#include <ldif.h> +#include <lutil.h> + +#include "slapcommon.h" + +static int +do_check( Connection *c, Operation *op, struct berval *id ) +{ + struct berval authcdn; + int rc; + + rc = slap_sasl_getdn( c, op, id, realm, &authcdn, SLAP_GETDN_AUTHCID ); + if ( rc != LDAP_SUCCESS ) { + fprintf( stderr, "ID: <%s> check failed %d (%s)\n", + id->bv_val, rc, + ldap_err2string( rc ) ); + rc = 1; + + } else { + if ( !BER_BVISNULL( &authzID ) ) { + rc = slap_sasl_authorized( op, &authcdn, &authzID ); + + fprintf( stderr, + "ID: <%s>\n" + "authcDN: <%s>\n" + "authzDN: <%s>\n" + "authorization %s\n", + id->bv_val, + authcdn.bv_val, + authzID.bv_val, + rc == LDAP_SUCCESS ? "OK" : "failed" ); + + } else { + fprintf( stderr, "ID: <%s> check succeeded\n" + "authcID: <%s>\n", + id->bv_val, + authcdn.bv_val ); + op->o_tmpfree( authcdn.bv_val, op->o_tmpmemctx ); + } + rc = 0; + } + + return rc; +} + +int +slapauth( int argc, char **argv ) +{ + int rc = EXIT_SUCCESS; + const char *progname = "slapauth"; + Connection conn = {0}; + OperationBuffer opbuf; + Operation *op; + void *thrctx; + + slap_tool_init( progname, SLAPAUTH, argc, argv ); + + argv = &argv[ optind ]; + argc -= optind; + + thrctx = ldap_pvt_thread_pool_context(); + connection_fake_init( &conn, &opbuf, thrctx ); + op = &opbuf.ob_op; + + conn.c_sasl_bind_mech = mech; + + if ( !BER_BVISNULL( &authzID ) ) { + struct berval authzdn; + + rc = slap_sasl_getdn( &conn, op, &authzID, NULL, &authzdn, + SLAP_GETDN_AUTHZID ); + if ( rc != LDAP_SUCCESS ) { + fprintf( stderr, "authzID: <%s> check failed %d (%s)\n", + authzID.bv_val, rc, + ldap_err2string( rc ) ); + rc = 1; + BER_BVZERO( &authzID ); + goto destroy; + } + + authzID = authzdn; + } + + + if ( !BER_BVISNULL( &authcID ) ) { + if ( !BER_BVISNULL( &authzID ) || argc == 0 ) { + rc = do_check( &conn, op, &authcID ); + goto destroy; + } + + for ( ; argc--; argv++ ) { + struct berval authzdn; + + ber_str2bv( argv[ 0 ], 0, 0, &authzID ); + + rc = slap_sasl_getdn( &conn, op, &authzID, NULL, &authzdn, + SLAP_GETDN_AUTHZID ); + if ( rc != LDAP_SUCCESS ) { + fprintf( stderr, "authzID: <%s> check failed %d (%s)\n", + authzID.bv_val, rc, + ldap_err2string( rc ) ); + rc = -1; + BER_BVZERO( &authzID ); + if ( !continuemode ) { + goto destroy; + } + } + + authzID = authzdn; + + rc = do_check( &conn, op, &authcID ); + + op->o_tmpfree( authzID.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &authzID ); + + if ( rc && !continuemode ) { + goto destroy; + } + } + + goto destroy; + } + + for ( ; argc--; argv++ ) { + struct berval id; + + ber_str2bv( argv[ 0 ], 0, 0, &id ); + + rc = do_check( &conn, op, &id ); + + if ( rc && !continuemode ) { + goto destroy; + } + } + +destroy:; + if ( !BER_BVISNULL( &authzID ) ) { + op->o_tmpfree( authzID.bv_val, op->o_tmpmemctx ); + } + if ( slap_tool_destroy()) + rc = EXIT_FAILURE; + + return rc; +} + diff --git a/servers/slapd/slapcat.c b/servers/slapd/slapcat.c new file mode 100644 index 0000000..35a6cb5 --- /dev/null +++ b/servers/slapd/slapcat.c @@ -0,0 +1,175 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * Portions Copyright 1998-2003 Kurt D. Zeilenga. + * Portions Copyright 2003 IBM Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Kurt Zeilenga for inclusion + * in OpenLDAP Software. Additional signficant contributors include + * Jong Hyuk Choi + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> +#include <ac/ctype.h> +#include <ac/socket.h> +#include <ac/string.h> + +#include "slapcommon.h" +#include "ldif.h" + +static volatile sig_atomic_t gotsig; + +static RETSIGTYPE +slapcat_sig( int sig ) +{ + gotsig=1; +} + +int +slapcat( int argc, char **argv ) +{ + ID id; + int rc = EXIT_SUCCESS; + Operation op = {0}; + const char *progname = "slapcat"; + int requestBSF; + int doBSF = 0; + + slap_tool_init( progname, SLAPCAT, argc, argv ); + + requestBSF = ( sub_ndn.bv_len || filter ); + +#ifdef SIGPIPE + (void) SIGNAL( SIGPIPE, slapcat_sig ); +#endif +#ifdef SIGHUP + (void) SIGNAL( SIGHUP, slapcat_sig ); +#endif + (void) SIGNAL( SIGINT, slapcat_sig ); + (void) SIGNAL( SIGTERM, slapcat_sig ); + + if( !be->be_entry_open || + !be->be_entry_close || + !( be->be_entry_first_x || be->be_entry_first ) || + !be->be_entry_next || + !be->be_entry_get ) + { + fprintf( stderr, "%s: database doesn't support necessary operations.\n", + progname ); + exit( EXIT_FAILURE ); + } + + if( be->be_entry_open( be, 0 ) != 0 ) { + fprintf( stderr, "%s: could not open database.\n", + progname ); + exit( EXIT_FAILURE ); + } + + op.o_bd = be; + if ( !requestBSF && be->be_entry_first ) { + id = be->be_entry_first( be ); + + } else { + if ( be->be_entry_first_x ) { + id = be->be_entry_first_x( be, + sub_ndn.bv_len ? &sub_ndn : NULL, scope, filter ); + + } else { + assert( be->be_entry_first != NULL ); + doBSF = 1; + id = be->be_entry_first( be ); + } + } + + for ( ; id != NOID; id = be->be_entry_next( be ) ) + { + char *data; + int len; + Entry* e; + + if ( gotsig ) + break; + + e = be->be_entry_get( be, id ); + if ( e == NULL ) { + printf("# no data for entry id=%08lx\n\n", (long) id ); + rc = EXIT_FAILURE; + if ( continuemode == 0 ) { + break; + + } else if ( continuemode == 1 ) { + continue; + } + + /* this is a last resort: linearly scan all ids + * trying to recover as much as possible (ITS#6482) */ + while ( ++id != NOID ) { + e = be->be_entry_get( be, id ); + if ( e != NULL ) break; + printf("# no data for entry id=%08lx\n\n", (long) id ); + } + + if ( e == NULL ) break; + } + + if ( doBSF ) { + if ( sub_ndn.bv_len && !dnIsSuffixScope( &e->e_nname, &sub_ndn, scope ) ) + { + be_entry_release_r( &op, e ); + continue; + } + + + if ( filter != NULL ) { + int rc = test_filter( NULL, e, filter ); + if ( rc != LDAP_COMPARE_TRUE ) { + be_entry_release_r( &op, e ); + continue; + } + } + } + + if ( verbose ) { + printf( "# id=%08lx\n", (long) id ); + } + + data = entry2str_wrap( e, &len, ldif_wrap ); + be_entry_release_r( &op, e ); + + if ( data == NULL ) { + printf("# bad data for entry id=%08lx\n\n", (long) id ); + rc = EXIT_FAILURE; + if( continuemode ) continue; + break; + } + + if ( fputs( data, ldiffp->fp ) == EOF || + fputs( "\n", ldiffp->fp ) == EOF ) { + fprintf(stderr, "%s: error writing output.\n", + progname); + rc = EXIT_FAILURE; + break; + } + } + + be->be_entry_close( be ); + + if ( slap_tool_destroy()) + rc = EXIT_FAILURE; + return rc; +} diff --git a/servers/slapd/slapcommon.c b/servers/slapd/slapcommon.c new file mode 100644 index 0000000..01574af --- /dev/null +++ b/servers/slapd/slapcommon.c @@ -0,0 +1,1228 @@ +/* slapcommon.c - common routine for the slap tools */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * Portions Copyright 1998-2003 Kurt D. Zeilenga. + * Portions Copyright 2003 IBM Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Kurt Zeilenga for inclusion + * in OpenLDAP Software. Additional signficant contributors include + * Jong Hyuk Choi + * Hallvard B. Furuseth + * Howard Chu + * Pierangelo Masarati + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/socket.h> +#include <ac/unistd.h> + +#include "slapcommon.h" +#include "lutil.h" +#include "ldif.h" + +tool_vars tool_globals; + +#ifdef CSRIMALLOC +static char *leakfilename; +static FILE *leakfile; +#endif + +static LDIFFP dummy; + +#if defined(LDAP_SYSLOG) && defined(LDAP_DEBUG) +int start_syslog; +static char **syslog_unknowns; +#ifdef LOG_LOCAL4 +static int syslogUser = SLAP_DEFAULT_SYSLOG_USER; +#endif /* LOG_LOCAL4 */ +#endif /* LDAP_DEBUG && LDAP_SYSLOG */ + +static void +usage( int tool, const char *progname ) +{ + char *options = NULL; + fprintf( stderr, + "usage: %s [-v] [-d debuglevel] [-f configfile] [-F configdir] [-o <name>[=<value>]]", + progname ); + + switch( tool ) { + case SLAPACL: + options = "\n\t[-U authcID | -D authcDN] [-X authzID | -o authzDN=<DN>]" + "\n\t-b DN [-u] [attr[/access][:value]] [...]\n"; + break; + + case SLAPADD: + options = " [-c]\n\t[-g] [-n databasenumber | -b suffix]\n" + "\t[-l ldiffile] [-j linenumber] [-q] [-u] [-s] [-w]\n"; + break; + + case SLAPAUTH: + options = "\n\t[-U authcID] [-X authzID] [-R realm] [-M mech] ID [...]\n"; + break; + + case SLAPCAT: + options = " [-c]\n\t[-g] [-n databasenumber | -b suffix]" + " [-l ldiffile] [-a filter] [-s subtree] [-H url]\n"; + break; + + case SLAPDN: + options = "\n\t[-N | -P] DN [...]\n"; + break; + + case SLAPINDEX: + options = " [-c]\n\t[-g] [-n databasenumber | -b suffix] [attr ...] [-q] [-t]\n"; + break; + + case SLAPTEST: + options = " [-n databasenumber] [-u] [-Q]\n"; + break; + + case SLAPSCHEMA: + options = " [-c]\n\t[-g] [-n databasenumber | -b suffix]" + " [-l errorfile] [-a filter] [-s subtree] [-H url]\n"; + break; + } + + if ( options != NULL ) { + fputs( options, stderr ); + } + exit( EXIT_FAILURE ); +} + +static int +parse_slapopt( int tool, int *mode ) +{ + size_t len = 0; + char *p; + + p = strchr( optarg, '=' ); + if ( p != NULL ) { + len = p - optarg; + p++; + } + + if ( strncasecmp( optarg, "sockurl", len ) == 0 ) { + if ( !BER_BVISNULL( &listener_url ) ) { + ber_memfree( listener_url.bv_val ); + } + ber_str2bv( p, 0, 1, &listener_url ); + + } else if ( strncasecmp( optarg, "domain", len ) == 0 ) { + if ( !BER_BVISNULL( &peer_domain ) ) { + ber_memfree( peer_domain.bv_val ); + } + ber_str2bv( p, 0, 1, &peer_domain ); + + } else if ( strncasecmp( optarg, "peername", len ) == 0 ) { + if ( !BER_BVISNULL( &peer_name ) ) { + ber_memfree( peer_name.bv_val ); + } + ber_str2bv( p, 0, 1, &peer_name ); + + } else if ( strncasecmp( optarg, "sockname", len ) == 0 ) { + if ( !BER_BVISNULL( &sock_name ) ) { + ber_memfree( sock_name.bv_val ); + } + ber_str2bv( p, 0, 1, &sock_name ); + + } else if ( strncasecmp( optarg, "ssf", len ) == 0 ) { + if ( lutil_atou( &ssf, p ) ) { + Debug( LDAP_DEBUG_ANY, "unable to parse ssf=\"%s\".\n", p, 0, 0 ); + return -1; + } + + } else if ( strncasecmp( optarg, "transport_ssf", len ) == 0 ) { + if ( lutil_atou( &transport_ssf, p ) ) { + Debug( LDAP_DEBUG_ANY, "unable to parse transport_ssf=\"%s\".\n", p, 0, 0 ); + return -1; + } + + } else if ( strncasecmp( optarg, "tls_ssf", len ) == 0 ) { + if ( lutil_atou( &tls_ssf, p ) ) { + Debug( LDAP_DEBUG_ANY, "unable to parse tls_ssf=\"%s\".\n", p, 0, 0 ); + return -1; + } + + } else if ( strncasecmp( optarg, "sasl_ssf", len ) == 0 ) { + if ( lutil_atou( &sasl_ssf, p ) ) { + Debug( LDAP_DEBUG_ANY, "unable to parse sasl_ssf=\"%s\".\n", p, 0, 0 ); + return -1; + } + + } else if ( strncasecmp( optarg, "authzDN", len ) == 0 ) { + ber_str2bv( p, 0, 1, &authzDN ); + +#if defined(LDAP_SYSLOG) && defined(LDAP_DEBUG) + } else if ( strncasecmp( optarg, "syslog", len ) == 0 ) { + if ( parse_debug_level( p, &ldap_syslog, &syslog_unknowns ) ) { + return -1; + } + start_syslog = 1; + + } else if ( strncasecmp( optarg, "syslog-level", len ) == 0 ) { + if ( parse_syslog_level( p, &ldap_syslog_level ) ) { + return -1; + } + start_syslog = 1; + +#ifdef LOG_LOCAL4 + } else if ( strncasecmp( optarg, "syslog-user", len ) == 0 ) { + if ( parse_syslog_user( p, &syslogUser ) ) { + return -1; + } + start_syslog = 1; +#endif /* LOG_LOCAL4 */ +#endif /* LDAP_DEBUG && LDAP_SYSLOG */ + + } else if ( strncasecmp( optarg, "schema-check", len ) == 0 ) { + switch ( tool ) { + case SLAPADD: + if ( strcasecmp( p, "yes" ) == 0 ) { + *mode &= ~SLAP_TOOL_NO_SCHEMA_CHECK; + } else if ( strcasecmp( p, "no" ) == 0 ) { + *mode |= SLAP_TOOL_NO_SCHEMA_CHECK; + } else { + Debug( LDAP_DEBUG_ANY, "unable to parse schema-check=\"%s\".\n", p, 0, 0 ); + return -1; + } + break; + + default: + Debug( LDAP_DEBUG_ANY, "schema-check meaningless for tool.\n", 0, 0, 0 ); + break; + } + + } else if ( strncasecmp( optarg, "value-check", len ) == 0 ) { + switch ( tool ) { + case SLAPADD: + if ( strcasecmp( p, "yes" ) == 0 ) { + *mode |= SLAP_TOOL_VALUE_CHECK; + } else if ( strcasecmp( p, "no" ) == 0 ) { + *mode &= ~SLAP_TOOL_VALUE_CHECK; + } else { + Debug( LDAP_DEBUG_ANY, "unable to parse value-check=\"%s\".\n", p, 0, 0 ); + return -1; + } + break; + + default: + Debug( LDAP_DEBUG_ANY, "value-check meaningless for tool.\n", 0, 0, 0 ); + break; + } + + } else if ( strncasecmp( optarg, "ldif-wrap", len ) == 0 ) { + switch ( tool ) { + case SLAPCAT: + if ( strcasecmp( p, "no" ) == 0 ) { + ldif_wrap = LDIF_LINE_WIDTH_MAX; + + } else { + unsigned int u; + if ( lutil_atou( &u, p ) ) { + Debug( LDAP_DEBUG_ANY, "unable to parse ldif-wrap=\"%s\".\n", p, 0, 0 ); + return -1; + } + ldif_wrap = (ber_len_t)u; + } + break; + + default: + Debug( LDAP_DEBUG_ANY, "ldif-wrap meaningless for tool.\n", 0, 0, 0 ); + break; + } + + } else { + return -1; + } + + return 0; +} + +/* + * slap_tool_init - initialize slap utility, handle program options. + * arguments: + * name program name + * tool tool code + * argc, argv command line arguments + */ + +static int need_shutdown; + +void +slap_tool_init( + const char* progname, + int tool, + int argc, char **argv ) +{ + char *options; + char *conffile = NULL; + char *confdir = NULL; + struct berval base = BER_BVNULL; + char *filterstr = NULL; + char *subtree = NULL; + char *ldiffile = NULL; + char **debug_unknowns = NULL; + int rc, i; + int mode = SLAP_TOOL_MODE; + int truncatemode = 0; + int use_glue = 1; + int writer; + +#ifdef LDAP_DEBUG + /* tools default to "none", so that at least LDAP_DEBUG_ANY + * messages show up; use -d 0 to reset */ + slap_debug = LDAP_DEBUG_NONE; + ldif_debug = slap_debug; +#endif + ldap_syslog = 0; + +#ifdef CSRIMALLOC + leakfilename = malloc( strlen( progname ) + STRLENOF( ".leak" ) + 1 ); + sprintf( leakfilename, "%s.leak", progname ); + if( ( leakfile = fopen( leakfilename, "w" )) == NULL ) { + leakfile = stderr; + } + free( leakfilename ); + leakfilename = NULL; +#endif + + ldif_wrap = LDIF_LINE_WIDTH; + + scope = LDAP_SCOPE_DEFAULT; + + switch( tool ) { + case SLAPADD: + options = "b:cd:f:F:gj:l:n:o:qsS:uvw"; + break; + + case SLAPCAT: + options = "a:b:cd:f:F:gH:l:n:o:s:v"; + mode |= SLAP_TOOL_READMAIN | SLAP_TOOL_READONLY; + break; + + case SLAPDN: + options = "d:f:F:No:Pv"; + mode |= SLAP_TOOL_READMAIN | SLAP_TOOL_READONLY; + break; + + case SLAPSCHEMA: + options = "a:b:cd:f:F:gH:l:n:o:s:v"; + mode |= SLAP_TOOL_READMAIN | SLAP_TOOL_READONLY; + break; + + case SLAPTEST: + options = "d:f:F:n:o:Quv"; + mode |= SLAP_TOOL_READMAIN | SLAP_TOOL_READONLY; + break; + + case SLAPAUTH: + options = "d:f:F:M:o:R:U:vX:"; + mode |= SLAP_TOOL_READMAIN | SLAP_TOOL_READONLY; + break; + + case SLAPINDEX: + options = "b:cd:f:F:gn:o:qtv"; + mode |= SLAP_TOOL_READMAIN; + break; + + case SLAPACL: + options = "b:D:d:f:F:o:uU:vX:"; + mode |= SLAP_TOOL_READMAIN | SLAP_TOOL_READONLY; + break; + + default: + fprintf( stderr, "%s: unknown tool mode (%d)\n", progname, tool ); + exit( EXIT_FAILURE ); + } + + dbnum = -1; + while ( (i = getopt( argc, argv, options )) != EOF ) { + switch ( i ) { + case 'a': + filterstr = ch_strdup( optarg ); + break; + + case 'b': + ber_str2bv( optarg, 0, 1, &base ); + break; + + case 'c': /* enable continue mode */ + continuemode++; + break; + + case 'd': { /* turn on debugging */ + int level = 0; + + if ( parse_debug_level( optarg, &level, &debug_unknowns ) ) { + usage( tool, progname ); + } +#ifdef LDAP_DEBUG + if ( level == 0 ) { + /* allow to reset log level */ + slap_debug = 0; + + } else { + slap_debug |= level; + } +#else + if ( level != 0 ) + fputs( "must compile with LDAP_DEBUG for debugging\n", + stderr ); +#endif + } break; + + case 'D': + ber_str2bv( optarg, 0, 1, &authcDN ); + break; + + case 'f': /* specify a conf file */ + conffile = ch_strdup( optarg ); + break; + + case 'F': /* specify a conf dir */ + confdir = ch_strdup( optarg ); + break; + + case 'g': /* disable subordinate glue */ + use_glue = 0; + break; + + case 'H': { + LDAPURLDesc *ludp; + int rc; + + rc = ldap_url_parse_ext( optarg, &ludp, + LDAP_PVT_URL_PARSE_NOEMPTY_HOST | LDAP_PVT_URL_PARSE_NOEMPTY_DN ); + if ( rc != LDAP_URL_SUCCESS ) { + usage( tool, progname ); + } + + /* don't accept host, port, attrs, extensions */ + if ( ldap_pvt_url_scheme2proto( ludp->lud_scheme ) != LDAP_PROTO_TCP ) { + usage( tool, progname ); + } + + if ( ludp->lud_host != NULL ) { + usage( tool, progname ); + } + + if ( ludp->lud_port != 0 ) { + usage( tool, progname ); + } + + if ( ludp->lud_attrs != NULL ) { + usage( tool, progname ); + } + + if ( ludp->lud_exts != NULL ) { + usage( tool, progname ); + } + + if ( ludp->lud_dn != NULL && ludp->lud_dn[0] != '\0' ) { + ch_free( subtree ); + subtree = ludp->lud_dn; + ludp->lud_dn = NULL; + } + + if ( ludp->lud_filter != NULL && ludp->lud_filter[0] != '\0' ) { + filterstr = ludp->lud_filter; + ludp->lud_filter = NULL; + } + + scope = ludp->lud_scope; + + ldap_free_urldesc( ludp ); + } break; + + case 'j': /* jump to linenumber */ + if ( lutil_atoul( &jumpline, optarg ) ) { + usage( tool, progname ); + } + break; + + case 'l': /* LDIF file */ + ldiffile = ch_strdup( optarg ); + break; + + case 'M': + ber_str2bv( optarg, 0, 0, &mech ); + break; + + case 'N': + if ( dn_mode && dn_mode != SLAP_TOOL_LDAPDN_NORMAL ) { + usage( tool, progname ); + } + dn_mode = SLAP_TOOL_LDAPDN_NORMAL; + break; + + case 'n': /* which config file db to index */ + if ( lutil_atoi( &dbnum, optarg ) || dbnum < 0 ) { + usage( tool, progname ); + } + break; + + case 'o': + if ( parse_slapopt( tool, &mode ) ) { + usage( tool, progname ); + } + break; + + case 'P': + if ( dn_mode && dn_mode != SLAP_TOOL_LDAPDN_PRETTY ) { + usage( tool, progname ); + } + dn_mode = SLAP_TOOL_LDAPDN_PRETTY; + break; + + case 'Q': + quiet++; + slap_debug = 0; + break; + + case 'q': /* turn on quick */ + mode |= SLAP_TOOL_QUICK; + break; + + case 'R': + realm = optarg; + break; + + case 'S': + if ( lutil_atou( &csnsid, optarg ) + || csnsid > SLAP_SYNC_SID_MAX ) + { + usage( tool, progname ); + } + break; + + case 's': + switch ( tool ) { + case SLAPADD: + /* no schema check */ + mode |= SLAP_TOOL_NO_SCHEMA_CHECK; + break; + + case SLAPCAT: + case SLAPSCHEMA: + /* dump subtree */ + ch_free( subtree ); + subtree = ch_strdup( optarg ); + break; + } + break; + + case 't': /* turn on truncate */ + truncatemode++; + mode |= SLAP_TRUNCATE_MODE; + break; + + case 'U': + ber_str2bv( optarg, 0, 0, &authcID ); + break; + + case 'u': /* dry run */ + dryrun++; + break; + + case 'v': /* turn on verbose */ + verbose++; + break; + + case 'w': /* write context csn at the end */ + update_ctxcsn++; + break; + + case 'X': + ber_str2bv( optarg, 0, 0, &authzID ); + break; + + default: + usage( tool, progname ); + break; + } + } + +#if defined(LDAP_SYSLOG) && defined(LDAP_DEBUG) + if ( start_syslog ) { + char *logName; +#ifdef HAVE_EBCDIC + logName = ch_strdup( progname ); + __atoe( logName ); +#else + logName = (char *)progname; +#endif + +#ifdef LOG_LOCAL4 + openlog( logName, OPENLOG_OPTIONS, syslogUser ); +#elif defined LOG_DEBUG + openlog( logName, OPENLOG_OPTIONS ); +#endif +#ifdef HAVE_EBCDIC + free( logName ); + logName = NULL; +#endif + } +#endif /* LDAP_DEBUG && LDAP_SYSLOG */ + + switch ( tool ) { + case SLAPCAT: + case SLAPSCHEMA: + writer = 1; + break; + + default: + writer = 0; + break; + } + + switch ( tool ) { + case SLAPADD: + case SLAPCAT: + case SLAPSCHEMA: + if ( ( argc != optind ) || (dbnum >= 0 && base.bv_val != NULL ) ) { + usage( tool, progname ); + } + + break; + + case SLAPINDEX: + if ( dbnum >= 0 && base.bv_val != NULL ) { + usage( tool, progname ); + } + + break; + + case SLAPDN: + if ( argc == optind ) { + usage( tool, progname ); + } + break; + + case SLAPAUTH: + if ( argc == optind && BER_BVISNULL( &authcID ) ) { + usage( tool, progname ); + } + break; + + case SLAPTEST: + if ( argc != optind ) { + usage( tool, progname ); + } + break; + + case SLAPACL: + if ( !BER_BVISNULL( &authcDN ) && !BER_BVISNULL( &authcID ) ) { + usage( tool, progname ); + } + if ( BER_BVISNULL( &base ) ) { + usage( tool, progname ); + } + ber_dupbv( &baseDN, &base ); + break; + + default: + break; + } + + if ( ldiffile == NULL ) { + dummy.fp = writer ? stdout : stdin; + ldiffp = &dummy; + + } else if ((ldiffp = ldif_open( ldiffile, writer ? "w" : "r" )) + == NULL ) + { + perror( ldiffile ); + exit( EXIT_FAILURE ); + } + + /* + * initialize stuff and figure out which backend we're dealing with + */ + + rc = slap_init( mode, progname ); + if ( rc != 0 ) { + fprintf( stderr, "%s: slap_init failed!\n", progname ); + exit( EXIT_FAILURE ); + } + + rc = read_config( conffile, confdir ); + + if ( rc != 0 ) { + fprintf( stderr, "%s: bad configuration %s!\n", + progname, confdir ? "directory" : "file" ); + exit( EXIT_FAILURE ); + } + + if ( debug_unknowns ) { + rc = parse_debug_unknowns( debug_unknowns, &slap_debug ); + ldap_charray_free( debug_unknowns ); + debug_unknowns = NULL; + if ( rc ) + exit( EXIT_FAILURE ); + } + +#if defined(LDAP_SYSLOG) && defined(LDAP_DEBUG) + if ( syslog_unknowns ) { + rc = parse_debug_unknowns( syslog_unknowns, &ldap_syslog ); + ldap_charray_free( syslog_unknowns ); + syslog_unknowns = NULL; + if ( rc ) + exit( EXIT_FAILURE ); + } +#endif + + at_oc_cache = 1; + + switch ( tool ) { + case SLAPADD: + case SLAPCAT: + case SLAPINDEX: + case SLAPSCHEMA: + if ( !nbackends ) { + fprintf( stderr, "No databases found " + "in config file\n" ); + exit( EXIT_FAILURE ); + } + break; + + default: + break; + } + + if ( use_glue ) { + rc = glue_sub_attach( 0 ); + + if ( rc != 0 ) { + fprintf( stderr, + "%s: subordinate configuration error\n", progname ); + exit( EXIT_FAILURE ); + } + } + + rc = slap_schema_check(); + + if ( rc != 0 ) { + fprintf( stderr, "%s: slap_schema_prep failed!\n", progname ); + exit( EXIT_FAILURE ); + } + + switch ( tool ) { + case SLAPTEST: + if ( dbnum >= 0 ) + goto get_db; + /* FALLTHRU */ + case SLAPDN: + case SLAPAUTH: + be = NULL; + goto startup; + + default: + break; + } + + if( filterstr ) { + filter = str2filter( filterstr ); + + if( filter == NULL ) { + fprintf( stderr, "Invalid filter '%s'\n", filterstr ); + exit( EXIT_FAILURE ); + } + + ch_free( filterstr ); + filterstr = NULL; + } + + if( subtree ) { + struct berval val; + ber_str2bv( subtree, 0, 0, &val ); + rc = dnNormalize( 0, NULL, NULL, &val, &sub_ndn, NULL ); + if( rc != LDAP_SUCCESS ) { + fprintf( stderr, "Invalid subtree DN '%s'\n", subtree ); + exit( EXIT_FAILURE ); + } + + if ( BER_BVISNULL( &base ) && dbnum == -1 ) { + base = val; + } else { + free( subtree ); + subtree = NULL; + } + } + + if( base.bv_val != NULL ) { + struct berval nbase; + + rc = dnNormalize( 0, NULL, NULL, &base, &nbase, NULL ); + if( rc != LDAP_SUCCESS ) { + fprintf( stderr, "%s: slap_init invalid suffix (\"%s\")\n", + progname, base.bv_val ); + exit( EXIT_FAILURE ); + } + + be = select_backend( &nbase, 0 ); + ber_memfree( nbase.bv_val ); + BER_BVZERO( &nbase ); + + if( be == NULL ) { + fprintf( stderr, "%s: slap_init no backend for \"%s\"\n", + progname, base.bv_val ); + exit( EXIT_FAILURE ); + } + switch ( tool ) { + case SLAPACL: + goto startup; + + default: + break; + } + + /* If the named base is a glue primary, operate on the + * entire context + */ + if ( SLAP_GLUE_INSTANCE( be ) ) { + nosubordinates = 1; + } + + ch_free( base.bv_val ); + BER_BVZERO( &base ); + + } else if ( dbnum == -1 ) { + /* no suffix and no dbnum specified, just default to + * the first available database + */ + if ( nbackends <= 0 ) { + fprintf( stderr, "No available databases\n" ); + exit( EXIT_FAILURE ); + } + LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) { + dbnum++; + + /* db #0 is cn=config, don't select it as a default */ + if ( dbnum < 1 ) continue; + + if ( SLAP_MONITOR(be)) + continue; + + /* If just doing the first by default and it is a + * glue subordinate, find the primary. + */ + if ( SLAP_GLUE_SUBORDINATE(be) ) { + nosubordinates = 1; + continue; + } + break; + } + + if ( !be ) { + fprintf( stderr, "Available database(s) " + "do not allow %s\n", progname ); + exit( EXIT_FAILURE ); + } + + if ( nosubordinates == 0 && dbnum > 1 ) { + Debug( LDAP_DEBUG_ANY, + "The first database does not allow %s;" + " using the first available one (%d)\n", + progname, dbnum, 0 ); + } + + } else if ( dbnum >= nbackends ) { + fprintf( stderr, + "Database number selected via -n is out of range\n" + "Must be in the range 0 to %d" + " (the number of configured databases)\n", + nbackends - 1 ); + exit( EXIT_FAILURE ); + + } else { +get_db: + LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) { + if ( dbnum == 0 ) break; + dbnum--; + } + } + + if ( scope != LDAP_SCOPE_DEFAULT && BER_BVISNULL( &sub_ndn ) ) { + if ( be && be->be_nsuffix ) { + ber_dupbv( &sub_ndn, be->be_nsuffix ); + + } else { + fprintf( stderr, + "<scope> needs a DN or a valid database\n" ); + exit( EXIT_FAILURE ); + } + } + +startup:; + if ( be ) { + BackendDB *bdtmp; + + dbnum = 0; + LDAP_STAILQ_FOREACH( bdtmp, &backendDB, be_next ) { + if ( bdtmp == be ) break; + dbnum++; + } + } + +#ifdef CSRIMALLOC + mal_leaktrace(1); +#endif + + if ( conffile != NULL ) { + ch_free( conffile ); + conffile = NULL; + } + + if ( confdir != NULL ) { + ch_free( confdir ); + confdir = NULL; + } + + if ( ldiffile != NULL ) { + ch_free( ldiffile ); + ldiffile = NULL; + } + + /* slapdn doesn't specify a backend to startup */ + if ( !dryrun && tool != SLAPDN ) { + need_shutdown = 1; + + if ( slap_startup( be ) ) { + switch ( tool ) { + case SLAPTEST: + fprintf( stderr, "slap_startup failed " + "(test would succeed using " + "the -u switch)\n" ); + break; + + default: + fprintf( stderr, "slap_startup failed\n" ); + break; + } + + exit( EXIT_FAILURE ); + } + } +} + +int slap_tool_destroy( void ) +{ + int rc = 0; + if ( !dryrun ) { + if ( need_shutdown ) { + if ( slap_shutdown( be )) + rc = EXIT_FAILURE; + } + if ( slap_destroy()) + rc = EXIT_FAILURE; + } +#ifdef SLAPD_MODULES + if ( slapMode == SLAP_SERVER_MODE ) { + /* always false. just pulls in necessary symbol references. */ + lutil_uuidstr(NULL, 0); + } + module_kill(); +#endif + schema_destroy(); +#ifdef HAVE_TLS + ldap_pvt_tls_destroy(); +#endif + config_destroy(); + +#ifdef CSRIMALLOC + mal_dumpleaktrace( leakfile ); +#endif + + if ( !BER_BVISNULL( &authcDN ) ) { + ch_free( authcDN.bv_val ); + BER_BVZERO( &authcDN ); + } + + if ( ldiffp && ldiffp != &dummy ) { + ldif_close( ldiffp ); + } + return rc; +} + +int +slap_tool_update_ctxcsn( + const char *progname, + unsigned long sid, + struct berval *bvtext ) +{ + struct berval ctxdn; + ID ctxcsn_id; + Entry *ctxcsn_e; + int rc = EXIT_SUCCESS; + + if ( !(update_ctxcsn && !dryrun && sid != SLAP_SYNC_SID_MAX + 1) ) { + return rc; + } + + if ( SLAP_SYNC_SUBENTRY( be )) { + build_new_dn( &ctxdn, &be->be_nsuffix[0], + (struct berval *)&slap_ldapsync_cn_bv, NULL ); + } else { + ctxdn = be->be_nsuffix[0]; + } + ctxcsn_id = be->be_dn2id_get( be, &ctxdn ); + if ( ctxcsn_id == NOID ) { + if ( SLAP_SYNC_SUBENTRY( be )) { + ctxcsn_e = slap_create_context_csn_entry( be, NULL ); + for ( sid = 0; sid <= SLAP_SYNC_SID_MAX; sid++ ) { + if ( maxcsn[ sid ].bv_len ) { + attr_merge_one( ctxcsn_e, slap_schema.si_ad_contextCSN, + &maxcsn[ sid ], NULL ); + } + } + ctxcsn_id = be->be_entry_put( be, ctxcsn_e, bvtext ); + if ( ctxcsn_id == NOID ) { + fprintf( stderr, "%s: couldn't create context entry\n", progname ); + rc = EXIT_FAILURE; + } + entry_free( ctxcsn_e ); + } else { + fprintf( stderr, "%s: context entry is missing\n", progname ); + rc = EXIT_FAILURE; + } + } else { + ctxcsn_e = be->be_entry_get( be, ctxcsn_id ); + if ( ctxcsn_e != NULL ) { + Operation op = { 0 }; + Entry *e = entry_dup( ctxcsn_e ); + Attribute *attr = attr_find( e->e_attrs, slap_schema.si_ad_contextCSN ); + + int change; + op.o_bd = be; + be_entry_release_r( &op, ctxcsn_e ); + + if ( attr ) { + int i; + + change = 0; + + for ( i = 0; !BER_BVISNULL( &attr->a_nvals[ i ] ); i++ ) { + int rc_sid; + int match; + const char *text = NULL; + + rc_sid = slap_parse_csn_sid( &attr->a_nvals[ i ] ); + if ( rc_sid < 0 ) { + Debug( LDAP_DEBUG_ANY, + "%s: unable to extract SID " + "from #%d contextCSN=%s\n", + progname, i, + attr->a_nvals[ i ].bv_val ); + continue; + } + + assert( rc_sid <= SLAP_SYNC_SID_MAX ); + + sid = (unsigned)rc_sid; + + if ( maxcsn[ sid ].bv_len == 0 ) { + match = -1; + + } else { + value_match( &match, slap_schema.si_ad_entryCSN, + slap_schema.si_ad_entryCSN->ad_type->sat_ordering, + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + &maxcsn[ sid ], &attr->a_nvals[i], &text ); + } + + if ( match > 0 ) { + change = 1; + } else { + AC_MEMCPY( maxcsn[ sid ].bv_val, + attr->a_nvals[ i ].bv_val, + attr->a_nvals[ i ].bv_len ); + maxcsn[ sid ].bv_val[ attr->a_nvals[ i ].bv_len ] = '\0'; + maxcsn[ sid ].bv_len = attr->a_nvals[ i ].bv_len; + } + } + + if ( change ) { + if ( attr->a_nvals != attr->a_vals ) { + ber_bvarray_free( attr->a_nvals ); + } + attr->a_nvals = NULL; + ber_bvarray_free( attr->a_vals ); + attr->a_vals = NULL; + attr->a_numvals = 0; + } + } else { + change = 1; + } + + if ( change ) { + for ( sid = 0; sid <= SLAP_SYNC_SID_MAX; sid++ ) { + if ( maxcsn[ sid ].bv_len ) { + attr_merge_one( e, slap_schema.si_ad_contextCSN, + &maxcsn[ sid], NULL ); + } + } + + ctxcsn_id = be->be_entry_modify( be, e, bvtext ); + if( ctxcsn_id == NOID ) { + fprintf( stderr, "%s: could not modify ctxcsn (%s)\n", + progname, bvtext->bv_val ? bvtext->bv_val : "" ); + rc = EXIT_FAILURE; + } else if ( verbose ) { + fprintf( stderr, "modified: \"%s\" (%08lx)\n", + e->e_dn, (long) ctxcsn_id ); + } + } + entry_free( e ); + } + } + + return rc; +} + +/* + * return value: + * -1: update_ctxcsn == 0 + * SLAP_SYNC_SID_MAX + 1: unable to extract SID + * 0 <= SLAP_SYNC_SID_MAX: the SID + */ +unsigned long +slap_tool_update_ctxcsn_check( + const char *progname, + Entry *e ) +{ + if ( update_ctxcsn ) { + unsigned long sid = SLAP_SYNC_SID_MAX + 1; + int rc_sid; + Attribute *attr; + + attr = attr_find( e->e_attrs, slap_schema.si_ad_entryCSN ); + assert( attr != NULL ); + + rc_sid = slap_parse_csn_sid( &attr->a_nvals[ 0 ] ); + if ( rc_sid < 0 ) { + Debug( LDAP_DEBUG_ANY, "%s: could not " + "extract SID from entryCSN=%s, entry dn=\"%s\"\n", + progname, attr->a_nvals[ 0 ].bv_val, e->e_name.bv_val ); + return (unsigned long)(-1); + + } else { + int match; + const char *text = NULL; + + assert( rc_sid <= SLAP_SYNC_SID_MAX ); + + sid = (unsigned)rc_sid; + if ( maxcsn[ sid ].bv_len != 0 ) { + match = 0; + value_match( &match, slap_schema.si_ad_entryCSN, + slap_schema.si_ad_entryCSN->ad_type->sat_ordering, + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + &maxcsn[ sid ], &attr->a_nvals[0], &text ); + } else { + match = -1; + } + if ( match < 0 ) { + strcpy( maxcsn[ sid ].bv_val, attr->a_nvals[0].bv_val ); + maxcsn[ sid ].bv_len = attr->a_nvals[0].bv_len; + } + } + } + + return (unsigned long)(-1); +} + +int +slap_tool_update_ctxcsn_init(void) +{ + if ( update_ctxcsn ) { + unsigned long sid; + maxcsn[ 0 ].bv_val = maxcsnbuf; + for ( sid = 1; sid <= SLAP_SYNC_SID_MAX; sid++ ) { + maxcsn[ sid ].bv_val = maxcsn[ sid - 1 ].bv_val + LDAP_PVT_CSNSTR_BUFSIZE; + maxcsn[ sid ].bv_len = 0; + } + } + + return 0; +} + +int +slap_tool_entry_check( + const char *progname, + Operation *op, + Entry *e, + int lineno, + const char **text, + char *textbuf, + size_t textlen ) +{ + /* NOTE: we may want to conditionally enable manage */ + int manage = 0; + + Attribute *oc = attr_find( e->e_attrs, + slap_schema.si_ad_objectClass ); + + if( oc == NULL ) { + fprintf( stderr, "%s: dn=\"%s\" (line=%d): %s\n", + progname, e->e_dn, lineno, + "no objectClass attribute"); + return LDAP_NO_SUCH_ATTRIBUTE; + } + + /* check schema */ + op->o_bd = be; + + if ( (slapMode & SLAP_TOOL_NO_SCHEMA_CHECK) == 0) { + int rc = entry_schema_check( op, e, NULL, manage, 1, NULL, + text, textbuf, textlen ); + + if( rc != LDAP_SUCCESS ) { + fprintf( stderr, "%s: dn=\"%s\" (line=%d): (%d) %s\n", + progname, e->e_dn, lineno, rc, *text ); + return rc; + } + textbuf[ 0 ] = '\0'; + } + + if ( (slapMode & SLAP_TOOL_VALUE_CHECK) != 0) { + Modifications *ml = NULL; + + int rc = slap_entry2mods( e, &ml, text, textbuf, textlen ); + if ( rc != LDAP_SUCCESS ) { + fprintf( stderr, "%s: dn=\"%s\" (line=%d): (%d) %s\n", + progname, e->e_dn, lineno, rc, *text ); + return rc; + } + textbuf[ 0 ] = '\0'; + + rc = slap_mods_check( op, ml, text, textbuf, textlen, NULL ); + slap_mods_free( ml, 1 ); + if ( rc != LDAP_SUCCESS ) { + fprintf( stderr, "%s: dn=\"%s\" (line=%d): (%d) %s\n", + progname, e->e_dn, lineno, rc, *text ); + return rc; + } + textbuf[ 0 ] = '\0'; + } + + return LDAP_SUCCESS; +} + diff --git a/servers/slapd/slapcommon.h b/servers/slapd/slapcommon.h new file mode 100644 index 0000000..2f6e566 --- /dev/null +++ b/servers/slapd/slapcommon.h @@ -0,0 +1,138 @@ +/* slapcommon.h - common definitions for the slap tools */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#ifndef SLAPCOMMON_H_ +#define SLAPCOMMON_H_ 1 + +#define SLAPD_TOOLS 1 +#include "slap.h" + +enum slaptool { + SLAPADD=1, /* LDIF -> database tool */ + SLAPCAT, /* database -> LDIF tool */ + SLAPDN, /* DN check w/ syntax tool */ + SLAPINDEX, /* database index tool */ + SLAPPASSWD, /* password generation tool */ + SLAPSCHEMA, /* schema checking tool */ + SLAPTEST, /* slapd.conf test tool */ + SLAPAUTH, /* test authz-regexp and authc/authz stuff */ + SLAPACL, /* test acl */ + SLAPLAST +}; + +typedef struct tool_vars { + Backend *tv_be; + int tv_dbnum; + int tv_verbose; + int tv_quiet; + int tv_update_ctxcsn; + int tv_continuemode; + int tv_nosubordinates; + int tv_dryrun; + unsigned long tv_jumpline; + struct berval tv_sub_ndn; + int tv_scope; + Filter *tv_filter; + struct LDIFFP *tv_ldiffp; + struct berval tv_baseDN; + struct berval tv_authcDN; + struct berval tv_authzDN; + struct berval tv_authcID; + struct berval tv_authzID; + struct berval tv_mech; + char *tv_realm; + struct berval tv_listener_url; + struct berval tv_peer_domain; + struct berval tv_peer_name; + struct berval tv_sock_name; + slap_ssf_t tv_ssf; + slap_ssf_t tv_transport_ssf; + slap_ssf_t tv_tls_ssf; + slap_ssf_t tv_sasl_ssf; + unsigned tv_dn_mode; + unsigned int tv_csnsid; + ber_len_t tv_ldif_wrap; + char tv_maxcsnbuf[ LDAP_PVT_CSNSTR_BUFSIZE * ( SLAP_SYNC_SID_MAX + 1 ) ]; + struct berval tv_maxcsn[ SLAP_SYNC_SID_MAX + 1 ]; +} tool_vars; + +extern tool_vars tool_globals; + +#define be tool_globals.tv_be +#define dbnum tool_globals.tv_dbnum +#define verbose tool_globals.tv_verbose +#define quiet tool_globals.tv_quiet +#define jumpline tool_globals.tv_jumpline +#define update_ctxcsn tool_globals.tv_update_ctxcsn +#define continuemode tool_globals.tv_continuemode +#define nosubordinates tool_globals.tv_nosubordinates +#define dryrun tool_globals.tv_dryrun +#define sub_ndn tool_globals.tv_sub_ndn +#define scope tool_globals.tv_scope +#define filter tool_globals.tv_filter +#define ldiffp tool_globals.tv_ldiffp +#define baseDN tool_globals.tv_baseDN +#define authcDN tool_globals.tv_authcDN +#define authzDN tool_globals.tv_authzDN +#define authcID tool_globals.tv_authcID +#define authzID tool_globals.tv_authzID +#define mech tool_globals.tv_mech +#define realm tool_globals.tv_realm +#define listener_url tool_globals.tv_listener_url +#define peer_domain tool_globals.tv_peer_domain +#define peer_name tool_globals.tv_peer_name +#define sock_name tool_globals.tv_sock_name +#define ssf tool_globals.tv_ssf +#define transport_ssf tool_globals.tv_transport_ssf +#define tls_ssf tool_globals.tv_tls_ssf +#define sasl_ssf tool_globals.tv_sasl_ssf +#define dn_mode tool_globals.tv_dn_mode +#define csnsid tool_globals.tv_csnsid +#define ldif_wrap tool_globals.tv_ldif_wrap +#define maxcsn tool_globals.tv_maxcsn +#define maxcsnbuf tool_globals.tv_maxcsnbuf + +#define SLAP_TOOL_LDAPDN_PRETTY SLAP_LDAPDN_PRETTY +#define SLAP_TOOL_LDAPDN_NORMAL (SLAP_LDAPDN_PRETTY << 1) + +void slap_tool_init LDAP_P(( + const char* name, + int tool, + int argc, char **argv )); + +int slap_tool_destroy LDAP_P((void)); + +int slap_tool_update_ctxcsn LDAP_P(( + const char *progname, + unsigned long sid, + struct berval *bvtext )); + +unsigned long slap_tool_update_ctxcsn_check LDAP_P(( + const char *progname, + Entry *e )); + +int slap_tool_update_ctxcsn_init LDAP_P((void)); + +int slap_tool_entry_check LDAP_P(( + const char *progname, + Operation *op, + Entry *e, + int lineno, + const char **text, + char *textbuf, + size_t textlen )); + +#endif /* SLAPCOMMON_H_ */ diff --git a/servers/slapd/slapd.conf b/servers/slapd/slapd.conf new file mode 100644 index 0000000..b225fe5 --- /dev/null +++ b/servers/slapd/slapd.conf @@ -0,0 +1,65 @@ +# +# See slapd.conf(5) for details on configuration options. +# This file should NOT be world readable. +# +include %SYSCONFDIR%/schema/core.schema + +# Define global ACLs to disable default read access. + +# Do not enable referrals until AFTER you have a working directory +# service AND an understanding of referrals. +#referral ldap://root.openldap.org + +pidfile %LOCALSTATEDIR%/run/slapd.pid +argsfile %LOCALSTATEDIR%/run/slapd.args + +# Load dynamic backend modules: +# modulepath %MODULEDIR% +# moduleload back_mdb.la +# moduleload back_ldap.la + +# Sample security restrictions +# Require integrity protection (prevent hijacking) +# Require 112-bit (3DES or better) encryption for updates +# Require 63-bit encryption for simple bind +# security ssf=1 update_ssf=112 simple_bind=64 + +# Sample access control policy: +# Root DSE: allow anyone to read it +# Subschema (sub)entry DSE: allow anyone to read it +# Other DSEs: +# Allow self write access +# Allow authenticated users read access +# Allow anonymous users to authenticate +# Directives needed to implement policy: +# access to dn.base="" by * read +# access to dn.base="cn=Subschema" by * read +# access to * +# by self write +# by users read +# by anonymous auth +# +# if no access controls are present, the default policy +# allows anyone and everyone to read anything but restricts +# updates to rootdn. (e.g., "access to * by * read") +# +# rootdn can always read and write EVERYTHING! + +####################################################################### +# MDB database definitions +####################################################################### + +database mdb +maxsize 1073741824 +suffix "dc=my-domain,dc=com" +rootdn "cn=Manager,dc=my-domain,dc=com" +# Cleartext passwords, especially for the rootdn, should +# be avoid. See slappasswd(8) and slapd.conf(5) for details. +# Use of strong authentication encouraged. +rootpw secret +# The database directory MUST exist prior to running slapd AND +# should only be accessible by the slapd and slap tools. +# Mode 700 recommended. +directory %LOCALSTATEDIR%/openldap-data +# Indices to maintain +index objectClass eq diff --git a/servers/slapd/slapd.ldif b/servers/slapd/slapd.ldif new file mode 100644 index 0000000..d038733 --- /dev/null +++ b/servers/slapd/slapd.ldif @@ -0,0 +1,96 @@ +# +# See slapd-config(5) for details on configuration options. +# This file should NOT be world readable. +# +dn: cn=config +objectClass: olcGlobal +cn: config +# +# +# Define global ACLs to disable default read access. +# +olcArgsFile: %LOCALSTATEDIR%/run/slapd.args +olcPidFile: %LOCALSTATEDIR%/run/slapd.pid +# +# Do not enable referrals until AFTER you have a working directory +# service AND an understanding of referrals. +#olcReferral: ldap://root.openldap.org +# +# Sample security restrictions +# Require integrity protection (prevent hijacking) +# Require 112-bit (3DES or better) encryption for updates +# Require 64-bit encryption for simple bind +#olcSecurity: ssf=1 update_ssf=112 simple_bind=64 + + +# +# Load dynamic backend modules: +# +#dn: cn=module,cn=config +#objectClass: olcModuleList +#cn: module +#olcModulepath: %MODULEDIR% +#olcModuleload: back_mdb.la +#olcModuleload: back_bdb.la +#olcModuleload: back_hdb.la +#olcModuleload: back_ldap.la +#olcModuleload: back_passwd.la +#olcModuleload: back_shell.la + + +dn: cn=schema,cn=config +objectClass: olcSchemaConfig +cn: schema + +include: file://%SYSCONFDIR%/schema/core.ldif + +# Frontend settings +# +dn: olcDatabase=frontend,cn=config +objectClass: olcDatabaseConfig +objectClass: olcFrontendConfig +olcDatabase: frontend +# +# Sample global access control policy: +# Root DSE: allow anyone to read it +# Subschema (sub)entry DSE: allow anyone to read it +# Other DSEs: +# Allow self write access +# Allow authenticated users read access +# Allow anonymous users to authenticate +# +#olcAccess: to dn.base="" by * read +#olcAccess: to dn.base="cn=Subschema" by * read +#olcAccess: to * +# by self write +# by users read +# by anonymous auth +# +# if no access controls are present, the default policy +# allows anyone and everyone to read anything but restricts +# updates to rootdn. (e.g., "access to * by * read") +# +# rootdn can always read and write EVERYTHING! +# + + +####################################################################### +# LMDB database definitions +####################################################################### +# +dn: olcDatabase=mdb,cn=config +objectClass: olcDatabaseConfig +objectClass: olcMdbConfig +olcDatabase: mdb +olcSuffix: dc=my-domain,dc=com +olcRootDN: cn=Manager,dc=my-domain,dc=com +# Cleartext passwords, especially for the rootdn, should +# be avoided. See slappasswd(8) and slapd-config(5) for details. +# Use of strong authentication encouraged. +olcRootPW: secret +# The database directory MUST exist prior to running slapd AND +# should only be accessible by the slapd and slap tools. +# Mode 700 recommended. +olcDbDirectory: %LOCALSTATEDIR%/openldap-data +# Indices to maintain +olcDbIndex: objectClass eq diff --git a/servers/slapd/slapdn.c b/servers/slapd/slapdn.c new file mode 100644 index 0000000..0d1e6ac --- /dev/null +++ b/servers/slapd/slapdn.c @@ -0,0 +1,107 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2021 The OpenLDAP Foundation. + * Portions Copyright 2004 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/socket.h> +#include <ac/unistd.h> + +#include <lber.h> +#include <ldif.h> +#include <lutil.h> + +#include "slapcommon.h" + +int +slapdn( int argc, char **argv ) +{ + int rc = 0; + const char *progname = "slapdn"; + + slap_tool_init( progname, SLAPDN, argc, argv ); + + argv = &argv[ optind ]; + argc -= optind; + + for ( ; argc--; argv++ ) { + struct berval dn, + pdn = BER_BVNULL, + ndn = BER_BVNULL; + + ber_str2bv( argv[ 0 ], 0, 0, &dn ); + + switch ( dn_mode ) { + case SLAP_TOOL_LDAPDN_PRETTY: + rc = dnPretty( NULL, &dn, &pdn, NULL ); + break; + + case SLAP_TOOL_LDAPDN_NORMAL: + rc = dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL ); + break; + + default: + rc = dnPrettyNormal( NULL, &dn, &pdn, &ndn, NULL ); + break; + } + + if ( rc != LDAP_SUCCESS ) { + fprintf( stderr, "DN: <%s> check failed %d (%s)\n", + dn.bv_val, rc, + ldap_err2string( rc ) ); + if ( !continuemode ) { + rc = -1; + break; + } + + } else { + switch ( dn_mode ) { + case SLAP_TOOL_LDAPDN_PRETTY: + printf( "%s\n", pdn.bv_val ); + break; + + case SLAP_TOOL_LDAPDN_NORMAL: + printf( "%s\n", ndn.bv_val ); + break; + + default: + printf( "DN: <%s> check succeeded\n" + "normalized: <%s>\n" + "pretty: <%s>\n", + dn.bv_val, + ndn.bv_val, pdn.bv_val ); + break; + } + + ch_free( ndn.bv_val ); + ch_free( pdn.bv_val ); + } + } + + if ( slap_tool_destroy()) + rc = EXIT_FAILURE; + + return rc; +} diff --git a/servers/slapd/slapi/Makefile.in b/servers/slapd/slapi/Makefile.in new file mode 100644 index 0000000..daf4f4f --- /dev/null +++ b/servers/slapd/slapi/Makefile.in @@ -0,0 +1,51 @@ +# Makefile.in for SLAPI +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## Portions Copyright IBM Corp. 1997,2002,2003 +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +LIBRARY = libslapi.la + +#all-common: $(LIBRARY) $(PROGRAMS) +# @touch plugin.c slapi_pblock.c slapi_utils.c slapi_ops.c slapi_ext.c + +NT_SRCS = nt_err.c +NT_OBJS = nt_err.lo + +LIB_DEFS = -DSLAPI_LIBRARY + +SRCS= plugin.c slapi_pblock.c slapi_utils.c printmsg.c slapi_ops.c slapi_dn.c slapi_ext.c slapi_overlay.c \ + $(@PLAT@_SRCS) +OBJS= plugin.lo slapi_pblock.lo slapi_utils.lo printmsg.lo slapi_ops.lo slapi_dn.lo slapi_ext.lo slapi_overlay.lo \ + $(@PLAT@_SRCS) + +XSRCS= version.c + +LDAP_INCDIR= ../../../include -I.. -I. +LDAP_LIBDIR= ../../../libraries + +XLIBS = $(LIBRARY) +XXLIBS = +NT_LINK_LIBS = $(AC_LIBS) + +XINCPATH = -I$(srcdir)/.. -I$(srcdir) +XDEFS = $(MODULES_CPPFLAGS) + +BUILD_MOD = @BUILD_SLAPI@ + +install-local: FORCE + if test "$(BUILD_MOD)" = "yes"; then \ + $(MKDIR) $(DESTDIR)$(libdir); \ + $(LTINSTALL) $(INSTALLFLAGS) -m 644 $(LIBRARY) $(DESTDIR)$(libdir); \ + fi + diff --git a/servers/slapd/slapi/TODO b/servers/slapd/slapi/TODO new file mode 100644 index 0000000..8916488 --- /dev/null +++ b/servers/slapd/slapi/TODO @@ -0,0 +1,16 @@ +- de-IBM SLAPI +- add a config statement, or redefine the dynamic backend one, + "modulepath", to set/modify the load path also for plugins + (both plugins and modules use ltdl, so "modulepath" suffices ...) +- improve slapi logging (use some [v]s[n]printf function) +- add a config statement to set the log file name, or better +- use syslog where available? +- add some plugin monitoring stuff in back-monitor (e.g. a subentry + for each plugin with data from struct Slapi_PluginDesc) +- This is a very tough task: try to implement a sandbox to execute + plugins in, trap deadly signals and possibly disable unsafe plugins + without crashing slapd (fork from inside thread? trap signals + and longjump to next plugin execution? Brrr). + +--- +$OpenLDAP$ diff --git a/servers/slapd/slapi/plugin.c b/servers/slapd/slapi/plugin.c new file mode 100644 index 0000000..506f427 --- /dev/null +++ b/servers/slapd/slapi/plugin.c @@ -0,0 +1,747 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2002-2021 The OpenLDAP Foundation. + * Portions Copyright 1997,2002-2003 IBM Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by IBM Corporation for use in + * IBM products and subsequently ported to OpenLDAP Software by + * Steve Omrani. Additional significant contributors include: + * Luke Howard + */ + +#include "portable.h" +#include "ldap_pvt_thread.h" +#include "slap.h" +#include "config.h" +#include "slapi.h" +#include "lutil.h" + +/* + * Note: if ltdl.h is not available, slapi should not be compiled + */ +#include <ltdl.h> + +static int slapi_int_load_plugin( Slapi_PBlock *, const char *, const char *, int, + SLAPI_FUNC *, lt_dlhandle * ); + +/* pointer to link list of extended objects */ +static ExtendedOp *pGExtendedOps = NULL; + +/********************************************************************* + * Function Name: plugin_pblock_new + * + * Description: This routine creates a new Slapi_PBlock structure, + * loads in the plugin module and executes the init + * function provided by the module. + * + * Input: type - type of the plugin, such as SASL, database, etc. + * path - the loadpath to load the module in + * initfunc - name of the plugin function to execute first + * argc - number of arguements + * argv[] - an array of char pointers point to + * the arguments passed in via + * the configuration file. + * + * Output: + * + * Return Values: a pointer to a newly created Slapi_PBlock structrue or + * NULL - function failed + * + * Messages: None + *********************************************************************/ + +static Slapi_PBlock * +plugin_pblock_new( + int type, + int argc, + char *argv[] ) +{ + Slapi_PBlock *pPlugin = NULL; + Slapi_PluginDesc *pPluginDesc = NULL; + lt_dlhandle hdLoadHandle; + int rc; + char **av2 = NULL, **ppPluginArgv; + char *path = argv[2]; + char *initfunc = argv[3]; + + pPlugin = slapi_pblock_new(); + if ( pPlugin == NULL ) { + rc = LDAP_NO_MEMORY; + goto done; + } + + slapi_pblock_set( pPlugin, SLAPI_PLUGIN_TYPE, (void *)&type ); + slapi_pblock_set( pPlugin, SLAPI_PLUGIN_ARGC, (void *)&argc ); + + av2 = ldap_charray_dup( argv ); + if ( av2 == NULL ) { + rc = LDAP_NO_MEMORY; + goto done; + } + + if ( argc > 0 ) { + ppPluginArgv = &av2[4]; + } else { + ppPluginArgv = NULL; + } + + slapi_pblock_set( pPlugin, SLAPI_PLUGIN_ARGV, (void *)ppPluginArgv ); + slapi_pblock_set( pPlugin, SLAPI_X_CONFIG_ARGV, (void *)av2 ); + + rc = slapi_int_load_plugin( pPlugin, path, initfunc, 1, NULL, &hdLoadHandle ); + if ( rc != 0 ) { + goto done; + } + + if ( slapi_pblock_get( pPlugin, SLAPI_PLUGIN_DESCRIPTION, (void **)&pPluginDesc ) == 0 && + pPluginDesc != NULL ) { + slapi_log_error(SLAPI_LOG_TRACE, "plugin_pblock_new", + "Registered plugin %s %s [%s] (%s)\n", + pPluginDesc->spd_id, + pPluginDesc->spd_version, + pPluginDesc->spd_vendor, + pPluginDesc->spd_description); + } + +done: + if ( rc != 0 && pPlugin != NULL ) { + slapi_pblock_destroy( pPlugin ); + pPlugin = NULL; + if ( av2 != NULL ) { + ldap_charray_free( av2 ); + } + } + + return pPlugin; +} + +/********************************************************************* + * Function Name: slapi_int_register_plugin + * + * Description: insert the slapi_pblock structure to the end of the plugin + * list + * + * Input: a pointer to a plugin slapi_pblock structure to be added to + * the list + * + * Output: none + * + * Return Values: LDAP_SUCCESS - successfully inserted. + * LDAP_LOCAL_ERROR. + * + * Messages: None + *********************************************************************/ +int +slapi_int_register_plugin( + Backend *be, + Slapi_PBlock *pPB ) +{ + Slapi_PBlock *pTmpPB; + Slapi_PBlock *pSavePB; + int rc = LDAP_SUCCESS; + + assert( be != NULL ); + + pTmpPB = SLAPI_BACKEND_PBLOCK( be ); + if ( pTmpPB == NULL ) { + SLAPI_BACKEND_PBLOCK( be ) = pPB; + } else { + while ( pTmpPB != NULL && rc == LDAP_SUCCESS ) { + pSavePB = pTmpPB; + rc = slapi_pblock_get( pTmpPB, SLAPI_IBM_PBLOCK, &pTmpPB ); + } + + if ( rc == LDAP_SUCCESS ) { + rc = slapi_pblock_set( pSavePB, SLAPI_IBM_PBLOCK, (void *)pPB ); + } + } + + return ( rc != LDAP_SUCCESS ) ? LDAP_OTHER : LDAP_SUCCESS; +} + +/********************************************************************* + * Function Name: slapi_int_get_plugins + * + * Description: get the desired type of function pointers defined + * in all the plugins + * + * Input: the type of the functions to get, such as pre-operation,etc. + * + * Output: none + * + * Return Values: this routine returns a pointer to an array of function + * pointers containing backend-specific plugin functions + * followed by global plugin functions + * + * Messages: None + *********************************************************************/ +int +slapi_int_get_plugins( + Backend *be, + int functype, + SLAPI_FUNC **ppFuncPtrs ) +{ + + Slapi_PBlock *pCurrentPB; + SLAPI_FUNC FuncPtr; + SLAPI_FUNC *pTmpFuncPtr; + int numPB = 0; + int rc = LDAP_SUCCESS; + + assert( ppFuncPtrs != NULL ); + + if ( be == NULL ) { + goto done; + } + + pCurrentPB = SLAPI_BACKEND_PBLOCK( be ); + + while ( pCurrentPB != NULL && rc == LDAP_SUCCESS ) { + rc = slapi_pblock_get( pCurrentPB, functype, &FuncPtr ); + if ( rc == LDAP_SUCCESS ) { + if ( FuncPtr != NULL ) { + numPB++; + } + rc = slapi_pblock_get( pCurrentPB, + SLAPI_IBM_PBLOCK, &pCurrentPB ); + } + } + + if ( numPB == 0 ) { + *ppFuncPtrs = NULL; + rc = LDAP_SUCCESS; + goto done; + } + + /* + * Now, build the function pointer array of backend-specific + * plugins followed by global plugins. + */ + *ppFuncPtrs = pTmpFuncPtr = + (SLAPI_FUNC *)ch_malloc( ( numPB + 1 ) * sizeof(SLAPI_FUNC) ); + if ( ppFuncPtrs == NULL ) { + rc = LDAP_NO_MEMORY; + goto done; + } + + pCurrentPB = SLAPI_BACKEND_PBLOCK( be ); + + while ( pCurrentPB != NULL && rc == LDAP_SUCCESS ) { + rc = slapi_pblock_get( pCurrentPB, functype, &FuncPtr ); + if ( rc == LDAP_SUCCESS ) { + if ( FuncPtr != NULL ) { + *pTmpFuncPtr = FuncPtr; + pTmpFuncPtr++; + } + rc = slapi_pblock_get( pCurrentPB, + SLAPI_IBM_PBLOCK, &pCurrentPB ); + } + } + + *pTmpFuncPtr = NULL; + + +done: + if ( rc != LDAP_SUCCESS && *ppFuncPtrs != NULL ) { + ch_free( *ppFuncPtrs ); + *ppFuncPtrs = NULL; + } + + return rc; +} + +/********************************************************************* + * Function Name: createExtendedOp + * + * Description: Creates an extended operation structure and + * initializes the fields + * + * Return value: A newly allocated structure or NULL + ********************************************************************/ +ExtendedOp * +createExtendedOp() +{ + ExtendedOp *ret; + + ret = (ExtendedOp *)slapi_ch_malloc(sizeof(ExtendedOp)); + ret->ext_oid.bv_val = NULL; + ret->ext_oid.bv_len = 0; + ret->ext_func = NULL; + ret->ext_be = NULL; + ret->ext_next = NULL; + + return ret; +} + + +/********************************************************************* + * Function Name: slapi_int_unregister_extop + * + * Description: This routine removes the ExtendedOp structures + * asscoiated with a particular extended operation + * plugin. + * + * Input: pBE - pointer to a backend structure + * opList - pointer to a linked list of extended + * operation structures + * pPB - pointer to a slapi parameter block + * + * Output: + * + * Return Value: none + * + * Messages: None + *********************************************************************/ +void +slapi_int_unregister_extop( + Backend *pBE, + ExtendedOp **opList, + Slapi_PBlock *pPB ) +{ + ExtendedOp *pTmpExtOp, *backExtOp; + char **pTmpOIDs; + int i; + +#if 0 + assert( pBE != NULL); /* unused */ +#endif /* 0 */ + assert( opList != NULL ); + assert( pPB != NULL ); + + if ( *opList == NULL ) { + return; + } + + slapi_pblock_get( pPB, SLAPI_PLUGIN_EXT_OP_OIDLIST, &pTmpOIDs ); + if ( pTmpOIDs == NULL ) { + return; + } + + for ( i = 0; pTmpOIDs[i] != NULL; i++ ) { + backExtOp = NULL; + pTmpExtOp = *opList; + for ( ; pTmpExtOp != NULL; pTmpExtOp = pTmpExtOp->ext_next) { + int rc; + rc = strcasecmp( pTmpExtOp->ext_oid.bv_val, + pTmpOIDs[ i ] ); + if ( rc == 0 ) { + if ( backExtOp == NULL ) { + *opList = pTmpExtOp->ext_next; + } else { + backExtOp->ext_next + = pTmpExtOp->ext_next; + } + + ch_free( pTmpExtOp ); + break; + } + backExtOp = pTmpExtOp; + } + } +} + + +/********************************************************************* + * Function Name: slapi_int_register_extop + * + * Description: This routine creates a new ExtendedOp structure, loads + * in the extended op module and put the extended op function address + * in the structure. The function will not be executed in + * this routine. + * + * Input: pBE - pointer to a backend structure + * opList - pointer to a linked list of extended + * operation structures + * pPB - pointer to a slapi parameter block + * + * Output: + * + * Return Value: an LDAP return code + * + * Messages: None + *********************************************************************/ +int +slapi_int_register_extop( + Backend *pBE, + ExtendedOp **opList, + Slapi_PBlock *pPB ) +{ + ExtendedOp *pTmpExtOp = NULL; + SLAPI_FUNC tmpFunc; + char **pTmpOIDs; + int rc = LDAP_OTHER; + int i; + + if ( (*opList) == NULL ) { + *opList = createExtendedOp(); + if ( (*opList) == NULL ) { + rc = LDAP_NO_MEMORY; + goto error_return; + } + pTmpExtOp = *opList; + + } else { /* Find the end of the list */ + for ( pTmpExtOp = *opList; pTmpExtOp->ext_next != NULL; + pTmpExtOp = pTmpExtOp->ext_next ) + ; /* EMPTY */ + pTmpExtOp->ext_next = createExtendedOp(); + if ( pTmpExtOp->ext_next == NULL ) { + rc = LDAP_NO_MEMORY; + goto error_return; + } + pTmpExtOp = pTmpExtOp->ext_next; + } + + rc = slapi_pblock_get( pPB,SLAPI_PLUGIN_EXT_OP_OIDLIST, &pTmpOIDs ); + if ( rc != 0 ) { + rc = LDAP_OTHER; + goto error_return; + } + + rc = slapi_pblock_get(pPB,SLAPI_PLUGIN_EXT_OP_FN, &tmpFunc); + if ( rc != 0 ) { + rc = LDAP_OTHER; + goto error_return; + } + + if ( (pTmpOIDs == NULL) || (tmpFunc == NULL) ) { + rc = LDAP_OTHER; + goto error_return; + } + + for ( i = 0; pTmpOIDs[i] != NULL; i++ ) { + pTmpExtOp->ext_oid.bv_val = pTmpOIDs[i]; + pTmpExtOp->ext_oid.bv_len = strlen( pTmpOIDs[i] ); + pTmpExtOp->ext_func = tmpFunc; + pTmpExtOp->ext_be = pBE; + if ( pTmpOIDs[i + 1] != NULL ) { + pTmpExtOp->ext_next = createExtendedOp(); + if ( pTmpExtOp->ext_next == NULL ) { + rc = LDAP_NO_MEMORY; + break; + } + pTmpExtOp = pTmpExtOp->ext_next; + } + } + +error_return: + return rc; +} + +/********************************************************************* + * Function Name: slapi_int_get_extop_plugin + * + * Description: This routine gets the function address for a given function + * name. + * + * Input: + * funcName - name of the extended op function, ie. an OID. + * + * Output: pFuncAddr - the function address of the requested function name. + * + * Return Values: a pointer to a newly created ExtendOp structrue or + * NULL - function failed + * + * Messages: None + *********************************************************************/ +int +slapi_int_get_extop_plugin( + struct berval *reqoid, + SLAPI_FUNC *pFuncAddr ) +{ + ExtendedOp *pTmpExtOp; + + assert( reqoid != NULL ); + assert( pFuncAddr != NULL ); + + *pFuncAddr = NULL; + + if ( pGExtendedOps == NULL ) { + return LDAP_OTHER; + } + + pTmpExtOp = pGExtendedOps; + while ( pTmpExtOp != NULL ) { + int rc; + + rc = strcasecmp( reqoid->bv_val, pTmpExtOp->ext_oid.bv_val ); + if ( rc == 0 ) { + *pFuncAddr = pTmpExtOp->ext_func; + break; + } + pTmpExtOp = pTmpExtOp->ext_next; + } + + return ( *pFuncAddr == NULL ? 1 : 0 ); +} + +/*************************************************************************** + * This function is similar to slapi_int_get_extop_plugin above. except it returns one OID + * per call. It is called from root_dse_info (root_dse.c). + * The function is a modified version of get_supported_extop (file extended.c). + ***************************************************************************/ +struct berval * +slapi_int_get_supported_extop( int index ) +{ + ExtendedOp *ext; + + for ( ext = pGExtendedOps ; ext != NULL && --index >= 0; + ext = ext->ext_next) { + ; /* empty */ + } + + if ( ext == NULL ) { + return NULL; + } + + return &ext->ext_oid ; +} + +/********************************************************************* + * Function Name: slapi_int_load_plugin + * + * Description: This routine loads the specified DLL, gets and executes the init function + * if requested. + * + * Input: + * pPlugin - a pointer to a Slapi_PBlock struct which will be passed to + * the DLL init function. + * path - path name of the DLL to be load. + * initfunc - either the DLL initialization function or an OID of the + * loaded extended operation. + * doInit - if it is TRUE, execute the init function, otherwise, save the + * function address but not execute it. + * + * Output: pInitFunc - the function address of the loaded function. This param + * should be not be null if doInit is FALSE. + * pLdHandle - handle returned by lt_dlopen() + * + * Return Values: LDAP_SUCCESS, LDAP_LOCAL_ERROR + * + * Messages: None + *********************************************************************/ + +static int +slapi_int_load_plugin( + Slapi_PBlock *pPlugin, + const char *path, + const char *initfunc, + int doInit, + SLAPI_FUNC *pInitFunc, + lt_dlhandle *pLdHandle ) +{ + int rc = LDAP_SUCCESS; + SLAPI_FUNC fpInitFunc = NULL; + + assert( pLdHandle != NULL ); + + if ( lt_dlinit() ) { + return LDAP_LOCAL_ERROR; + } + + /* load in the module */ + *pLdHandle = lt_dlopen( path ); + if ( *pLdHandle == NULL ) { + fprintf( stderr, "failed to load plugin %s: %s\n", + path, lt_dlerror() ); + return LDAP_LOCAL_ERROR; + } + + fpInitFunc = (SLAPI_FUNC)lt_dlsym( *pLdHandle, initfunc ); + if ( fpInitFunc == NULL ) { + fprintf( stderr, "failed to find symbol %s in plugin %s: %s\n", + initfunc, path, lt_dlerror() ); + lt_dlclose( *pLdHandle ); + return LDAP_LOCAL_ERROR; + } + + if ( doInit ) { + rc = ( *fpInitFunc )( pPlugin ); + if ( rc != LDAP_SUCCESS ) { + lt_dlclose( *pLdHandle ); + } + + } else { + *pInitFunc = fpInitFunc; + } + + return rc; +} + +/* + * Special support for computed attribute plugins + */ +int +slapi_int_call_plugins( + Backend *be, + int funcType, + Slapi_PBlock *pPB ) +{ + + int rc = 0; + SLAPI_FUNC *pGetPlugin = NULL, *tmpPlugin = NULL; + + if ( pPB == NULL ) { + return 1; + } + + rc = slapi_int_get_plugins( be, funcType, &tmpPlugin ); + if ( rc != LDAP_SUCCESS || tmpPlugin == NULL ) { + /* Nothing to do, front-end should ignore. */ + return rc; + } + + for ( pGetPlugin = tmpPlugin ; *pGetPlugin != NULL; pGetPlugin++ ) { + rc = (*pGetPlugin)(pPB); + + /* + * Only non-postoperation plugins abort processing on + * failure (confirmed with SLAPI specification). + */ + if ( !SLAPI_PLUGIN_IS_POST_FN( funcType ) && rc != 0 ) { + /* + * Plugins generally return negative error codes + * to indicate failure, although in the case of + * bind plugins they may return SLAPI_BIND_xxx + */ + break; + } + } + + slapi_ch_free( (void **)&tmpPlugin ); + + return rc; +} + +int +slapi_int_read_config( + Backend *be, + const char *fname, + int lineno, + int argc, + char **argv ) +{ + int iType = -1; + int numPluginArgc = 0; + + if ( argc < 4 ) { + fprintf( stderr, + "%s: line %d: missing arguments " + "in \"plugin <plugin_type> <lib_path> " + "<init_function> [<arguments>]\" line\n", + fname, lineno ); + return 1; + } + + /* automatically instantiate overlay if necessary */ + if ( !slapi_over_is_inst( be ) ) { + ConfigReply cr = { 0 }; + if ( slapi_over_config( be, &cr ) != 0 ) { + fprintf( stderr, "Failed to instantiate SLAPI overlay: " + "err=%d msg=\"%s\"\n", cr.err, cr.msg ); + return -1; + } + } + + if ( strcasecmp( argv[1], "preoperation" ) == 0 ) { + iType = SLAPI_PLUGIN_PREOPERATION; + } else if ( strcasecmp( argv[1], "postoperation" ) == 0 ) { + iType = SLAPI_PLUGIN_POSTOPERATION; + } else if ( strcasecmp( argv[1], "extendedop" ) == 0 ) { + iType = SLAPI_PLUGIN_EXTENDEDOP; + } else if ( strcasecmp( argv[1], "object" ) == 0 ) { + iType = SLAPI_PLUGIN_OBJECT; + } else { + fprintf( stderr, "%s: line %d: invalid plugin type \"%s\".\n", + fname, lineno, argv[1] ); + return 1; + } + + numPluginArgc = argc - 4; + + if ( iType == SLAPI_PLUGIN_PREOPERATION || + iType == SLAPI_PLUGIN_EXTENDEDOP || + iType == SLAPI_PLUGIN_POSTOPERATION || + iType == SLAPI_PLUGIN_OBJECT ) { + int rc; + Slapi_PBlock *pPlugin; + + pPlugin = plugin_pblock_new( iType, numPluginArgc, argv ); + if (pPlugin == NULL) { + return 1; + } + + if (iType == SLAPI_PLUGIN_EXTENDEDOP) { + rc = slapi_int_register_extop(be, &pGExtendedOps, pPlugin); + if ( rc != LDAP_SUCCESS ) { + slapi_pblock_destroy( pPlugin ); + return 1; + } + } + + rc = slapi_int_register_plugin( be, pPlugin ); + if ( rc != LDAP_SUCCESS ) { + if ( iType == SLAPI_PLUGIN_EXTENDEDOP ) { + slapi_int_unregister_extop( be, &pGExtendedOps, pPlugin ); + } + slapi_pblock_destroy( pPlugin ); + return 1; + } + } + + return 0; +} + +void +slapi_int_plugin_unparse( + Backend *be, + BerVarray *out +) +{ + Slapi_PBlock *pp; + int i, j; + char **argv, ibuf[32], *ptr; + struct berval idx, bv; + + *out = NULL; + idx.bv_val = ibuf; + i = 0; + + for ( pp = SLAPI_BACKEND_PBLOCK( be ); + pp != NULL; + slapi_pblock_get( pp, SLAPI_IBM_PBLOCK, &pp ) ) + { + slapi_pblock_get( pp, SLAPI_X_CONFIG_ARGV, &argv ); + if ( argv == NULL ) /* could be dynamic plugin */ + continue; + idx.bv_len = snprintf( idx.bv_val, sizeof( ibuf ), "{%d}", i ); + if ( idx.bv_len >= sizeof( ibuf ) ) { + /* FIXME: just truncating by now */ + idx.bv_len = sizeof( ibuf ) - 1; + } + bv.bv_len = idx.bv_len; + for (j=1; argv[j]; j++) { + bv.bv_len += strlen(argv[j]); + if ( j ) bv.bv_len++; + } + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + ptr = lutil_strcopy( bv.bv_val, ibuf ); + for (j=1; argv[j]; j++) { + if ( j ) *ptr++ = ' '; + ptr = lutil_strcopy( ptr, argv[j] ); + } + ber_bvarray_add( out, &bv ); + } +} + diff --git a/servers/slapd/slapi/printmsg.c b/servers/slapd/slapi/printmsg.c new file mode 100644 index 0000000..498e78d --- /dev/null +++ b/servers/slapd/slapi/printmsg.c @@ -0,0 +1,100 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2002-2021 The OpenLDAP Foundation. + * Portions Copyright 1997,2002-2003 IBM Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by IBM Corporation for use in + * IBM products and subsequently ported to OpenLDAP Software by + * Steve Omrani. + */ + +#include <portable.h> +#include <stdio.h> +#include <ac/string.h> +#include <ac/stdarg.h> +#include <ac/unistd.h> +#include <fcntl.h> +#include <ac/errno.h> + +#include <ldap.h> +#include <ldap_config.h> +#include <slap.h> +#include <slapi.h> + +#include <ldap_pvt_thread.h> + +/* Single threads access to routine */ +ldap_pvt_thread_mutex_t slapi_printmessage_mutex; +char *slapi_log_file = NULL; +int slapi_log_level = SLAPI_LOG_PLUGIN; + +int +slapi_int_log_error( + int level, + char *subsystem, + char *fmt, + va_list arglist ) +{ + int rc = 0; + FILE *fp = NULL; + + char timeStr[100]; + struct tm *ltm; + time_t currentTime; + + assert( subsystem != NULL ); + assert( fmt != NULL ); + + ldap_pvt_thread_mutex_lock( &slapi_printmessage_mutex ) ; + + /* for now, we log all severities */ + if ( level <= slapi_log_level ) { + fp = fopen( slapi_log_file, "a" ); + if ( fp == NULL) { + rc = -1; + goto done; + } + + /* + * FIXME: could block + */ + while ( lockf( fileno( fp ), F_LOCK, 0 ) != 0 ) { + /* DO NOTHING */ ; + } + + time( ¤tTime ); + ltm = localtime( ¤tTime ); + strftime( timeStr, sizeof(timeStr), "%x %X", ltm ); + fputs( timeStr, fp ); + + fprintf( fp, " %s: ", subsystem ); + vfprintf( fp, fmt, arglist ); + if ( fmt[ strlen( fmt ) - 1 ] != '\n' ) { + fputs( "\n", fp ); + } + fflush( fp ); + + lockf( fileno( fp ), F_ULOCK, 0 ); + + fclose( fp ); + + } else { + rc = -1; + } + +done: + ldap_pvt_thread_mutex_unlock( &slapi_printmessage_mutex ); + + return rc; +} diff --git a/servers/slapd/slapi/proto-slapi.h b/servers/slapd/slapi/proto-slapi.h new file mode 100644 index 0000000..0f11145 --- /dev/null +++ b/servers/slapd/slapi/proto-slapi.h @@ -0,0 +1,91 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2002-2021 The OpenLDAP Foundation. + * Portions Copyright 1997,2002-2003 IBM Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by IBM Corporation for use in + * IBM products and subsequently ported to OpenLDAP Software by + * Steve Omrani. Additional significant contributors include: + * Luke Howard + */ + +#ifndef _PROTO_SLAPI_H +#define _PROTO_SLAPI_H + +LDAP_BEGIN_DECL + +/* slapi_utils.c */ +LDAP_SLAPI_F (LDAPMod **) slapi_int_modifications2ldapmods LDAP_P(( Modifications * )); +LDAP_SLAPI_F (Modifications *) slapi_int_ldapmods2modifications LDAP_P(( Operation *op, LDAPMod ** )); +LDAP_SLAPI_F (int) slapi_int_count_controls LDAP_P(( LDAPControl **ctrls )); +LDAP_SLAPI_F (char **) slapi_get_supported_extended_ops LDAP_P((void)); +LDAP_SLAPI_F (int) slapi_int_access_allowed LDAP_P((Operation *op, Entry *entry, AttributeDescription *desc, struct berval *val, slap_access_t access, AccessControlState *state )); + +/* slapi_ops.c */ +LDAP_SLAPI_F (int) slapi_int_response LDAP_P(( Slapi_Operation *op, SlapReply *rs )); +LDAP_SLAPI_F (void) slapi_int_connection_init_pb LDAP_P(( Slapi_PBlock *pb, ber_tag_t OpType )); +LDAP_SLAPI_F (void) slapi_int_connection_done_pb LDAP_P(( Slapi_PBlock *pb )); + +/* slapi_pblock.c */ +LDAP_SLAPI_F (int) slapi_pblock_delete_param LDAP_P(( Slapi_PBlock *p, int param )); +LDAP_SLAPI_F (void) slapi_pblock_clear LDAP_P(( Slapi_PBlock *pb )); + +LDAP_SLAPI_F (int) slapi_int_pblock_get_first LDAP_P(( Backend *be, Slapi_PBlock **pb )); +LDAP_SLAPI_F (int) slapi_int_pblock_get_next LDAP_P(( Slapi_PBlock **pb )); + +#define PBLOCK_ASSERT_CONN( _pb ) do { \ + assert( (_pb) != NULL ); \ + assert( (_pb)->pb_conn != NULL ); \ + } while (0) + +#define PBLOCK_ASSERT_OP( _pb, _tag ) do { \ + PBLOCK_ASSERT_CONN( _pb ); \ + assert( (_pb)->pb_op != NULL ); \ + assert( (_pb)->pb_rs != NULL ); \ + if ( _tag != 0 ) \ + assert( (_pb)->pb_op->o_tag == (_tag)); \ + } while (0) + +#define PBLOCK_ASSERT_INTOP( _pb, _tag ) do { \ + PBLOCK_ASSERT_OP( _pb, _tag ); \ + assert( (_pb)->pb_intop ); \ + assert( (_pb)->pb_op == (Operation *)pb->pb_conn->c_pending_ops.stqh_first ); \ + } while (0) + +/* plugin.c */ +LDAP_SLAPI_F (int) slapi_int_register_plugin LDAP_P((Backend *be, Slapi_PBlock *pPB)); +LDAP_SLAPI_F (int) slapi_int_call_plugins LDAP_P((Backend *be, int funcType, Slapi_PBlock * pPB)); +LDAP_SLAPI_F (int) slapi_int_get_plugins LDAP_P((Backend *be, int functype, SLAPI_FUNC **ppFuncPtrs)); +LDAP_SLAPI_F (int) slapi_int_register_extop LDAP_P((Backend *pBE, ExtendedOp **opList, Slapi_PBlock *pPB)); +LDAP_SLAPI_F (int) slapi_int_get_extop_plugin LDAP_P((struct berval *reqoid, SLAPI_FUNC *pFuncAddr )); +LDAP_SLAPI_F (struct berval *) slapi_int_get_supported_extop LDAP_P(( int )); +LDAP_SLAPI_F (int) slapi_int_read_config LDAP_P((Backend *be, const char *fname, int lineno, + int argc, char **argv )); +LDAP_SLAPI_F (void) slapi_int_plugin_unparse LDAP_P((Backend *be, BerVarray *out )); +LDAP_SLAPI_F (int) slapi_int_initialize LDAP_P((void)); + +/* slapi_ext.c */ +LDAP_SLAPI_F (int) slapi_int_init_object_extensions LDAP_P((void)); +LDAP_SLAPI_F (int) slapi_int_free_object_extensions LDAP_P((int objecttype, void *object)); +LDAP_SLAPI_F (int) slapi_int_create_object_extensions LDAP_P((int objecttype, void *object)); +LDAP_SLAPI_F (int) slapi_int_clear_object_extensions LDAP_P((int objecttype, void *object)); + +/* slapi_overlay.c */ +LDAP_SLAPI_F (int) slapi_over_is_inst LDAP_P((BackendDB *)); +LDAP_SLAPI_F (int) slapi_over_config LDAP_P((BackendDB *, ConfigReply *)); + +LDAP_END_DECL + +#endif /* _PROTO_SLAPI_H */ + diff --git a/servers/slapd/slapi/slapi.h b/servers/slapd/slapi/slapi.h new file mode 100644 index 0000000..f772743 --- /dev/null +++ b/servers/slapd/slapi/slapi.h @@ -0,0 +1,204 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2002-2021 The OpenLDAP Foundation. + * Portions Copyright 1997,2002-2003 IBM Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by IBM Corporation for use in + * IBM products and subsequently ported to OpenLDAP Software by + * Steve Omrani. Additional significant contributors include: + * Luke Howard + */ + +#ifdef LDAP_SLAPI /* SLAPI is OPTIONAL */ + +#ifndef _SLAPI_H +#define _SLAPI_H + +LDAP_BEGIN_DECL + +/* + * Quick 'n' dirty to make struct slapi_* in slapi-plugin.h opaque + */ +#define slapi_entry Entry +#define slapi_attr Attribute +#define slapi_value berval +#define slapi_valueset berval * +#define slapi_filter Filter + +LDAP_END_DECL + +#include <slapi-plugin.h> + +LDAP_BEGIN_DECL + +#define SLAPI_OVERLAY_NAME "slapi" + +#define SLAPI_OPERATION_PBLOCK(_op) ((_op)->o_callback->sc_private) +#define SLAPI_BACKEND_PBLOCK(_be) ((_be)->be_pb) + +#define SLAPI_OPERATION_EXTENSIONS(_op) ((_op)->o_hdr->oh_extensions) +#define SLAPI_CONNECTION_EXTENSIONS(_conn) ((_conn)->c_extensions) + +#define SLAPI_CONTROL_MANAGEDSAIT_OID LDAP_CONTROL_MANAGEDSAIT +#define SLAPI_CONTROL_SORTEDSEARCH_OID LDAP_CONTROL_SORTREQUEST +#define SLAPI_CONTROL_PAGED_RESULTS_OID LDAP_CONTROL_PAGEDRESULTS + +typedef int (*SLAPI_FUNC)( Slapi_PBlock *pb ); + +typedef struct _slapi_control { + int s_ctrl_num; + char **s_ctrl_oids; + unsigned long *s_ctrl_ops; +} Slapi_Control; + +typedef struct _ExtendedOp { + struct berval ext_oid; + SLAPI_FUNC ext_func; + Backend *ext_be; + struct _ExtendedOp *ext_next; +} ExtendedOp; + +/* Computed attribute support */ +struct _computed_attr_context { + Slapi_PBlock *cac_pb; + Operation *cac_op; + void *cac_private; +}; + +/* for slapi_attr_type_cmp() */ +#define SLAPI_TYPE_CMP_EXACT 0 +#define SLAPI_TYPE_CMP_BASE 1 +#define SLAPI_TYPE_CMP_SUBTYPE 2 + +typedef enum slapi_extension_e { + SLAPI_X_EXT_CONNECTION = 0, + SLAPI_X_EXT_OPERATION = 1, + SLAPI_X_EXT_MAX = 2 +} slapi_extension_t; + +struct slapi_dn { + unsigned char flag; + struct berval dn; + struct berval ndn; +}; + +struct slapi_rdn { + unsigned char flag; + struct berval bv; + LDAPRDN rdn; +}; + +/* + * Was: slapi_pblock.h + */ + +#ifndef NO_PBLOCK_CLASS /* where's this test from? */ + +typedef enum slapi_pblock_class_e { + PBLOCK_CLASS_INVALID = 0, + PBLOCK_CLASS_INTEGER, + PBLOCK_CLASS_LONG_INTEGER, + PBLOCK_CLASS_POINTER, + PBLOCK_CLASS_FUNCTION_POINTER +} slapi_pblock_class_t; + +#define PBLOCK_SUCCESS (0) +#define PBLOCK_ERROR (-1) +#define PBLOCK_MAX_PARAMS 100 + +union slapi_pblock_value { + int pv_integer; + long pv_long_integer; + void *pv_pointer; + int (*pv_function_pointer)(); +}; + +struct slapi_pblock { + ldap_pvt_thread_mutex_t pb_mutex; + int pb_nParams; + int pb_params[PBLOCK_MAX_PARAMS]; + union slapi_pblock_value pb_values[PBLOCK_MAX_PARAMS]; + /* native types */ + Connection *pb_conn; + Operation *pb_op; + SlapReply *pb_rs; + int pb_intop; + char pb_textbuf[ SLAP_TEXT_BUFLEN ]; +}; + +#endif /* !NO_PBLOCK_CLASS */ + +/* + * Was: plugin.h + */ + +#define SLAPI_PLUGIN_IS_POST_FN(x) ((x) >= SLAPI_PLUGIN_POST_BIND_FN && (x) <= SLAPI_PLUGIN_BE_POST_DELETE_FN) + +#define SLAPI_IBM_PBLOCK -3 + +#define SLAPI_ENTRY_PRE_OP 52 +#define SLAPI_ENTRY_POST_OP 53 + +/* This is the spelling in the SunOne 5.2 docs */ +#define SLAPI_RES_CONTROLS SLAPI_RESCONTROLS + +#define SLAPI_ABANDON_MSGID 120 + +#define SLAPI_OPERATION_PARAMETERS 138 + +#define SLAPI_SEQ_TYPE 150 +#define SLAPI_SEQ_ATTRNAME 151 +#define SLAPI_SEQ_VAL 152 + +#define SLAPI_MR_FILTER_ENTRY 170 +#define SLAPI_MR_FILTER_TYPE 171 +#define SLAPI_MR_FILTER_VALUE 172 +#define SLAPI_MR_FILTER_OID 173 +#define SLAPI_MR_FILTER_DNATTRS 174 + +#define SLAPI_LDIF2DB_FILE 180 +#define SLAPI_LDIF2DB_REMOVEDUPVALS 185 + +#define SLAPI_DB2LDIF_PRINTKEY 183 + +#define SLAPI_CHANGENUMBER 197 +#define SLAPI_LOG_OPERATION 198 + +#define SLAPI_DBSIZE 199 + +#define SLAPI_PLUGIN_DB_TEST_FN 227 +#define SLAPI_PLUGIN_DB_NO_ACL 250 + +/* OpenLDAP private parametrs */ +#define SLAPI_PLUGIN_COMPUTE_EVALUATOR_FN 1200 +#define SLAPI_PLUGIN_COMPUTE_SEARCH_REWRITER_FN 1201 + +#define SLAPI_X_CONFIG_ARGV 1400 +#define SLAPI_X_INTOP_FLAGS 1401 +#define SLAPI_X_INTOP_RESULT_CALLBACK 1402 +#define SLAPI_X_INTOP_SEARCH_ENTRY_CALLBACK 1403 +#define SLAPI_X_INTOP_REFERRAL_ENTRY_CALLBACK 1404 +#define SLAPI_X_INTOP_CALLBACK_DATA 1405 +#define SLAPI_X_OLD_RESCONTROLS 1406 + +LDAP_SLAPI_V (ldap_pvt_thread_mutex_t) slapi_hn_mutex; +LDAP_SLAPI_V (ldap_pvt_thread_mutex_t) slapi_time_mutex; +LDAP_SLAPI_V (ldap_pvt_thread_mutex_t) slapi_printmessage_mutex; +LDAP_SLAPI_V (char *) slapi_log_file; +LDAP_SLAPI_V (int) slapi_log_level; + +#include "proto-slapi.h" + +#endif /* _SLAPI_H */ +#endif /* LDAP_SLAPI */ diff --git a/servers/slapd/slapi/slapi_dn.c b/servers/slapd/slapi/slapi_dn.c new file mode 100644 index 0000000..238a48e --- /dev/null +++ b/servers/slapd/slapi/slapi_dn.c @@ -0,0 +1,669 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Luke Howard for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <ac/string.h> +#include <ac/stdarg.h> +#include <ac/ctype.h> +#include <ac/unistd.h> +#include <ldap_pvt.h> + +#include <slap.h> +#include <slapi.h> + +#ifdef LDAP_SLAPI +#define FLAG_DN 0x1 +#define FLAG_NDN 0x2 + +void slapi_sdn_init( Slapi_DN *sdn ) +{ + sdn->flag = 0; + BER_BVZERO( &sdn->dn ); + BER_BVZERO( &sdn->ndn ); +} + +Slapi_DN *slapi_sdn_new( void ) +{ + Slapi_DN *sdn; + + sdn = (Slapi_DN *)slapi_ch_malloc( sizeof(*sdn )); + slapi_sdn_init( sdn ); + + return sdn; +} + +void slapi_sdn_done( Slapi_DN *sdn ) +{ + if ( sdn == NULL ) + return; + + if ( sdn->flag & FLAG_DN ) { + slapi_ch_free_string( &sdn->dn.bv_val ); + } + if ( sdn->flag & FLAG_NDN ) { + slapi_ch_free_string( &sdn->ndn.bv_val ); + } + + slapi_sdn_init( sdn ); +} + +void slapi_sdn_free( Slapi_DN **sdn ) +{ + slapi_sdn_done( *sdn ); + slapi_ch_free( (void **)sdn ); +} + +const char *slapi_sdn_get_dn( const Slapi_DN *sdn ) +{ + if ( !BER_BVISNULL( &sdn->dn ) ) + return sdn->dn.bv_val; + else + return sdn->ndn.bv_val; +} + +const char *slapi_sdn_get_ndn( const Slapi_DN *sdn ) +{ + if ( BER_BVISNULL( &sdn->ndn ) ) { + dnNormalize( 0, NULL, NULL, + (struct berval *)&sdn->dn, (struct berval *)&sdn->ndn, NULL ); + ((Slapi_DN *)sdn)->flag |= FLAG_NDN; + } + + return sdn->ndn.bv_val; +} + +Slapi_DN *slapi_sdn_new_dn_byval( const char *dn ) +{ + Slapi_DN *sdn; + + sdn = slapi_sdn_new(); + return slapi_sdn_set_dn_byval( sdn, dn ); +} + +Slapi_DN *slapi_sdn_new_ndn_byval( const char *ndn ) +{ + Slapi_DN *sdn; + + sdn = slapi_sdn_new(); + return slapi_sdn_set_ndn_byval( sdn, ndn ); +} + +Slapi_DN *slapi_sdn_new_dn_byref( const char *dn ) +{ + Slapi_DN *sdn; + + sdn = slapi_sdn_new(); + return slapi_sdn_set_dn_byref( sdn, dn ); +} + +Slapi_DN *slapi_sdn_new_ndn_byref( const char *ndn ) +{ + Slapi_DN *sdn; + + sdn = slapi_sdn_new(); + return slapi_sdn_set_ndn_byref( sdn, ndn ); +} + +Slapi_DN *slapi_sdn_new_dn_passin( const char *dn ) +{ + Slapi_DN *sdn; + + sdn = slapi_sdn_new(); + return slapi_sdn_set_dn_passin( sdn, dn ); +} + +Slapi_DN *slapi_sdn_set_dn_byval( Slapi_DN *sdn, const char *dn ) +{ + if ( sdn == NULL ) { + return NULL; + } + + slapi_sdn_done( sdn ); + if ( dn != NULL ) { + sdn->dn.bv_val = slapi_ch_strdup( dn ); + sdn->dn.bv_len = strlen( dn ); + } + sdn->flag |= FLAG_DN; + + return sdn; +} + +Slapi_DN *slapi_sdn_set_dn_byref( Slapi_DN *sdn, const char *dn ) +{ + if ( sdn == NULL ) + return NULL; + + slapi_sdn_done( sdn ); + if ( dn != NULL ) { + sdn->dn.bv_val = (char *)dn; + sdn->dn.bv_len = strlen( dn ); + } + + return sdn; +} + +Slapi_DN *slapi_sdn_set_dn_passin( Slapi_DN *sdn, const char *dn ) +{ + if ( sdn == NULL ) + return NULL; + + slapi_sdn_set_dn_byref( sdn, dn ); + sdn->flag |= FLAG_DN; + + return sdn; +} + +Slapi_DN *slapi_sdn_set_ndn_byval( Slapi_DN *sdn, const char *ndn ) +{ + if ( sdn == NULL ) { + return NULL; + } + + slapi_sdn_done( sdn ); + if ( ndn != NULL ) { + sdn->ndn.bv_val = slapi_ch_strdup( ndn ); + sdn->ndn.bv_len = strlen( ndn ); + } + sdn->flag |= FLAG_NDN; + + return sdn; +} + +Slapi_DN *slapi_sdn_set_ndn_byref( Slapi_DN *sdn, const char *ndn ) +{ + if ( sdn == NULL ) + return NULL; + + slapi_sdn_done( sdn ); + if ( ndn != NULL ) { + sdn->ndn.bv_val = (char *)ndn; + sdn->ndn.bv_len = strlen( ndn ); + } + + return sdn; +} + +Slapi_DN *slapi_sdn_set_ndn_passin( Slapi_DN *sdn, const char *ndn ) +{ + if ( sdn == NULL ) + return NULL; + + slapi_sdn_set_ndn_byref( sdn, ndn ); + sdn->flag |= FLAG_NDN; + + return sdn; +} + +void slapi_sdn_get_parent( const Slapi_DN *sdn, Slapi_DN *sdn_parent ) +{ + struct berval parent_dn; + + if ( !(sdn->flag & FLAG_DN) ) { + dnParent( (struct berval *)&sdn->ndn, &parent_dn ); + slapi_sdn_set_ndn_byval( sdn_parent, parent_dn.bv_val ); + } else { + dnParent( (struct berval *)&sdn->dn, &parent_dn ); + slapi_sdn_set_dn_byval( sdn_parent, parent_dn.bv_val ); + } +} + +void slapi_sdn_get_backend_parent( const Slapi_DN *sdn, + Slapi_DN *sdn_parent, + const Slapi_Backend *backend ) +{ + slapi_sdn_get_ndn( sdn ); + + if ( backend == NULL || + be_issuffix( (Slapi_Backend *)backend, (struct berval *)&sdn->ndn ) == 0 ) { + slapi_sdn_get_parent( sdn, sdn_parent ); + } + +} + +Slapi_DN * slapi_sdn_dup( const Slapi_DN *sdn ) +{ + Slapi_DN *new_sdn; + + new_sdn = slapi_sdn_new(); + slapi_sdn_copy( sdn, new_sdn ); + + return new_sdn; +} + +void slapi_sdn_copy( const Slapi_DN *from, Slapi_DN *to ) +{ + slapi_sdn_set_dn_byval( to, from->dn.bv_val ); +} + +int slapi_sdn_compare( const Slapi_DN *sdn1, const Slapi_DN *sdn2 ) +{ + int match = -1; + + slapi_sdn_get_ndn( sdn1 ); + slapi_sdn_get_ndn( sdn2 ); + + dnMatch( &match, 0, slap_schema.si_syn_distinguishedName, NULL, + (struct berval *)&sdn1->ndn, (void *)&sdn2->ndn ); + + return match; +} + +int slapi_sdn_isempty( const Slapi_DN *sdn) +{ + return ( BER_BVISEMPTY( &sdn->dn ) && BER_BVISEMPTY( &sdn->ndn ) ); +} + +int slapi_sdn_issuffix( const Slapi_DN *sdn, const Slapi_DN *suffix_sdn ) +{ + slapi_sdn_get_ndn( sdn ); + slapi_sdn_get_ndn( suffix_sdn ); + + return dnIsSuffix( &sdn->ndn, &suffix_sdn->ndn ); +} + +int slapi_sdn_isparent( const Slapi_DN *parent, const Slapi_DN *child ) +{ + Slapi_DN child_parent; + + slapi_sdn_get_ndn( child ); + + slapi_sdn_init( &child_parent ); + dnParent( (struct berval *)&child->ndn, &child_parent.ndn ); + + return ( slapi_sdn_compare( parent, &child_parent ) == 0 ); +} + +int slapi_sdn_isgrandparent( const Slapi_DN *parent, const Slapi_DN *child ) +{ + Slapi_DN child_grandparent; + + slapi_sdn_get_ndn( child ); + + slapi_sdn_init( &child_grandparent ); + dnParent( (struct berval *)&child->ndn, &child_grandparent.ndn ); + if ( child_grandparent.ndn.bv_len == 0 ) { + return 0; + } + + dnParent( &child_grandparent.ndn, &child_grandparent.ndn ); + + return ( slapi_sdn_compare( parent, &child_grandparent ) == 0 ); +} + +int slapi_sdn_get_ndn_len( const Slapi_DN *sdn ) +{ + slapi_sdn_get_ndn( sdn ); + + return sdn->ndn.bv_len; +} + +int slapi_sdn_scope_test( const Slapi_DN *dn, const Slapi_DN *base, int scope ) +{ + int rc; + + switch ( scope ) { + case LDAP_SCOPE_BASE: + rc = ( slapi_sdn_compare( dn, base ) == 0 ); + break; + case LDAP_SCOPE_ONELEVEL: + rc = slapi_sdn_isparent( base, dn ); + break; + case LDAP_SCOPE_SUBTREE: + rc = slapi_sdn_issuffix( dn, base ); + break; + default: + rc = 0; + break; + } + + return rc; +} + +void slapi_rdn_init( Slapi_RDN *rdn ) +{ + rdn->flag = 0; + BER_BVZERO( &rdn->bv ); + rdn->rdn = NULL; +} + +Slapi_RDN *slapi_rdn_new( void ) +{ + Slapi_RDN *rdn; + + rdn = (Slapi_RDN *)slapi_ch_malloc( sizeof(*rdn )); + slapi_rdn_init( rdn ); + + return rdn; +} + +Slapi_RDN *slapi_rdn_new_dn( const char *dn ) +{ + Slapi_RDN *rdn; + + rdn = slapi_rdn_new(); + slapi_rdn_init_dn( rdn, dn ); + return rdn; +} + +Slapi_RDN *slapi_rdn_new_sdn( const Slapi_DN *sdn ) +{ + return slapi_rdn_new_dn( slapi_sdn_get_dn( sdn ) ); +} + +Slapi_RDN *slapi_rdn_new_rdn( const Slapi_RDN *fromrdn ) +{ + return slapi_rdn_new_dn( fromrdn->bv.bv_val ); +} + +void slapi_rdn_init_dn( Slapi_RDN *rdn, const char *dn ) +{ + slapi_rdn_init( rdn ); + slapi_rdn_set_dn( rdn, dn ); +} + +void slapi_rdn_init_sdn( Slapi_RDN *rdn, const Slapi_DN *sdn ) +{ + slapi_rdn_init( rdn ); + slapi_rdn_set_sdn( rdn, sdn ); +} + +void slapi_rdn_init_rdn( Slapi_RDN *rdn, const Slapi_RDN *fromrdn ) +{ + slapi_rdn_init( rdn ); + slapi_rdn_set_rdn( rdn, fromrdn ); +} + +void slapi_rdn_set_dn( Slapi_RDN *rdn, const char *dn ) +{ + struct berval bv; + + slapi_rdn_done( rdn ); + + BER_BVZERO( &bv ); + + if ( dn != NULL ) { + bv.bv_val = (char *)dn; + bv.bv_len = strlen( dn ); + } + + dnExtractRdn( &bv, &rdn->bv, NULL ); + rdn->flag |= FLAG_DN; +} + +void slapi_rdn_set_sdn( Slapi_RDN *rdn, const Slapi_DN *sdn ) +{ + slapi_rdn_set_dn( rdn, slapi_sdn_get_dn( sdn ) ); +} + +void slapi_rdn_set_rdn( Slapi_RDN *rdn, const Slapi_RDN *fromrdn ) +{ + slapi_rdn_set_dn( rdn, fromrdn->bv.bv_val ); +} + +void slapi_rdn_free( Slapi_RDN **rdn ) +{ + slapi_rdn_done( *rdn ); + slapi_ch_free( (void **)rdn ); +} + +void slapi_rdn_done( Slapi_RDN *rdn ) +{ + if ( rdn->rdn != NULL ) { + ldap_rdnfree( rdn->rdn ); + rdn->rdn = NULL; + } + slapi_ch_free_string( &rdn->bv.bv_val ); + slapi_rdn_init( rdn ); +} + +const char *slapi_rdn_get_rdn( const Slapi_RDN *rdn ) +{ + return rdn->bv.bv_val; +} + +static int slapi_int_rdn_explode( Slapi_RDN *rdn ) +{ + char *next; + + if ( rdn->rdn != NULL ) { + return LDAP_SUCCESS; + } + + return ldap_bv2rdn( &rdn->bv, &rdn->rdn, &next, LDAP_DN_FORMAT_LDAP ); +} + +static int slapi_int_rdn_implode( Slapi_RDN *rdn ) +{ + struct berval bv; + int rc; + + if ( rdn->rdn == NULL ) { + return LDAP_SUCCESS; + } + + rc = ldap_rdn2bv( rdn->rdn, &bv, LDAP_DN_FORMAT_LDAPV3 | LDAP_DN_PRETTY ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + slapi_ch_free_string( &rdn->bv.bv_val ); + rdn->bv = bv; + + return 0; +} + +int slapi_rdn_get_num_components( Slapi_RDN *rdn ) +{ + int i; + + if ( slapi_int_rdn_explode( rdn ) != LDAP_SUCCESS ) + return 0; + + for ( i = 0; rdn->rdn[i] != NULL; i++ ) + ; + + return i; +} + +int slapi_rdn_get_first( Slapi_RDN *rdn, char **type, char **value ) +{ + return slapi_rdn_get_next( rdn, 0, type, value ); +} + +int slapi_rdn_get_next( Slapi_RDN *rdn, int index, char **type, char **value ) +{ + slapi_int_rdn_explode( rdn ); + + if ( rdn->rdn == NULL || rdn->rdn[index] == NULL ) + return -1; + + *type = rdn->rdn[index]->la_attr.bv_val; + *value = rdn->rdn[index]->la_value.bv_val; + + return index + 1; +} + +int slapi_rdn_get_index( Slapi_RDN *rdn, const char *type, const char *value, size_t length ) +{ + int i, match; + struct berval bv; + AttributeDescription *ad = NULL; + const char *text; + + slapi_int_rdn_explode( rdn ); + + if ( slap_str2ad( type, &ad, &text ) != LDAP_SUCCESS ) { + return -1; + } + + bv.bv_val = (char *)value; + bv.bv_len = length; + + for ( i = 0; rdn->rdn[i] != NULL; i++ ) { + if ( !slapi_attr_types_equivalent( ad->ad_cname.bv_val, type )) + continue; + + if ( value_match( &match, ad, ad->ad_type->sat_equality, 0, + &rdn->rdn[i]->la_value, (void *)&bv, &text ) != LDAP_SUCCESS ) + match = -1; + + if ( match == 0 ) + return i; + } + + return -1; +} + +int slapi_rdn_get_index_attr( Slapi_RDN *rdn, const char *type, char **value ) +{ + int i; + + for ( i = 0; rdn->rdn[i] != NULL; i++ ) { + if ( slapi_attr_types_equivalent( rdn->rdn[i]->la_attr.bv_val, type ) ) { + *value = rdn->rdn[i]->la_value.bv_val; + return i; + } + } + + return -1; +} + +int slapi_rdn_contains( Slapi_RDN *rdn, const char *type, const char *value, size_t length ) +{ + return ( slapi_rdn_get_index( rdn, type, value, length ) != -1 ); +} + +int slapi_rdn_contains_attr( Slapi_RDN *rdn, const char *type, char **value ) +{ + return ( slapi_rdn_get_index_attr( rdn, type, value ) != -1 ); +} + +int slapi_rdn_compare( Slapi_RDN *rdn1, Slapi_RDN *rdn2 ) +{ + struct berval nrdn1 = BER_BVNULL; + struct berval nrdn2 = BER_BVNULL; + int match; + + rdnNormalize( 0, NULL, NULL, (struct berval *)&rdn1->bv, &nrdn1, NULL ); + rdnNormalize( 0, NULL, NULL, (struct berval *)&rdn2->bv, &nrdn2, NULL ); + + if ( rdnMatch( &match, 0, NULL, NULL, &nrdn1, (void *)&nrdn2 ) != LDAP_SUCCESS) { + match = -1; + } + + return match; +} + +int slapi_rdn_isempty( const Slapi_RDN *rdn ) +{ + return ( BER_BVISEMPTY( &rdn->bv ) ); +} + +int slapi_rdn_add( Slapi_RDN *rdn, const char *type, const char *value ) +{ + char *s; + size_t len; + + len = strlen(type) + 1 + strlen( value ); + if ( !BER_BVISEMPTY( &rdn->bv ) ) { + len += 1 + rdn->bv.bv_len; + } + + s = slapi_ch_malloc( len + 1 ); + + if ( BER_BVISEMPTY( &rdn->bv ) ) { + snprintf( s, len + 1, "%s=%s", type, value ); + } else { + snprintf( s, len + 1, "%s=%s+%s", type, value, rdn->bv.bv_val ); + } + + slapi_rdn_done( rdn ); + + rdn->bv.bv_len = len; + rdn->bv.bv_val = s; + + return 1; +} + +int slapi_rdn_remove_index( Slapi_RDN *rdn, int atindex ) +{ + int count, i; + + count = slapi_rdn_get_num_components( rdn ); + + if ( atindex < 0 || atindex >= count ) + return 0; + + if ( rdn->rdn == NULL ) + return 0; + + slapi_ch_free_string( &rdn->rdn[atindex]->la_attr.bv_val ); + slapi_ch_free_string( &rdn->rdn[atindex]->la_value.bv_val ); + + for ( i = atindex; i < count; i++ ) { + rdn->rdn[i] = rdn->rdn[i + 1]; + } + + if ( slapi_int_rdn_implode( rdn ) != LDAP_SUCCESS ) + return 0; + + return 1; +} + +int slapi_rdn_remove( Slapi_RDN *rdn, const char *type, const char *value, size_t length ) +{ + int index = slapi_rdn_get_index( rdn, type, value, length ); + + return slapi_rdn_remove_index( rdn, index ); +} + +int slapi_rdn_remove_attr( Slapi_RDN *rdn, const char *type ) +{ + char *value; + int index = slapi_rdn_get_index_attr( rdn, type, &value ); + + return slapi_rdn_remove_index( rdn, index ); +} + +Slapi_DN *slapi_sdn_add_rdn( Slapi_DN *sdn, const Slapi_RDN *rdn ) +{ + struct berval bv; + + build_new_dn( &bv, &sdn->dn, (struct berval *)&rdn->bv, NULL ); + + slapi_sdn_done( sdn ); + sdn->dn = bv; + + return sdn; +} + +Slapi_DN *slapi_sdn_set_parent( Slapi_DN *sdn, const Slapi_DN *parentdn ) +{ + Slapi_RDN rdn; + + slapi_rdn_init_sdn( &rdn, sdn ); + slapi_sdn_set_dn_byref( sdn, slapi_sdn_get_dn( parentdn ) ); + slapi_sdn_add_rdn( sdn, &rdn ); + slapi_rdn_done( &rdn ); + + return sdn; +} + +#endif /* LDAP_SLAPI */ diff --git a/servers/slapd/slapi/slapi_ext.c b/servers/slapd/slapi/slapi_ext.c new file mode 100644 index 0000000..5380a2c --- /dev/null +++ b/servers/slapd/slapi/slapi_ext.c @@ -0,0 +1,349 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* (C) Copyright PADL Software Pty Ltd. 2003 + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that this notice is preserved + * and that due credit is given to PADL Software Pty Ltd. This software + * is provided ``as is'' without express or implied warranty. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Luke Howard for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <ac/string.h> +#include <ac/stdarg.h> +#include <ac/ctype.h> +#include <ac/unistd.h> + +#ifdef LDAP_SLAPI + +#include <slap.h> +#include <slapi.h> + +/* + * Object extensions + * + * We only support two types -- connection and operation extensions. + * Define more types in slapi.h + */ + +/* global state */ +struct slapi_registered_extension_set { + ldap_pvt_thread_mutex_t mutex; + struct slapi_registered_extension { + int active; + int count; + slapi_extension_constructor_fnptr *constructors; + slapi_extension_destructor_fnptr *destructors; + } extensions[SLAPI_X_EXT_MAX]; +} registered_extensions; + +/* per-object state */ +struct slapi_extension_block { + void **extensions; +}; + +static int get_extension_block(int objecttype, void *object, struct slapi_extension_block **eblock, void **parent) +{ + switch ((slapi_extension_t) objecttype) { + case SLAPI_X_EXT_CONNECTION: + *eblock = ((Connection *)object)->c_extensions; + *parent = NULL; + break; + case SLAPI_X_EXT_OPERATION: + *eblock = ((Operation *)object)->o_hdr->oh_extensions; + *parent = ((Operation *)object)->o_conn; + break; + default: + return -1; + break; + } + + if ( *eblock == NULL ) { + return -1; + } + + return 0; +} + +static int map_extension_type(const char *objectname, slapi_extension_t *type) +{ + if ( strcasecmp( objectname, SLAPI_EXT_CONNECTION ) == 0 ) { + *type = SLAPI_X_EXT_CONNECTION; + } else if ( strcasecmp( objectname, SLAPI_EXT_OPERATION ) == 0 ) { + *type = SLAPI_X_EXT_OPERATION; + } else { + return -1; + } + + return 0; +} + +static void new_extension(struct slapi_extension_block *eblock, + int objecttype, void *object, void *parent, + int extensionhandle ) +{ + slapi_extension_constructor_fnptr constructor; + + assert( objecttype < SLAPI_X_EXT_MAX ); + assert( extensionhandle < registered_extensions.extensions[objecttype].count ); + + assert( registered_extensions.extensions[objecttype].constructors != NULL ); + constructor = registered_extensions.extensions[objecttype].constructors[extensionhandle]; + + assert( eblock->extensions[extensionhandle] == NULL ); + + if ( constructor != NULL ) { + eblock->extensions[extensionhandle] = (*constructor)( object, parent ); + } else { + eblock->extensions[extensionhandle] = NULL; + } +} + +static void free_extension(struct slapi_extension_block *eblock, int objecttype, void *object, void *parent, int extensionhandle ) +{ + slapi_extension_destructor_fnptr destructor; + + assert( objecttype < SLAPI_X_EXT_MAX ); + assert( extensionhandle < registered_extensions.extensions[objecttype].count ); + + if ( eblock->extensions[extensionhandle] != NULL ) { + assert( registered_extensions.extensions[objecttype].destructors != NULL ); + destructor = registered_extensions.extensions[objecttype].destructors[extensionhandle]; + if ( destructor != NULL ) { + (*destructor)( eblock->extensions[extensionhandle], object, parent ); + } + eblock->extensions[extensionhandle] = NULL; + } +} + +void *slapi_get_object_extension(int objecttype, void *object, int extensionhandle) +{ + struct slapi_extension_block *eblock; + void *parent; + + if ( get_extension_block( objecttype, object, &eblock, &parent ) != 0 ) { + return NULL; + } + + if ( extensionhandle < registered_extensions.extensions[objecttype].count ) { + return eblock->extensions[extensionhandle]; + } + + return NULL; +} + +void slapi_set_object_extension(int objecttype, void *object, int extensionhandle, void *extension) +{ + struct slapi_extension_block *eblock; + void *parent; + + if ( get_extension_block( objecttype, object, &eblock, &parent ) != 0 ) { + return; + } + + if ( extensionhandle < registered_extensions.extensions[objecttype].count ) { + /* free the old one */ + free_extension( eblock, objecttype, object, parent, extensionhandle ); + + /* constructed by caller */ + eblock->extensions[extensionhandle] = extension; + } +} + +int slapi_register_object_extension( + const char *pluginname, + const char *objectname, + slapi_extension_constructor_fnptr constructor, + slapi_extension_destructor_fnptr destructor, + int *objecttype, + int *extensionhandle) +{ + int rc; + slapi_extension_t type; + struct slapi_registered_extension *re; + + ldap_pvt_thread_mutex_lock( ®istered_extensions.mutex ); + + rc = map_extension_type( objectname, &type ); + if ( rc != 0 ) { + ldap_pvt_thread_mutex_unlock( ®istered_extensions.mutex ); + return rc; + } + + *objecttype = (int)type; + + re = ®istered_extensions.extensions[*objecttype]; + + *extensionhandle = re->count; + + if ( re->active ) { + /* can't add new extensions after objects have been created */ + ldap_pvt_thread_mutex_unlock( ®istered_extensions.mutex ); + return -1; + } + + re->count++; + + if ( re->constructors == NULL ) { + re->constructors = (slapi_extension_constructor_fnptr *)slapi_ch_calloc( re->count, + sizeof( slapi_extension_constructor_fnptr ) ); + } else { + re->constructors = (slapi_extension_constructor_fnptr *)slapi_ch_realloc( (char *)re->constructors, + re->count * sizeof( slapi_extension_constructor_fnptr ) ); + } + re->constructors[*extensionhandle] = constructor; + + if ( re->destructors == NULL ) { + re->destructors = (slapi_extension_destructor_fnptr *)slapi_ch_calloc( re->count, + sizeof( slapi_extension_destructor_fnptr ) ); + } else { + re->destructors = (slapi_extension_destructor_fnptr *)slapi_ch_realloc( (char *)re->destructors, + re->count * sizeof( slapi_extension_destructor_fnptr ) ); + } + re->destructors[*extensionhandle] = destructor; + + ldap_pvt_thread_mutex_unlock( ®istered_extensions.mutex ); + + return 0; +} + +int slapi_int_create_object_extensions(int objecttype, void *object) +{ + int i; + struct slapi_extension_block *eblock; + void **peblock; + void *parent; + + switch ((slapi_extension_t) objecttype) { + case SLAPI_X_EXT_CONNECTION: + peblock = &(((Connection *)object)->c_extensions); + parent = NULL; + break; + case SLAPI_X_EXT_OPERATION: + peblock = &(((Operation *)object)->o_hdr->oh_extensions); + parent = ((Operation *)object)->o_conn; + break; + default: + return -1; + break; + } + + *peblock = NULL; + + ldap_pvt_thread_mutex_lock( ®istered_extensions.mutex ); + if ( registered_extensions.extensions[objecttype].active == 0 ) { + /* + * once we've created some extensions, no new extensions can + * be registered. + */ + registered_extensions.extensions[objecttype].active = 1; + } + ldap_pvt_thread_mutex_unlock( ®istered_extensions.mutex ); + + eblock = (struct slapi_extension_block *)slapi_ch_calloc( 1, sizeof(*eblock) ); + + if ( registered_extensions.extensions[objecttype].count ) { + eblock->extensions = (void **)slapi_ch_calloc( registered_extensions.extensions[objecttype].count, sizeof(void *) ); + for ( i = 0; i < registered_extensions.extensions[objecttype].count; i++ ) { + new_extension( eblock, objecttype, object, parent, i ); + } + } else { + eblock->extensions = NULL; + } + + *peblock = eblock; + + return 0; +} + +int slapi_int_free_object_extensions(int objecttype, void *object) +{ + int i; + struct slapi_extension_block *eblock; + void **peblock; + void *parent; + + switch ((slapi_extension_t) objecttype) { + case SLAPI_X_EXT_CONNECTION: + peblock = &(((Connection *)object)->c_extensions); + parent = NULL; + break; + case SLAPI_X_EXT_OPERATION: + peblock = &(((Operation *)object)->o_hdr->oh_extensions); + parent = ((Operation *)object)->o_conn; + break; + default: + return -1; + break; + } + + eblock = (struct slapi_extension_block *)*peblock; + + if ( eblock->extensions != NULL ) { + for ( i = registered_extensions.extensions[objecttype].count - 1; i >= 0; --i ) { + free_extension( eblock, objecttype, object, parent, i ); + } + + slapi_ch_free( (void **)&eblock->extensions ); + } + + slapi_ch_free( peblock ); + + return 0; +} + +/* for reusable object types */ +int slapi_int_clear_object_extensions(int objecttype, void *object) +{ + int i; + struct slapi_extension_block *eblock; + void *parent; + + if ( get_extension_block( objecttype, object, &eblock, &parent ) != 0 ) { + return -1; + } + + if ( eblock->extensions == NULL ) { + /* no extensions */ + return 0; + } + + for ( i = registered_extensions.extensions[objecttype].count - 1; i >= 0; --i ) { + free_extension( eblock, objecttype, object, parent, i ); + } + + for ( i = 0; i < registered_extensions.extensions[objecttype].count; i++ ) { + new_extension( eblock, objecttype, object, parent, i ); + } + + return 0; +} + +int slapi_int_init_object_extensions(void) +{ + memset( ®istered_extensions, 0, sizeof( registered_extensions ) ); + + if ( ldap_pvt_thread_mutex_init( ®istered_extensions.mutex ) != 0 ) { + return -1; + } + + return 0; +} + +#endif /* LDAP_SLAPI */ diff --git a/servers/slapd/slapi/slapi_ops.c b/servers/slapd/slapi/slapi_ops.c new file mode 100644 index 0000000..831562d --- /dev/null +++ b/servers/slapd/slapi/slapi_ops.c @@ -0,0 +1,956 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2002-2021 The OpenLDAP Foundation. + * Portions Copyright 1997,2002-2003 IBM Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by IBM Corporation for use in + * IBM products and subsequently ported to OpenLDAP Software by + * Steve Omrani. Additional significant contributors include: + * Luke Howard + */ + +#include "portable.h" + +#include <ac/string.h> +#include <ac/stdarg.h> +#include <ac/ctype.h> +#include <ac/unistd.h> + +#include <slap.h> +#include <lber_pvt.h> +#include <slapi.h> + +#ifdef LDAP_SLAPI + +static struct Listener slapi_listener = { + BER_BVC("slapi://"), + BER_BVC("slapi://") +}; + +static LDAPControl ** +slapi_int_dup_controls( LDAPControl **controls ) +{ + LDAPControl **c; + size_t i; + + if ( controls == NULL ) + return NULL; + + for ( i = 0; controls[i] != NULL; i++ ) + ; + + c = (LDAPControl **) slapi_ch_calloc( i + 1, sizeof(LDAPControl *) ); + + for ( i = 0; controls[i] != NULL; i++ ) { + c[i] = slapi_dup_control( controls[i] ); + } + + return c; +} + +static int +slapi_int_result( + Operation *op, + SlapReply *rs ) +{ + Slapi_PBlock *pb = SLAPI_OPERATION_PBLOCK( op ); + plugin_result_callback prc = NULL; + void *callback_data = NULL; + LDAPControl **ctrls = NULL; + + assert( pb != NULL ); + + slapi_pblock_get( pb, SLAPI_X_INTOP_RESULT_CALLBACK, (void **)&prc ); + slapi_pblock_get( pb, SLAPI_X_INTOP_CALLBACK_DATA, &callback_data ); + + /* we need to duplicate controls because they might go out of scope */ + ctrls = slapi_int_dup_controls( rs->sr_ctrls ); + slapi_pblock_set( pb, SLAPI_RESCONTROLS, ctrls ); + + if ( prc != NULL ) { + (*prc)( rs->sr_err, callback_data ); + } + + return rs->sr_err; +} + +static int +slapi_int_search_entry( + Operation *op, + SlapReply *rs ) +{ + Slapi_PBlock *pb = SLAPI_OPERATION_PBLOCK( op ); + plugin_search_entry_callback psec = NULL; + void *callback_data = NULL; + int rc = LDAP_SUCCESS; + + assert( pb != NULL ); + + slapi_pblock_get( pb, SLAPI_X_INTOP_SEARCH_ENTRY_CALLBACK, (void **)&psec ); + slapi_pblock_get( pb, SLAPI_X_INTOP_CALLBACK_DATA, &callback_data ); + + if ( psec != NULL ) { + rc = (*psec)( rs->sr_entry, callback_data ); + } + + return rc; +} + +static int +slapi_int_search_reference( + Operation *op, + SlapReply *rs ) +{ + int i, rc = LDAP_SUCCESS; + plugin_referral_entry_callback prec = NULL; + void *callback_data = NULL; + Slapi_PBlock *pb = SLAPI_OPERATION_PBLOCK( op ); + + assert( pb != NULL ); + + slapi_pblock_get( pb, SLAPI_X_INTOP_REFERRAL_ENTRY_CALLBACK, (void **)&prec ); + slapi_pblock_get( pb, SLAPI_X_INTOP_CALLBACK_DATA, &callback_data ); + + if ( prec != NULL ) { + for ( i = 0; rs->sr_ref[i].bv_val != NULL; i++ ) { + rc = (*prec)( rs->sr_ref[i].bv_val, callback_data ); + if ( rc != LDAP_SUCCESS ) { + break; + } + } + } + + return rc; +} + +int +slapi_int_response( Slapi_Operation *op, SlapReply *rs ) +{ + int rc; + + switch ( rs->sr_type ) { + case REP_RESULT: + rc = slapi_int_result( op, rs ); + break; + case REP_SEARCH: + rc = slapi_int_search_entry( op, rs ); + break; + case REP_SEARCHREF: + rc = slapi_int_search_reference( op, rs ); + break; + default: + rc = LDAP_OTHER; + break; + } + + assert( rc != SLAP_CB_CONTINUE ); /* never try to send a wire response */ + + return rc; +} + +static int +slapi_int_get_ctrls( Slapi_PBlock *pb ) +{ + LDAPControl **c; + int rc = LDAP_SUCCESS; + + if ( pb->pb_op->o_ctrls != NULL ) { + for ( c = pb->pb_op->o_ctrls; *c != NULL; c++ ) { + rc = slap_parse_ctrl( pb->pb_op, pb->pb_rs, *c, &pb->pb_rs->sr_text ); + if ( rc != LDAP_SUCCESS ) + break; + } + } + + return rc; +} + +void +slapi_int_connection_init_pb( Slapi_PBlock *pb, ber_tag_t tag ) +{ + Connection *conn; + Operation *op; + ber_len_t max = sockbuf_max_incoming; + + conn = (Connection *) slapi_ch_calloc( 1, sizeof(Connection) ); + + LDAP_STAILQ_INIT( &conn->c_pending_ops ); + + op = (Operation *) slapi_ch_calloc( 1, sizeof(OperationBuffer) ); + op->o_hdr = &((OperationBuffer *) op)->ob_hdr; + op->o_controls = ((OperationBuffer *) op)->ob_controls; + + op->o_callback = (slap_callback *) slapi_ch_calloc( 1, sizeof(slap_callback) ); + op->o_callback->sc_response = slapi_int_response; + op->o_callback->sc_cleanup = NULL; + op->o_callback->sc_private = pb; + op->o_callback->sc_next = NULL; + + conn->c_pending_ops.stqh_first = op; + + /* connection object authorization information */ + conn->c_authtype = LDAP_AUTH_NONE; + BER_BVZERO( &conn->c_authmech ); + BER_BVZERO( &conn->c_dn ); + BER_BVZERO( &conn->c_ndn ); + + conn->c_listener = &slapi_listener; + ber_dupbv( &conn->c_peer_domain, (struct berval *)&slap_unknown_bv ); + ber_dupbv( &conn->c_peer_name, (struct berval *)&slap_unknown_bv ); + + LDAP_STAILQ_INIT( &conn->c_ops ); + + BER_BVZERO( &conn->c_sasl_bind_mech ); + conn->c_sasl_authctx = NULL; + conn->c_sasl_sockctx = NULL; + conn->c_sasl_extra = NULL; + + conn->c_sb = ber_sockbuf_alloc(); + + ber_sockbuf_ctrl( conn->c_sb, LBER_SB_OPT_SET_MAX_INCOMING, &max ); + + conn->c_currentber = NULL; + + /* should check status of thread calls */ + ldap_pvt_thread_mutex_init( &conn->c_mutex ); + ldap_pvt_thread_mutex_init( &conn->c_write1_mutex ); + ldap_pvt_thread_mutex_init( &conn->c_write2_mutex ); + ldap_pvt_thread_cond_init( &conn->c_write1_cv ); + ldap_pvt_thread_cond_init( &conn->c_write2_cv ); + + ldap_pvt_thread_mutex_lock( &conn->c_mutex ); + + conn->c_n_ops_received = 0; + conn->c_n_ops_executing = 0; + conn->c_n_ops_pending = 0; + conn->c_n_ops_completed = 0; + + conn->c_n_get = 0; + conn->c_n_read = 0; + conn->c_n_write = 0; + + conn->c_protocol = LDAP_VERSION3; + + conn->c_activitytime = conn->c_starttime = slap_get_time(); + + /* + * A real connection ID is required, because syncrepl associates + * pending CSNs with unique ( connection, operation ) tuples. + * Setting a fake connection ID will cause slap_get_commit_csn() + * to return a stale value. + */ + connection_assign_nextid( conn ); + + conn->c_conn_state = 0x01; /* SLAP_C_ACTIVE */ + conn->c_struct_state = 0x02; /* SLAP_C_USED */ + + conn->c_ssf = conn->c_transport_ssf = local_ssf; + conn->c_tls_ssf = 0; + + backend_connection_init( conn ); + + conn->c_send_ldap_result = slap_send_ldap_result; + conn->c_send_search_entry = slap_send_search_entry; + conn->c_send_ldap_extended = slap_send_ldap_extended; + conn->c_send_search_reference = slap_send_search_reference; + + /* operation object */ + op->o_tag = tag; + op->o_protocol = LDAP_VERSION3; + BER_BVZERO( &op->o_authmech ); + op->o_time = slap_get_time(); + op->o_do_not_cache = 1; + op->o_threadctx = ldap_pvt_thread_pool_context(); + op->o_tmpmemctx = NULL; + op->o_tmpmfuncs = &ch_mfuncs; + op->o_conn = conn; + op->o_connid = conn->c_connid; + op->o_bd = frontendDB; + + /* extensions */ + slapi_int_create_object_extensions( SLAPI_X_EXT_OPERATION, op ); + slapi_int_create_object_extensions( SLAPI_X_EXT_CONNECTION, conn ); + + pb->pb_rs = (SlapReply *)slapi_ch_calloc( 1, sizeof(SlapReply) ); + pb->pb_op = op; + pb->pb_conn = conn; + pb->pb_intop = 1; + + ldap_pvt_thread_mutex_unlock( &conn->c_mutex ); +} + +static void +slapi_int_set_operation_dn( Slapi_PBlock *pb ) +{ + Backend *be; + Operation *op = pb->pb_op; + + if ( BER_BVISNULL( &op->o_ndn ) ) { + /* set to root DN */ + be = select_backend( &op->o_req_ndn, 1 ); + if ( be != NULL ) { + ber_dupbv( &op->o_dn, &be->be_rootdn ); + ber_dupbv( &op->o_ndn, &be->be_rootndn ); + } + } +} + +void +slapi_int_connection_done_pb( Slapi_PBlock *pb ) +{ + Connection *conn; + Operation *op; + + PBLOCK_ASSERT_INTOP( pb, 0 ); + + conn = pb->pb_conn; + op = pb->pb_op; + + /* free allocated DNs */ + if ( !BER_BVISNULL( &op->o_dn ) ) + op->o_tmpfree( op->o_dn.bv_val, op->o_tmpmemctx ); + if ( !BER_BVISNULL( &op->o_ndn ) ) + op->o_tmpfree( op->o_ndn.bv_val, op->o_tmpmemctx ); + + if ( !BER_BVISNULL( &op->o_req_dn ) ) + op->o_tmpfree( op->o_req_dn.bv_val, op->o_tmpmemctx ); + if ( !BER_BVISNULL( &op->o_req_ndn ) ) + op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx ); + + switch ( op->o_tag ) { + case LDAP_REQ_MODRDN: + if ( !BER_BVISNULL( &op->orr_newrdn )) + op->o_tmpfree( op->orr_newrdn.bv_val, op->o_tmpmemctx ); + if ( !BER_BVISNULL( &op->orr_nnewrdn )) + op->o_tmpfree( op->orr_nnewrdn.bv_val, op->o_tmpmemctx ); + if ( op->orr_newSup != NULL ) { + assert( !BER_BVISNULL( op->orr_newSup ) ); + op->o_tmpfree( op->orr_newSup->bv_val, op->o_tmpmemctx ); + op->o_tmpfree( op->orr_newSup, op->o_tmpmemctx ); + } + if ( op->orr_nnewSup != NULL ) { + assert( !BER_BVISNULL( op->orr_nnewSup ) ); + op->o_tmpfree( op->orr_nnewSup->bv_val, op->o_tmpmemctx ); + op->o_tmpfree( op->orr_nnewSup, op->o_tmpmemctx ); + } + slap_mods_free( op->orr_modlist, 1 ); + break; + case LDAP_REQ_ADD: + slap_mods_free( op->ora_modlist, 0 ); + break; + case LDAP_REQ_MODIFY: + slap_mods_free( op->orm_modlist, 1 ); + break; + case LDAP_REQ_SEARCH: + if ( op->ors_attrs != NULL ) { + op->o_tmpfree( op->ors_attrs, op->o_tmpmemctx ); + op->ors_attrs = NULL; + } + break; + default: + break; + } + + slapi_ch_free_string( &conn->c_authmech.bv_val ); + slapi_ch_free_string( &conn->c_dn.bv_val ); + slapi_ch_free_string( &conn->c_ndn.bv_val ); + slapi_ch_free_string( &conn->c_peer_domain.bv_val ); + slapi_ch_free_string( &conn->c_peer_name.bv_val ); + + if ( conn->c_sb != NULL ) { + ber_sockbuf_free( conn->c_sb ); + } + + slapi_int_free_object_extensions( SLAPI_X_EXT_OPERATION, op ); + slapi_int_free_object_extensions( SLAPI_X_EXT_CONNECTION, conn ); + + slapi_ch_free( (void **)&pb->pb_op->o_callback ); + slapi_ch_free( (void **)&pb->pb_op ); + slapi_ch_free( (void **)&pb->pb_conn ); + slapi_ch_free( (void **)&pb->pb_rs ); +} + +static int +slapi_int_func_internal_pb( Slapi_PBlock *pb, slap_operation_t which ) +{ + BI_op_bind **func; + SlapReply *rs = pb->pb_rs; + int rc; + + PBLOCK_ASSERT_INTOP( pb, 0 ); + + rc = slapi_int_get_ctrls( pb ); + if ( rc != LDAP_SUCCESS ) { + rs->sr_err = rc; + return rc; + } + + pb->pb_op->o_bd = frontendDB; + func = &frontendDB->be_bind; + + return func[which]( pb->pb_op, pb->pb_rs ); +} + +int +slapi_delete_internal_pb( Slapi_PBlock *pb ) +{ + if ( pb == NULL ) { + return -1; + } + + PBLOCK_ASSERT_INTOP( pb, LDAP_REQ_DELETE ); + + slapi_int_func_internal_pb( pb, op_delete ); + + return 0; +} + +int +slapi_add_internal_pb( Slapi_PBlock *pb ) +{ + SlapReply *rs; + Slapi_Entry *entry_orig = NULL; + OpExtraDB oex; + int rc; + + if ( pb == NULL ) { + return -1; + } + + PBLOCK_ASSERT_INTOP( pb, LDAP_REQ_ADD ); + + rs = pb->pb_rs; + + entry_orig = pb->pb_op->ora_e; + pb->pb_op->ora_e = NULL; + + /* + * The caller can specify a new entry, or a target DN and set + * of modifications, but not both. + */ + if ( entry_orig != NULL ) { + if ( pb->pb_op->ora_modlist != NULL || !BER_BVISNULL( &pb->pb_op->o_req_ndn )) { + rs->sr_err = LDAP_PARAM_ERROR; + goto cleanup; + } + + assert( BER_BVISNULL( &pb->pb_op->o_req_dn ) ); /* shouldn't get set */ + ber_dupbv( &pb->pb_op->o_req_dn, &entry_orig->e_name ); + ber_dupbv( &pb->pb_op->o_req_ndn, &entry_orig->e_nname ); + } else if ( pb->pb_op->ora_modlist == NULL || BER_BVISNULL( &pb->pb_op->o_req_ndn )) { + rs->sr_err = LDAP_PARAM_ERROR; + goto cleanup; + } + + pb->pb_op->ora_e = (Entry *)slapi_ch_calloc( 1, sizeof(Entry) ); + ber_dupbv( &pb->pb_op->ora_e->e_name, &pb->pb_op->o_req_dn ); + ber_dupbv( &pb->pb_op->ora_e->e_nname, &pb->pb_op->o_req_ndn ); + + if ( entry_orig != NULL ) { + assert( pb->pb_op->ora_modlist == NULL ); + + rs->sr_err = slap_entry2mods( entry_orig, &pb->pb_op->ora_modlist, + &rs->sr_text, pb->pb_textbuf, sizeof( pb->pb_textbuf ) ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto cleanup; + } + } else { + assert( pb->pb_op->ora_modlist != NULL ); + } + + rs->sr_err = slap_mods_check( pb->pb_op, pb->pb_op->ora_modlist, &rs->sr_text, + pb->pb_textbuf, sizeof( pb->pb_textbuf ), NULL ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto cleanup; + } + + /* Duplicate the values, because we may call slapi_entry_free() */ + rs->sr_err = slap_mods2entry( pb->pb_op->ora_modlist, &pb->pb_op->ora_e, + 1, 0, &rs->sr_text, pb->pb_textbuf, sizeof( pb->pb_textbuf ) ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto cleanup; + } + + oex.oe.oe_key = (void *)do_add; + oex.oe_db = NULL; + LDAP_SLIST_INSERT_HEAD(&pb->pb_op->o_extra, &oex.oe, oe_next); + rc = slapi_int_func_internal_pb( pb, op_add ); + LDAP_SLIST_REMOVE(&pb->pb_op->o_extra, &oex.oe, OpExtra, oe_next); + + if ( !rc ) { + if ( pb->pb_op->ora_e != NULL && oex.oe_db != NULL ) { + BackendDB *bd = pb->pb_op->o_bd; + + pb->pb_op->o_bd = oex.oe_db; + be_entry_release_w( pb->pb_op, pb->pb_op->ora_e ); + pb->pb_op->ora_e = NULL; + pb->pb_op->o_bd = bd; + } + } + +cleanup: + + if ( pb->pb_op->ora_e != NULL ) { + slapi_entry_free( pb->pb_op->ora_e ); + pb->pb_op->ora_e = NULL; + } + if ( entry_orig != NULL ) { + pb->pb_op->ora_e = entry_orig; + slap_mods_free( pb->pb_op->ora_modlist, 1 ); + pb->pb_op->ora_modlist = NULL; + } + + return 0; +} + +int +slapi_modrdn_internal_pb( Slapi_PBlock *pb ) +{ + if ( pb == NULL ) { + return -1; + } + + PBLOCK_ASSERT_INTOP( pb, LDAP_REQ_MODRDN ); + + if ( BER_BVISEMPTY( &pb->pb_op->o_req_ndn ) ) { + pb->pb_rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + goto cleanup; + } + + slapi_int_func_internal_pb( pb, op_modrdn ); + +cleanup: + + return 0; +} + +int +slapi_modify_internal_pb( Slapi_PBlock *pb ) +{ + SlapReply *rs; + + if ( pb == NULL ) { + return -1; + } + + PBLOCK_ASSERT_INTOP( pb, LDAP_REQ_MODIFY ); + + rs = pb->pb_rs; + + if ( pb->pb_op->orm_modlist == NULL ) { + rs->sr_err = LDAP_PARAM_ERROR; + goto cleanup; + } + + if ( BER_BVISEMPTY( &pb->pb_op->o_req_ndn ) ) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + goto cleanup; + } + + rs->sr_err = slap_mods_check( pb->pb_op, pb->pb_op->orm_modlist, + &rs->sr_text, pb->pb_textbuf, sizeof( pb->pb_textbuf ), NULL ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto cleanup; + } + + slapi_int_func_internal_pb( pb, op_modify ); + +cleanup: + + return 0; +} + +static int +slapi_int_search_entry_callback( Slapi_Entry *entry, void *callback_data ) +{ + int nentries = 0, i = 0; + Slapi_Entry **head = NULL, **tp; + Slapi_PBlock *pb = (Slapi_PBlock *)callback_data; + + PBLOCK_ASSERT_INTOP( pb, LDAP_REQ_SEARCH ); + + entry = slapi_entry_dup( entry ); + if ( entry == NULL ) { + return LDAP_NO_MEMORY; + } + + slapi_pblock_get( pb, SLAPI_NENTRIES, &nentries ); + slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &head ); + + i = nentries + 1; + if ( nentries == 0 ) { + tp = (Slapi_Entry **)slapi_ch_malloc( 2 * sizeof(Slapi_Entry *) ); + if ( tp == NULL ) { + slapi_entry_free( entry ); + return LDAP_NO_MEMORY; + } + + tp[0] = entry; + } else { + tp = (Slapi_Entry **)slapi_ch_realloc( (char *)head, + sizeof(Slapi_Entry *) * ( i + 1 ) ); + if ( tp == NULL ) { + slapi_entry_free( entry ); + return LDAP_NO_MEMORY; + } + tp[i - 1] = entry; + } + tp[i] = NULL; + + slapi_pblock_set( pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, (void *)tp ); + slapi_pblock_set( pb, SLAPI_NENTRIES, (void *)&i ); + + return LDAP_SUCCESS; +} + +int +slapi_search_internal_pb( Slapi_PBlock *pb ) +{ + return slapi_search_internal_callback_pb( pb, + (void *)pb, + NULL, + slapi_int_search_entry_callback, + NULL ); +} + +int +slapi_search_internal_callback_pb( Slapi_PBlock *pb, + void *callback_data, + plugin_result_callback prc, + plugin_search_entry_callback psec, + plugin_referral_entry_callback prec ) +{ + int free_filter = 0; + SlapReply *rs; + + if ( pb == NULL ) { + return -1; + } + + PBLOCK_ASSERT_INTOP( pb, LDAP_REQ_SEARCH ); + + rs = pb->pb_rs; + + /* search callback and arguments */ + slapi_pblock_set( pb, SLAPI_X_INTOP_RESULT_CALLBACK, (void *)prc ); + slapi_pblock_set( pb, SLAPI_X_INTOP_SEARCH_ENTRY_CALLBACK, (void *)psec ); + slapi_pblock_set( pb, SLAPI_X_INTOP_REFERRAL_ENTRY_CALLBACK, (void *)prec ); + slapi_pblock_set( pb, SLAPI_X_INTOP_CALLBACK_DATA, (void *)callback_data ); + + if ( BER_BVISEMPTY( &pb->pb_op->ors_filterstr )) { + rs->sr_err = LDAP_PARAM_ERROR; + goto cleanup; + } + + if ( pb->pb_op->ors_filter == NULL ) { + pb->pb_op->ors_filter = slapi_str2filter( pb->pb_op->ors_filterstr.bv_val ); + if ( pb->pb_op->ors_filter == NULL ) { + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto cleanup; + } + + free_filter = 1; + } + + slapi_int_func_internal_pb( pb, op_search ); + +cleanup: + if ( free_filter ) { + slapi_filter_free( pb->pb_op->ors_filter, 1 ); + pb->pb_op->ors_filter = NULL; + } + + slapi_pblock_delete_param( pb, SLAPI_X_INTOP_RESULT_CALLBACK ); + slapi_pblock_delete_param( pb, SLAPI_X_INTOP_SEARCH_ENTRY_CALLBACK ); + slapi_pblock_delete_param( pb, SLAPI_X_INTOP_REFERRAL_ENTRY_CALLBACK ); + slapi_pblock_delete_param( pb, SLAPI_X_INTOP_CALLBACK_DATA ); + + return 0; +} + +/* Wrappers for old API */ + +void +slapi_search_internal_set_pb( Slapi_PBlock *pb, + const char *base, + int scope, + const char *filter, + char **attrs, + int attrsonly, + LDAPControl **controls, + const char *uniqueid, + Slapi_ComponentId *plugin_identity, + int operation_flags ) +{ + int no_limit = SLAP_NO_LIMIT; + int deref = LDAP_DEREF_NEVER; + + slapi_int_connection_init_pb( pb, LDAP_REQ_SEARCH ); + slapi_pblock_set( pb, SLAPI_SEARCH_TARGET, (void *)base ); + slapi_pblock_set( pb, SLAPI_SEARCH_SCOPE, (void *)&scope ); + slapi_pblock_set( pb, SLAPI_SEARCH_FILTER, (void *)0 ); + slapi_pblock_set( pb, SLAPI_SEARCH_STRFILTER, (void *)filter ); + slapi_pblock_set( pb, SLAPI_SEARCH_ATTRS, (void *)attrs ); + slapi_pblock_set( pb, SLAPI_SEARCH_ATTRSONLY, (void *)&attrsonly ); + slapi_pblock_set( pb, SLAPI_REQCONTROLS, (void *)controls ); + slapi_pblock_set( pb, SLAPI_TARGET_UNIQUEID, (void *)uniqueid ); + slapi_pblock_set( pb, SLAPI_PLUGIN_IDENTITY, (void *)plugin_identity ); + slapi_pblock_set( pb, SLAPI_X_INTOP_FLAGS, (void *)&operation_flags ); + slapi_pblock_set( pb, SLAPI_SEARCH_DEREF, (void *)&deref ); + slapi_pblock_set( pb, SLAPI_SEARCH_SIZELIMIT, (void *)&no_limit ); + slapi_pblock_set( pb, SLAPI_SEARCH_TIMELIMIT, (void *)&no_limit ); + + slapi_int_set_operation_dn( pb ); +} + +Slapi_PBlock * +slapi_search_internal( + char *ldn, + int scope, + char *filStr, + LDAPControl **controls, + char **attrs, + int attrsonly ) +{ + Slapi_PBlock *pb; + + pb = slapi_pblock_new(); + + slapi_search_internal_set_pb( pb, ldn, scope, filStr, + attrs, attrsonly, + controls, NULL, NULL, 0 ); + + slapi_search_internal_pb( pb ); + + return pb; +} + +void +slapi_modify_internal_set_pb( Slapi_PBlock *pb, + const char *dn, + LDAPMod **mods, + LDAPControl **controls, + const char *uniqueid, + Slapi_ComponentId *plugin_identity, + int operation_flags ) +{ + slapi_int_connection_init_pb( pb, LDAP_REQ_MODIFY ); + slapi_pblock_set( pb, SLAPI_MODIFY_TARGET, (void *)dn ); + slapi_pblock_set( pb, SLAPI_MODIFY_MODS, (void *)mods ); + slapi_pblock_set( pb, SLAPI_REQCONTROLS, (void *)controls ); + slapi_pblock_set( pb, SLAPI_TARGET_UNIQUEID, (void *)uniqueid ); + slapi_pblock_set( pb, SLAPI_PLUGIN_IDENTITY, (void *)plugin_identity ); + slapi_pblock_set( pb, SLAPI_X_INTOP_FLAGS, (void *)&operation_flags ); + slapi_int_set_operation_dn( pb ); +} + +/* Function : slapi_modify_internal + * + * Description: Plugin functions call this routine to modify an entry + * in the backend directly + * Return values : LDAP_SUCCESS + * LDAP_PARAM_ERROR + * LDAP_NO_MEMORY + * LDAP_OTHER + * LDAP_UNWILLING_TO_PERFORM +*/ +Slapi_PBlock * +slapi_modify_internal( + char *ldn, + LDAPMod **mods, + LDAPControl **controls, + int log_change ) +{ + Slapi_PBlock *pb; + + pb = slapi_pblock_new(); + + slapi_modify_internal_set_pb( pb, ldn, mods, controls, NULL, NULL, 0 ); + slapi_pblock_set( pb, SLAPI_LOG_OPERATION, (void *)&log_change ); + slapi_modify_internal_pb( pb ); + + return pb; +} + +int +slapi_add_internal_set_pb( Slapi_PBlock *pb, + const char *dn, + LDAPMod **attrs, + LDAPControl **controls, + Slapi_ComponentId *plugin_identity, + int operation_flags ) +{ + slapi_int_connection_init_pb( pb, LDAP_REQ_ADD ); + slapi_pblock_set( pb, SLAPI_ADD_TARGET, (void *)dn ); + slapi_pblock_set( pb, SLAPI_MODIFY_MODS, (void *)attrs ); + slapi_pblock_set( pb, SLAPI_REQCONTROLS, (void *)controls ); + slapi_pblock_set( pb, SLAPI_PLUGIN_IDENTITY, (void *)plugin_identity ); + slapi_pblock_set( pb, SLAPI_X_INTOP_FLAGS, (void *)&operation_flags ); + slapi_int_set_operation_dn( pb ); + + return 0; +} + +Slapi_PBlock * +slapi_add_internal( + char * dn, + LDAPMod **attrs, + LDAPControl **controls, + int log_change ) +{ + Slapi_PBlock *pb; + + pb = slapi_pblock_new(); + + slapi_add_internal_set_pb( pb, dn, attrs, controls, NULL, 0); + slapi_pblock_set( pb, SLAPI_LOG_OPERATION, (void *)&log_change ); + slapi_add_internal_pb( pb ); + + return pb; +} + +void +slapi_add_entry_internal_set_pb( Slapi_PBlock *pb, + Slapi_Entry *e, + LDAPControl **controls, + Slapi_ComponentId *plugin_identity, + int operation_flags ) +{ + slapi_int_connection_init_pb( pb, LDAP_REQ_ADD ); + slapi_pblock_set( pb, SLAPI_ADD_ENTRY, (void *)e ); + slapi_pblock_set( pb, SLAPI_REQCONTROLS, (void *)controls ); + slapi_pblock_set( pb, SLAPI_PLUGIN_IDENTITY, (void *)plugin_identity ); + slapi_pblock_set( pb, SLAPI_X_INTOP_FLAGS, (void *)&operation_flags ); + slapi_int_set_operation_dn( pb ); +} + +Slapi_PBlock * +slapi_add_entry_internal( + Slapi_Entry *e, + LDAPControl **controls, + int log_change ) +{ + Slapi_PBlock *pb; + + pb = slapi_pblock_new(); + + slapi_add_entry_internal_set_pb( pb, e, controls, NULL, 0 ); + slapi_pblock_set( pb, SLAPI_LOG_OPERATION, (void *)&log_change ); + slapi_add_internal_pb( pb ); + + return pb; +} + +void +slapi_rename_internal_set_pb( Slapi_PBlock *pb, + const char *olddn, + const char *newrdn, + const char *newsuperior, + int deloldrdn, + LDAPControl **controls, + const char *uniqueid, + Slapi_ComponentId *plugin_identity, + int operation_flags ) +{ + slapi_int_connection_init_pb( pb, LDAP_REQ_MODRDN ); + slapi_pblock_set( pb, SLAPI_MODRDN_TARGET, (void *)olddn ); + slapi_pblock_set( pb, SLAPI_MODRDN_NEWRDN, (void *)newrdn ); + slapi_pblock_set( pb, SLAPI_MODRDN_NEWSUPERIOR, (void *)newsuperior ); + slapi_pblock_set( pb, SLAPI_MODRDN_DELOLDRDN, (void *)&deloldrdn ); + slapi_pblock_set( pb, SLAPI_REQCONTROLS, (void *)controls ); + slapi_pblock_set( pb, SLAPI_TARGET_UNIQUEID, (void *)uniqueid ); + slapi_pblock_set( pb, SLAPI_PLUGIN_IDENTITY, (void *)plugin_identity ); + slapi_pblock_set( pb, SLAPI_X_INTOP_FLAGS, (void *)&operation_flags ); + slap_modrdn2mods( pb->pb_op, pb->pb_rs ); + slapi_int_set_operation_dn( pb ); +} + +/* Function : slapi_modrdn_internal + * + * Description : Plugin functions call this routine to modify the rdn + * of an entry in the backend directly + * Return values : LDAP_SUCCESS + * LDAP_PARAM_ERROR + * LDAP_NO_MEMORY + * LDAP_OTHER + * LDAP_UNWILLING_TO_PERFORM + * + * NOTE: This function does not support the "newSuperior" option from LDAP V3. + */ +Slapi_PBlock * +slapi_modrdn_internal( + char *olddn, + char *lnewrdn, + int deloldrdn, + LDAPControl **controls, + int log_change ) +{ + Slapi_PBlock *pb; + + pb = slapi_pblock_new (); + + slapi_rename_internal_set_pb( pb, olddn, lnewrdn, NULL, + deloldrdn, controls, NULL, NULL, 0 ); + slapi_pblock_set( pb, SLAPI_LOG_OPERATION, (void *)&log_change ); + slapi_modrdn_internal_pb( pb ); + + return pb; +} + +void +slapi_delete_internal_set_pb( Slapi_PBlock *pb, + const char *dn, + LDAPControl **controls, + const char *uniqueid, + Slapi_ComponentId *plugin_identity, + int operation_flags ) +{ + slapi_int_connection_init_pb( pb, LDAP_REQ_DELETE ); + slapi_pblock_set( pb, SLAPI_TARGET_DN, (void *)dn ); + slapi_pblock_set( pb, SLAPI_REQCONTROLS, (void *)controls ); + slapi_pblock_set( pb, SLAPI_TARGET_UNIQUEID, (void *)uniqueid ); + slapi_pblock_set( pb, SLAPI_PLUGIN_IDENTITY, (void *)plugin_identity ); + slapi_pblock_set( pb, SLAPI_X_INTOP_FLAGS, (void *)&operation_flags ); + slapi_int_set_operation_dn( pb ); +} + +/* Function : slapi_delete_internal + * + * Description : Plugin functions call this routine to delete an entry + * in the backend directly + * Return values : LDAP_SUCCESS + * LDAP_PARAM_ERROR + * LDAP_NO_MEMORY + * LDAP_OTHER + * LDAP_UNWILLING_TO_PERFORM +*/ +Slapi_PBlock * +slapi_delete_internal( + char *ldn, + LDAPControl **controls, + int log_change ) +{ + Slapi_PBlock *pb; + + pb = slapi_pblock_new(); + + slapi_delete_internal_set_pb( pb, ldn, controls, NULL, NULL, 0 ); + slapi_pblock_set( pb, SLAPI_LOG_OPERATION, (void *)&log_change ); + slapi_delete_internal_pb( pb ); + + return pb; +} + +#endif /* LDAP_SLAPI */ + diff --git a/servers/slapd/slapi/slapi_overlay.c b/servers/slapd/slapi/slapi_overlay.c new file mode 100644 index 0000000..88e7f1c --- /dev/null +++ b/servers/slapd/slapi/slapi_overlay.c @@ -0,0 +1,952 @@ +/* slapi_overlay.c - SLAPI overlay */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2001-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Luke Howard for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "slapi.h" +#include "config.h" + +#ifdef LDAP_SLAPI + +static slap_overinst slapi; +static int slapi_over_initialized = 0; + +static int slapi_over_response( Operation *op, SlapReply *rs ); +static int slapi_over_cleanup( Operation *op, SlapReply *rs ); + +static Slapi_PBlock * +slapi_over_pblock_new( Operation *op, SlapReply *rs ) +{ + Slapi_PBlock *pb; + + pb = slapi_pblock_new(); + pb->pb_op = op; + pb->pb_conn = op->o_conn; + pb->pb_rs = rs; + pb->pb_intop = 0; + + PBLOCK_ASSERT_OP( pb, op->o_tag ); + + return pb; +} + +static int +slapi_op_internal_p( Operation *op, SlapReply *rs, slap_callback *cb ) +{ + int internal_op = 0; + Slapi_PBlock *pb = NULL; + slap_callback *pcb; + + /* + * Abstraction violating check for SLAPI internal operations + * allows pblock to remain consistent when invoking internal + * op plugins + */ + for ( pcb = op->o_callback; pcb != NULL; pcb = pcb->sc_next ) { + if ( pcb->sc_response == slapi_int_response ) { + pb = (Slapi_PBlock *)pcb->sc_private; + PBLOCK_ASSERT_INTOP( pb, 0 ); + internal_op = 1; + break; + } + } + + if ( cb != NULL ) { + if ( pb == NULL ) { + pb = slapi_over_pblock_new( op, rs ); + } + + cb->sc_response = slapi_over_response; + cb->sc_cleanup = slapi_over_cleanup; + cb->sc_private = pb; + cb->sc_writewait = 0; + cb->sc_next = op->o_callback; + op->o_callback = cb; + } + + return internal_op; +} + +static int +slapi_over_compute_output( + computed_attr_context *c, + Slapi_Attr *attribute, + Slapi_Entry *entry +) +{ + Attribute **a; + AttributeDescription *desc; + SlapReply *rs; + + if ( c == NULL || attribute == NULL || entry == NULL ) { + return 0; + } + + rs = (SlapReply *)c->cac_private; + + assert( rs->sr_entry == entry ); + + desc = attribute->a_desc; + + if ( rs->sr_attrs == NULL ) { + /* All attrs request, skip operational attributes */ + if ( is_at_operational( desc->ad_type ) ) { + return 0; + } + } else { + /* Specific attributes requested */ + if ( is_at_operational( desc->ad_type ) ) { + if ( !SLAP_OPATTRS( rs->sr_attr_flags ) && + !ad_inlist( desc, rs->sr_attrs ) ) { + return 0; + } + } else { + if ( !SLAP_USERATTRS( rs->sr_attr_flags ) && + !ad_inlist( desc, rs->sr_attrs ) ) { + return 0; + } + } + } + + /* XXX perhaps we should check for existing attributes and merge */ + for ( a = &rs->sr_operational_attrs; *a != NULL; a = &(*a)->a_next ) + ; + + *a = slapi_attr_dup( attribute ); + + return 0; +} + +static int +slapi_over_aux_operational( Operation *op, SlapReply *rs ) +{ + /* Support for computed attribute plugins */ + computed_attr_context ctx; + AttributeName *anp; + + if ( slapi_op_internal_p( op, rs, NULL ) ) { + return SLAP_CB_CONTINUE; + } + + ctx.cac_pb = slapi_over_pblock_new( op, rs ); + ctx.cac_op = op; + ctx.cac_private = rs; + + if ( rs->sr_entry != NULL ) { + /* + * For each client requested attribute, call the plugins. + */ + if ( rs->sr_attrs != NULL ) { + for ( anp = rs->sr_attrs; anp->an_name.bv_val != NULL; anp++ ) { + if ( compute_evaluator( &ctx, anp->an_name.bv_val, + rs->sr_entry, slapi_over_compute_output ) == 1 ) { + break; + } + } + } else { + /* + * Technically we shouldn't be returning operational attributes + * when the user requested only user attributes. We'll let the + * plugin decide whether to be naughty or not. + */ + compute_evaluator( &ctx, "*", rs->sr_entry, slapi_over_compute_output ); + } + } + + slapi_pblock_destroy( ctx.cac_pb ); + + return SLAP_CB_CONTINUE; +} + +/* + * We need this function to call frontendDB (global) plugins before + * database plugins, if we are invoked by a slap_callback. + */ +static int +slapi_over_call_plugins( Slapi_PBlock *pb, int type ) +{ + int rc = 1; /* means no plugins called */ + Operation *op; + + PBLOCK_ASSERT_OP( pb, 0 ); + op = pb->pb_op; + + if ( !be_match( op->o_bd, frontendDB ) ) { + rc = slapi_int_call_plugins( frontendDB, type, pb ); + } + if ( rc >= 0 ) { + rc = slapi_int_call_plugins( op->o_bd, type, pb ); + } + + return rc; +} + +static int +slapi_over_search( Operation *op, SlapReply *rs, int type ) +{ + int rc; + Slapi_PBlock *pb; + + assert( rs->sr_type == REP_SEARCH || rs->sr_type == REP_SEARCHREF ); + + /* create a new pblock to not trample on result controls */ + pb = slapi_over_pblock_new( op, rs ); + + rc = slapi_over_call_plugins( pb, type ); + if ( rc >= 0 ) /* 1 means no plugins called */ + rc = SLAP_CB_CONTINUE; + else + rc = LDAP_SUCCESS; /* confusing: don't abort, but don't send */ + + slapi_pblock_destroy(pb); + + return rc; +} + +/* + * Call pre- and post-result plugins + */ +static int +slapi_over_result( Operation *op, SlapReply *rs, int type ) +{ + Slapi_PBlock *pb = SLAPI_OPERATION_PBLOCK( op ); + + assert( rs->sr_type == REP_RESULT || rs->sr_type == REP_SASL || rs->sr_type == REP_EXTENDED ); + + slapi_over_call_plugins( pb, type ); + + return SLAP_CB_CONTINUE; +} + + +static int +slapi_op_bind_callback( Operation *op, SlapReply *rs, int prc ) +{ + switch ( prc ) { + case SLAPI_BIND_SUCCESS: + /* Continue with backend processing */ + break; + case SLAPI_BIND_FAIL: + /* Failure, frontend (that's us) sends result */ + rs->sr_err = LDAP_INVALID_CREDENTIALS; + send_ldap_result( op, rs ); + return rs->sr_err; + break; + case SLAPI_BIND_ANONYMOUS: /* undocumented */ + default: /* plugin sent result or no plugins called */ + BER_BVZERO( &op->orb_edn ); + + if ( rs->sr_err == LDAP_SUCCESS ) { + /* + * Plugin will have called slapi_pblock_set(LDAP_CONN_DN) which + * will have set conn->c_dn and conn->c_ndn + */ + if ( BER_BVISNULL( &op->o_conn->c_ndn ) && prc == 1 ) { + /* No plugins were called; continue processing */ + return LDAP_SUCCESS; + } + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + if ( !BER_BVISEMPTY( &op->o_conn->c_ndn ) ) { + ber_len_t max = sockbuf_max_incoming_auth; + ber_sockbuf_ctrl( op->o_conn->c_sb, + LBER_SB_OPT_SET_MAX_INCOMING, &max ); + } + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + + /* log authorization identity */ + Statslog( LDAP_DEBUG_STATS, + "%s BIND dn=\"%s\" mech=%s (SLAPI) ssf=0\n", + op->o_log_prefix, + BER_BVISNULL( &op->o_conn->c_dn ) + ? "<empty>" : op->o_conn->c_dn.bv_val, + BER_BVISNULL( &op->orb_mech ) + ? "<empty>" : op->orb_mech.bv_val, 0, 0 ); + + return -1; + } + break; + } + + return rs->sr_err; +} + +static int +slapi_op_search_callback( Operation *op, SlapReply *rs, int prc ) +{ + Slapi_PBlock *pb = SLAPI_OPERATION_PBLOCK( op ); + Filter *f = op->ors_filter; + + /* check preoperation result code */ + if ( prc < 0 ) { + return rs->sr_err; + } + + rs->sr_err = LDAP_SUCCESS; + + if ( pb->pb_intop == 0 && + slapi_int_call_plugins( op->o_bd, SLAPI_PLUGIN_COMPUTE_SEARCH_REWRITER_FN, pb ) == 0 ) { + /* + * The plugin can set the SLAPI_SEARCH_FILTER. + * SLAPI_SEARCH_STRFILER is not normative. + */ + if (f != op->ors_filter) { + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + filter2bv_x( op, op->ors_filter, &op->ors_filterstr ); + } + } + + return LDAP_SUCCESS; +} + +struct slapi_op_info { + int soi_preop; /* preoperation plugin parameter */ + int soi_postop; /* postoperation plugin parameter */ + int soi_internal_preop; /* internal preoperation plugin parameter */ + int soi_internal_postop; /* internal postoperation plugin parameter */ + int (*soi_callback)(Operation *, SlapReply *, int); /* preoperation result handler */ +} slapi_op_dispatch_table[] = { + { + SLAPI_PLUGIN_PRE_BIND_FN, + SLAPI_PLUGIN_POST_BIND_FN, + SLAPI_PLUGIN_INTERNAL_PRE_BIND_FN, + SLAPI_PLUGIN_INTERNAL_POST_BIND_FN, + slapi_op_bind_callback + }, + { + SLAPI_PLUGIN_PRE_UNBIND_FN, + SLAPI_PLUGIN_POST_UNBIND_FN, + SLAPI_PLUGIN_INTERNAL_PRE_UNBIND_FN, + SLAPI_PLUGIN_INTERNAL_POST_UNBIND_FN, + NULL + }, + { + SLAPI_PLUGIN_PRE_SEARCH_FN, + SLAPI_PLUGIN_POST_SEARCH_FN, + SLAPI_PLUGIN_INTERNAL_PRE_SEARCH_FN, + SLAPI_PLUGIN_INTERNAL_POST_SEARCH_FN, + slapi_op_search_callback + }, + { + SLAPI_PLUGIN_PRE_COMPARE_FN, + SLAPI_PLUGIN_POST_COMPARE_FN, + SLAPI_PLUGIN_INTERNAL_PRE_COMPARE_FN, + SLAPI_PLUGIN_INTERNAL_POST_COMPARE_FN, + NULL + }, + { + SLAPI_PLUGIN_PRE_MODIFY_FN, + SLAPI_PLUGIN_POST_MODIFY_FN, + SLAPI_PLUGIN_INTERNAL_PRE_MODIFY_FN, + SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN, + NULL + }, + { + SLAPI_PLUGIN_PRE_MODRDN_FN, + SLAPI_PLUGIN_POST_MODRDN_FN, + SLAPI_PLUGIN_INTERNAL_PRE_MODRDN_FN, + SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN, + NULL + }, + { + SLAPI_PLUGIN_PRE_ADD_FN, + SLAPI_PLUGIN_POST_ADD_FN, + SLAPI_PLUGIN_INTERNAL_PRE_ADD_FN, + SLAPI_PLUGIN_INTERNAL_POST_ADD_FN, + NULL + }, + { + SLAPI_PLUGIN_PRE_DELETE_FN, + SLAPI_PLUGIN_POST_DELETE_FN, + SLAPI_PLUGIN_INTERNAL_PRE_DELETE_FN, + SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN, + NULL + }, + { + SLAPI_PLUGIN_PRE_ABANDON_FN, + SLAPI_PLUGIN_POST_ABANDON_FN, + SLAPI_PLUGIN_INTERNAL_PRE_ABANDON_FN, + SLAPI_PLUGIN_INTERNAL_POST_ABANDON_FN, + NULL + }, + { + 0, + 0, + 0, + 0, + NULL + } +}; + +slap_operation_t +slapi_tag2op( ber_tag_t tag ) +{ + slap_operation_t op; + + switch ( tag ) { + case LDAP_REQ_BIND: + op = op_bind; + break; + case LDAP_REQ_ADD: + op = op_add; + break; + case LDAP_REQ_DELETE: + op = op_delete; + break; + case LDAP_REQ_MODRDN: + op = op_modrdn; + break; + case LDAP_REQ_MODIFY: + op = op_modify; + break; + case LDAP_REQ_COMPARE: + op = op_compare; + break; + case LDAP_REQ_SEARCH: + op = op_search; + break; + case LDAP_REQ_UNBIND: + op = op_unbind; + break; + default: + op = op_last; + break; + } + + return op; +} + +/* Add SLAPI_RESCONTROLS to rs->sr_ctrls, with care, because + * rs->sr_ctrls could be allocated on the stack */ +static int +slapi_over_merge_controls( Operation *op, SlapReply *rs ) +{ + Slapi_PBlock *pb = SLAPI_OPERATION_PBLOCK( op ); + LDAPControl **ctrls = NULL; + LDAPControl **slapi_ctrls = NULL; + size_t n_slapi_ctrls = 0; + size_t n_rs_ctrls = 0; + size_t i; + + slapi_pblock_get( pb, SLAPI_RESCONTROLS, (void **)&slapi_ctrls ); + + n_slapi_ctrls = slapi_int_count_controls( slapi_ctrls ); + n_rs_ctrls = slapi_int_count_controls( rs->sr_ctrls ); + + if ( n_slapi_ctrls == 0 ) + return LDAP_SUCCESS; /* no SLAPI controls */ + + slapi_pblock_set( pb, SLAPI_X_OLD_RESCONTROLS, (void *)rs->sr_ctrls ); + + ctrls = (LDAPControl **) op->o_tmpalloc( + ( n_slapi_ctrls + n_rs_ctrls + 1 ) * sizeof(LDAPControl *), + op->o_tmpmemctx ); + + for ( i = 0; i < n_slapi_ctrls; i++ ) { + ctrls[i] = slapi_ctrls[i]; + } + if ( rs->sr_ctrls != NULL ) { + for ( i = 0; i < n_rs_ctrls; i++ ) { + ctrls[n_slapi_ctrls + i] = rs->sr_ctrls[i]; + } + } + ctrls[n_slapi_ctrls + n_rs_ctrls] = NULL; + + rs->sr_ctrls = ctrls; + + return LDAP_SUCCESS; +} + +static int +slapi_over_unmerge_controls( Operation *op, SlapReply *rs ) +{ + Slapi_PBlock *pb = SLAPI_OPERATION_PBLOCK( op ); + LDAPControl **rs_ctrls = NULL; + + slapi_pblock_get( pb, SLAPI_X_OLD_RESCONTROLS, (void **)&rs_ctrls ); + + if ( rs_ctrls == NULL || rs->sr_ctrls == rs_ctrls ) { + /* no copying done */ + return LDAP_SUCCESS; + } + + op->o_tmpfree( rs->sr_ctrls, op->o_tmpmemctx ); + rs->sr_ctrls = rs_ctrls; + + return LDAP_SUCCESS; +} + +static int +slapi_over_response( Operation *op, SlapReply *rs ) +{ + Slapi_PBlock *pb = SLAPI_OPERATION_PBLOCK( op ); + int rc = SLAP_CB_CONTINUE; + + if ( pb->pb_intop == 0 ) { + switch ( rs->sr_type ) { + case REP_RESULT: + case REP_SASL: + case REP_EXTENDED: + rc = slapi_over_result( op, rs, SLAPI_PLUGIN_PRE_RESULT_FN ); + break; + case REP_SEARCH: + rc = slapi_over_search( op, rs, SLAPI_PLUGIN_PRE_ENTRY_FN ); + break; + case REP_SEARCHREF: + rc = slapi_over_search( op, rs, SLAPI_PLUGIN_PRE_REFERRAL_FN ); + break; + default: + break; + } + } + + slapi_over_merge_controls( op, rs ); + + return rc; +} + +static int +slapi_over_cleanup( Operation *op, SlapReply *rs ) +{ + Slapi_PBlock *pb = SLAPI_OPERATION_PBLOCK( op ); + int rc = SLAP_CB_CONTINUE; + + slapi_over_unmerge_controls( op, rs ); + + if ( pb->pb_intop == 0 ) { + switch ( rs->sr_type ) { + case REP_RESULT: + case REP_SASL: + case REP_EXTENDED: + rc = slapi_over_result( op, rs, SLAPI_PLUGIN_POST_RESULT_FN ); + break; + case REP_SEARCH: + rc = slapi_over_search( op, rs, SLAPI_PLUGIN_POST_ENTRY_FN ); + break; + case REP_SEARCHREF: + rc = slapi_over_search( op, rs, SLAPI_PLUGIN_POST_REFERRAL_FN ); + break; + default: + break; + } + } + + return rc; +} + +static int +slapi_op_func( Operation *op, SlapReply *rs ) +{ + Slapi_PBlock *pb; + slap_operation_t which; + struct slapi_op_info *opinfo; + int rc; + slap_overinfo *oi; + slap_overinst *on; + slap_callback cb; + int internal_op; + int preop_type, postop_type; + BackendDB *be; + + if ( !slapi_plugins_used ) + return SLAP_CB_CONTINUE; + + /* + * Find the SLAPI operation information for this LDAP + * operation; this will contain the preop and postop + * plugin types, as well as optional callbacks for + * setting up the SLAPI environment. + */ + which = slapi_tag2op( op->o_tag ); + if ( which >= op_last ) { + /* invalid operation, but let someone else deal with it */ + return SLAP_CB_CONTINUE; + } + + opinfo = &slapi_op_dispatch_table[which]; + if ( opinfo == NULL ) { + /* no SLAPI plugin types for this operation */ + return SLAP_CB_CONTINUE; + } + + internal_op = slapi_op_internal_p( op, rs, &cb ); + + if ( internal_op ) { + preop_type = opinfo->soi_internal_preop; + postop_type = opinfo->soi_internal_postop; + } else { + preop_type = opinfo->soi_preop; + postop_type = opinfo->soi_postop; + } + + if ( preop_type == 0 ) { + /* no SLAPI plugin types for this operation */ + pb = NULL; + rc = SLAP_CB_CONTINUE; + goto cleanup; + } + + pb = SLAPI_OPERATION_PBLOCK( op ); + + /* cache backend so we call correct postop plugins */ + be = pb->pb_op->o_bd; + + rc = slapi_int_call_plugins( be, preop_type, pb ); + + /* + * soi_callback is responsible for examining the result code + * of the preoperation plugin and determining whether to + * abort. This is needed because of special SLAPI behaviour + e with bind preoperation plugins. + * + * The soi_callback function is also used to reset any values + * returned from the preoperation plugin before calling the + * backend (for the success case). + */ + if ( opinfo->soi_callback == NULL ) { + /* default behaviour is preop plugin can abort operation */ + if ( rc < 0 ) { + rc = rs->sr_err; + goto cleanup; + } + } else { + rc = (opinfo->soi_callback)( op, rs, rc ); + if ( rc ) + goto cleanup; + } + + /* + * Call actual backend (or next overlay in stack). We need to + * do this rather than returning SLAP_CB_CONTINUE and calling + * postoperation plugins in a response handler to match the + * behaviour of SLAPI in OpenLDAP 2.2, where postoperation + * plugins are called after the backend has completely + * finished processing the operation. + */ + on = (slap_overinst *)op->o_bd->bd_info; + oi = on->on_info; + + rc = overlay_op_walk( op, rs, which, oi, on->on_next ); + + /* + * Call postoperation plugins + */ + slapi_int_call_plugins( be, postop_type, pb ); + +cleanup: + if ( !internal_op ) { + slapi_pblock_destroy(pb); + cb.sc_private = NULL; + } + + op->o_callback = cb.sc_next; + + return rc; +} + +static int +slapi_over_extended( Operation *op, SlapReply *rs ) +{ + Slapi_PBlock *pb; + SLAPI_FUNC callback; + int rc; + int internal_op; + slap_callback cb; + + slapi_int_get_extop_plugin( &op->ore_reqoid, &callback ); + if ( callback == NULL ) { + return SLAP_CB_CONTINUE; + } + + internal_op = slapi_op_internal_p( op, rs, &cb ); + if ( internal_op ) { + return SLAP_CB_CONTINUE; + } + + pb = SLAPI_OPERATION_PBLOCK( op ); + + rc = (*callback)( pb ); + if ( rc == SLAPI_PLUGIN_EXTENDED_SENT_RESULT ) { + goto cleanup; + } else if ( rc == SLAPI_PLUGIN_EXTENDED_NOT_HANDLED ) { + rc = SLAP_CB_CONTINUE; + goto cleanup; + } + + assert( rs->sr_rspoid != NULL ); + + send_ldap_extended( op, rs ); + +#if 0 + slapi_ch_free_string( (char **)&rs->sr_rspoid ); +#endif + + if ( rs->sr_rspdata != NULL ) + ber_bvfree( rs->sr_rspdata ); + + rc = rs->sr_err; + +cleanup: + slapi_pblock_destroy( pb ); + op->o_callback = cb.sc_next; + + return rc; +} + +static int +slapi_over_access_allowed( + Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + slap_access_t access, + AccessControlState *state, + slap_mask_t *maskp ) +{ + int rc; + Slapi_PBlock *pb; + slap_callback cb; + int internal_op; + SlapReply rs = { REP_RESULT }; + + internal_op = slapi_op_internal_p( op, &rs, &cb ); + + cb.sc_response = NULL; + cb.sc_cleanup = NULL; + cb.sc_writewait = NULL; + + pb = SLAPI_OPERATION_PBLOCK( op ); + + rc = slapi_int_access_allowed( op, e, desc, val, access, state ); + if ( rc ) { + rc = SLAP_CB_CONTINUE; + } + + if ( !internal_op ) { + slapi_pblock_destroy( pb ); + } + + op->o_callback = cb.sc_next; + + return rc; +} + +static int +slapi_over_acl_group( + Operation *op, + Entry *target, + struct berval *gr_ndn, + struct berval *op_ndn, + ObjectClass *group_oc, + AttributeDescription *group_at ) +{ + Slapi_Entry *e; + int rc; + Slapi_PBlock *pb; + BackendDB *be = op->o_bd; + GroupAssertion *g; + SlapReply rs = { REP_RESULT }; + + op->o_bd = select_backend( gr_ndn, 0 ); + + for ( g = op->o_groups; g; g = g->ga_next ) { + if ( g->ga_be != op->o_bd || g->ga_oc != group_oc || + g->ga_at != group_at || g->ga_len != gr_ndn->bv_len ) + { + continue; + } + if ( strcmp( g->ga_ndn, gr_ndn->bv_val ) == 0 ) { + break; + } + } + if ( g != NULL ) { + rc = g->ga_res; + goto done; + } + + if ( target != NULL && dn_match( &target->e_nname, gr_ndn ) ) { + e = target; + rc = 0; + } else { + rc = be_entry_get_rw( op, gr_ndn, group_oc, group_at, 0, &e ); + } + if ( e != NULL ) { + int internal_op; + slap_callback cb; + + internal_op = slapi_op_internal_p( op, &rs, &cb ); + + cb.sc_response = NULL; + cb.sc_cleanup = NULL; + cb.sc_writewait = NULL; + + pb = SLAPI_OPERATION_PBLOCK( op ); + + slapi_pblock_set( pb, SLAPI_X_GROUP_ENTRY, (void *)e ); + slapi_pblock_set( pb, SLAPI_X_GROUP_OPERATION_DN, (void *)op_ndn->bv_val ); + slapi_pblock_set( pb, SLAPI_X_GROUP_ATTRIBUTE, (void *)group_at->ad_cname.bv_val ); + slapi_pblock_set( pb, SLAPI_X_GROUP_TARGET_ENTRY, (void *)target ); + + rc = slapi_over_call_plugins( pb, SLAPI_X_PLUGIN_PRE_GROUP_FN ); + if ( rc >= 0 ) /* 1 means no plugins called */ + rc = SLAP_CB_CONTINUE; + else + rc = pb->pb_rs->sr_err; + + slapi_pblock_delete_param( pb, SLAPI_X_GROUP_ENTRY ); + slapi_pblock_delete_param( pb, SLAPI_X_GROUP_OPERATION_DN ); + slapi_pblock_delete_param( pb, SLAPI_X_GROUP_ATTRIBUTE ); + slapi_pblock_delete_param( pb, SLAPI_X_GROUP_TARGET_ENTRY ); + + if ( !internal_op ) + slapi_pblock_destroy( pb ); + + if ( e != target ) { + be_entry_release_r( op, e ); + } + + op->o_callback = cb.sc_next; + } else { + rc = LDAP_NO_SUCH_OBJECT; /* return SLAP_CB_CONTINUE for correctness? */ + } + + if ( op->o_tag != LDAP_REQ_BIND && !op->o_do_not_cache && + rc != SLAP_CB_CONTINUE ) { + g = op->o_tmpalloc( sizeof( GroupAssertion ) + gr_ndn->bv_len, + op->o_tmpmemctx ); + g->ga_be = op->o_bd; + g->ga_oc = group_oc; + g->ga_at = group_at; + g->ga_res = rc; + g->ga_len = gr_ndn->bv_len; + strcpy( g->ga_ndn, gr_ndn->bv_val ); + g->ga_next = op->o_groups; + op->o_groups = g; + } + /* + * XXX don't call POST_GROUP_FN, I have no idea what the point of + * that plugin function was anyway + */ +done: + op->o_bd = be; + return rc; +} + +static int +slapi_over_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + Slapi_PBlock *pb; + int rc; + + pb = slapi_pblock_new(); + + rc = slapi_int_call_plugins( be, SLAPI_PLUGIN_START_FN, pb ); + + slapi_pblock_destroy( pb ); + + return rc; +} + +static int +slapi_over_db_close( + BackendDB *be, + ConfigReply *cr ) +{ + Slapi_PBlock *pb; + int rc; + + pb = slapi_pblock_new(); + + rc = slapi_int_call_plugins( be, SLAPI_PLUGIN_CLOSE_FN, pb ); + + slapi_pblock_destroy( pb ); + + return rc; +} + +static int +slapi_over_init() +{ + memset( &slapi, 0, sizeof(slapi) ); + + slapi.on_bi.bi_type = SLAPI_OVERLAY_NAME; + + slapi.on_bi.bi_op_bind = slapi_op_func; + slapi.on_bi.bi_op_unbind = slapi_op_func; + slapi.on_bi.bi_op_search = slapi_op_func; + slapi.on_bi.bi_op_compare = slapi_op_func; + slapi.on_bi.bi_op_modify = slapi_op_func; + slapi.on_bi.bi_op_modrdn = slapi_op_func; + slapi.on_bi.bi_op_add = slapi_op_func; + slapi.on_bi.bi_op_delete = slapi_op_func; + slapi.on_bi.bi_op_abandon = slapi_op_func; + slapi.on_bi.bi_op_cancel = slapi_op_func; + + slapi.on_bi.bi_db_open = slapi_over_db_open; + slapi.on_bi.bi_db_close = slapi_over_db_close; + + slapi.on_bi.bi_extended = slapi_over_extended; + slapi.on_bi.bi_access_allowed = slapi_over_access_allowed; + slapi.on_bi.bi_operational = slapi_over_aux_operational; + slapi.on_bi.bi_acl_group = slapi_over_acl_group; + + return overlay_register( &slapi ); +} + +int slapi_over_is_inst( BackendDB *be ) +{ + return overlay_is_inst( be, SLAPI_OVERLAY_NAME ); +} + +int slapi_over_config( BackendDB *be, ConfigReply *cr ) +{ + if ( slapi_over_initialized == 0 ) { + int rc; + + /* do global initializaiton */ + ldap_pvt_thread_mutex_init( &slapi_hn_mutex ); + ldap_pvt_thread_mutex_init( &slapi_time_mutex ); + ldap_pvt_thread_mutex_init( &slapi_printmessage_mutex ); + + if ( slapi_log_file == NULL ) + slapi_log_file = slapi_ch_strdup( LDAP_RUNDIR LDAP_DIRSEP "errors" ); + + rc = slapi_int_init_object_extensions(); + if ( rc != 0 ) + return rc; + + rc = slapi_over_init(); + if ( rc != 0 ) + return rc; + + slapi_over_initialized = 1; + } + + return overlay_config( be, SLAPI_OVERLAY_NAME, -1, NULL, cr ); +} + +#endif /* LDAP_SLAPI */ diff --git a/servers/slapd/slapi/slapi_pblock.c b/servers/slapd/slapi/slapi_pblock.c new file mode 100644 index 0000000..65b308a --- /dev/null +++ b/servers/slapd/slapi/slapi_pblock.c @@ -0,0 +1,1426 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2002-2021 The OpenLDAP Foundation. + * Portions Copyright 1997,2002-2003 IBM Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by IBM Corporation for use in + * IBM products and subsequently ported to OpenLDAP Software by + * Steve Omrani. Additional significant contributors include: + * Luke Howard + */ + +#include "portable.h" +#include <slap.h> +#include <slapi.h> + +#ifdef LDAP_SLAPI + +/* some parameters require a valid connection and operation */ +#define PBLOCK_LOCK_CONN( _pb ) do { \ + ldap_pvt_thread_mutex_lock( &(_pb)->pb_conn->c_mutex ); \ + } while (0) + +#define PBLOCK_UNLOCK_CONN( _pb ) do { \ + ldap_pvt_thread_mutex_unlock( &(_pb)->pb_conn->c_mutex ); \ + } while (0) + +/* some parameters are only settable for internal operations */ +#define PBLOCK_VALIDATE_IS_INTOP( _pb ) do { if ( (_pb)->pb_intop == 0 ) break; } while ( 0 ) + +static slapi_pblock_class_t +pblock_get_param_class( int param ) +{ + switch ( param ) { + case SLAPI_PLUGIN_TYPE: + case SLAPI_PLUGIN_ARGC: + case SLAPI_PLUGIN_OPRETURN: + case SLAPI_PLUGIN_INTOP_RESULT: + case SLAPI_CONFIG_LINENO: + case SLAPI_CONFIG_ARGC: + case SLAPI_BIND_METHOD: + case SLAPI_MODRDN_DELOLDRDN: + case SLAPI_SEARCH_SCOPE: + case SLAPI_SEARCH_DEREF: + case SLAPI_SEARCH_SIZELIMIT: + case SLAPI_SEARCH_TIMELIMIT: + case SLAPI_SEARCH_ATTRSONLY: + case SLAPI_NENTRIES: + case SLAPI_CHANGENUMBER: + case SLAPI_DBSIZE: + case SLAPI_REQUESTOR_ISROOT: + case SLAPI_BE_READONLY: + case SLAPI_BE_LASTMOD: + case SLAPI_DB2LDIF_PRINTKEY: + case SLAPI_LDIF2DB_REMOVEDUPVALS: + case SLAPI_MANAGEDSAIT: + case SLAPI_X_RELAX: + case SLAPI_X_OPERATION_NO_SCHEMA_CHECK: + case SLAPI_IS_REPLICATED_OPERATION: + case SLAPI_X_CONN_IS_UDP: + case SLAPI_X_CONN_SSF: + case SLAPI_RESULT_CODE: + case SLAPI_LOG_OPERATION: + case SLAPI_IS_INTERNAL_OPERATION: + return PBLOCK_CLASS_INTEGER; + break; + + case SLAPI_CONN_ID: + case SLAPI_OPERATION_ID: + case SLAPI_OPINITIATED_TIME: + case SLAPI_ABANDON_MSGID: + case SLAPI_X_OPERATION_DELETE_GLUE_PARENT: + case SLAPI_OPERATION_MSGID: + return PBLOCK_CLASS_LONG_INTEGER; + break; + + case SLAPI_PLUGIN_DESTROY_FN: + case SLAPI_PLUGIN_DB_BIND_FN: + case SLAPI_PLUGIN_DB_UNBIND_FN: + case SLAPI_PLUGIN_DB_SEARCH_FN: + case SLAPI_PLUGIN_DB_COMPARE_FN: + case SLAPI_PLUGIN_DB_MODIFY_FN: + case SLAPI_PLUGIN_DB_MODRDN_FN: + case SLAPI_PLUGIN_DB_ADD_FN: + case SLAPI_PLUGIN_DB_DELETE_FN: + case SLAPI_PLUGIN_DB_ABANDON_FN: + case SLAPI_PLUGIN_DB_CONFIG_FN: + case SLAPI_PLUGIN_CLOSE_FN: + case SLAPI_PLUGIN_DB_FLUSH_FN: + case SLAPI_PLUGIN_START_FN: + case SLAPI_PLUGIN_DB_SEQ_FN: + case SLAPI_PLUGIN_DB_ENTRY_FN: + case SLAPI_PLUGIN_DB_REFERRAL_FN: + case SLAPI_PLUGIN_DB_RESULT_FN: + case SLAPI_PLUGIN_DB_LDIF2DB_FN: + case SLAPI_PLUGIN_DB_DB2LDIF_FN: + case SLAPI_PLUGIN_DB_BEGIN_FN: + case SLAPI_PLUGIN_DB_COMMIT_FN: + case SLAPI_PLUGIN_DB_ABORT_FN: + case SLAPI_PLUGIN_DB_ARCHIVE2DB_FN: + case SLAPI_PLUGIN_DB_DB2ARCHIVE_FN: + case SLAPI_PLUGIN_DB_NEXT_SEARCH_ENTRY_FN: + case SLAPI_PLUGIN_DB_FREE_RESULT_SET_FN: + case SLAPI_PLUGIN_DB_SIZE_FN: + case SLAPI_PLUGIN_DB_TEST_FN: + case SLAPI_PLUGIN_DB_NO_ACL: + case SLAPI_PLUGIN_EXT_OP_FN: + case SLAPI_PLUGIN_EXT_OP_OIDLIST: + case SLAPI_PLUGIN_PRE_BIND_FN: + case SLAPI_PLUGIN_PRE_UNBIND_FN: + case SLAPI_PLUGIN_PRE_SEARCH_FN: + case SLAPI_PLUGIN_PRE_COMPARE_FN: + case SLAPI_PLUGIN_PRE_MODIFY_FN: + case SLAPI_PLUGIN_PRE_MODRDN_FN: + case SLAPI_PLUGIN_PRE_ADD_FN: + case SLAPI_PLUGIN_PRE_DELETE_FN: + case SLAPI_PLUGIN_PRE_ABANDON_FN: + case SLAPI_PLUGIN_PRE_ENTRY_FN: + case SLAPI_PLUGIN_PRE_REFERRAL_FN: + case SLAPI_PLUGIN_PRE_RESULT_FN: + case SLAPI_PLUGIN_INTERNAL_PRE_ADD_FN: + case SLAPI_PLUGIN_INTERNAL_PRE_MODIFY_FN: + case SLAPI_PLUGIN_INTERNAL_PRE_MODRDN_FN: + case SLAPI_PLUGIN_INTERNAL_PRE_DELETE_FN: + case SLAPI_PLUGIN_BE_PRE_ADD_FN: + case SLAPI_PLUGIN_BE_PRE_MODIFY_FN: + case SLAPI_PLUGIN_BE_PRE_MODRDN_FN: + case SLAPI_PLUGIN_BE_PRE_DELETE_FN: + case SLAPI_PLUGIN_POST_BIND_FN: + case SLAPI_PLUGIN_POST_UNBIND_FN: + case SLAPI_PLUGIN_POST_SEARCH_FN: + case SLAPI_PLUGIN_POST_COMPARE_FN: + case SLAPI_PLUGIN_POST_MODIFY_FN: + case SLAPI_PLUGIN_POST_MODRDN_FN: + case SLAPI_PLUGIN_POST_ADD_FN: + case SLAPI_PLUGIN_POST_DELETE_FN: + case SLAPI_PLUGIN_POST_ABANDON_FN: + case SLAPI_PLUGIN_POST_ENTRY_FN: + case SLAPI_PLUGIN_POST_REFERRAL_FN: + case SLAPI_PLUGIN_POST_RESULT_FN: + case SLAPI_PLUGIN_INTERNAL_POST_ADD_FN: + case SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN: + case SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN: + case SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN: + case SLAPI_PLUGIN_BE_POST_ADD_FN: + case SLAPI_PLUGIN_BE_POST_MODIFY_FN: + case SLAPI_PLUGIN_BE_POST_MODRDN_FN: + case SLAPI_PLUGIN_BE_POST_DELETE_FN: + case SLAPI_PLUGIN_MR_FILTER_CREATE_FN: + case SLAPI_PLUGIN_MR_INDEXER_CREATE_FN: + case SLAPI_PLUGIN_MR_FILTER_MATCH_FN: + case SLAPI_PLUGIN_MR_FILTER_INDEX_FN: + case SLAPI_PLUGIN_MR_FILTER_RESET_FN: + case SLAPI_PLUGIN_MR_INDEX_FN: + case SLAPI_PLUGIN_COMPUTE_EVALUATOR_FN: + case SLAPI_PLUGIN_COMPUTE_SEARCH_REWRITER_FN: + case SLAPI_PLUGIN_ACL_ALLOW_ACCESS: + case SLAPI_X_PLUGIN_PRE_GROUP_FN: + case SLAPI_X_PLUGIN_POST_GROUP_FN: + case SLAPI_PLUGIN_AUDIT_FN: + case SLAPI_PLUGIN_INTERNAL_PRE_BIND_FN: + case SLAPI_PLUGIN_INTERNAL_PRE_UNBIND_FN: + case SLAPI_PLUGIN_INTERNAL_PRE_SEARCH_FN: + case SLAPI_PLUGIN_INTERNAL_PRE_COMPARE_FN: + case SLAPI_PLUGIN_INTERNAL_PRE_ABANDON_FN: + case SLAPI_PLUGIN_INTERNAL_POST_BIND_FN: + case SLAPI_PLUGIN_INTERNAL_POST_UNBIND_FN: + case SLAPI_PLUGIN_INTERNAL_POST_SEARCH_FN: + case SLAPI_PLUGIN_INTERNAL_POST_COMPARE_FN: + case SLAPI_PLUGIN_INTERNAL_POST_ABANDON_FN: + return PBLOCK_CLASS_FUNCTION_POINTER; + break; + + case SLAPI_BACKEND: + case SLAPI_CONNECTION: + case SLAPI_OPERATION: + case SLAPI_OPERATION_PARAMETERS: + case SLAPI_OPERATION_TYPE: + case SLAPI_OPERATION_AUTHTYPE: + case SLAPI_BE_MONITORDN: + case SLAPI_BE_TYPE: + case SLAPI_REQUESTOR_DN: + case SLAPI_CONN_DN: + case SLAPI_CONN_CLIENTIP: + case SLAPI_CONN_SERVERIP: + case SLAPI_CONN_AUTHTYPE: + case SLAPI_CONN_AUTHMETHOD: + case SLAPI_CONN_CERT: + case SLAPI_X_CONN_CLIENTPATH: + case SLAPI_X_CONN_SERVERPATH: + case SLAPI_X_CONN_SASL_CONTEXT: + case SLAPI_X_CONFIG_ARGV: + case SLAPI_X_INTOP_FLAGS: + case SLAPI_X_INTOP_RESULT_CALLBACK: + case SLAPI_X_INTOP_SEARCH_ENTRY_CALLBACK: + case SLAPI_X_INTOP_REFERRAL_ENTRY_CALLBACK: + case SLAPI_X_INTOP_CALLBACK_DATA: + case SLAPI_PLUGIN_MR_OID: + case SLAPI_PLUGIN_MR_TYPE: + case SLAPI_PLUGIN_MR_VALUE: + case SLAPI_PLUGIN_MR_VALUES: + case SLAPI_PLUGIN_MR_KEYS: + case SLAPI_PLUGIN: + case SLAPI_PLUGIN_PRIVATE: + case SLAPI_PLUGIN_ARGV: + case SLAPI_PLUGIN_OBJECT: + case SLAPI_PLUGIN_DESCRIPTION: + case SLAPI_PLUGIN_IDENTITY: + case SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES: + case SLAPI_PLUGIN_INTOP_SEARCH_REFERRALS: + case SLAPI_PLUGIN_MR_FILTER_REUSABLE: + case SLAPI_PLUGIN_MR_QUERY_OPERATOR: + case SLAPI_PLUGIN_MR_USAGE: + case SLAPI_OP_LESS: + case SLAPI_OP_LESS_OR_EQUAL: + case SLAPI_PLUGIN_MR_USAGE_INDEX: + case SLAPI_PLUGIN_SYNTAX_FILTER_AVA: + case SLAPI_PLUGIN_SYNTAX_FILTER_SUB: + case SLAPI_PLUGIN_SYNTAX_VALUES2KEYS: + case SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA: + case SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_SUB: + case SLAPI_PLUGIN_SYNTAX_NAMES: + case SLAPI_PLUGIN_SYNTAX_OID: + case SLAPI_PLUGIN_SYNTAX_FLAGS: + case SLAPI_PLUGIN_SYNTAX_COMPARE: + case SLAPI_CONFIG_FILENAME: + case SLAPI_CONFIG_ARGV: + case SLAPI_TARGET_ADDRESS: + case SLAPI_TARGET_UNIQUEID: + case SLAPI_TARGET_DN: + case SLAPI_REQCONTROLS: + case SLAPI_ENTRY_PRE_OP: + case SLAPI_ENTRY_POST_OP: + case SLAPI_RESCONTROLS: + case SLAPI_X_OLD_RESCONTROLS: + case SLAPI_ADD_RESCONTROL: + case SLAPI_CONTROLS_ARG: + case SLAPI_ADD_ENTRY: + case SLAPI_ADD_EXISTING_DN_ENTRY: + case SLAPI_ADD_PARENT_ENTRY: + case SLAPI_ADD_PARENT_UNIQUEID: + case SLAPI_ADD_EXISTING_UNIQUEID_ENTRY: + case SLAPI_BIND_CREDENTIALS: + case SLAPI_BIND_SASLMECHANISM: + case SLAPI_BIND_RET_SASLCREDS: + case SLAPI_COMPARE_TYPE: + case SLAPI_COMPARE_VALUE: + case SLAPI_MODIFY_MODS: + case SLAPI_MODRDN_NEWRDN: + case SLAPI_MODRDN_NEWSUPERIOR: + case SLAPI_MODRDN_PARENT_ENTRY: + case SLAPI_MODRDN_NEWPARENT_ENTRY: + case SLAPI_MODRDN_TARGET_ENTRY: + case SLAPI_MODRDN_NEWSUPERIOR_ADDRESS: + case SLAPI_SEARCH_FILTER: + case SLAPI_SEARCH_STRFILTER: + case SLAPI_SEARCH_ATTRS: + case SLAPI_SEQ_TYPE: + case SLAPI_SEQ_ATTRNAME: + case SLAPI_SEQ_VAL: + case SLAPI_EXT_OP_REQ_OID: + case SLAPI_EXT_OP_REQ_VALUE: + case SLAPI_EXT_OP_RET_OID: + case SLAPI_EXT_OP_RET_VALUE: + case SLAPI_MR_FILTER_ENTRY: + case SLAPI_MR_FILTER_TYPE: + case SLAPI_MR_FILTER_VALUE: + case SLAPI_MR_FILTER_OID: + case SLAPI_MR_FILTER_DNATTRS: + case SLAPI_LDIF2DB_FILE: + case SLAPI_PARENT_TXN: + case SLAPI_TXN: + case SLAPI_SEARCH_RESULT_SET: + case SLAPI_SEARCH_RESULT_ENTRY: + case SLAPI_SEARCH_REFERRALS: + case SLAPI_RESULT_TEXT: + case SLAPI_RESULT_MATCHED: + case SLAPI_X_GROUP_ENTRY: + case SLAPI_X_GROUP_ATTRIBUTE: + case SLAPI_X_GROUP_OPERATION_DN: + case SLAPI_X_GROUP_TARGET_ENTRY: + case SLAPI_X_ADD_STRUCTURAL_CLASS: + case SLAPI_PLUGIN_AUDIT_DATA: + case SLAPI_IBM_PBLOCK: + case SLAPI_PLUGIN_VERSION: + return PBLOCK_CLASS_POINTER; + break; + default: + break; + } + + return PBLOCK_CLASS_INVALID; +} + +static void +pblock_lock( Slapi_PBlock *pb ) +{ + ldap_pvt_thread_mutex_lock( &pb->pb_mutex ); +} + +static void +pblock_unlock( Slapi_PBlock *pb ) +{ + ldap_pvt_thread_mutex_unlock( &pb->pb_mutex ); +} + +static int +pblock_get_default( Slapi_PBlock *pb, int param, void **value ) +{ + int i; + slapi_pblock_class_t pbClass; + + pbClass = pblock_get_param_class( param ); + if ( pbClass == PBLOCK_CLASS_INVALID ) { + return PBLOCK_ERROR; + } + + switch ( pbClass ) { + case PBLOCK_CLASS_INTEGER: + *((int *)value) = 0; + break; + case PBLOCK_CLASS_LONG_INTEGER: + *((long *)value) = 0L; + break; + case PBLOCK_CLASS_POINTER: + case PBLOCK_CLASS_FUNCTION_POINTER: + *value = NULL; + break; + case PBLOCK_CLASS_INVALID: + return PBLOCK_ERROR; + } + + for ( i = 0; i < pb->pb_nParams; i++ ) { + if ( pb->pb_params[i] == param ) { + switch ( pbClass ) { + case PBLOCK_CLASS_INTEGER: + *((int *)value) = pb->pb_values[i].pv_integer; + break; + case PBLOCK_CLASS_LONG_INTEGER: + *((long *)value) = pb->pb_values[i].pv_long_integer; + break; + case PBLOCK_CLASS_POINTER: + *value = pb->pb_values[i].pv_pointer; + break; + case PBLOCK_CLASS_FUNCTION_POINTER: + *value = pb->pb_values[i].pv_function_pointer; + break; + default: + break; + } + break; + } + } + + return PBLOCK_SUCCESS; +} + +static char * +pblock_get_authtype( AuthorizationInformation *authz, int is_tls ) +{ + char *authType; + + switch ( authz->sai_method ) { + case LDAP_AUTH_SASL: + authType = SLAPD_AUTH_SASL; + break; + case LDAP_AUTH_SIMPLE: + authType = SLAPD_AUTH_SIMPLE; + break; + case LDAP_AUTH_NONE: + authType = SLAPD_AUTH_NONE; + break; + default: + authType = NULL; + break; + } + + if ( is_tls && authType == NULL ) { + authType = SLAPD_AUTH_SSL; + } + + return authType; +} + +static int +pblock_set_default( Slapi_PBlock *pb, int param, void *value ) +{ + slapi_pblock_class_t pbClass; + int i; + + pbClass = pblock_get_param_class( param ); + if ( pbClass == PBLOCK_CLASS_INVALID ) { + return PBLOCK_ERROR; + } + + if ( pb->pb_nParams == PBLOCK_MAX_PARAMS ) { + return PBLOCK_ERROR; + } + + for ( i = 0; i < pb->pb_nParams; i++ ) { + if ( pb->pb_params[i] == param ) + break; + } + if ( i >= pb->pb_nParams ) { + pb->pb_params[i] = param; + pb->pb_nParams++; + } + + switch ( pbClass ) { + case PBLOCK_CLASS_INTEGER: + pb->pb_values[i].pv_integer = (*((int *)value)); + break; + case PBLOCK_CLASS_LONG_INTEGER: + pb->pb_values[i].pv_long_integer = (*((long *)value)); + break; + case PBLOCK_CLASS_POINTER: + pb->pb_values[i].pv_pointer = value; + break; + case PBLOCK_CLASS_FUNCTION_POINTER: + pb->pb_values[i].pv_function_pointer = value; + break; + default: + break; + } + + return PBLOCK_SUCCESS; +} + +static int +pblock_be_call( Slapi_PBlock *pb, int (*bep)(Operation *) ) +{ + BackendDB *be_orig; + Operation *op; + int rc; + + PBLOCK_ASSERT_OP( pb, 0 ); + op = pb->pb_op; + + be_orig = op->o_bd; + op->o_bd = select_backend( &op->o_req_ndn, 0 ); + rc = (*bep)( op ); + op->o_bd = be_orig; + + return rc; +} + +static int +pblock_get( Slapi_PBlock *pb, int param, void **value ) +{ + int rc = PBLOCK_SUCCESS; + + pblock_lock( pb ); + + switch ( param ) { + case SLAPI_OPERATION: + *value = pb->pb_op; + break; + case SLAPI_OPINITIATED_TIME: + PBLOCK_ASSERT_OP( pb, 0 ); + *((long *)value) = pb->pb_op->o_time; + break; + case SLAPI_OPERATION_ID: + PBLOCK_ASSERT_OP( pb, 0 ); + *((long *)value) = pb->pb_op->o_opid; + break; + case SLAPI_OPERATION_TYPE: + PBLOCK_ASSERT_OP( pb, 0 ); + *((ber_tag_t *)value) = pb->pb_op->o_tag; + break; + case SLAPI_OPERATION_MSGID: + PBLOCK_ASSERT_OP( pb, 0 ); + *((long *)value) = pb->pb_op->o_msgid; + break; + case SLAPI_X_OPERATION_DELETE_GLUE_PARENT: + PBLOCK_ASSERT_OP( pb, 0 ); + *((int *)value) = pb->pb_op->o_delete_glue_parent; + break; + case SLAPI_X_OPERATION_NO_SCHEMA_CHECK: + PBLOCK_ASSERT_OP( pb, 0 ); + *((int *)value) = get_no_schema_check( pb->pb_op ); + break; + case SLAPI_X_ADD_STRUCTURAL_CLASS: + PBLOCK_ASSERT_OP( pb, 0 ); + + if ( pb->pb_op->o_tag == LDAP_REQ_ADD ) { + struct berval tmpval = BER_BVNULL; + + rc = mods_structural_class( pb->pb_op->ora_modlist, + &tmpval, &pb->pb_rs->sr_text, + pb->pb_textbuf, sizeof( pb->pb_textbuf ), + pb->pb_op->o_tmpmemctx ); + *((char **)value) = tmpval.bv_val; + } else { + rc = PBLOCK_ERROR; + } + break; + case SLAPI_X_OPERATION_NO_SUBORDINATE_GLUE: + PBLOCK_ASSERT_OP( pb, 0 ); + *((int *)value) = pb->pb_op->o_no_subordinate_glue; + break; + case SLAPI_REQCONTROLS: + PBLOCK_ASSERT_OP( pb, 0 ); + *((LDAPControl ***)value) = pb->pb_op->o_ctrls; + break; + case SLAPI_REQUESTOR_DN: + PBLOCK_ASSERT_OP( pb, 0 ); + *((char **)value) = pb->pb_op->o_dn.bv_val; + break; + case SLAPI_MANAGEDSAIT: + PBLOCK_ASSERT_OP( pb, 0 ); + *((int *)value) = get_manageDSAit( pb->pb_op ); + break; + case SLAPI_X_RELAX: + PBLOCK_ASSERT_OP( pb, 0 ); + *((int *)value) = get_relax( pb->pb_op ); + break; + case SLAPI_BACKEND: + PBLOCK_ASSERT_OP( pb, 0 ); + *((BackendDB **)value) = select_backend( &pb->pb_op->o_req_ndn, 0 ); + break; + case SLAPI_BE_TYPE: + PBLOCK_ASSERT_OP( pb, 0 ); + if ( pb->pb_op->o_bd != NULL ) + *((char **)value) = pb->pb_op->o_bd->bd_info->bi_type; + else + *value = NULL; + break; + case SLAPI_CONNECTION: + *value = pb->pb_conn; + break; + case SLAPI_X_CONN_SSF: + PBLOCK_ASSERT_OP( pb, 0 ); + *((slap_ssf_t *)value) = pb->pb_conn->c_ssf; + break; + case SLAPI_X_CONN_SASL_CONTEXT: + PBLOCK_ASSERT_CONN( pb ); + if ( pb->pb_conn->c_sasl_authctx != NULL ) + *value = pb->pb_conn->c_sasl_authctx; + else + *value = pb->pb_conn->c_sasl_sockctx; + break; + case SLAPI_TARGET_DN: + PBLOCK_ASSERT_OP( pb, 0 ); + *((char **)value) = pb->pb_op->o_req_dn.bv_val; + break; + case SLAPI_REQUESTOR_ISROOT: + *((int *)value) = pblock_be_call( pb, be_isroot ); + break; + case SLAPI_IS_REPLICATED_OPERATION: + *((int *)value) = pblock_be_call( pb, be_slurp_update ); + break; + case SLAPI_CONN_AUTHTYPE: + case SLAPI_CONN_AUTHMETHOD: /* XXX should return SASL mech */ + PBLOCK_ASSERT_CONN( pb ); + *((char **)value) = pblock_get_authtype( &pb->pb_conn->c_authz, +#ifdef HAVE_TLS + pb->pb_conn->c_is_tls +#else + 0 +#endif + ); + break; + case SLAPI_IS_INTERNAL_OPERATION: + *((int *)value) = pb->pb_intop; + break; + case SLAPI_X_CONN_IS_UDP: + PBLOCK_ASSERT_CONN( pb ); +#ifdef LDAP_CONNECTIONLESS + *((int *)value) = pb->pb_conn->c_is_udp; +#else + *((int *)value) = 0; +#endif + break; + case SLAPI_CONN_ID: + PBLOCK_ASSERT_CONN( pb ); + *((long *)value) = pb->pb_conn->c_connid; + break; + case SLAPI_CONN_DN: + PBLOCK_ASSERT_CONN( pb ); +#if 0 + /* This would be necessary to keep plugin compat after the fix in ITS#4158 */ + if ( pb->pb_op->o_tag == LDAP_REQ_BIND && pb->pb_rs->sr_err == LDAP_SUCCESS ) + *((char **)value) = pb->pb_op->orb_edn.bv_val; + else +#endif + *((char **)value) = pb->pb_conn->c_dn.bv_val; + break; + case SLAPI_CONN_CLIENTIP: + PBLOCK_ASSERT_CONN( pb ); + if ( strncmp( pb->pb_conn->c_peer_name.bv_val, "IP=", 3 ) == 0 ) + *((char **)value) = &pb->pb_conn->c_peer_name.bv_val[3]; + else + *value = NULL; + break; + case SLAPI_X_CONN_CLIENTPATH: + PBLOCK_ASSERT_CONN( pb ); + if ( strncmp( pb->pb_conn->c_peer_name.bv_val, "PATH=", 3 ) == 0 ) + *((char **)value) = &pb->pb_conn->c_peer_name.bv_val[5]; + else + *value = NULL; + break; + case SLAPI_CONN_SERVERIP: + PBLOCK_ASSERT_CONN( pb ); + if ( strncmp( pb->pb_conn->c_sock_name.bv_val, "IP=", 3 ) == 0 ) + *((char **)value) = &pb->pb_conn->c_sock_name.bv_val[3]; + else + *value = NULL; + break; + case SLAPI_X_CONN_SERVERPATH: + PBLOCK_ASSERT_CONN( pb ); + if ( strncmp( pb->pb_conn->c_sock_name.bv_val, "PATH=", 3 ) == 0 ) + *((char **)value) = &pb->pb_conn->c_sock_name.bv_val[5]; + else + *value = NULL; + break; + case SLAPI_RESULT_CODE: + case SLAPI_PLUGIN_INTOP_RESULT: + PBLOCK_ASSERT_OP( pb, 0 ); + *((int *)value) = pb->pb_rs->sr_err; + break; + case SLAPI_RESULT_TEXT: + PBLOCK_ASSERT_OP( pb, 0 ); + *((const char **)value) = pb->pb_rs->sr_text; + break; + case SLAPI_RESULT_MATCHED: + PBLOCK_ASSERT_OP( pb, 0 ); + *((const char **)value) = pb->pb_rs->sr_matched; + break; + case SLAPI_ADD_ENTRY: + PBLOCK_ASSERT_OP( pb, 0 ); + if ( pb->pb_op->o_tag == LDAP_REQ_ADD ) + *((Slapi_Entry **)value) = pb->pb_op->ora_e; + else + *value = NULL; + break; + case SLAPI_MODIFY_MODS: { + LDAPMod **mods = NULL; + Modifications *ml = NULL; + + pblock_get_default( pb, param, (void **)&mods ); + if ( mods == NULL && pb->pb_intop == 0 ) { + switch ( pb->pb_op->o_tag ) { + case LDAP_REQ_MODIFY: + ml = pb->pb_op->orm_modlist; + break; + case LDAP_REQ_MODRDN: + ml = pb->pb_op->orr_modlist; + break; + default: + rc = PBLOCK_ERROR; + break; + } + if ( rc != PBLOCK_ERROR ) { + mods = slapi_int_modifications2ldapmods( ml ); + pblock_set_default( pb, param, (void *)mods ); + } + } + *((LDAPMod ***)value) = mods; + break; + } + case SLAPI_MODRDN_NEWRDN: + PBLOCK_ASSERT_OP( pb, 0 ); + if ( pb->pb_op->o_tag == LDAP_REQ_MODRDN ) + *((char **)value) = pb->pb_op->orr_newrdn.bv_val; + else + *value = NULL; + break; + case SLAPI_MODRDN_NEWSUPERIOR: + PBLOCK_ASSERT_OP( pb, 0 ); + if ( pb->pb_op->o_tag == LDAP_REQ_MODRDN && pb->pb_op->orr_newSup != NULL ) + *((char **)value) = pb->pb_op->orr_newSup->bv_val; + else + *value = NULL; + break; + case SLAPI_MODRDN_DELOLDRDN: + PBLOCK_ASSERT_OP( pb, 0 ); + if ( pb->pb_op->o_tag == LDAP_REQ_MODRDN ) + *((int *)value) = pb->pb_op->orr_deleteoldrdn; + else + *((int *)value) = 0; + break; + case SLAPI_SEARCH_SCOPE: + PBLOCK_ASSERT_OP( pb, 0 ); + if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH ) + *((int *)value) = pb->pb_op->ors_scope; + else + *((int *)value) = 0; + break; + case SLAPI_SEARCH_DEREF: + PBLOCK_ASSERT_OP( pb, 0 ); + if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH ) + *((int *)value) = pb->pb_op->ors_deref; + else + *((int *)value) = 0; + break; + case SLAPI_SEARCH_SIZELIMIT: + PBLOCK_ASSERT_OP( pb, 0 ); + if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH ) + *((int *)value) = pb->pb_op->ors_slimit; + else + *((int *)value) = 0; + break; + case SLAPI_SEARCH_TIMELIMIT: + PBLOCK_ASSERT_OP( pb, 0 ); + if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH ) + *((int *)value) = pb->pb_op->ors_tlimit; + else + *((int *)value) = 0; + break; + case SLAPI_SEARCH_FILTER: + PBLOCK_ASSERT_OP( pb, 0 ); + if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH ) + *((Slapi_Filter **)value) = pb->pb_op->ors_filter; + else + *((Slapi_Filter **)value) = NULL; + break; + case SLAPI_SEARCH_STRFILTER: + PBLOCK_ASSERT_OP( pb, 0 ); + if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH ) + *((char **)value) = pb->pb_op->ors_filterstr.bv_val; + else + *((char **)value) = NULL; + break; + case SLAPI_SEARCH_ATTRS: { + char **attrs = NULL; + + PBLOCK_ASSERT_OP( pb, 0 ); + if ( pb->pb_op->o_tag != LDAP_REQ_SEARCH ) { + rc = PBLOCK_ERROR; + break; + } + pblock_get_default( pb, param, (void **)&attrs ); + if ( attrs == NULL && pb->pb_intop == 0 ) { + attrs = anlist2charray_x( pb->pb_op->ors_attrs, 0, pb->pb_op->o_tmpmemctx ); + pblock_set_default( pb, param, (void *)attrs ); + } + *((char ***)value) = attrs; + break; + } + case SLAPI_SEARCH_ATTRSONLY: + PBLOCK_ASSERT_OP( pb, 0 ); + if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH ) + *((int *)value) = pb->pb_op->ors_attrsonly; + else + *((int *)value) = 0; + break; + case SLAPI_SEARCH_RESULT_ENTRY: + PBLOCK_ASSERT_OP( pb, 0 ); + *((Slapi_Entry **)value) = pb->pb_rs->sr_entry; + break; + case SLAPI_BIND_RET_SASLCREDS: + PBLOCK_ASSERT_OP( pb, 0 ); + *((struct berval **)value) = pb->pb_rs->sr_sasldata; + break; + case SLAPI_EXT_OP_REQ_OID: + *((const char **)value) = pb->pb_op->ore_reqoid.bv_val; + break; + case SLAPI_EXT_OP_REQ_VALUE: + *((struct berval **)value) = pb->pb_op->ore_reqdata; + break; + case SLAPI_EXT_OP_RET_OID: + PBLOCK_ASSERT_OP( pb, 0 ); + *((const char **)value) = pb->pb_rs->sr_rspoid; + break; + case SLAPI_EXT_OP_RET_VALUE: + PBLOCK_ASSERT_OP( pb, 0 ); + *((struct berval **)value) = pb->pb_rs->sr_rspdata; + break; + case SLAPI_BIND_METHOD: + if ( pb->pb_op->o_tag == LDAP_REQ_BIND ) + *((int *)value) = pb->pb_op->orb_method; + else + *((int *)value) = 0; + break; + case SLAPI_BIND_CREDENTIALS: + if ( pb->pb_op->o_tag == LDAP_REQ_BIND ) + *((struct berval **)value) = &pb->pb_op->orb_cred; + else + *value = NULL; + break; + case SLAPI_COMPARE_TYPE: + if ( pb->pb_op->o_tag == LDAP_REQ_COMPARE ) + *((char **)value) = pb->pb_op->orc_ava->aa_desc->ad_cname.bv_val; + else + *value = NULL; + break; + case SLAPI_COMPARE_VALUE: + if ( pb->pb_op->o_tag == LDAP_REQ_COMPARE ) + *((struct berval **)value) = &pb->pb_op->orc_ava->aa_value; + else + *value = NULL; + break; + case SLAPI_ABANDON_MSGID: + if ( pb->pb_op->o_tag == LDAP_REQ_ABANDON ) + *((int *)value) = pb->pb_op->orn_msgid; + else + *((int *)value) = 0; + break; + default: + rc = pblock_get_default( pb, param, value ); + break; + } + + pblock_unlock( pb ); + + return rc; +} + +static int +pblock_add_control( Slapi_PBlock *pb, LDAPControl *control ) +{ + LDAPControl **controls = NULL; + size_t i; + + pblock_get_default( pb, SLAPI_RESCONTROLS, (void **)&controls ); + + if ( controls != NULL ) { + for ( i = 0; controls[i] != NULL; i++ ) + ; + } else { + i = 0; + } + + controls = (LDAPControl **)slapi_ch_realloc( (char *)controls, + ( i + 2 ) * sizeof(LDAPControl *)); + controls[i++] = slapi_dup_control( control ); + controls[i] = NULL; + + return pblock_set_default( pb, SLAPI_RESCONTROLS, (void *)controls ); +} + +static int +pblock_set_dn( void *value, struct berval *dn, struct berval *ndn, void *memctx ) +{ + struct berval bv; + + if ( !BER_BVISNULL( dn )) { + slap_sl_free( dn->bv_val, memctx ); + BER_BVZERO( dn ); + } + if ( !BER_BVISNULL( ndn )) { + slap_sl_free( ndn->bv_val, memctx ); + BER_BVZERO( ndn ); + } + + bv.bv_val = (char *)value; + bv.bv_len = ( value != NULL ) ? strlen( bv.bv_val ) : 0; + + return dnPrettyNormal( NULL, &bv, dn, ndn, memctx ); +} + +static int +pblock_set( Slapi_PBlock *pb, int param, void *value ) +{ + int rc = PBLOCK_SUCCESS; + + pblock_lock( pb ); + + switch ( param ) { + case SLAPI_OPERATION: + pb->pb_op = (Operation *)value; + break; + case SLAPI_OPINITIATED_TIME: + PBLOCK_ASSERT_OP( pb, 0 ); + pb->pb_op->o_time = *((long *)value); + break; + case SLAPI_OPERATION_ID: + PBLOCK_ASSERT_OP( pb, 0 ); + pb->pb_op->o_opid = *((long *)value); + break; + case SLAPI_OPERATION_TYPE: + PBLOCK_ASSERT_OP( pb, 0 ); + pb->pb_op->o_tag = *((ber_tag_t *)value); + break; + case SLAPI_OPERATION_MSGID: + PBLOCK_ASSERT_OP( pb, 0 ); + pb->pb_op->o_msgid = *((long *)value); + break; + case SLAPI_X_OPERATION_DELETE_GLUE_PARENT: + PBLOCK_ASSERT_OP( pb, 0 ); + pb->pb_op->o_delete_glue_parent = *((int *)value); + break; + case SLAPI_X_OPERATION_NO_SCHEMA_CHECK: + PBLOCK_ASSERT_OP( pb, 0 ); + pb->pb_op->o_no_schema_check = *((int *)value); + break; + case SLAPI_X_OPERATION_NO_SUBORDINATE_GLUE: + PBLOCK_ASSERT_OP( pb, 0 ); + pb->pb_op->o_no_subordinate_glue = *((int *)value); + break; + case SLAPI_REQCONTROLS: + PBLOCK_ASSERT_OP( pb, 0 ); + pb->pb_op->o_ctrls = (LDAPControl **)value; + break; + case SLAPI_RESCONTROLS: { + LDAPControl **ctrls = NULL; + + pblock_get_default( pb, param, (void **)&ctrls ); + if ( ctrls != NULL ) { + /* free old ones first */ + ldap_controls_free( ctrls ); + } + rc = pblock_set_default( pb, param, value ); + break; + } + case SLAPI_ADD_RESCONTROL: + PBLOCK_ASSERT_OP( pb, 0 ); + rc = pblock_add_control( pb, (LDAPControl *)value ); + break; + case SLAPI_REQUESTOR_DN: + PBLOCK_ASSERT_OP( pb, 0 ); + rc = pblock_set_dn( value, &pb->pb_op->o_dn, &pb->pb_op->o_ndn, pb->pb_op->o_tmpmemctx ); + break; + case SLAPI_MANAGEDSAIT: + PBLOCK_ASSERT_OP( pb, 0 ); + pb->pb_op->o_managedsait = *((int *)value); + break; + case SLAPI_X_RELAX: + PBLOCK_ASSERT_OP( pb, 0 ); + pb->pb_op->o_relax = *((int *)value); + break; + case SLAPI_BACKEND: + PBLOCK_ASSERT_OP( pb, 0 ); + pb->pb_op->o_bd = (BackendDB *)value; + break; + case SLAPI_CONNECTION: + pb->pb_conn = (Connection *)value; + break; + case SLAPI_X_CONN_SSF: + PBLOCK_ASSERT_CONN( pb ); + PBLOCK_LOCK_CONN( pb ); + pb->pb_conn->c_ssf = (slap_ssf_t)(long)value; + PBLOCK_UNLOCK_CONN( pb ); + break; + case SLAPI_X_CONN_SASL_CONTEXT: + PBLOCK_ASSERT_CONN( pb ); + PBLOCK_LOCK_CONN( pb ); + pb->pb_conn->c_sasl_authctx = value; + PBLOCK_UNLOCK_CONN( pb ); + break; + case SLAPI_TARGET_DN: + PBLOCK_ASSERT_OP( pb, 0 ); + rc = pblock_set_dn( value, &pb->pb_op->o_req_dn, &pb->pb_op->o_req_ndn, pb->pb_op->o_tmpmemctx ); + break; + case SLAPI_CONN_ID: + PBLOCK_ASSERT_CONN( pb ); + PBLOCK_LOCK_CONN( pb ); + pb->pb_conn->c_connid = *((long *)value); + PBLOCK_UNLOCK_CONN( pb ); + break; + case SLAPI_CONN_DN: + PBLOCK_ASSERT_CONN( pb ); + PBLOCK_LOCK_CONN( pb ); + rc = pblock_set_dn( value, &pb->pb_conn->c_dn, &pb->pb_conn->c_ndn, NULL ); + PBLOCK_UNLOCK_CONN( pb ); + break; + case SLAPI_RESULT_CODE: + case SLAPI_PLUGIN_INTOP_RESULT: + PBLOCK_ASSERT_OP( pb, 0 ); + pb->pb_rs->sr_err = *((int *)value); + break; + case SLAPI_RESULT_TEXT: + PBLOCK_ASSERT_OP( pb, 0 ); + snprintf( pb->pb_textbuf, sizeof( pb->pb_textbuf ), "%s", (char *)value ); + pb->pb_rs->sr_text = pb->pb_textbuf; + break; + case SLAPI_RESULT_MATCHED: + PBLOCK_ASSERT_OP( pb, 0 ); + pb->pb_rs->sr_matched = (char *)value; /* XXX should dup? */ + break; + case SLAPI_ADD_ENTRY: + PBLOCK_ASSERT_OP( pb, 0 ); + if ( pb->pb_op->o_tag == LDAP_REQ_ADD ) + pb->pb_op->ora_e = (Slapi_Entry *)value; + else + rc = PBLOCK_ERROR; + break; + case SLAPI_MODIFY_MODS: { + Modifications **mlp; + Modifications *newmods; + + PBLOCK_ASSERT_OP( pb, 0 ); + rc = pblock_set_default( pb, param, value ); + if ( rc != PBLOCK_SUCCESS ) { + break; + } + + if ( pb->pb_op->o_tag == LDAP_REQ_MODIFY ) { + mlp = &pb->pb_op->orm_modlist; + } else if ( pb->pb_op->o_tag == LDAP_REQ_ADD ) { + mlp = &pb->pb_op->ora_modlist; + } else if ( pb->pb_op->o_tag == LDAP_REQ_MODRDN ) { + mlp = &pb->pb_op->orr_modlist; + } else { + break; + } + + newmods = slapi_int_ldapmods2modifications( pb->pb_op, (LDAPMod **)value ); + if ( newmods != NULL ) { + slap_mods_free( *mlp, 1 ); + *mlp = newmods; + } + break; + } + case SLAPI_MODRDN_NEWRDN: + PBLOCK_ASSERT_OP( pb, 0 ); + PBLOCK_VALIDATE_IS_INTOP( pb ); + if ( pb->pb_op->o_tag == LDAP_REQ_MODRDN ) { + rc = pblock_set_dn( value, &pb->pb_op->orr_newrdn, &pb->pb_op->orr_nnewrdn, pb->pb_op->o_tmpmemctx ); + if ( rc == LDAP_SUCCESS ) + rc = rdn_validate( &pb->pb_op->orr_nnewrdn ); + } else { + rc = PBLOCK_ERROR; + } + break; + case SLAPI_MODRDN_NEWSUPERIOR: + PBLOCK_ASSERT_OP( pb, 0 ); + PBLOCK_VALIDATE_IS_INTOP( pb ); + if ( pb->pb_op->o_tag == LDAP_REQ_MODRDN ) { + if ( value == NULL ) { + if ( pb->pb_op->orr_newSup != NULL ) { + pb->pb_op->o_tmpfree( pb->pb_op->orr_newSup, pb->pb_op->o_tmpmemctx ); + BER_BVZERO( pb->pb_op->orr_newSup ); + pb->pb_op->orr_newSup = NULL; + } + if ( pb->pb_op->orr_newSup != NULL ) { + pb->pb_op->o_tmpfree( pb->pb_op->orr_nnewSup, pb->pb_op->o_tmpmemctx ); + BER_BVZERO( pb->pb_op->orr_nnewSup ); + pb->pb_op->orr_nnewSup = NULL; + } + } else { + if ( pb->pb_op->orr_newSup == NULL ) { + pb->pb_op->orr_newSup = (struct berval *)pb->pb_op->o_tmpalloc( + sizeof(struct berval), pb->pb_op->o_tmpmemctx ); + BER_BVZERO( pb->pb_op->orr_newSup ); + } + if ( pb->pb_op->orr_nnewSup == NULL ) { + pb->pb_op->orr_nnewSup = (struct berval *)pb->pb_op->o_tmpalloc( + sizeof(struct berval), pb->pb_op->o_tmpmemctx ); + BER_BVZERO( pb->pb_op->orr_nnewSup ); + } + rc = pblock_set_dn( value, pb->pb_op->orr_newSup, pb->pb_op->orr_nnewSup, pb->pb_op->o_tmpmemctx ); + } + } else { + rc = PBLOCK_ERROR; + } + break; + case SLAPI_MODRDN_DELOLDRDN: + PBLOCK_ASSERT_OP( pb, 0 ); + PBLOCK_VALIDATE_IS_INTOP( pb ); + if ( pb->pb_op->o_tag == LDAP_REQ_MODRDN ) + pb->pb_op->orr_deleteoldrdn = *((int *)value); + else + rc = PBLOCK_ERROR; + break; + case SLAPI_SEARCH_SCOPE: { + int scope = *((int *)value); + + PBLOCK_ASSERT_OP( pb, 0 ); + if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH ) { + switch ( *((int *)value) ) { + case LDAP_SCOPE_BASE: + case LDAP_SCOPE_ONELEVEL: + case LDAP_SCOPE_SUBTREE: + case LDAP_SCOPE_SUBORDINATE: + pb->pb_op->ors_scope = scope; + break; + default: + rc = PBLOCK_ERROR; + break; + } + } else { + rc = PBLOCK_ERROR; + } + break; + } + case SLAPI_SEARCH_DEREF: + PBLOCK_ASSERT_OP( pb, 0 ); + if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH ) + pb->pb_op->ors_deref = *((int *)value); + else + rc = PBLOCK_ERROR; + break; + case SLAPI_SEARCH_SIZELIMIT: + PBLOCK_ASSERT_OP( pb, 0 ); + if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH ) + pb->pb_op->ors_slimit = *((int *)value); + else + rc = PBLOCK_ERROR; + break; + case SLAPI_SEARCH_TIMELIMIT: + PBLOCK_ASSERT_OP( pb, 0 ); + if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH ) + pb->pb_op->ors_tlimit = *((int *)value); + else + rc = PBLOCK_ERROR; + break; + case SLAPI_SEARCH_FILTER: + PBLOCK_ASSERT_OP( pb, 0 ); + if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH ) + pb->pb_op->ors_filter = (Slapi_Filter *)value; + else + rc = PBLOCK_ERROR; + break; + case SLAPI_SEARCH_STRFILTER: + PBLOCK_ASSERT_OP( pb, 0 ); + if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH ) { + pb->pb_op->ors_filterstr.bv_val = (char *)value; + pb->pb_op->ors_filterstr.bv_len = strlen((char *)value); + } else { + rc = PBLOCK_ERROR; + } + break; + case SLAPI_SEARCH_ATTRS: { + AttributeName *an = NULL; + size_t i = 0, j = 0; + char **attrs = (char **)value; + + PBLOCK_ASSERT_OP( pb, 0 ); + PBLOCK_VALIDATE_IS_INTOP( pb ); + + if ( pb->pb_op->o_tag != LDAP_REQ_SEARCH ) { + rc = PBLOCK_ERROR; + break; + } + /* also set mapped attrs */ + rc = pblock_set_default( pb, param, value ); + if ( rc != PBLOCK_SUCCESS ) { + break; + } + if ( pb->pb_op->ors_attrs != NULL ) { + pb->pb_op->o_tmpfree( pb->pb_op->ors_attrs, pb->pb_op->o_tmpmemctx ); + pb->pb_op->ors_attrs = NULL; + } + if ( attrs != NULL ) { + for ( i = 0; attrs[i] != NULL; i++ ) + ; + } + if ( i ) { + an = (AttributeName *)pb->pb_op->o_tmpcalloc( i + 1, + sizeof(AttributeName), pb->pb_op->o_tmpmemctx ); + for ( i = 0; attrs[i] != NULL; i++ ) { + an[j].an_desc = NULL; + an[j].an_oc = NULL; + an[j].an_flags = 0; + an[j].an_name.bv_val = attrs[i]; + an[j].an_name.bv_len = strlen( attrs[i] ); + if ( slap_bv2ad( &an[j].an_name, &an[j].an_desc, &pb->pb_rs->sr_text ) == LDAP_SUCCESS ) { + j++; + } + } + an[j].an_name.bv_val = NULL; + an[j].an_name.bv_len = 0; + } + pb->pb_op->ors_attrs = an; + break; + } + case SLAPI_SEARCH_ATTRSONLY: + PBLOCK_ASSERT_OP( pb, 0 ); + PBLOCK_VALIDATE_IS_INTOP( pb ); + + if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH ) + pb->pb_op->ors_attrsonly = *((int *)value); + else + rc = PBLOCK_ERROR; + break; + case SLAPI_SEARCH_RESULT_ENTRY: + PBLOCK_ASSERT_OP( pb, 0 ); + rs_replace_entry( pb->pb_op, pb->pb_rs, NULL, (Slapi_Entry *)value ); + /* TODO: Should REP_ENTRY_MODIFIABLE be set? */ + pb->pb_rs->sr_flags |= REP_ENTRY_MUSTBEFREED; + break; + case SLAPI_BIND_RET_SASLCREDS: + PBLOCK_ASSERT_OP( pb, 0 ); + pb->pb_rs->sr_sasldata = (struct berval *)value; + break; + case SLAPI_EXT_OP_REQ_OID: + PBLOCK_ASSERT_OP( pb, 0 ); + PBLOCK_VALIDATE_IS_INTOP( pb ); + + if ( pb->pb_op->o_tag == LDAP_REQ_EXTENDED ) { + pb->pb_op->ore_reqoid.bv_val = (char *)value; + pb->pb_op->ore_reqoid.bv_len = strlen((char *)value); + } else { + rc = PBLOCK_ERROR; + } + break; + case SLAPI_EXT_OP_REQ_VALUE: + PBLOCK_ASSERT_OP( pb, 0 ); + PBLOCK_VALIDATE_IS_INTOP( pb ); + + if ( pb->pb_op->o_tag == LDAP_REQ_EXTENDED ) + pb->pb_op->ore_reqdata = (struct berval *)value; + else + rc = PBLOCK_ERROR; + break; + case SLAPI_EXT_OP_RET_OID: + PBLOCK_ASSERT_OP( pb, 0 ); + pb->pb_rs->sr_rspoid = (char *)value; + break; + case SLAPI_EXT_OP_RET_VALUE: + PBLOCK_ASSERT_OP( pb, 0 ); + pb->pb_rs->sr_rspdata = (struct berval *)value; + break; + case SLAPI_BIND_METHOD: + PBLOCK_ASSERT_OP( pb, 0 ); + PBLOCK_VALIDATE_IS_INTOP( pb ); + + if ( pb->pb_op->o_tag == LDAP_REQ_BIND ) + pb->pb_op->orb_method = *((int *)value); + else + rc = PBLOCK_ERROR; + break; + case SLAPI_BIND_CREDENTIALS: + PBLOCK_ASSERT_OP( pb, 0 ); + PBLOCK_VALIDATE_IS_INTOP( pb ); + + if ( pb->pb_op->o_tag == LDAP_REQ_BIND ) + pb->pb_op->orb_cred = *((struct berval *)value); + else + rc = PBLOCK_ERROR; + break; + case SLAPI_COMPARE_TYPE: + PBLOCK_ASSERT_OP( pb, 0 ); + PBLOCK_VALIDATE_IS_INTOP( pb ); + + if ( pb->pb_op->o_tag == LDAP_REQ_COMPARE ) { + const char *text; + + pb->pb_op->orc_ava->aa_desc = NULL; + rc = slap_str2ad( (char *)value, &pb->pb_op->orc_ava->aa_desc, &text ); + } else { + rc = PBLOCK_ERROR; + } + break; + case SLAPI_COMPARE_VALUE: + PBLOCK_ASSERT_OP( pb, 0 ); + PBLOCK_VALIDATE_IS_INTOP( pb ); + + if ( pb->pb_op->o_tag == LDAP_REQ_COMPARE ) + pb->pb_op->orc_ava->aa_value = *((struct berval *)value); + else + rc = PBLOCK_ERROR; + break; + case SLAPI_ABANDON_MSGID: + PBLOCK_ASSERT_OP( pb, 0 ); + PBLOCK_VALIDATE_IS_INTOP( pb ); + + if ( pb->pb_op->o_tag == LDAP_REQ_ABANDON) + pb->pb_op->orn_msgid = *((int *)value); + else + rc = PBLOCK_ERROR; + break; + case SLAPI_REQUESTOR_ISROOT: + case SLAPI_IS_REPLICATED_OPERATION: + case SLAPI_CONN_AUTHTYPE: + case SLAPI_CONN_AUTHMETHOD: + case SLAPI_IS_INTERNAL_OPERATION: + case SLAPI_X_CONN_IS_UDP: + case SLAPI_CONN_CLIENTIP: + case SLAPI_X_CONN_CLIENTPATH: + case SLAPI_CONN_SERVERIP: + case SLAPI_X_CONN_SERVERPATH: + case SLAPI_X_ADD_STRUCTURAL_CLASS: + /* These parameters cannot be set */ + rc = PBLOCK_ERROR; + break; + default: + rc = pblock_set_default( pb, param, value ); + break; + } + + pblock_unlock( pb ); + + return rc; +} + +static void +pblock_clear( Slapi_PBlock *pb ) +{ + pb->pb_nParams = 1; +} + +static int +pblock_delete_param( Slapi_PBlock *p, int param ) +{ + int i; + + pblock_lock(p); + + for ( i = 0; i < p->pb_nParams; i++ ) { + if ( p->pb_params[i] == param ) { + break; + } + } + + if (i >= p->pb_nParams ) { + pblock_unlock( p ); + return PBLOCK_ERROR; + } + + /* move last parameter to index of deleted parameter */ + if ( p->pb_nParams > 1 ) { + p->pb_params[i] = p->pb_params[p->pb_nParams - 1]; + p->pb_values[i] = p->pb_values[p->pb_nParams - 1]; + } + p->pb_nParams--; + + pblock_unlock( p ); + + return PBLOCK_SUCCESS; +} + +Slapi_PBlock * +slapi_pblock_new(void) +{ + Slapi_PBlock *pb; + + pb = (Slapi_PBlock *) ch_calloc( 1, sizeof(Slapi_PBlock) ); + if ( pb != NULL ) { + ldap_pvt_thread_mutex_init( &pb->pb_mutex ); + + pb->pb_params[0] = SLAPI_IBM_PBLOCK; + pb->pb_values[0].pv_pointer = NULL; + pb->pb_nParams = 1; + pb->pb_conn = NULL; + pb->pb_op = NULL; + pb->pb_rs = NULL; + pb->pb_intop = 0; + } + return pb; +} + +static void +pblock_destroy( Slapi_PBlock *pb ) +{ + LDAPControl **controls = NULL; + LDAPMod **mods = NULL; + char **attrs = NULL; + + assert( pb != NULL ); + + pblock_get_default( pb, SLAPI_RESCONTROLS, (void **)&controls ); + if ( controls != NULL ) { + ldap_controls_free( controls ); + } + + if ( pb->pb_intop ) { + slapi_int_connection_done_pb( pb ); + } else { + pblock_get_default( pb, SLAPI_MODIFY_MODS, (void **)&mods ); + ldap_mods_free( mods, 1 ); + + pblock_get_default( pb, SLAPI_SEARCH_ATTRS, (void **)&attrs ); + if ( attrs != NULL ) + pb->pb_op->o_tmpfree( attrs, pb->pb_op->o_tmpmemctx ); + } + + ldap_pvt_thread_mutex_destroy( &pb->pb_mutex ); + slapi_ch_free( (void **)&pb ); +} + +void +slapi_pblock_destroy( Slapi_PBlock *pb ) +{ + if ( pb != NULL ) { + pblock_destroy( pb ); + } +} + +int +slapi_pblock_get( Slapi_PBlock *pb, int arg, void *value ) +{ + return pblock_get( pb, arg, (void **)value ); +} + +int +slapi_pblock_set( Slapi_PBlock *pb, int arg, void *value ) +{ + return pblock_set( pb, arg, value ); +} + +void +slapi_pblock_clear( Slapi_PBlock *pb ) +{ + pblock_clear( pb ); +} + +int +slapi_pblock_delete_param( Slapi_PBlock *p, int param ) +{ + return pblock_delete_param( p, param ); +} + +/* + * OpenLDAP extension + */ +int +slapi_int_pblock_get_first( Backend *be, Slapi_PBlock **pb ) +{ + assert( pb != NULL ); + *pb = SLAPI_BACKEND_PBLOCK( be ); + return (*pb == NULL ? LDAP_OTHER : LDAP_SUCCESS); +} + +/* + * OpenLDAP extension + */ +int +slapi_int_pblock_get_next( Slapi_PBlock **pb ) +{ + assert( pb != NULL ); + return slapi_pblock_get( *pb, SLAPI_IBM_PBLOCK, pb ); +} + +#endif /* LDAP_SLAPI */ diff --git a/servers/slapd/slapi/slapi_utils.c b/servers/slapd/slapi/slapi_utils.c new file mode 100644 index 0000000..447fe2a --- /dev/null +++ b/servers/slapd/slapi/slapi_utils.c @@ -0,0 +1,3473 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2002-2021 The OpenLDAP Foundation. + * Portions Copyright 1997,2002-2003 IBM Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by IBM Corporation for use in + * IBM products and subsequently ported to OpenLDAP Software by + * Steve Omrani. Additional significant contributors include: + * Luke Howard + */ + +#include "portable.h" + +#include <ac/string.h> +#include <ac/stdarg.h> +#include <ac/ctype.h> +#include <ac/unistd.h> +#include <lutil.h> + +#include <slap.h> +#include <slapi.h> + +#include <netdb.h> + +#ifdef LDAP_SLAPI + +/* + * server start time (should we use a struct timeval also in slapd? + */ +static struct timeval base_time; +ldap_pvt_thread_mutex_t slapi_hn_mutex; +ldap_pvt_thread_mutex_t slapi_time_mutex; + +struct slapi_mutex { + ldap_pvt_thread_mutex_t mutex; +}; + +struct slapi_condvar { + ldap_pvt_thread_cond_t cond; + ldap_pvt_thread_mutex_t mutex; +}; + +static int checkBVString(const struct berval *bv) +{ + ber_len_t i; + + for ( i = 0; i < bv->bv_len; i++ ) { + if ( bv->bv_val[i] == '\0' ) + return 0; + } + if ( bv->bv_val[i] != '\0' ) + return 0; + + return 1; +} + +/* + * This function converts an array of pointers to berval objects to + * an array of berval objects. + */ + +int +bvptr2obj( + struct berval **bvptr, + BerVarray *bvobj, + unsigned *num ) +{ + int rc = LDAP_SUCCESS; + int i; + BerVarray tmpberval; + + if ( bvptr == NULL || *bvptr == NULL ) { + return LDAP_OTHER; + } + + for ( i = 0; bvptr != NULL && bvptr[i] != NULL; i++ ) { + ; /* EMPTY */ + } + if ( num ) + *num = i; + + tmpberval = (BerVarray)slapi_ch_malloc( (i + 1)*sizeof(struct berval)); + if ( tmpberval == NULL ) { + return LDAP_NO_MEMORY; + } + + for ( i = 0; bvptr[i] != NULL; i++ ) { + tmpberval[i].bv_val = bvptr[i]->bv_val; + tmpberval[i].bv_len = bvptr[i]->bv_len; + } + tmpberval[i].bv_val = NULL; + tmpberval[i].bv_len = 0; + + if ( rc == LDAP_SUCCESS ) { + *bvobj = tmpberval; + } + + return rc; +} + +Slapi_Entry * +slapi_str2entry( + char *s, + int flags ) +{ + return str2entry( s ); +} + +char * +slapi_entry2str( + Slapi_Entry *e, + int *len ) +{ + char *ret = NULL; + char *s; + + ldap_pvt_thread_mutex_lock( &entry2str_mutex ); + s = entry2str( e, len ); + if ( s != NULL ) + ret = slapi_ch_strdup( s ); + ldap_pvt_thread_mutex_unlock( &entry2str_mutex ); + + return ret; +} + +char * +slapi_entry_get_dn( Slapi_Entry *e ) +{ + return e->e_name.bv_val; +} + +int +slapi_x_entry_get_id( Slapi_Entry *e ) +{ + return e->e_id; +} + +static int +slapi_int_dn_pretty( struct berval *in, struct berval *out ) +{ + Syntax *syntax = slap_schema.si_syn_distinguishedName; + + assert( syntax != NULL ); + + return (syntax->ssyn_pretty)( syntax, in, out, NULL ); +} + +static int +slapi_int_dn_normalize( struct berval *in, struct berval *out ) +{ + MatchingRule *mr = slap_schema.si_mr_distinguishedNameMatch; + Syntax *syntax = slap_schema.si_syn_distinguishedName; + + assert( mr != NULL ); + + return (mr->smr_normalize)( 0, syntax, mr, in, out, NULL ); +} + +void +slapi_entry_set_dn( + Slapi_Entry *e, + char *ldn ) +{ + struct berval dn = BER_BVNULL; + + dn.bv_val = ldn; + dn.bv_len = strlen( ldn ); + + slapi_int_dn_pretty( &dn, &e->e_name ); + slapi_int_dn_normalize( &dn, &e->e_nname ); +} + +Slapi_Entry * +slapi_entry_dup( Slapi_Entry *e ) +{ + return entry_dup( e ); +} + +int +slapi_entry_attr_delete( + Slapi_Entry *e, + char *type ) +{ + AttributeDescription *ad = NULL; + const char *text; + + if ( slap_str2ad( type, &ad, &text ) != LDAP_SUCCESS ) { + return 1; /* LDAP_NO_SUCH_ATTRIBUTE */ + } + + if ( attr_delete( &e->e_attrs, ad ) == LDAP_SUCCESS ) { + return 0; /* attribute is deleted */ + } else { + return -1; /* something went wrong */ + } +} + +Slapi_Entry * +slapi_entry_alloc( void ) +{ + return (Slapi_Entry *)entry_alloc(); +} + +void +slapi_entry_free( Slapi_Entry *e ) +{ + if ( e != NULL ) + entry_free( e ); +} + +int +slapi_entry_attr_merge( + Slapi_Entry *e, + char *type, + struct berval **vals ) +{ + AttributeDescription *ad = NULL; + const char *text; + BerVarray bv; + int rc; + + rc = slap_str2ad( type, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + return -1; + } + + rc = bvptr2obj( vals, &bv, NULL ); + if ( rc != LDAP_SUCCESS ) { + return -1; + } + + rc = attr_merge_normalize( e, ad, bv, NULL ); + ch_free( bv ); + + return rc; +} + +int +slapi_entry_attr_find( + Slapi_Entry *e, + char *type, + Slapi_Attr **attr ) +{ + AttributeDescription *ad = NULL; + const char *text; + int rc; + + rc = slap_str2ad( type, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + return -1; + } + + *attr = attr_find( e->e_attrs, ad ); + if ( *attr == NULL ) { + return -1; + } + + return 0; +} + +char * +slapi_entry_attr_get_charptr( const Slapi_Entry *e, const char *type ) +{ + AttributeDescription *ad = NULL; + const char *text; + int rc; + Attribute *attr; + + rc = slap_str2ad( type, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + return NULL; + } + + attr = attr_find( e->e_attrs, ad ); + if ( attr == NULL ) { + return NULL; + } + + if ( attr->a_vals != NULL && attr->a_vals[0].bv_len != 0 ) { + const char *p; + + p = slapi_value_get_string( &attr->a_vals[0] ); + if ( p != NULL ) { + return slapi_ch_strdup( p ); + } + } + + return NULL; +} + +int +slapi_entry_attr_get_int( const Slapi_Entry *e, const char *type ) +{ + AttributeDescription *ad = NULL; + const char *text; + int rc; + Attribute *attr; + + rc = slap_str2ad( type, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + return 0; + } + + attr = attr_find( e->e_attrs, ad ); + if ( attr == NULL ) { + return 0; + } + + return slapi_value_get_int( attr->a_vals ); +} + +long +slapi_entry_attr_get_long( const Slapi_Entry *e, const char *type ) +{ + AttributeDescription *ad = NULL; + const char *text; + int rc; + Attribute *attr; + + rc = slap_str2ad( type, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + return 0; + } + + attr = attr_find( e->e_attrs, ad ); + if ( attr == NULL ) { + return 0; + } + + return slapi_value_get_long( attr->a_vals ); +} + +unsigned int +slapi_entry_attr_get_uint( const Slapi_Entry *e, const char *type ) +{ + AttributeDescription *ad = NULL; + const char *text; + int rc; + Attribute *attr; + + rc = slap_str2ad( type, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + return 0; + } + + attr = attr_find( e->e_attrs, ad ); + if ( attr == NULL ) { + return 0; + } + + return slapi_value_get_uint( attr->a_vals ); +} + +unsigned long +slapi_entry_attr_get_ulong( const Slapi_Entry *e, const char *type ) +{ + AttributeDescription *ad = NULL; + const char *text; + int rc; + Attribute *attr; + + rc = slap_str2ad( type, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + return 0; + } + + attr = attr_find( e->e_attrs, ad ); + if ( attr == NULL ) { + return 0; + } + + return slapi_value_get_ulong( attr->a_vals ); +} + +int +slapi_entry_attr_hasvalue( Slapi_Entry *e, const char *type, const char *value ) +{ + struct berval bv; + AttributeDescription *ad = NULL; + const char *text; + int rc; + Attribute *attr; + + rc = slap_str2ad( type, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + return 0; + } + + attr = attr_find( e->e_attrs, ad ); + if ( attr == NULL ) { + return 0; + } + + bv.bv_val = (char *)value; + bv.bv_len = strlen( value ); + + return ( slapi_attr_value_find( attr, &bv ) != -1 ); +} + +void +slapi_entry_attr_set_charptr(Slapi_Entry* e, const char *type, const char *value) +{ + AttributeDescription *ad = NULL; + const char *text; + int rc; + struct berval bv; + + rc = slap_str2ad( type, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + return; + } + + attr_delete ( &e->e_attrs, ad ); + if ( value != NULL ) { + bv.bv_val = (char *)value; + bv.bv_len = strlen(value); + attr_merge_normalize_one( e, ad, &bv, NULL ); + } +} + +void +slapi_entry_attr_set_int( Slapi_Entry* e, const char *type, int l) +{ + char buf[64]; + + snprintf( buf, sizeof( buf ), "%d", l ); + slapi_entry_attr_set_charptr( e, type, buf ); +} + +void +slapi_entry_attr_set_uint( Slapi_Entry* e, const char *type, unsigned int l) +{ + char buf[64]; + + snprintf( buf, sizeof( buf ), "%u", l ); + slapi_entry_attr_set_charptr( e, type, buf ); +} + +void +slapi_entry_attr_set_long(Slapi_Entry* e, const char *type, long l) +{ + char buf[64]; + + snprintf( buf, sizeof( buf ), "%ld", l ); + slapi_entry_attr_set_charptr( e, type, buf ); +} + +void +slapi_entry_attr_set_ulong(Slapi_Entry* e, const char *type, unsigned long l) +{ + char buf[64]; + + snprintf( buf, sizeof( buf ), "%lu", l ); + slapi_entry_attr_set_charptr( e, type, buf ); +} + +int +slapi_is_rootdse( const char *dn ) +{ + return ( dn == NULL || dn[0] == '\0' ); +} + +int +slapi_entry_has_children( const Slapi_Entry *e ) +{ + Slapi_PBlock *pb; + Backend *be = select_backend( (struct berval *)&e->e_nname, 0 ); + int rc, hasSubordinates = 0; + + if ( be == NULL || be->be_has_subordinates == 0 ) { + return 0; + } + + pb = slapi_pblock_new(); + if ( pb == NULL ) { + return 0; + } + slapi_int_connection_init_pb( pb, LDAP_REQ_SEARCH ); + + rc = slapi_pblock_set( pb, SLAPI_TARGET_DN, slapi_entry_get_dn( + (Entry *) e )); + if ( rc == LDAP_SUCCESS ) { + pb->pb_op->o_bd = be; + rc = be->be_has_subordinates( pb->pb_op, (Entry *) e, + &hasSubordinates ); + } + + slapi_pblock_destroy( pb ); + + return ( rc == LDAP_SUCCESS && hasSubordinates == LDAP_COMPARE_TRUE ); +} + +/* + * Return approximate size of the entry rounded to the nearest + * 1K. Only the size of the attribute values are counted in the + * Sun implementation. + * + * http://docs.sun.com/source/816-6701-10/funcref.html#1017388 + */ +size_t slapi_entry_size(Slapi_Entry *e) +{ + size_t size; + Attribute *a; + int i; + + for ( size = 0, a = e->e_attrs; a != NULL; a = a->a_next ) { + for ( i = 0; a->a_vals[i].bv_val != NULL; i++ ) { + size += a->a_vals[i].bv_len + 1; + } + } + + size += 1023; + size -= (size % 1024); + + return size; +} + +/* + * Add values to entry. + * + * Returns: + * LDAP_SUCCESS Values added to entry + * LDAP_TYPE_OR_VALUE_EXISTS One or more values exist in entry already + * LDAP_CONSTRAINT_VIOLATION Any other error (odd, but it's the spec) + */ +int +slapi_entry_add_values( Slapi_Entry *e, const char *type, struct berval **vals ) +{ + Modification mod; + const char *text; + int rc; + char textbuf[SLAP_TEXT_BUFLEN]; + + mod.sm_op = LDAP_MOD_ADD; + mod.sm_flags = 0; + mod.sm_desc = NULL; + mod.sm_type.bv_val = (char *)type; + mod.sm_type.bv_len = strlen( type ); + + rc = slap_str2ad( type, &mod.sm_desc, &text ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( vals == NULL ) { + /* Apparently vals can be NULL + * FIXME: sm_values = NULL ? */ + mod.sm_values = (BerVarray)ch_malloc( sizeof(struct berval) ); + mod.sm_values->bv_val = NULL; + mod.sm_numvals = 0; + + } else { + rc = bvptr2obj( vals, &mod.sm_values, &mod.sm_numvals ); + if ( rc != LDAP_SUCCESS ) { + return LDAP_CONSTRAINT_VIOLATION; + } + } + mod.sm_nvalues = NULL; + + rc = modify_add_values( e, &mod, 0, &text, textbuf, sizeof(textbuf) ); + + slapi_ch_free( (void **)&mod.sm_values ); + + return (rc == LDAP_SUCCESS) ? LDAP_SUCCESS : LDAP_CONSTRAINT_VIOLATION; +} + +int +slapi_entry_add_values_sv( Slapi_Entry *e, const char *type, Slapi_Value **vals ) +{ + return slapi_entry_add_values( e, type, vals ); +} + +int +slapi_entry_add_valueset(Slapi_Entry *e, const char *type, Slapi_ValueSet *vs) +{ + AttributeDescription *ad = NULL; + const char *text; + int rc; + + rc = slap_str2ad( type, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + return -1; + } + + return attr_merge_normalize( e, ad, *vs, NULL ); +} + +int +slapi_entry_delete_values( Slapi_Entry *e, const char *type, struct berval **vals ) +{ + Modification mod; + const char *text; + int rc; + char textbuf[SLAP_TEXT_BUFLEN]; + + mod.sm_op = LDAP_MOD_DELETE; + mod.sm_flags = 0; + mod.sm_desc = NULL; + mod.sm_type.bv_val = (char *)type; + mod.sm_type.bv_len = strlen( type ); + + if ( vals == NULL ) { + /* If vals is NULL, this is a NOOP. */ + return LDAP_SUCCESS; + } + + rc = slap_str2ad( type, &mod.sm_desc, &text ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( vals[0] == NULL ) { + /* SLAPI doco says LDApb_opERATIONS_ERROR but LDAP_OTHER is better */ + return attr_delete( &e->e_attrs, mod.sm_desc ) ? LDAP_OTHER : LDAP_SUCCESS; + } + + rc = bvptr2obj( vals, &mod.sm_values, &mod.sm_numvals ); + if ( rc != LDAP_SUCCESS ) { + return LDAP_CONSTRAINT_VIOLATION; + } + mod.sm_nvalues = NULL; + + rc = modify_delete_values( e, &mod, 0, &text, textbuf, sizeof(textbuf) ); + + slapi_ch_free( (void **)&mod.sm_values ); + + return rc; +} + +int +slapi_entry_delete_values_sv( Slapi_Entry *e, const char *type, Slapi_Value **vals ) +{ + return slapi_entry_delete_values( e, type, vals ); +} + +int +slapi_entry_merge_values_sv( Slapi_Entry *e, const char *type, Slapi_Value **vals ) +{ + return slapi_entry_attr_merge( e, (char *)type, vals ); +} + +int +slapi_entry_add_value(Slapi_Entry *e, const char *type, const Slapi_Value *value) +{ + AttributeDescription *ad = NULL; + int rc; + const char *text; + + rc = slap_str2ad( type, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + return -1; + } + + rc = attr_merge_normalize_one( e, ad, (Slapi_Value *)value, NULL ); + if ( rc != LDAP_SUCCESS ) { + return -1; + } + + return 0; +} + +int +slapi_entry_add_string(Slapi_Entry *e, const char *type, const char *value) +{ + Slapi_Value val; + + val.bv_val = (char *)value; + val.bv_len = strlen( value ); + + return slapi_entry_add_value( e, type, &val ); +} + +int +slapi_entry_delete_string(Slapi_Entry *e, const char *type, const char *value) +{ + Slapi_Value *vals[2]; + Slapi_Value val; + + val.bv_val = (char *)value; + val.bv_len = strlen( value ); + vals[0] = &val; + vals[1] = NULL; + + return slapi_entry_delete_values_sv( e, type, vals ); +} + +int +slapi_entry_attr_merge_sv( Slapi_Entry *e, const char *type, Slapi_Value **vals ) +{ + return slapi_entry_attr_merge( e, (char *)type, vals ); +} + +int +slapi_entry_first_attr( const Slapi_Entry *e, Slapi_Attr **attr ) +{ + if ( e == NULL ) { + return -1; + } + + *attr = e->e_attrs; + + return ( *attr != NULL ) ? 0 : -1; +} + +int +slapi_entry_next_attr( const Slapi_Entry *e, Slapi_Attr *prevattr, Slapi_Attr **attr ) +{ + if ( e == NULL ) { + return -1; + } + + if ( prevattr == NULL ) { + return -1; + } + + *attr = prevattr->a_next; + + return ( *attr != NULL ) ? 0 : -1; +} + +int +slapi_entry_attr_replace_sv( Slapi_Entry *e, const char *type, Slapi_Value **vals ) +{ + AttributeDescription *ad = NULL; + const char *text; + int rc; + BerVarray bv; + + rc = slap_str2ad( type, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + return 0; + } + + attr_delete( &e->e_attrs, ad ); + + rc = bvptr2obj( vals, &bv, NULL ); + if ( rc != LDAP_SUCCESS ) { + return -1; + } + + rc = attr_merge_normalize( e, ad, bv, NULL ); + slapi_ch_free( (void **)&bv ); + if ( rc != LDAP_SUCCESS ) { + return -1; + } + + return 0; +} + +/* + * FIXME -- The caller must free the allocated memory. + * In Netscape they do not have to. + */ +int +slapi_attr_get_values( + Slapi_Attr *attr, + struct berval ***vals ) +{ + int i, j; + struct berval **bv; + + if ( attr == NULL ) { + return 1; + } + + for ( i = 0; attr->a_vals[i].bv_val != NULL; i++ ) { + ; /* EMPTY */ + } + + bv = (struct berval **)ch_malloc( (i + 1) * sizeof(struct berval *) ); + for ( j = 0; j < i; j++ ) { + bv[j] = ber_dupbv( NULL, &attr->a_vals[j] ); + } + bv[j] = NULL; + + *vals = (struct berval **)bv; + + return 0; +} + +char * +slapi_dn_normalize( char *dn ) +{ + struct berval bdn; + struct berval pdn; + + assert( dn != NULL ); + + bdn.bv_val = dn; + bdn.bv_len = strlen( dn ); + + if ( slapi_int_dn_pretty( &bdn, &pdn ) != LDAP_SUCCESS ) { + return NULL; + } + + return pdn.bv_val; +} + +char * +slapi_dn_normalize_case( char *dn ) +{ + struct berval bdn; + struct berval ndn; + + assert( dn != NULL ); + + bdn.bv_val = dn; + bdn.bv_len = strlen( dn ); + + if ( slapi_int_dn_normalize( &bdn, &ndn ) != LDAP_SUCCESS ) { + return NULL; + } + + return ndn.bv_val; +} + +int +slapi_dn_issuffix( + char *dn, + char *suffix ) +{ + struct berval bdn, ndn; + struct berval bsuffix, nsuffix; + int rc; + + assert( dn != NULL ); + assert( suffix != NULL ); + + bdn.bv_val = dn; + bdn.bv_len = strlen( dn ); + + bsuffix.bv_val = suffix; + bsuffix.bv_len = strlen( suffix ); + + if ( dnNormalize( 0, NULL, NULL, &bdn, &ndn, NULL ) != LDAP_SUCCESS ) { + return 0; + } + + if ( dnNormalize( 0, NULL, NULL, &bsuffix, &nsuffix, NULL ) + != LDAP_SUCCESS ) + { + slapi_ch_free( (void **)&ndn.bv_val ); + return 0; + } + + rc = dnIsSuffix( &ndn, &nsuffix ); + + slapi_ch_free( (void **)&ndn.bv_val ); + slapi_ch_free( (void **)&nsuffix.bv_val ); + + return rc; +} + +int +slapi_dn_isparent( + const char *parentdn, + const char *childdn ) +{ + struct berval assertedParentDN, normalizedAssertedParentDN; + struct berval childDN, normalizedChildDN; + struct berval normalizedParentDN; + int match; + + assert( parentdn != NULL ); + assert( childdn != NULL ); + + assertedParentDN.bv_val = (char *)parentdn; + assertedParentDN.bv_len = strlen( parentdn ); + + if ( dnNormalize( 0, NULL, NULL, &assertedParentDN, + &normalizedAssertedParentDN, NULL ) != LDAP_SUCCESS ) + { + return 0; + } + + childDN.bv_val = (char *)childdn; + childDN.bv_len = strlen( childdn ); + + if ( dnNormalize( 0, NULL, NULL, &childDN, + &normalizedChildDN, NULL ) != LDAP_SUCCESS ) + { + slapi_ch_free( (void **)&normalizedAssertedParentDN.bv_val ); + return 0; + } + + dnParent( &normalizedChildDN, &normalizedParentDN ); + + if ( dnMatch( &match, 0, slap_schema.si_syn_distinguishedName, NULL, + &normalizedParentDN, (void *)&normalizedAssertedParentDN ) != LDAP_SUCCESS ) + { + match = -1; + } + + slapi_ch_free( (void **)&normalizedAssertedParentDN.bv_val ); + slapi_ch_free( (void **)&normalizedChildDN.bv_val ); + + return ( match == 0 ); +} + +/* + * Returns DN of the parent entry, or NULL if the DN is + * an empty string or NULL, or has no parent. + */ +char * +slapi_dn_parent( const char *_dn ) +{ + struct berval dn, prettyDN; + struct berval parentDN; + char *ret; + + if ( _dn == NULL ) { + return NULL; + } + + dn.bv_val = (char *)_dn; + dn.bv_len = strlen( _dn ); + + if ( dn.bv_len == 0 ) { + return NULL; + } + + if ( dnPretty( NULL, &dn, &prettyDN, NULL ) != LDAP_SUCCESS ) { + return NULL; + } + + dnParent( &prettyDN, &parentDN ); /* in-place */ + + if ( parentDN.bv_len == 0 ) { + slapi_ch_free_string( &prettyDN.bv_val ); + return NULL; + } + + ret = slapi_ch_strdup( parentDN.bv_val ); + slapi_ch_free_string( &prettyDN.bv_val ); + + return ret; +} + +int slapi_dn_isbesuffix( Slapi_PBlock *pb, char *ldn ) +{ + struct berval ndn; + Backend *be; + + if ( slapi_is_rootdse( ldn ) ) { + return 0; + } + + /* according to spec should already be normalized */ + ndn.bv_len = strlen( ldn ); + ndn.bv_val = ldn; + + be = select_backend( &pb->pb_op->o_req_ndn, 0 ); + if ( be == NULL ) { + return 0; + } + + return be_issuffix( be, &ndn ); +} + +/* + * Returns DN of the parent entry; or NULL if the DN is + * an empty string, if the DN has no parent, or if the + * DN is the suffix of the backend database + */ +char *slapi_dn_beparent( Slapi_PBlock *pb, const char *ldn ) +{ + Backend *be; + struct berval dn, prettyDN; + struct berval normalizedDN, parentDN; + char *parent = NULL; + + if ( pb == NULL ) { + return NULL; + } + + PBLOCK_ASSERT_OP( pb, 0 ); + + if ( slapi_is_rootdse( ldn ) ) { + return NULL; + } + + dn.bv_val = (char *)ldn; + dn.bv_len = strlen( ldn ); + + if ( dnPrettyNormal( NULL, &dn, &prettyDN, &normalizedDN, NULL ) != LDAP_SUCCESS ) { + return NULL; + } + + be = select_backend( &pb->pb_op->o_req_ndn, 0 ); + + if ( be == NULL || be_issuffix( be, &normalizedDN ) == 0 ) { + dnParent( &prettyDN, &parentDN ); + + if ( parentDN.bv_len != 0 ) + parent = slapi_ch_strdup( parentDN.bv_val ); + } + + slapi_ch_free_string( &prettyDN.bv_val ); + slapi_ch_free_string( &normalizedDN.bv_val ); + + return parent; +} + +char * +slapi_dn_ignore_case( char *dn ) +{ + return slapi_dn_normalize_case( dn ); +} + +char * +slapi_ch_malloc( unsigned long size ) +{ + return ch_malloc( size ); +} + +void +slapi_ch_free( void **ptr ) +{ + if ( ptr == NULL || *ptr == NULL ) + return; + ch_free( *ptr ); + *ptr = NULL; +} + +void +slapi_ch_free_string( char **ptr ) +{ + slapi_ch_free( (void **)ptr ); +} + +void +slapi_ch_array_free( char **arrayp ) +{ + char **p; + + if ( arrayp != NULL ) { + for ( p = arrayp; *p != NULL; p++ ) { + slapi_ch_free( (void **)p ); + } + slapi_ch_free( (void **)&arrayp ); + } +} + +struct berval * +slapi_ch_bvdup(const struct berval *v) +{ + return ber_dupbv(NULL, (struct berval *)v); +} + +struct berval ** +slapi_ch_bvecdup(const struct berval **v) +{ + int i; + struct berval **rv; + + if ( v == NULL ) { + return NULL; + } + + for ( i = 0; v[i] != NULL; i++ ) + ; + + rv = (struct berval **) slapi_ch_malloc( (i + 1) * sizeof(struct berval *) ); + + for ( i = 0; v[i] != NULL; i++ ) { + rv[i] = slapi_ch_bvdup( v[i] ); + } + rv[i] = NULL; + + return rv; +} + +char * +slapi_ch_calloc( + unsigned long nelem, + unsigned long size ) +{ + return ch_calloc( nelem, size ); +} + +char * +slapi_ch_realloc( + char *block, + unsigned long size ) +{ + return ch_realloc( block, size ); +} + +char * +slapi_ch_strdup( const char *s ) +{ + return ch_strdup( s ); +} + +size_t +slapi_ch_stlen( const char *s ) +{ + return strlen( s ); +} + +int +slapi_control_present( + LDAPControl **controls, + char *oid, + struct berval **val, + int *iscritical ) +{ + int i; + int rc = 0; + + if ( val ) { + *val = NULL; + } + + if ( iscritical ) { + *iscritical = 0; + } + + for ( i = 0; controls != NULL && controls[i] != NULL; i++ ) { + if ( strcmp( controls[i]->ldctl_oid, oid ) != 0 ) { + continue; + } + + rc = 1; + if ( controls[i]->ldctl_value.bv_len != 0 ) { + if ( val ) { + *val = &controls[i]->ldctl_value; + } + } + + if ( iscritical ) { + *iscritical = controls[i]->ldctl_iscritical; + } + + break; + } + + return rc; +} + +static void +slapControlMask2SlapiControlOp(slap_mask_t slap_mask, + unsigned long *slapi_mask) +{ + *slapi_mask = SLAPI_OPERATION_NONE; + + if ( slap_mask & SLAP_CTRL_ABANDON ) + *slapi_mask |= SLAPI_OPERATION_ABANDON; + + if ( slap_mask & SLAP_CTRL_ADD ) + *slapi_mask |= SLAPI_OPERATION_ADD; + + if ( slap_mask & SLAP_CTRL_BIND ) + *slapi_mask |= SLAPI_OPERATION_BIND; + + if ( slap_mask & SLAP_CTRL_COMPARE ) + *slapi_mask |= SLAPI_OPERATION_COMPARE; + + if ( slap_mask & SLAP_CTRL_DELETE ) + *slapi_mask |= SLAPI_OPERATION_DELETE; + + if ( slap_mask & SLAP_CTRL_MODIFY ) + *slapi_mask |= SLAPI_OPERATION_MODIFY; + + if ( slap_mask & SLAP_CTRL_RENAME ) + *slapi_mask |= SLAPI_OPERATION_MODDN; + + if ( slap_mask & SLAP_CTRL_SEARCH ) + *slapi_mask |= SLAPI_OPERATION_SEARCH; + + if ( slap_mask & SLAP_CTRL_UNBIND ) + *slapi_mask |= SLAPI_OPERATION_UNBIND; +} + +static void +slapiControlOp2SlapControlMask(unsigned long slapi_mask, + slap_mask_t *slap_mask) +{ + *slap_mask = 0; + + if ( slapi_mask & SLAPI_OPERATION_BIND ) + *slap_mask |= SLAP_CTRL_BIND; + + if ( slapi_mask & SLAPI_OPERATION_UNBIND ) + *slap_mask |= SLAP_CTRL_UNBIND; + + if ( slapi_mask & SLAPI_OPERATION_SEARCH ) + *slap_mask |= SLAP_CTRL_SEARCH; + + if ( slapi_mask & SLAPI_OPERATION_MODIFY ) + *slap_mask |= SLAP_CTRL_MODIFY; + + if ( slapi_mask & SLAPI_OPERATION_ADD ) + *slap_mask |= SLAP_CTRL_ADD; + + if ( slapi_mask & SLAPI_OPERATION_DELETE ) + *slap_mask |= SLAP_CTRL_DELETE; + + if ( slapi_mask & SLAPI_OPERATION_MODDN ) + *slap_mask |= SLAP_CTRL_RENAME; + + if ( slapi_mask & SLAPI_OPERATION_COMPARE ) + *slap_mask |= SLAP_CTRL_COMPARE; + + if ( slapi_mask & SLAPI_OPERATION_ABANDON ) + *slap_mask |= SLAP_CTRL_ABANDON; + + *slap_mask |= SLAP_CTRL_GLOBAL; +} + +static int +slapi_int_parse_control( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + /* Plugins must deal with controls themselves. */ + + return LDAP_SUCCESS; +} + +void +slapi_register_supported_control( + char *controloid, + unsigned long controlops ) +{ + slap_mask_t controlmask; + + slapiControlOp2SlapControlMask( controlops, &controlmask ); + + register_supported_control( controloid, controlmask, NULL, slapi_int_parse_control, NULL ); +} + +int +slapi_get_supported_controls( + char ***ctrloidsp, + unsigned long **ctrlopsp ) +{ + int i, rc; + + rc = get_supported_controls( ctrloidsp, (slap_mask_t **)ctrlopsp ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + for ( i = 0; (*ctrloidsp)[i] != NULL; i++ ) { + /* In place, naughty. */ + slapControlMask2SlapiControlOp( (*ctrlopsp)[i], &((*ctrlopsp)[i]) ); + } + + return LDAP_SUCCESS; +} + +LDAPControl * +slapi_dup_control( LDAPControl *ctrl ) +{ + LDAPControl *ret; + + ret = (LDAPControl *)slapi_ch_malloc( sizeof(*ret) ); + ret->ldctl_oid = slapi_ch_strdup( ctrl->ldctl_oid ); + ber_dupbv( &ret->ldctl_value, &ctrl->ldctl_value ); + ret->ldctl_iscritical = ctrl->ldctl_iscritical; + + return ret; +} + +void +slapi_register_supported_saslmechanism( char *mechanism ) +{ + /* FIXME -- can not add saslmechanism to OpenLDAP dynamically */ + slapi_log_error( SLAPI_LOG_FATAL, "slapi_register_supported_saslmechanism", + "OpenLDAP does not support dynamic registration of SASL mechanisms\n" ); +} + +char ** +slapi_get_supported_saslmechanisms( void ) +{ + /* FIXME -- can not get the saslmechanism without a connection. */ + slapi_log_error( SLAPI_LOG_FATAL, "slapi_get_supported_saslmechanisms", + "can not get the SASL mechanism list " + "without a connection\n" ); + return NULL; +} + +char ** +slapi_get_supported_extended_ops( void ) +{ + int i, j, k; + char **ppExtOpOID = NULL; + int numExtOps = 0; + + for ( i = 0; get_supported_extop( i ) != NULL; i++ ) { + ; + } + + for ( j = 0; slapi_int_get_supported_extop( j ) != NULL; j++ ) { + ; + } + + numExtOps = i + j; + if ( numExtOps == 0 ) { + return NULL; + } + + ppExtOpOID = (char **)slapi_ch_malloc( (numExtOps + 1) * sizeof(char *) ); + for ( k = 0; k < i; k++ ) { + struct berval *bv; + + bv = get_supported_extop( k ); + assert( bv != NULL ); + + ppExtOpOID[ k ] = bv->bv_val; + } + + for ( ; k < j; k++ ) { + struct berval *bv; + + bv = slapi_int_get_supported_extop( k ); + assert( bv != NULL ); + + ppExtOpOID[ i + k ] = bv->bv_val; + } + ppExtOpOID[ i + k ] = NULL; + + return ppExtOpOID; +} + +void +slapi_send_ldap_result( + Slapi_PBlock *pb, + int err, + char *matched, + char *text, + int nentries, + struct berval **urls ) +{ + SlapReply *rs; + + PBLOCK_ASSERT_OP( pb, 0 ); + + rs = pb->pb_rs; + + rs->sr_err = err; + rs->sr_matched = matched; + rs->sr_text = text; + rs->sr_ref = NULL; + + if ( err == LDAP_SASL_BIND_IN_PROGRESS ) { + send_ldap_sasl( pb->pb_op, rs ); + } else if ( rs->sr_rspoid != NULL ) { + send_ldap_extended( pb->pb_op, rs ); + } else { + if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH ) + rs->sr_nentries = nentries; + if ( urls != NULL ) + bvptr2obj( urls, &rs->sr_ref, NULL ); + + send_ldap_result( pb->pb_op, rs ); + + if ( urls != NULL ) + slapi_ch_free( (void **)&rs->sr_ref ); + } +} + +int +slapi_send_ldap_search_entry( + Slapi_PBlock *pb, + Slapi_Entry *e, + LDAPControl **ectrls, + char **attrs, + int attrsonly ) +{ + SlapReply rs = { REP_SEARCH }; + int i = 0, j = 0; + AttributeName *an = NULL; + const char *text; + int rc; + + assert( pb->pb_op != NULL ); + + if ( attrs != NULL ) { + for ( i = 0; attrs[ i ] != NULL; i++ ) { + ; /* empty */ + } + } + + if ( i ) { + an = (AttributeName *) slapi_ch_calloc( i + 1, sizeof(AttributeName) ); + for ( i = 0; attrs[i] != NULL; i++ ) { + an[j].an_name.bv_val = attrs[i]; + an[j].an_name.bv_len = strlen( attrs[i] ); + an[j].an_desc = NULL; + if ( slap_bv2ad( &an[j].an_name, &an[j].an_desc, &text ) == LDAP_SUCCESS) { + j++; + } + } + an[j].an_name.bv_len = 0; + an[j].an_name.bv_val = NULL; + } + + rs.sr_err = LDAP_SUCCESS; + rs.sr_matched = NULL; + rs.sr_text = NULL; + rs.sr_ref = NULL; + rs.sr_ctrls = ectrls; + rs.sr_attrs = an; + rs.sr_operational_attrs = NULL; + rs.sr_entry = e; + rs.sr_v2ref = NULL; + rs.sr_flags = 0; + + rc = send_search_entry( pb->pb_op, &rs ); + + slapi_ch_free( (void **)&an ); + + return rc; +} + +int +slapi_send_ldap_search_reference( + Slapi_PBlock *pb, + Slapi_Entry *e, + struct berval **references, + LDAPControl **ectrls, + struct berval **v2refs + ) +{ + SlapReply rs = { REP_SEARCHREF }; + int rc; + + rs.sr_err = LDAP_SUCCESS; + rs.sr_matched = NULL; + rs.sr_text = NULL; + + rc = bvptr2obj( references, &rs.sr_ref, NULL ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + rs.sr_ctrls = ectrls; + rs.sr_attrs = NULL; + rs.sr_operational_attrs = NULL; + rs.sr_entry = e; + + if ( v2refs != NULL ) { + rc = bvptr2obj( v2refs, &rs.sr_v2ref, NULL ); + if ( rc != LDAP_SUCCESS ) { + slapi_ch_free( (void **)&rs.sr_ref ); + return rc; + } + } else { + rs.sr_v2ref = NULL; + } + + rc = send_search_reference( pb->pb_op, &rs ); + + slapi_ch_free( (void **)&rs.sr_ref ); + slapi_ch_free( (void **)&rs.sr_v2ref ); + + return rc; +} + +Slapi_Filter * +slapi_str2filter( char *str ) +{ + return str2filter( str ); +} + +void +slapi_filter_free( + Slapi_Filter *f, + int recurse ) +{ + filter_free( f ); +} + +Slapi_Filter * +slapi_filter_dup( Slapi_Filter *filter ) +{ + return filter_dup( filter, NULL ); +} + +int +slapi_filter_get_choice( Slapi_Filter *f ) +{ + int rc; + + if ( f != NULL ) { + rc = f->f_choice; + } else { + rc = 0; + } + + return rc; +} + +int +slapi_filter_get_ava( + Slapi_Filter *f, + char **type, + struct berval **bval ) +{ + int ftype; + int rc = LDAP_SUCCESS; + + assert( type != NULL ); + assert( bval != NULL ); + + *type = NULL; + *bval = NULL; + + ftype = f->f_choice; + if ( ftype == LDAP_FILTER_EQUALITY + || ftype == LDAP_FILTER_GE + || ftype == LDAP_FILTER_LE + || ftype == LDAP_FILTER_APPROX ) { + /* + * According to the SLAPI Reference Manual these are + * not duplicated. + */ + *type = f->f_un.f_un_ava->aa_desc->ad_cname.bv_val; + *bval = &f->f_un.f_un_ava->aa_value; + } else { /* filter type not supported */ + rc = -1; + } + + return rc; +} + +Slapi_Filter * +slapi_filter_list_first( Slapi_Filter *f ) +{ + int ftype; + + if ( f == NULL ) { + return NULL; + } + + ftype = f->f_choice; + if ( ftype == LDAP_FILTER_AND + || ftype == LDAP_FILTER_OR + || ftype == LDAP_FILTER_NOT ) { + return (Slapi_Filter *)f->f_list; + } else { + return NULL; + } +} + +Slapi_Filter * +slapi_filter_list_next( + Slapi_Filter *f, + Slapi_Filter *fprev ) +{ + int ftype; + + if ( f == NULL ) { + return NULL; + } + + ftype = f->f_choice; + if ( ftype == LDAP_FILTER_AND + || ftype == LDAP_FILTER_OR + || ftype == LDAP_FILTER_NOT ) + { + return fprev->f_next; + } + + return NULL; +} + +int +slapi_filter_get_attribute_type( Slapi_Filter *f, char **type ) +{ + if ( f == NULL ) { + return -1; + } + + switch ( f->f_choice ) { + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_APPROX: + *type = f->f_av_desc->ad_cname.bv_val; + break; + case LDAP_FILTER_SUBSTRINGS: + *type = f->f_sub_desc->ad_cname.bv_val; + break; + case LDAP_FILTER_PRESENT: + *type = f->f_desc->ad_cname.bv_val; + break; + case LDAP_FILTER_EXT: + *type = f->f_mr_desc->ad_cname.bv_val; + break; + default: + /* Complex filters need not apply. */ + *type = NULL; + return -1; + } + + return 0; +} + +int +slapi_x_filter_set_attribute_type( Slapi_Filter *f, const char *type ) +{ + AttributeDescription **adp, *ad = NULL; + const char *text; + int rc; + + if ( f == NULL ) { + return -1; + } + + switch ( f->f_choice ) { + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_APPROX: + adp = &f->f_av_desc; + break; + case LDAP_FILTER_SUBSTRINGS: + adp = &f->f_sub_desc; + break; + case LDAP_FILTER_PRESENT: + adp = &f->f_desc; + break; + case LDAP_FILTER_EXT: + adp = &f->f_mr_desc; + break; + default: + /* Complex filters need not apply. */ + return -1; + } + + rc = slap_str2ad( type, &ad, &text ); + if ( rc == LDAP_SUCCESS ) + *adp = ad; + + return ( rc == LDAP_SUCCESS ) ? 0 : -1; +} + +int +slapi_filter_get_subfilt( Slapi_Filter *f, char **type, char **initial, + char ***any, char **final ) +{ + int i; + + if ( f->f_choice != LDAP_FILTER_SUBSTRINGS ) { + return -1; + } + + /* + * The caller shouldn't free but we can't return an + * array of char *s from an array of bervals without + * allocating memory, so we may as well be consistent. + * XXX + */ + *type = f->f_sub_desc->ad_cname.bv_val; + *initial = f->f_sub_initial.bv_val ? slapi_ch_strdup(f->f_sub_initial.bv_val) : NULL; + if ( f->f_sub_any != NULL ) { + for ( i = 0; f->f_sub_any[i].bv_val != NULL; i++ ) + ; + *any = (char **)slapi_ch_malloc( (i + 1) * sizeof(char *) ); + for ( i = 0; f->f_sub_any[i].bv_val != NULL; i++ ) { + (*any)[i] = slapi_ch_strdup(f->f_sub_any[i].bv_val); + } + (*any)[i] = NULL; + } else { + *any = NULL; + } + *final = f->f_sub_final.bv_val ? slapi_ch_strdup(f->f_sub_final.bv_val) : NULL; + + return 0; +} + +Slapi_Filter * +slapi_filter_join( int ftype, Slapi_Filter *f1, Slapi_Filter *f2 ) +{ + Slapi_Filter *f = NULL; + + if ( ftype == LDAP_FILTER_AND || + ftype == LDAP_FILTER_OR || + ftype == LDAP_FILTER_NOT ) + { + f = (Slapi_Filter *)slapi_ch_malloc( sizeof(*f) ); + f->f_choice = ftype; + f->f_list = f1; + f->f_list->f_next = f2; + f->f_next = NULL; + } + + return f; +} + +int +slapi_x_filter_append( int ftype, + Slapi_Filter **pContainingFilter, /* NULL on first call */ + Slapi_Filter **pNextFilter, + Slapi_Filter *filterToAppend ) +{ + if ( ftype == LDAP_FILTER_AND || + ftype == LDAP_FILTER_OR || + ftype == LDAP_FILTER_NOT ) + { + if ( *pContainingFilter == NULL ) { + *pContainingFilter = (Slapi_Filter *)slapi_ch_malloc( sizeof(Slapi_Filter) ); + (*pContainingFilter)->f_choice = ftype; + (*pContainingFilter)->f_list = filterToAppend; + (*pContainingFilter)->f_next = NULL; + } else { + if ( (*pContainingFilter)->f_choice != ftype ) { + /* Sanity check */ + return -1; + } + (*pNextFilter)->f_next = filterToAppend; + } + *pNextFilter = filterToAppend; + + return 0; + } + return -1; +} + +int +slapi_filter_test( Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Filter *f, + int verify_access ) +{ + Operation *op; + int rc; + + if ( f == NULL ) { + /* spec says return zero if no filter. */ + return 0; + } + + if ( verify_access ) { + op = pb->pb_op; + if ( op == NULL ) + return LDAP_PARAM_ERROR; + } else { + op = NULL; + } + + /* + * According to acl.c it is safe to call test_filter() with + * NULL arguments... + */ + rc = test_filter( op, e, f ); + switch (rc) { + case LDAP_COMPARE_TRUE: + rc = 0; + break; + case LDAP_COMPARE_FALSE: + break; + case SLAPD_COMPARE_UNDEFINED: + rc = LDAP_OTHER; + break; + case LDAP_PROTOCOL_ERROR: + /* filter type unknown: spec says return -1 */ + rc = -1; + break; + } + + return rc; +} + +int +slapi_filter_test_simple( Slapi_Entry *e, Slapi_Filter *f) +{ + return slapi_filter_test( NULL, e, f, 0 ); +} + +int +slapi_filter_apply( Slapi_Filter *f, FILTER_APPLY_FN fn, void *arg, int *error_code ) +{ + switch ( f->f_choice ) { + case LDAP_FILTER_AND: + case LDAP_FILTER_NOT: + case LDAP_FILTER_OR: { + int rc; + + /* + * FIXME: altering f; should we use a temporary? + */ + for ( f = f->f_list; f != NULL; f = f->f_next ) { + rc = slapi_filter_apply( f, fn, arg, error_code ); + if ( rc != 0 ) { + return rc; + } + if ( *error_code == SLAPI_FILTER_SCAN_NOMORE ) { + break; + } + } + break; + } + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_SUBSTRINGS: + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + case LDAP_FILTER_PRESENT: + case LDAP_FILTER_APPROX: + case LDAP_FILTER_EXT: + *error_code = fn( f, arg ); + break; + default: + *error_code = SLAPI_FILTER_UNKNOWN_FILTER_TYPE; + } + + if ( *error_code == SLAPI_FILTER_SCAN_NOMORE || + *error_code == SLAPI_FILTER_SCAN_CONTINUE ) { + return 0; + } + + return -1; +} + +int +slapi_pw_find( + struct berval **vals, + struct berval *v ) +{ + int i; + + if( ( vals == NULL ) || ( v == NULL ) ) + return 1; + + for ( i = 0; vals[i] != NULL; i++ ) { + if ( !lutil_passwd( vals[i], v, NULL, NULL ) ) + return 0; + } + + return 1; +} + +/* Get connected client IP address. + * + * The user must free the returned client IP after its use. + * Compatible with IBM Tivoli call. + * + * Errors: + * * LDAP_PARAM_ERROR - If the pb parameter is null. + * * LDAP_OPERATIONS_ERROR - If the API encounters error processing the request. + * * LDAP_NO_MEMORY - Failed to allocate required memory. + */ +int +slapi_get_client_ip(Slapi_PBlock *pb, char **clientIP) +{ + char *s = NULL; + + if(pb == NULL || pb->pb_conn == NULL) return(LDAP_PARAM_ERROR); + if((s = (char *) slapi_ch_malloc(pb->pb_conn->c_peer_name.bv_len + 1)) == NULL) { + return(LDAP_NO_MEMORY); + } + + memcpy(s, pb->pb_conn->c_peer_name.bv_val, pb->pb_conn->c_peer_name.bv_len); + + s[pb->pb_conn->c_peer_name.bv_len] = 0; + + *clientIP = s; + + return(LDAP_SUCCESS); +} + +/* Free previously allocated client IP address. */ +void +slapi_free_client_ip(char **clientIP) +{ + slapi_ch_free((void **) clientIP); +} + +#define MAX_HOSTNAME 512 + +char * +slapi_get_hostname( void ) +{ + char *hn = NULL; + static int been_here = 0; + static char *static_hn = NULL; + + ldap_pvt_thread_mutex_lock( &slapi_hn_mutex ); + if ( !been_here ) { + static_hn = (char *)slapi_ch_malloc( MAX_HOSTNAME ); + if ( static_hn == NULL) { + slapi_log_error( SLAPI_LOG_FATAL, "slapi_get_hostname", + "Cannot allocate memory for hostname\n" ); + static_hn = NULL; + ldap_pvt_thread_mutex_unlock( &slapi_hn_mutex ); + + return hn; + + } else { + if ( gethostname( static_hn, MAX_HOSTNAME ) != 0 ) { + slapi_log_error( SLAPI_LOG_FATAL, + "SLAPI", + "can't get hostname\n" ); + slapi_ch_free( (void **)&static_hn ); + static_hn = NULL; + ldap_pvt_thread_mutex_unlock( &slapi_hn_mutex ); + + return hn; + + } else { + been_here = 1; + } + } + } + ldap_pvt_thread_mutex_unlock( &slapi_hn_mutex ); + + hn = ch_strdup( static_hn ); + + return hn; +} + +/* + * FIXME: this should go in an appropriate header ... + */ +extern int slapi_int_log_error( int level, char *subsystem, char *fmt, va_list arglist ); + +int +slapi_log_error( + int severity, + char *subsystem, + char *fmt, + ... ) +{ + int rc = LDAP_SUCCESS; + va_list arglist; + + va_start( arglist, fmt ); + rc = slapi_int_log_error( severity, subsystem, fmt, arglist ); + va_end( arglist ); + + return rc; +} + + +unsigned long +slapi_timer_current_time( void ) +{ + static int first_time = 1; +#if !defined (_WIN32) + struct timeval now; + unsigned long ret; + + ldap_pvt_thread_mutex_lock( &slapi_time_mutex ); + if (first_time) { + first_time = 0; + gettimeofday( &base_time, NULL ); + } + gettimeofday( &now, NULL ); + ret = ( now.tv_sec - base_time.tv_sec ) * 1000000 + + (now.tv_usec - base_time.tv_usec); + ldap_pvt_thread_mutex_unlock( &slapi_time_mutex ); + + return ret; + + /* + * Ain't it better? + return (slap_get_time() - starttime) * 1000000; + */ +#else /* _WIN32 */ + LARGE_INTEGER now; + + if ( first_time ) { + first_time = 0; + performance_counter_present = QueryPerformanceCounter( &base_time ); + QueryPerformanceFrequency( &performance_freq ); + } + + if ( !performance_counter_present ) + return 0; + + QueryPerformanceCounter( &now ); + return (1000000*(now.QuadPart-base_time.QuadPart))/performance_freq.QuadPart; +#endif /* _WIN32 */ +} + +/* + * FIXME ? + */ +unsigned long +slapi_timer_get_time( char *label ) +{ + unsigned long start = slapi_timer_current_time(); + printf("%10ld %10d usec %s\n", start, 0, label); + return start; +} + +/* + * FIXME ? + */ +void +slapi_timer_elapsed_time( + char *label, + unsigned long start ) +{ + unsigned long stop = slapi_timer_current_time(); + printf ("%10ld %10ld usec %s\n", stop, stop - start, label); +} + +void +slapi_free_search_results_internal( Slapi_PBlock *pb ) +{ + Slapi_Entry **entries; + int k = 0, nEnt = 0; + + slapi_pblock_get( pb, SLAPI_NENTRIES, &nEnt ); + slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries ); + if ( nEnt == 0 || entries == NULL ) { + return; + } + + for ( k = 0; k < nEnt; k++ ) { + slapi_entry_free( entries[k] ); + entries[k] = NULL; + } + + slapi_ch_free( (void **)&entries ); +} + +int slapi_is_connection_ssl( Slapi_PBlock *pb, int *isSSL ) +{ + if ( pb == NULL ) + return LDAP_PARAM_ERROR; + + if ( pb->pb_conn == NULL ) + return LDAP_PARAM_ERROR; + +#ifdef HAVE_TLS + *isSSL = pb->pb_conn->c_is_tls; +#else + *isSSL = 0; +#endif + + return LDAP_SUCCESS; +} + +/* + * DS 5.x compatability API follow + */ + +int slapi_attr_get_flags( const Slapi_Attr *attr, unsigned long *flags ) +{ + AttributeType *at; + + if ( attr == NULL ) + return LDAP_PARAM_ERROR; + + at = attr->a_desc->ad_type; + + *flags = SLAPI_ATTR_FLAG_STD_ATTR; + + if ( is_at_single_value( at ) ) + *flags |= SLAPI_ATTR_FLAG_SINGLE; + if ( is_at_operational( at ) ) + *flags |= SLAPI_ATTR_FLAG_OPATTR; + if ( is_at_obsolete( at ) ) + *flags |= SLAPI_ATTR_FLAG_OBSOLETE; + if ( is_at_collective( at ) ) + *flags |= SLAPI_ATTR_FLAG_COLLECTIVE; + if ( is_at_no_user_mod( at ) ) + *flags |= SLAPI_ATTR_FLAG_NOUSERMOD; + + return LDAP_SUCCESS; +} + +int slapi_attr_flag_is_set( const Slapi_Attr *attr, unsigned long flag ) +{ + unsigned long flags; + + if ( slapi_attr_get_flags( attr, &flags ) != 0 ) + return 0; + return (flags & flag) ? 1 : 0; +} + +Slapi_Attr *slapi_attr_new( void ) +{ + Attribute *ad; + + ad = (Attribute *)slapi_ch_calloc( 1, sizeof(*ad) ); + + return ad; +} + +Slapi_Attr *slapi_attr_init( Slapi_Attr *a, const char *type ) +{ + const char *text; + AttributeDescription *ad = NULL; + + if( slap_str2ad( type, &ad, &text ) != LDAP_SUCCESS ) { + return NULL; + } + + a->a_desc = ad; + a->a_vals = NULL; + a->a_nvals = NULL; + a->a_next = NULL; + a->a_flags = 0; + + return a; +} + +void slapi_attr_free( Slapi_Attr **a ) +{ + attr_free( *a ); + *a = NULL; +} + +Slapi_Attr *slapi_attr_dup( const Slapi_Attr *attr ) +{ + return attr_dup( (Slapi_Attr *)attr ); +} + +int slapi_attr_add_value( Slapi_Attr *a, const Slapi_Value *v ) +{ + struct berval nval; + struct berval *nvalp; + int rc; + AttributeDescription *desc = a->a_desc; + + if ( desc->ad_type->sat_equality && + desc->ad_type->sat_equality->smr_normalize ) { + rc = (*desc->ad_type->sat_equality->smr_normalize)( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + desc->ad_type->sat_syntax, + desc->ad_type->sat_equality, + (Slapi_Value *)v, &nval, NULL ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + nvalp = &nval; + } else { + nvalp = NULL; + } + + rc = attr_valadd( a, (Slapi_Value *)v, nvalp, 1 ); + + if ( nvalp != NULL ) { + slapi_ch_free_string( &nval.bv_val ); + } + + return rc; +} + +int slapi_attr_type2plugin( const char *type, void **pi ) +{ + *pi = NULL; + + return LDAP_OTHER; +} + +int slapi_attr_get_type( const Slapi_Attr *attr, char **type ) +{ + if ( attr == NULL ) { + return LDAP_PARAM_ERROR; + } + + *type = attr->a_desc->ad_cname.bv_val; + + return LDAP_SUCCESS; +} + +int slapi_attr_get_oid_copy( const Slapi_Attr *attr, char **oidp ) +{ + if ( attr == NULL ) { + return LDAP_PARAM_ERROR; + } + *oidp = attr->a_desc->ad_type->sat_oid; + + return LDAP_SUCCESS; +} + +int slapi_attr_value_cmp( const Slapi_Attr *a, const struct berval *v1, const struct berval *v2 ) +{ + MatchingRule *mr; + int ret; + int rc; + const char *text; + + mr = a->a_desc->ad_type->sat_equality; + rc = value_match( &ret, a->a_desc, mr, + SLAP_MR_VALUE_OF_ASSERTION_SYNTAX, + (struct berval *)v1, (void *)v2, &text ); + if ( rc != LDAP_SUCCESS ) + return -1; + + return ( ret == 0 ) ? 0 : -1; +} + +int slapi_attr_value_find( const Slapi_Attr *a, struct berval *v ) +{ + int rc; + + if ( a ->a_vals == NULL ) { + return -1; + } + rc = attr_valfind( (Attribute *)a, SLAP_MR_VALUE_OF_ASSERTION_SYNTAX, v, + NULL, NULL ); + return rc == 0 ? 0 : -1; +} + +int slapi_attr_type_cmp( const char *t1, const char *t2, int opt ) +{ + AttributeDescription *a1 = NULL; + AttributeDescription *a2 = NULL; + const char *text; + int ret; + + if ( slap_str2ad( t1, &a1, &text ) != LDAP_SUCCESS ) { + return -1; + } + + if ( slap_str2ad( t2, &a2, &text ) != LDAP_SUCCESS ) { + return 1; + } + +#define ad_base_cmp(l,r) (((l)->ad_type->sat_cname.bv_len < (r)->ad_type->sat_cname.bv_len) \ + ? -1 : (((l)->ad_type->sat_cname.bv_len > (r)->ad_type->sat_cname.bv_len) \ + ? 1 : strcasecmp((l)->ad_type->sat_cname.bv_val, (r)->ad_type->sat_cname.bv_val ))) + + switch ( opt ) { + case SLAPI_TYPE_CMP_EXACT: + ret = ad_cmp( a1, a2 ); + break; + case SLAPI_TYPE_CMP_BASE: + ret = ad_base_cmp( a1, a2 ); + break; + case SLAPI_TYPE_CMP_SUBTYPE: + ret = is_ad_subtype( a2, a2 ); + break; + default: + ret = -1; + break; + } + + return ret; +} + +int slapi_attr_types_equivalent( const char *t1, const char *t2 ) +{ + return ( slapi_attr_type_cmp( t1, t2, SLAPI_TYPE_CMP_EXACT ) == 0 ); +} + +int slapi_attr_first_value( Slapi_Attr *a, Slapi_Value **v ) +{ + return slapi_valueset_first_value( &a->a_vals, v ); +} + +int slapi_attr_next_value( Slapi_Attr *a, int hint, Slapi_Value **v ) +{ + return slapi_valueset_next_value( &a->a_vals, hint, v ); +} + +int slapi_attr_get_numvalues( const Slapi_Attr *a, int *numValues ) +{ + *numValues = slapi_valueset_count( &a->a_vals ); + + return 0; +} + +int slapi_attr_get_valueset( const Slapi_Attr *a, Slapi_ValueSet **vs ) +{ + *vs = &((Slapi_Attr *)a)->a_vals; + + return 0; +} + +int slapi_attr_get_bervals_copy( Slapi_Attr *a, struct berval ***vals ) +{ + return slapi_attr_get_values( a, vals ); +} + +char *slapi_attr_syntax_normalize( const char *s ) +{ + AttributeDescription *ad = NULL; + const char *text; + + if ( slap_str2ad( s, &ad, &text ) != LDAP_SUCCESS ) { + return NULL; + } + + return ad->ad_cname.bv_val; +} + +Slapi_Value *slapi_value_new( void ) +{ + struct berval *bv; + + bv = (struct berval *)slapi_ch_malloc( sizeof(*bv) ); + + return bv; +} + +Slapi_Value *slapi_value_new_berval(const struct berval *bval) +{ + return ber_dupbv( NULL, (struct berval *)bval ); +} + +Slapi_Value *slapi_value_new_value(const Slapi_Value *v) +{ + return slapi_value_new_berval( v ); +} + +Slapi_Value *slapi_value_new_string(const char *s) +{ + struct berval bv; + + bv.bv_val = (char *)s; + bv.bv_len = strlen( s ); + + return slapi_value_new_berval( &bv ); +} + +Slapi_Value *slapi_value_init(Slapi_Value *val) +{ + val->bv_val = NULL; + val->bv_len = 0; + + return val; +} + +Slapi_Value *slapi_value_init_berval(Slapi_Value *v, struct berval *bval) +{ + return ber_dupbv( v, bval ); +} + +Slapi_Value *slapi_value_init_string(Slapi_Value *v, const char *s) +{ + v->bv_val = slapi_ch_strdup( s ); + v->bv_len = strlen( s ); + + return v; +} + +Slapi_Value *slapi_value_dup(const Slapi_Value *v) +{ + return slapi_value_new_value( v ); +} + +void slapi_value_free(Slapi_Value **value) +{ + if ( value == NULL ) { + return; + } + + if ( (*value) != NULL ) { + slapi_ch_free( (void **)&(*value)->bv_val ); + slapi_ch_free( (void **)value ); + } +} + +const struct berval *slapi_value_get_berval( const Slapi_Value *value ) +{ + return value; +} + +Slapi_Value *slapi_value_set_berval( Slapi_Value *value, const struct berval *bval ) +{ + if ( value == NULL ) { + return NULL; + } + if ( value->bv_val != NULL ) { + slapi_ch_free( (void **)&value->bv_val ); + } + slapi_value_init_berval( value, (struct berval *)bval ); + + return value; +} + +Slapi_Value *slapi_value_set_value( Slapi_Value *value, const Slapi_Value *vfrom) +{ + if ( value == NULL ) { + return NULL; + } + return slapi_value_set_berval( value, vfrom ); +} + +Slapi_Value *slapi_value_set( Slapi_Value *value, void *val, unsigned long len) +{ + if ( value == NULL ) { + return NULL; + } + if ( value->bv_val != NULL ) { + slapi_ch_free( (void **)&value->bv_val ); + } + value->bv_val = slapi_ch_malloc( len ); + value->bv_len = len; + AC_MEMCPY( value->bv_val, val, len ); + + return value; +} + +int slapi_value_set_string(Slapi_Value *value, const char *strVal) +{ + if ( value == NULL ) { + return -1; + } + slapi_value_set( value, (void *)strVal, strlen( strVal ) ); + return 0; +} + +int slapi_value_set_int(Slapi_Value *value, int intVal) +{ + char buf[64]; + + snprintf( buf, sizeof( buf ), "%d", intVal ); + + return slapi_value_set_string( value, buf ); +} + +const char *slapi_value_get_string(const Slapi_Value *value) +{ + if ( value == NULL ) return NULL; + if ( value->bv_val == NULL ) return NULL; + if ( !checkBVString( value ) ) return NULL; + + return value->bv_val; +} + +int slapi_value_get_int(const Slapi_Value *value) +{ + if ( value == NULL ) return 0; + if ( value->bv_val == NULL ) return 0; + if ( !checkBVString( value ) ) return 0; + + return (int)strtol( value->bv_val, NULL, 10 ); +} + +unsigned int slapi_value_get_uint(const Slapi_Value *value) +{ + if ( value == NULL ) return 0; + if ( value->bv_val == NULL ) return 0; + if ( !checkBVString( value ) ) return 0; + + return (unsigned int)strtoul( value->bv_val, NULL, 10 ); +} + +long slapi_value_get_long(const Slapi_Value *value) +{ + if ( value == NULL ) return 0; + if ( value->bv_val == NULL ) return 0; + if ( !checkBVString( value ) ) return 0; + + return strtol( value->bv_val, NULL, 10 ); +} + +unsigned long slapi_value_get_ulong(const Slapi_Value *value) +{ + if ( value == NULL ) return 0; + if ( value->bv_val == NULL ) return 0; + if ( !checkBVString( value ) ) return 0; + + return strtoul( value->bv_val, NULL, 10 ); +} + +size_t slapi_value_get_length(const Slapi_Value *value) +{ + if ( value == NULL ) + return 0; + + return (size_t) value->bv_len; +} + +int slapi_value_compare(const Slapi_Attr *a, const Slapi_Value *v1, const Slapi_Value *v2) +{ + return slapi_attr_value_cmp( a, v1, v2 ); +} + +/* A ValueSet is a container for a BerVarray. */ +Slapi_ValueSet *slapi_valueset_new( void ) +{ + Slapi_ValueSet *vs; + + vs = (Slapi_ValueSet *)slapi_ch_malloc( sizeof( *vs ) ); + *vs = NULL; + + return vs; +} + +void slapi_valueset_free(Slapi_ValueSet *vs) +{ + if ( vs != NULL ) { + BerVarray vp = *vs; + + ber_bvarray_free( vp ); + vp = NULL; + + slapi_ch_free( (void **)&vp ); + } +} + +void slapi_valueset_init(Slapi_ValueSet *vs) +{ + if ( vs != NULL && *vs == NULL ) { + *vs = (Slapi_ValueSet)slapi_ch_calloc( 1, sizeof(struct berval) ); + (*vs)->bv_val = NULL; + (*vs)->bv_len = 0; + } +} + +void slapi_valueset_done(Slapi_ValueSet *vs) +{ + BerVarray vp; + + if ( vs == NULL ) + return; + + for ( vp = *vs; vp->bv_val != NULL; vp++ ) { + vp->bv_len = 0; + slapi_ch_free( (void **)&vp->bv_val ); + } + /* but don't free *vs or vs */ +} + +void slapi_valueset_add_value(Slapi_ValueSet *vs, const Slapi_Value *addval) +{ + struct berval bv; + + ber_dupbv( &bv, (Slapi_Value *)addval ); + ber_bvarray_add( vs, &bv ); +} + +int slapi_valueset_first_value( Slapi_ValueSet *vs, Slapi_Value **v ) +{ + return slapi_valueset_next_value( vs, 0, v ); +} + +int slapi_valueset_next_value( Slapi_ValueSet *vs, int index, Slapi_Value **v) +{ + int i; + BerVarray vp; + + if ( vs == NULL ) + return -1; + + vp = *vs; + + for ( i = 0; vp[i].bv_val != NULL; i++ ) { + if ( i == index ) { + *v = &vp[i]; + return index + 1; + } + } + + return -1; +} + +int slapi_valueset_count( const Slapi_ValueSet *vs ) +{ + int i; + BerVarray vp; + + if ( vs == NULL ) + return 0; + + vp = *vs; + + if ( vp == NULL ) + return 0; + + for ( i = 0; vp[i].bv_val != NULL; i++ ) + ; + + return i; + +} + +void slapi_valueset_set_valueset(Slapi_ValueSet *vs1, const Slapi_ValueSet *vs2) +{ + BerVarray vp; + + for ( vp = *vs2; vp->bv_val != NULL; vp++ ) { + slapi_valueset_add_value( vs1, vp ); + } +} + +int slapi_access_allowed( Slapi_PBlock *pb, Slapi_Entry *e, char *attr, + struct berval *val, int access ) +{ + int rc; + slap_access_t slap_access; + AttributeDescription *ad = NULL; + const char *text; + + rc = slap_str2ad( attr, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + /* + * Whilst the SLAPI access types are arranged as a bitmask, the + * documentation indicates that they are to be used separately. + */ + switch ( access & SLAPI_ACL_ALL ) { + case SLAPI_ACL_COMPARE: + slap_access = ACL_COMPARE; + break; + case SLAPI_ACL_SEARCH: + slap_access = ACL_SEARCH; + break; + case SLAPI_ACL_READ: + slap_access = ACL_READ; + break; + case SLAPI_ACL_WRITE: + slap_access = ACL_WRITE; + break; + case SLAPI_ACL_DELETE: + slap_access = ACL_WDEL; + break; + case SLAPI_ACL_ADD: + slap_access = ACL_WADD; + break; + case SLAPI_ACL_SELF: /* not documented */ + case SLAPI_ACL_PROXY: /* not documented */ + default: + return LDAP_INSUFFICIENT_ACCESS; + break; + } + + assert( pb->pb_op != NULL ); + + if ( access_allowed( pb->pb_op, e, ad, val, slap_access, NULL ) ) { + return LDAP_SUCCESS; + } + + return LDAP_INSUFFICIENT_ACCESS; +} + +int slapi_acl_check_mods(Slapi_PBlock *pb, Slapi_Entry *e, LDAPMod **mods, char **errbuf) +{ + int rc = LDAP_SUCCESS; + Modifications *ml; + + if ( pb == NULL || pb->pb_op == NULL ) + return LDAP_PARAM_ERROR; + + ml = slapi_int_ldapmods2modifications( pb->pb_op, mods ); + if ( ml == NULL ) { + return LDAP_OTHER; + } + + if ( rc == LDAP_SUCCESS ) { + rc = acl_check_modlist( pb->pb_op, e, ml ) ? LDAP_SUCCESS : LDAP_INSUFFICIENT_ACCESS; + } + + slap_mods_free( ml, 1 ); + + return rc; +} + +/* + * Synthesise an LDAPMod array from a Modifications list to pass + * to SLAPI. + */ +LDAPMod **slapi_int_modifications2ldapmods( Modifications *modlist ) +{ + Modifications *ml; + LDAPMod **mods, *modp; + int i, j; + + for( i = 0, ml = modlist; ml != NULL; i++, ml = ml->sml_next ) + ; + + mods = (LDAPMod **)slapi_ch_malloc( (i + 1) * sizeof(LDAPMod *) ); + + for( i = 0, ml = modlist; ml != NULL; ml = ml->sml_next ) { + mods[i] = (LDAPMod *)slapi_ch_malloc( sizeof(LDAPMod) ); + modp = mods[i]; + modp->mod_op = ml->sml_op | LDAP_MOD_BVALUES; + if ( BER_BVISNULL( &ml->sml_type ) ) { + /* may happen for internally generated mods */ + assert( ml->sml_desc != NULL ); + modp->mod_type = slapi_ch_strdup( ml->sml_desc->ad_cname.bv_val ); + } else { + modp->mod_type = slapi_ch_strdup( ml->sml_type.bv_val ); + } + + if ( ml->sml_values != NULL ) { + for( j = 0; ml->sml_values[j].bv_val != NULL; j++ ) + ; + modp->mod_bvalues = (struct berval **)slapi_ch_malloc( (j + 1) * + sizeof(struct berval *) ); + for( j = 0; ml->sml_values[j].bv_val != NULL; j++ ) { + modp->mod_bvalues[j] = (struct berval *)slapi_ch_malloc( + sizeof(struct berval) ); + ber_dupbv( modp->mod_bvalues[j], &ml->sml_values[j] ); + } + modp->mod_bvalues[j] = NULL; + } else { + modp->mod_bvalues = NULL; + } + i++; + } + + mods[i] = NULL; + + return mods; +} + +/* + * Convert a potentially modified array of LDAPMods back to a + * Modification list. Unfortunately the values need to be + * duplicated because slap_mods_check() will try to free them + * before prettying (and we can't easily get out of calling + * slap_mods_check() because we need normalized values). + */ +Modifications *slapi_int_ldapmods2modifications ( Operation *op, LDAPMod **mods ) +{ + Modifications *modlist = NULL, **modtail; + LDAPMod **modp; + char textbuf[SLAP_TEXT_BUFLEN]; + const char *text; + + if ( mods == NULL ) { + return NULL; + } + + modtail = &modlist; + + for ( modp = mods; *modp != NULL; modp++ ) { + Modifications *mod; + LDAPMod *lmod = *modp; + int i; + const char *text; + AttributeDescription *ad = NULL; + + if ( slap_str2ad( lmod->mod_type, &ad, &text ) != LDAP_SUCCESS ) { + continue; + } + + mod = (Modifications *) slapi_ch_malloc( sizeof(Modifications) ); + mod->sml_op = lmod->mod_op & ~(LDAP_MOD_BVALUES); + mod->sml_flags = 0; + mod->sml_type = ad->ad_cname; + mod->sml_desc = ad; + mod->sml_next = NULL; + + i = 0; + if ( lmod->mod_op & LDAP_MOD_BVALUES ) { + if ( lmod->mod_bvalues != NULL ) { + while ( lmod->mod_bvalues[i] != NULL ) + i++; + } + } else { + if ( lmod->mod_values != NULL ) { + while ( lmod->mod_values[i] != NULL ) + i++; + } + } + mod->sml_numvals = i; + + if ( i == 0 ) { + mod->sml_values = NULL; + } else { + mod->sml_values = (BerVarray) slapi_ch_malloc( (i + 1) * sizeof(struct berval) ); + + /* NB: This implicitly trusts a plugin to return valid modifications. */ + if ( lmod->mod_op & LDAP_MOD_BVALUES ) { + for ( i = 0; lmod->mod_bvalues[i] != NULL; i++ ) { + ber_dupbv( &mod->sml_values[i], lmod->mod_bvalues[i] ); + } + } else { + for ( i = 0; lmod->mod_values[i] != NULL; i++ ) { + mod->sml_values[i].bv_val = slapi_ch_strdup( lmod->mod_values[i] ); + mod->sml_values[i].bv_len = strlen( lmod->mod_values[i] ); + } + } + mod->sml_values[i].bv_val = NULL; + mod->sml_values[i].bv_len = 0; + } + mod->sml_nvalues = NULL; + + *modtail = mod; + modtail = &mod->sml_next; + } + + if ( slap_mods_check( op, modlist, &text, textbuf, sizeof( textbuf ), NULL ) != LDAP_SUCCESS ) { + slap_mods_free( modlist, 1 ); + modlist = NULL; + } + + return modlist; +} + +/* + * Sun ONE DS 5.x computed attribute support. Computed attributes + * allow for dynamically generated operational attributes, a very + * useful thing indeed. + */ + +/* + * For some reason Sun don't use the normal plugin mechanism + * registration path to register an "evaluator" function (an + * "evaluator" is responsible for adding computed attributes; + * the nomenclature is somewhat confusing). + * + * As such slapi_compute_add_evaluator() registers the + * function directly. + */ +int slapi_compute_add_evaluator(slapi_compute_callback_t function) +{ + Slapi_PBlock *pPlugin = NULL; + int rc; + int type = SLAPI_PLUGIN_OBJECT; + + pPlugin = slapi_pblock_new(); + if ( pPlugin == NULL ) { + rc = LDAP_NO_MEMORY; + goto done; + } + + rc = slapi_pblock_set( pPlugin, SLAPI_PLUGIN_TYPE, (void *)&type ); + if ( rc != LDAP_SUCCESS ) { + goto done; + } + + rc = slapi_pblock_set( pPlugin, SLAPI_PLUGIN_COMPUTE_EVALUATOR_FN, (void *)function ); + if ( rc != LDAP_SUCCESS ) { + goto done; + } + + rc = slapi_int_register_plugin( frontendDB, pPlugin ); + if ( rc != 0 ) { + rc = LDAP_OTHER; + goto done; + } + +done: + if ( rc != LDAP_SUCCESS ) { + if ( pPlugin != NULL ) { + slapi_pblock_destroy( pPlugin ); + } + return -1; + } + + return 0; +} + +/* + * See notes above regarding slapi_compute_add_evaluator(). + */ +int slapi_compute_add_search_rewriter(slapi_search_rewrite_callback_t function) +{ + Slapi_PBlock *pPlugin = NULL; + int rc; + int type = SLAPI_PLUGIN_OBJECT; + + pPlugin = slapi_pblock_new(); + if ( pPlugin == NULL ) { + rc = LDAP_NO_MEMORY; + goto done; + } + + rc = slapi_pblock_set( pPlugin, SLAPI_PLUGIN_TYPE, (void *)&type ); + if ( rc != LDAP_SUCCESS ) { + goto done; + } + + rc = slapi_pblock_set( pPlugin, SLAPI_PLUGIN_COMPUTE_SEARCH_REWRITER_FN, (void *)function ); + if ( rc != LDAP_SUCCESS ) { + goto done; + } + + rc = slapi_int_register_plugin( frontendDB, pPlugin ); + if ( rc != 0 ) { + rc = LDAP_OTHER; + goto done; + } + +done: + if ( rc != LDAP_SUCCESS ) { + if ( pPlugin != NULL ) { + slapi_pblock_destroy( pPlugin ); + } + return -1; + } + + return 0; +} + +/* + * Call compute evaluators + */ +int compute_evaluator(computed_attr_context *c, char *type, Slapi_Entry *e, slapi_compute_output_t outputfn) +{ + int rc = 0; + slapi_compute_callback_t *pGetPlugin, *tmpPlugin; + + rc = slapi_int_get_plugins( frontendDB, SLAPI_PLUGIN_COMPUTE_EVALUATOR_FN, (SLAPI_FUNC **)&tmpPlugin ); + if ( rc != LDAP_SUCCESS || tmpPlugin == NULL ) { + /* Nothing to do; front-end should ignore. */ + return 0; + } + + for ( pGetPlugin = tmpPlugin; *pGetPlugin != NULL; pGetPlugin++ ) { + /* + * -1: no attribute matched requested type + * 0: one attribute matched + * >0: error happened + */ + rc = (*pGetPlugin)( c, type, e, outputfn ); + if ( rc > 0 ) { + break; + } + } + + slapi_ch_free( (void **)&tmpPlugin ); + + return rc; +} + +int +compute_rewrite_search_filter( Slapi_PBlock *pb ) +{ + if ( pb == NULL || pb->pb_op == NULL ) + return LDAP_PARAM_ERROR; + + return slapi_int_call_plugins( pb->pb_op->o_bd, SLAPI_PLUGIN_COMPUTE_SEARCH_REWRITER_FN, pb ); +} + +/* + * New API to provide the plugin with access to the search + * pblock. Have informed Sun DS team. + */ +int +slapi_x_compute_get_pblock(computed_attr_context *c, Slapi_PBlock **pb) +{ + if ( c == NULL ) + return -1; + + if ( c->cac_pb == NULL ) + return -1; + + *pb = c->cac_pb; + + return 0; +} + +Slapi_Mutex *slapi_new_mutex( void ) +{ + Slapi_Mutex *m; + + m = (Slapi_Mutex *)slapi_ch_malloc( sizeof(*m) ); + if ( ldap_pvt_thread_mutex_init( &m->mutex ) != 0 ) { + slapi_ch_free( (void **)&m ); + return NULL; + } + + return m; +} + +void slapi_destroy_mutex( Slapi_Mutex *mutex ) +{ + if ( mutex != NULL ) { + ldap_pvt_thread_mutex_destroy( &mutex->mutex ); + slapi_ch_free( (void **)&mutex); + } +} + +void slapi_lock_mutex( Slapi_Mutex *mutex ) +{ + ldap_pvt_thread_mutex_lock( &mutex->mutex ); +} + +int slapi_unlock_mutex( Slapi_Mutex *mutex ) +{ + return ldap_pvt_thread_mutex_unlock( &mutex->mutex ); +} + +Slapi_CondVar *slapi_new_condvar( Slapi_Mutex *mutex ) +{ + Slapi_CondVar *cv; + + if ( mutex == NULL ) { + return NULL; + } + + cv = (Slapi_CondVar *)slapi_ch_malloc( sizeof(*cv) ); + if ( ldap_pvt_thread_cond_init( &cv->cond ) != 0 ) { + slapi_ch_free( (void **)&cv ); + return NULL; + } + + cv->mutex = mutex->mutex; + + return cv; +} + +void slapi_destroy_condvar( Slapi_CondVar *cvar ) +{ + if ( cvar != NULL ) { + ldap_pvt_thread_cond_destroy( &cvar->cond ); + slapi_ch_free( (void **)&cvar ); + } +} + +int slapi_wait_condvar( Slapi_CondVar *cvar, struct timeval *timeout ) +{ + if ( cvar == NULL ) { + return -1; + } + + return ldap_pvt_thread_cond_wait( &cvar->cond, &cvar->mutex ); +} + +int slapi_notify_condvar( Slapi_CondVar *cvar, int notify_all ) +{ + if ( cvar == NULL ) { + return -1; + } + + if ( notify_all ) { + return ldap_pvt_thread_cond_broadcast( &cvar->cond ); + } + + return ldap_pvt_thread_cond_signal( &cvar->cond ); +} + +int slapi_int_access_allowed( Operation *op, + Entry *entry, + AttributeDescription *desc, + struct berval *val, + slap_access_t access, + AccessControlState *state ) +{ + int rc, slap_access = 0; + slapi_acl_callback_t *pGetPlugin, *tmpPlugin; + Slapi_PBlock *pb; + + pb = SLAPI_OPERATION_PBLOCK( op ); + if ( pb == NULL ) { + /* internal operation */ + return 1; + } + + switch ( access ) { + case ACL_COMPARE: + slap_access |= SLAPI_ACL_COMPARE; + break; + case ACL_SEARCH: + slap_access |= SLAPI_ACL_SEARCH; + break; + case ACL_READ: + slap_access |= SLAPI_ACL_READ; + break; + case ACL_WRITE: + slap_access |= SLAPI_ACL_WRITE; + break; + case ACL_WDEL: + slap_access |= SLAPI_ACL_DELETE; + break; + case ACL_WADD: + slap_access |= SLAPI_ACL_ADD; + break; + default: + break; + } + + rc = slapi_int_get_plugins( frontendDB, SLAPI_PLUGIN_ACL_ALLOW_ACCESS, (SLAPI_FUNC **)&tmpPlugin ); + if ( rc != LDAP_SUCCESS || tmpPlugin == NULL ) { + /* nothing to do; allowed access */ + return 1; + } + + rc = 1; /* default allow policy */ + + for ( pGetPlugin = tmpPlugin; *pGetPlugin != NULL; pGetPlugin++ ) { + /* + * 0 access denied + * 1 access granted + */ + rc = (*pGetPlugin)( pb, entry, desc->ad_cname.bv_val, + val, slap_access, (void *)state ); + if ( rc == 0 ) { + break; + } + } + + slapi_ch_free( (void **)&tmpPlugin ); + + return rc; +} + +/* + * There is no documentation for this. + */ +int slapi_rdn2typeval( char *rdn, char **type, struct berval *bv ) +{ + LDAPRDN lrdn; + LDAPAVA *ava; + int rc; + char *p; + + *type = NULL; + + bv->bv_len = 0; + bv->bv_val = NULL; + + rc = ldap_str2rdn( rdn, &lrdn, &p, LDAP_DN_FORMAT_LDAPV3 ); + if ( rc != LDAP_SUCCESS ) { + return -1; + } + + if ( lrdn[1] != NULL ) { + return -1; /* not single valued */ + } + + ava = lrdn[0]; + + *type = slapi_ch_strdup( ava->la_attr.bv_val ); + ber_dupbv( bv, &ava->la_value ); + + ldap_rdnfree(lrdn); + + return 0; +} + +char *slapi_dn_plus_rdn( const char *dn, const char *rdn ) +{ + struct berval new_dn, parent_dn, newrdn; + + new_dn.bv_val = NULL; + + parent_dn.bv_val = (char *)dn; + parent_dn.bv_len = strlen( dn ); + + newrdn.bv_val = (char *)rdn; + newrdn.bv_len = strlen( rdn ); + + build_new_dn( &new_dn, &parent_dn, &newrdn, NULL ); + + return new_dn.bv_val; +} + +int slapi_entry_schema_check( Slapi_PBlock *pb, Slapi_Entry *e ) +{ + Backend *be_orig; + const char *text; + char textbuf[SLAP_TEXT_BUFLEN] = { '\0' }; + size_t textlen = sizeof textbuf; + int rc = LDAP_SUCCESS; + + PBLOCK_ASSERT_OP( pb, 0 ); + + be_orig = pb->pb_op->o_bd; + + pb->pb_op->o_bd = select_backend( &e->e_nname, 0 ); + if ( pb->pb_op->o_bd != NULL ) { + rc = entry_schema_check( pb->pb_op, e, NULL, 0, 0, NULL, + &text, textbuf, textlen ); + } + pb->pb_op->o_bd = be_orig; + + return ( rc == LDAP_SUCCESS ) ? 0 : 1; +} + +int slapi_entry_rdn_values_present( const Slapi_Entry *e ) +{ + LDAPDN dn; + int rc; + int i = 0, match = 0; + + rc = ldap_bv2dn( &((Entry *)e)->e_name, &dn, LDAP_DN_FORMAT_LDAPV3 ); + if ( rc != LDAP_SUCCESS ) { + return 0; + } + + if ( dn[0] != NULL ) { + LDAPRDN rdn = dn[0]; + + for ( i = 0; rdn[i] != NULL; i++ ) { + LDAPAVA *ava = &rdn[0][i]; + Slapi_Attr *a = NULL; + + if ( slapi_entry_attr_find( (Slapi_Entry *)e, ava->la_attr.bv_val, &a ) == 0 && + slapi_attr_value_find( a, &ava->la_value ) == 0 ) + match++; + } + } + + ldap_dnfree( dn ); + + return ( i == match ); +} + +int slapi_entry_add_rdn_values( Slapi_Entry *e ) +{ + LDAPDN dn; + int i, rc; + + rc = ldap_bv2dn( &e->e_name, &dn, LDAP_DN_FORMAT_LDAPV3 ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( dn[0] != NULL ) { + LDAPRDN rdn = dn[0]; + struct berval *vals[2]; + + for ( i = 0; rdn[i] != NULL; i++ ) { + LDAPAVA *ava = &rdn[0][i]; + Slapi_Attr *a = NULL; + + if ( slapi_entry_attr_find( e, ava->la_attr.bv_val, &a ) == 0 && + slapi_attr_value_find( a, &ava->la_value ) == 0 ) + continue; + + vals[0] = &ava->la_value; + vals[1] = NULL; + + slapi_entry_attr_merge( e, ava->la_attr.bv_val, vals ); + } + } + + ldap_dnfree( dn ); + + return LDAP_SUCCESS; +} + +const char *slapi_entry_get_uniqueid( const Slapi_Entry *e ) +{ + Attribute *attr; + + attr = attr_find( e->e_attrs, slap_schema.si_ad_entryUUID ); + if ( attr == NULL ) { + return NULL; + } + + if ( attr->a_vals != NULL && attr->a_vals[0].bv_len != 0 ) { + return slapi_value_get_string( &attr->a_vals[0] ); + } + + return NULL; +} + +void slapi_entry_set_uniqueid( Slapi_Entry *e, char *uniqueid ) +{ + struct berval bv; + + attr_delete ( &e->e_attrs, slap_schema.si_ad_entryUUID ); + + bv.bv_val = uniqueid; + bv.bv_len = strlen( uniqueid ); + attr_merge_normalize_one( e, slap_schema.si_ad_entryUUID, &bv, NULL ); +} + +LDAP *slapi_ldap_init( char *ldaphost, int ldapport, int secure, int shared ) +{ + LDAP *ld; + char *url; + size_t size; + int rc; + + size = sizeof("ldap:///"); + if ( secure ) { + size++; + } + size += strlen( ldaphost ); + if ( ldapport != 0 ) { + size += 32; + } + + url = slapi_ch_malloc( size ); + + if ( ldapport != 0 ) { + rc = snprintf( url, size, "ldap%s://%s:%d/", ( secure ? "s" : "" ), ldaphost, ldapport ); + } else { + rc = snprintf( url, size, "ldap%s://%s/", ( secure ? "s" : "" ), ldaphost ); + } + + if ( rc > 0 && (size_t) rc < size ) { + rc = ldap_initialize( &ld, url ); + } else { + ld = NULL; + } + + slapi_ch_free_string( &url ); + + return ( rc == LDAP_SUCCESS ) ? ld : NULL; +} + +void slapi_ldap_unbind( LDAP *ld ) +{ + ldap_unbind_ext_s( ld, NULL, NULL ); +} + +int slapi_x_backend_get_flags( const Slapi_Backend *be, unsigned long *flags ) +{ + if ( be == NULL ) + return LDAP_PARAM_ERROR; + + *flags = SLAP_DBFLAGS(be); + + return LDAP_SUCCESS; +} + +int +slapi_int_count_controls( LDAPControl **ctrls ) +{ + size_t i; + + if ( ctrls == NULL ) + return 0; + + for ( i = 0; ctrls[i] != NULL; i++ ) + ; + + return i; +} + +int +slapi_op_abandoned( Slapi_PBlock *pb ) +{ + if ( pb->pb_op == NULL ) + return 0; + + return ( pb->pb_op->o_abandon ); +} + +char * +slapi_op_type_to_string(unsigned long type) +{ + char *str; + + switch (type) { + case SLAPI_OPERATION_BIND: + str = "bind"; + break; + case SLAPI_OPERATION_UNBIND: + str = "unbind"; + break; + case SLAPI_OPERATION_SEARCH: + str = "search"; + break; + case SLAPI_OPERATION_MODIFY: + str = "modify"; + break; + case SLAPI_OPERATION_ADD: + str = "add"; + break; + case SLAPI_OPERATION_DELETE: + str = "delete"; + break; + case SLAPI_OPERATION_MODDN: + str = "modrdn"; + break; + case SLAPI_OPERATION_COMPARE: + str = "compare"; + break; + case SLAPI_OPERATION_ABANDON: + str = "abandon"; + break; + case SLAPI_OPERATION_EXTENDED: + str = "extended"; + break; + default: + str = "unknown operation type"; + break; + } + return str; +} + +unsigned long +slapi_op_get_type(Slapi_Operation * op) +{ + unsigned long type; + + switch ( op->o_tag ) { + case LDAP_REQ_BIND: + type = SLAPI_OPERATION_BIND; + break; + case LDAP_REQ_UNBIND: + type = SLAPI_OPERATION_UNBIND; + break; + case LDAP_REQ_SEARCH: + type = SLAPI_OPERATION_SEARCH; + break; + case LDAP_REQ_MODIFY: + type = SLAPI_OPERATION_MODIFY; + break; + case LDAP_REQ_ADD: + type = SLAPI_OPERATION_ADD; + break; + case LDAP_REQ_DELETE: + type = SLAPI_OPERATION_DELETE; + break; + case LDAP_REQ_MODRDN: + type = SLAPI_OPERATION_MODDN; + break; + case LDAP_REQ_COMPARE: + type = SLAPI_OPERATION_COMPARE; + break; + case LDAP_REQ_ABANDON: + type = SLAPI_OPERATION_ABANDON; + break; + case LDAP_REQ_EXTENDED: + type = SLAPI_OPERATION_EXTENDED; + break; + default: + type = SLAPI_OPERATION_NONE; + break; + } + return type; +} + +void slapi_be_set_readonly( Slapi_Backend *be, int readonly ) +{ + if ( be == NULL ) + return; + + if ( readonly ) + be->be_restrictops |= SLAP_RESTRICT_OP_WRITES; + else + be->be_restrictops &= ~(SLAP_RESTRICT_OP_WRITES); +} + +int slapi_be_get_readonly( Slapi_Backend *be ) +{ + if ( be == NULL ) + return 0; + + return ( (be->be_restrictops & SLAP_RESTRICT_OP_WRITES) == SLAP_RESTRICT_OP_WRITES ); +} + +const char *slapi_x_be_get_updatedn( Slapi_Backend *be ) +{ + if ( be == NULL ) + return NULL; + + return be->be_update_ndn.bv_val; +} + +Slapi_Backend *slapi_be_select( const Slapi_DN *sdn ) +{ + Slapi_Backend *be; + + slapi_sdn_get_ndn( sdn ); + + be = select_backend( (struct berval *)&sdn->ndn, 0 ); + + return be; +} + +#if 0 +void +slapi_operation_set_flag(Slapi_Operation *op, unsigned long flag) +{ +} + +void +slapi_operation_clear_flag(Slapi_Operation *op, unsigned long flag) +{ +} + +int +slapi_operation_is_flag_set(Slapi_Operation *op, unsigned long flag) +{ +} +#endif + +#endif /* LDAP_SLAPI */ + diff --git a/servers/slapd/slapindex.c b/servers/slapd/slapindex.c new file mode 100644 index 0000000..0f22387 --- /dev/null +++ b/servers/slapd/slapindex.c @@ -0,0 +1,110 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * Portions Copyright 1998-2003 Kurt D. Zeilenga. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Kurt Zeilenga for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/socket.h> +#include <ac/unistd.h> + +#include "slapcommon.h" + +int +slapindex( int argc, char **argv ) +{ + ID id; + int rc = EXIT_SUCCESS; + const char *progname = "slapindex"; + AttributeDescription *ad, **adv = NULL; + + slap_tool_init( progname, SLAPINDEX, argc, argv ); + + if( !be->be_entry_open || + !be->be_entry_close || + !( be->be_entry_first || be->be_entry_first_x ) || + !be->be_entry_next || + !be->be_entry_reindex ) + { + fprintf( stderr, "%s: database doesn't support necessary operations.\n", + progname ); + exit( EXIT_FAILURE ); + } + + argc -= optind; + if ( argc > 0 ) { + const char *text; + int i; + + argv = &argv[optind]; + adv = (AttributeDescription **)argv; + + for (i = 0; i < argc; i++ ) { + ad = NULL; + rc = slap_str2ad( argv[i], &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + fprintf( stderr, "slap_str2ad(%s) failed %d (%s)\n", + argv[i], rc, ldap_err2string( rc )); + exit( EXIT_FAILURE ); + } + adv[i] = ad; + } + } + + if( be->be_entry_open( be, 0 ) != 0 ) { + fprintf( stderr, "%s: could not open database.\n", + progname ); + exit( EXIT_FAILURE ); + } + + if ( be->be_entry_first ) { + id = be->be_entry_first( be ); + + } else { + assert( be->be_entry_first_x != NULL ); + id = be->be_entry_first_x( be, NULL, LDAP_SCOPE_DEFAULT, NULL ); + } + + for ( ; id != NOID; id = be->be_entry_next( be ) ) { + int rtn; + + if( verbose ) { + printf("indexing id=%08lx\n", (long) id ); + } + + rtn = be->be_entry_reindex( be, id, adv ); + + if( rtn != LDAP_SUCCESS ) { + rc = EXIT_FAILURE; + if( continuemode ) continue; + break; + } + } + + (void) be->be_entry_close( be ); + + if ( slap_tool_destroy()) + rc = EXIT_FAILURE; + return( rc ); +} diff --git a/servers/slapd/slappasswd.c b/servers/slapd/slappasswd.c new file mode 100644 index 0000000..f58a219 --- /dev/null +++ b/servers/slapd/slappasswd.c @@ -0,0 +1,284 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * Portions Copyright 1998-2003 Kurt D. Zeilenga. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Kurt Zeilenga for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/ctype.h> +#include <ac/signal.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> +#include <ac/unistd.h> + +#include <ldap.h> +#include <lber_pvt.h> +#include <lutil.h> +#include <lutil_sha1.h> + +#include "ldap_defaults.h" +#include "slap.h" + +static int verbose = 0; +static char *modulepath = NULL; +static char *moduleload = NULL; + +static void +usage(const char *s) +{ + fprintf(stderr, + "Usage: %s [options]\n" + " -c format\tcrypt(3) salt format\n" + " -g\t\tgenerate random password\n" + " -h hash\tpassword scheme\n" + " -n\t\tomit trailing newline\n" + " -o <opt>[=val] specify an option with a(n optional) value\n" + " \tmodule-path=<pathspec>\n" + " \tmodule-load=<filename>\n" + " -s secret\tnew password\n" + " -u\t\tgenerate RFC2307 values (default)\n" + " -v\t\tincrease verbosity\n" + " -T file\tread file for new password\n" + , s ); + + exit( EXIT_FAILURE ); +} + +static int +parse_slappasswdopt( void ) +{ + size_t len = 0; + char *p; + + p = strchr( optarg, '=' ); + if ( p != NULL ) { + len = p - optarg; + p++; + } + + if ( strncasecmp( optarg, "module-path", len ) == 0 ) { + if ( modulepath ) + ch_free( modulepath ); + modulepath = ch_strdup( p ); + + } else if ( strncasecmp( optarg, "module-load", len ) == 0 ) { + if ( moduleload ) + ch_free( moduleload ); + moduleload = ch_strdup( p ); + + } else { + return -1; + } + + return 0; +} + +int +slappasswd( int argc, char *argv[] ) +{ + int rc = EXIT_SUCCESS; +#ifdef LUTIL_SHA1_BYTES + char *default_scheme = "{SSHA}"; +#else + char *default_scheme = "{SMD5}"; +#endif + char *scheme = default_scheme; + + char *newpw = NULL; + char *pwfile = NULL; + const char *text; + const char *progname = "slappasswd"; + + int i; + char *newline = "\n"; + struct berval passwd = BER_BVNULL; + struct berval hash; + +#ifdef LDAP_DEBUG + /* tools default to "none", so that at least LDAP_DEBUG_ANY + * messages show up; use -d 0 to reset */ + slap_debug = LDAP_DEBUG_NONE; +#endif + ldap_syslog = 0; + + while( (i = getopt( argc, argv, + "c:d:gh:no:s:T:vu" )) != EOF ) + { + switch (i) { + case 'c': /* crypt salt format */ + scheme = "{CRYPT}"; + lutil_salt_format( optarg ); + break; + + case 'g': /* new password (generate) */ + if ( pwfile != NULL ) { + fprintf( stderr, "Option -g incompatible with -T\n" ); + return EXIT_FAILURE; + + } else if ( newpw != NULL ) { + fprintf( stderr, "New password already provided\n" ); + return EXIT_FAILURE; + + } else if ( lutil_passwd_generate( &passwd, 8 )) { + fprintf( stderr, "Password generation failed\n" ); + return EXIT_FAILURE; + } + break; + + case 'h': /* scheme */ + if ( scheme != default_scheme ) { + fprintf( stderr, "Scheme already provided\n" ); + return EXIT_FAILURE; + + } else { + scheme = ch_strdup( optarg ); + } + break; + + case 'n': + newline = ""; + break; + + case 'o': + if ( parse_slappasswdopt() ) { + usage ( progname ); + } + break; + + case 's': /* new password (secret) */ + if ( pwfile != NULL ) { + fprintf( stderr, "Option -s incompatible with -T\n" ); + return EXIT_FAILURE; + + } else if ( newpw != NULL ) { + fprintf( stderr, "New password already provided\n" ); + return EXIT_FAILURE; + + } else { + char* p; + newpw = ch_strdup( optarg ); + + for( p = optarg; *p != '\0'; p++ ) { + *p = '\0'; + } + } + break; + + case 'T': /* password file */ + if ( pwfile != NULL ) { + fprintf( stderr, "Password file already provided\n" ); + return EXIT_FAILURE; + + } else if ( newpw != NULL ) { + fprintf( stderr, "Option -T incompatible with -s/-g\n" ); + return EXIT_FAILURE; + + } + pwfile = optarg; + break; + + case 'u': /* RFC2307 userPassword */ + break; + + case 'v': /* verbose */ + verbose++; + break; + + default: + usage ( progname ); + } + } + + if( argc - optind != 0 ) { + usage( progname ); + } + +#ifdef SLAPD_MODULES + if ( module_init() != 0 ) { + fprintf( stderr, "%s: module_init failed\n", progname ); + return EXIT_FAILURE; + } + + if ( modulepath && module_path(modulepath) ) { + rc = EXIT_FAILURE; + goto destroy; + } + + if ( moduleload && module_load(moduleload, 0, NULL) ) { + rc = EXIT_FAILURE; + goto destroy; + } +#endif + + if( pwfile != NULL ) { + if( lutil_get_filed_password( pwfile, &passwd )) { + rc = EXIT_FAILURE; + goto destroy; + } + } else if ( BER_BVISEMPTY( &passwd )) { + if( newpw == NULL ) { + /* prompt for new password */ + char *cknewpw; + newpw = ch_strdup(getpassphrase("New password: ")); + cknewpw = getpassphrase("Re-enter new password: "); + + if( strcmp( newpw, cknewpw )) { + fprintf( stderr, "Password values do not match\n" ); + rc = EXIT_FAILURE; + goto destroy; + } + } + + passwd.bv_val = newpw; + passwd.bv_len = strlen(passwd.bv_val); + } else { + hash = passwd; + goto print_pw; + } + + lutil_passwd_hash( &passwd, scheme, &hash, &text ); + if( hash.bv_val == NULL ) { + fprintf( stderr, + "Password generation failed for scheme %s: %s\n", + scheme, text ? text : "" ); + rc = EXIT_FAILURE; + goto destroy; + } + + if( lutil_passwd( &hash, &passwd, NULL, &text ) ) { + fprintf( stderr, "Password verification failed. %s\n", + text ? text : "" ); + rc = EXIT_FAILURE; + goto destroy; + } + +print_pw:; + printf( "%s%s" , hash.bv_val, newline ); + +destroy:; +#ifdef SLAPD_MODULES + module_kill(); +#endif + + return rc; +} diff --git a/servers/slapd/slapschema.c b/servers/slapd/slapschema.c new file mode 100644 index 0000000..9310796 --- /dev/null +++ b/servers/slapd/slapschema.c @@ -0,0 +1,165 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * Portions Copyright 1998-2003 Kurt D. Zeilenga. + * Portions Copyright 2003 IBM Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. Code portions borrowed from slapcat.c; + * contributors are Kurt Zeilenga and Jong Hyuk Choi + */ + +#include "portable.h" + +#include <stdio.h> + +#include "ac/stdlib.h" +#include "ac/ctype.h" +#include "ac/socket.h" +#include "ac/string.h" + +#include "slapcommon.h" +#include "ldif.h" + +static volatile sig_atomic_t gotsig; + +static RETSIGTYPE +slapcat_sig( int sig ) +{ + gotsig=1; +} + +int +slapschema( int argc, char **argv ) +{ + ID id; + int rc = EXIT_SUCCESS; + const char *progname = "slapschema"; + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation *op = NULL; + void *thrctx; + int requestBSF = 0; + int doBSF = 0; + + slap_tool_init( progname, SLAPCAT, argc, argv ); + + requestBSF = ( sub_ndn.bv_len || filter ); + +#ifdef SIGPIPE + (void) SIGNAL( SIGPIPE, slapcat_sig ); +#endif +#ifdef SIGHUP + (void) SIGNAL( SIGHUP, slapcat_sig ); +#endif + (void) SIGNAL( SIGINT, slapcat_sig ); + (void) SIGNAL( SIGTERM, slapcat_sig ); + + if( !be->be_entry_open || + !be->be_entry_close || + !( be->be_entry_first || be->be_entry_first_x ) || + !be->be_entry_next || + !be->be_entry_get ) + { + fprintf( stderr, "%s: database doesn't support necessary operations.\n", + progname ); + exit( EXIT_FAILURE ); + } + + if( be->be_entry_open( be, 0 ) != 0 ) { + fprintf( stderr, "%s: could not open database.\n", + progname ); + exit( EXIT_FAILURE ); + } + + thrctx = ldap_pvt_thread_pool_context(); + connection_fake_init( &conn, &opbuf, thrctx ); + op = &opbuf.ob_op; + op->o_tmpmemctx = NULL; + op->o_bd = be; + + + if ( !requestBSF && be->be_entry_first ) { + id = be->be_entry_first( be ); + + } else { + if ( be->be_entry_first_x ) { + id = be->be_entry_first_x( be, + sub_ndn.bv_len ? &sub_ndn : NULL, scope, filter ); + + } else { + assert( be->be_entry_first != NULL ); + doBSF = 1; + id = be->be_entry_first( be ); + } + } + + for ( ; id != NOID; id = be->be_entry_next( be ) ) { + Entry* e; + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof(textbuf); + const char *text = NULL; + + if ( gotsig ) + break; + + e = be->be_entry_get( be, id ); + if ( e == NULL ) { + printf("# no data for entry id=%08lx\n\n", (long) id ); + rc = EXIT_FAILURE; + if( continuemode ) continue; + break; + } + + if ( doBSF ) { + if ( sub_ndn.bv_len && !dnIsSuffixScope( &e->e_nname, &sub_ndn, scope ) ) + { + be_entry_release_r( op, e ); + continue; + } + + + if ( filter != NULL ) { + int rc = test_filter( NULL, e, filter ); + if ( rc != LDAP_COMPARE_TRUE ) { + be_entry_release_r( op, e ); + continue; + } + } + } + + if( verbose ) { + printf( "# id=%08lx\n", (long) id ); + } + + rc = entry_schema_check( op, e, NULL, 0, 0, NULL, + &text, textbuf, textlen ); + if ( rc != LDAP_SUCCESS ) { + fprintf( ldiffp->fp, "# (%d) %s%s%s\n", + rc, ldap_err2string( rc ), + text ? ": " : "", + text ? text : "" ); + fprintf( ldiffp->fp, "dn: %s\n\n", e->e_name.bv_val ); + } + + be_entry_release_r( op, e ); + } + + be->be_entry_close( be ); + + if ( slap_tool_destroy() ) + rc = EXIT_FAILURE; + + return rc; +} diff --git a/servers/slapd/slaptest.c b/servers/slapd/slaptest.c new file mode 100644 index 0000000..0a3a8bb --- /dev/null +++ b/servers/slapd/slaptest.c @@ -0,0 +1,120 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2021 The OpenLDAP Foundation. + * Portions Copyright 2004 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/socket.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <ac/unistd.h> +#include <ac/errno.h> + +#include <lber.h> +#include <ldif.h> +#include <lutil.h> + +#include "slapcommon.h" + +#ifndef S_IWRITE +#define S_IWRITE S_IWUSR +#endif + +static int +test_file( const char *fname, const char *ftype ) +{ + struct stat st; + int save_errno; + + switch ( stat( fname, &st ) ) { + case 0: + if ( !( st.st_mode & S_IWRITE ) ) { + Debug( LDAP_DEBUG_ANY, "%s file " + "\"%s\" exists, but user does not have access\n", + ftype, fname, 0 ); + return -1; + } + break; + + case -1: + default: + save_errno = errno; + if ( save_errno == ENOENT ) { + FILE *fp = fopen( fname, "w" ); + + if ( fp == NULL ) { + save_errno = errno; + + Debug( LDAP_DEBUG_ANY, "unable to open file " + "\"%s\": %d (%s)\n", + fname, + save_errno, strerror( save_errno ) ); + + return -1; + } + fclose( fp ); + unlink( fname ); + break; + } + + Debug( LDAP_DEBUG_ANY, "unable to stat file " + "\"%s\": %d (%s)\n", + slapd_pid_file, + save_errno, strerror( save_errno ) ); + return -1; + } + + return 0; +} + +int +slaptest( int argc, char **argv ) +{ + int rc = EXIT_SUCCESS; + const char *progname = "slaptest"; + + slap_tool_init( progname, SLAPTEST, argc, argv ); + + if ( slapd_pid_file != NULL ) { + if ( test_file( slapd_pid_file, "pid" ) ) { + return EXIT_FAILURE; + } + } + + if ( slapd_args_file != NULL ) { + if ( test_file( slapd_args_file, "args" ) ) { + return EXIT_FAILURE; + } + } + + if ( !quiet ) { + fprintf( stderr, "config file testing succeeded\n"); + } + + if ( slap_tool_destroy()) + rc = EXIT_FAILURE; + + return rc; +} diff --git a/servers/slapd/starttls.c b/servers/slapd/starttls.c new file mode 100644 index 0000000..993e04d --- /dev/null +++ b/servers/slapd/starttls.c @@ -0,0 +1,112 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "lber_pvt.h" + +const struct berval slap_EXOP_START_TLS = BER_BVC(LDAP_EXOP_START_TLS); + +#ifdef HAVE_TLS +int +starttls_extop ( Operation *op, SlapReply *rs ) +{ + int rc; + + Statslog( LDAP_DEBUG_STATS, "%s STARTTLS\n", + op->o_log_prefix, 0, 0, 0, 0 ); + + if ( op->ore_reqdata != NULL ) { + /* no request data should be provided */ + rs->sr_text = "no request data expected"; + return LDAP_PROTOCOL_ERROR; + } + + /* acquire connection lock */ + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + + /* can't start TLS if it is already started */ + if (op->o_conn->c_is_tls != 0) { + rs->sr_text = "TLS already started"; + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + /* can't start TLS if there are other op's around */ + if (( !LDAP_STAILQ_EMPTY(&op->o_conn->c_ops) && + (LDAP_STAILQ_FIRST(&op->o_conn->c_ops) != op || + LDAP_STAILQ_NEXT(op, o_next) != NULL)) || + ( !LDAP_STAILQ_EMPTY(&op->o_conn->c_pending_ops) )) + { + rs->sr_text = "cannot start TLS when operations are outstanding"; + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + if ( !( global_disallows & SLAP_DISALLOW_TLS_2_ANON ) && + ( op->o_conn->c_dn.bv_len != 0 ) ) + { + Statslog( LDAP_DEBUG_STATS, + "%s AUTHZ anonymous mech=starttls ssf=0\n", + op->o_log_prefix, 0, 0, 0, 0 ); + + /* force to anonymous */ + connection2anonymous( op->o_conn ); + } + + if ( ( global_disallows & SLAP_DISALLOW_TLS_AUTHC ) && + ( op->o_conn->c_dn.bv_len != 0 ) ) + { + rs->sr_text = "cannot start TLS after authentication"; + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + /* fail if TLS could not be initialized */ + if ( slap_tls_ctx == NULL ) { + if (default_referral != NULL) { + /* caller will put the referral in the result */ + rc = LDAP_REFERRAL; + goto done; + } + + rs->sr_text = "Could not initialize TLS"; + rc = LDAP_UNAVAILABLE; + goto done; + } + + op->o_conn->c_is_tls = 1; + op->o_conn->c_needs_tls_accept = 1; + + rc = LDAP_SUCCESS; + +done: + /* give up connection lock */ + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + + /* FIXME: RACE CONDITION! we give up lock before sending result + * Should be resolved by reworking connection state, not + * by moving send here (so as to ensure proper TLS sequencing) + */ + + return rc; +} + +#endif /* HAVE_TLS */ diff --git a/servers/slapd/str2filter.c b/servers/slapd/str2filter.c new file mode 100644 index 0000000..a8c187a --- /dev/null +++ b/servers/slapd/str2filter.c @@ -0,0 +1,84 @@ +/* str2filter.c - parse an RFC 4515 string filter */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/ctype.h> +#include <ac/socket.h> + +#include "slap.h" + + +Filter * +str2filter_x( Operation *op, const char *str ) +{ + int rc; + Filter *f = NULL; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + const char *text = NULL; + + Debug( LDAP_DEBUG_FILTER, "str2filter \"%s\"\n", str, 0, 0 ); + + if ( str == NULL || *str == '\0' ) { + return NULL; + } + + ber_init2( ber, NULL, LBER_USE_DER ); + if ( op->o_tmpmemctx ) { + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + } + + rc = ldap_pvt_put_filter( ber, str ); + if( rc < 0 ) { + goto done; + } + + ber_reset( ber, 1 ); + + rc = get_filter( op, ber, &f, &text ); + +done: + ber_free_buf( ber ); + + return f; +} + +Filter * +str2filter( const char *str ) +{ + Operation op = {0}; + Opheader ohdr = {0}; + + op.o_hdr = &ohdr; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + return str2filter_x( &op, str ); +} diff --git a/servers/slapd/syncrepl.c b/servers/slapd/syncrepl.c new file mode 100644 index 0000000..8198e8e --- /dev/null +++ b/servers/slapd/syncrepl.c @@ -0,0 +1,6046 @@ +/* syncrepl.c -- Replication Engine which uses the LDAP Sync protocol */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * Portions Copyright 2003 by IBM Corporation. + * Portions Copyright 2003-2008 by Howard Chu, Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "lutil.h" +#include "slap.h" +#include "lutil_ldap.h" + +#include "config.h" + +#include "ldap_rq.h" + +#ifdef ENABLE_REWRITE +#include "rewrite.h" +#define SUFFIXM_CTX "<suffix massage>" +#endif + +#define UUIDLEN 16 + +struct nonpresent_entry { + struct berval *npe_name; + struct berval *npe_nname; + LDAP_LIST_ENTRY(nonpresent_entry) npe_link; +}; + +typedef struct cookie_vals { + struct berval *cv_vals; + int *cv_sids; + int cv_num; +} cookie_vals; + +typedef struct cookie_state { + ldap_pvt_thread_mutex_t cs_mutex; + ldap_pvt_thread_cond_t cs_cond; + struct berval *cs_vals; + int *cs_sids; + int cs_num; + int cs_age; + int cs_ref; + int cs_updating; + + /* pending changes, not yet committed */ + ldap_pvt_thread_mutex_t cs_pmutex; + struct berval *cs_pvals; + int *cs_psids; + int cs_pnum; +} cookie_state; + +#define SYNCDATA_DEFAULT 0 /* entries are plain LDAP entries */ +#define SYNCDATA_ACCESSLOG 1 /* entries are accesslog format */ +#define SYNCDATA_CHANGELOG 2 /* entries are changelog format */ + +#define SYNCLOG_LOGGING 0 /* doing a log-based update */ +#define SYNCLOG_FALLBACK 1 /* doing a full refresh */ + +#define RETRYNUM_FOREVER (-1) /* retry forever */ +#define RETRYNUM_TAIL (-2) /* end of retrynum array */ +#define RETRYNUM_VALID(n) ((n) >= RETRYNUM_FOREVER) /* valid retrynum */ +#define RETRYNUM_FINITE(n) ((n) > RETRYNUM_FOREVER) /* not forever */ + +typedef struct syncinfo_s { + struct syncinfo_s *si_next; + BackendDB *si_be; + BackendDB *si_wbe; + struct re_s *si_re; + int si_rid; + char si_ridtxt[ STRLENOF("rid=999") + 1 ]; + slap_bindconf si_bindconf; + struct berval si_base; + struct berval si_logbase; + struct berval si_filterstr; + struct berval si_logfilterstr; + Filter *si_filter; + Filter *si_logfilter; + struct berval si_contextdn; + int si_scope; + int si_attrsonly; + char *si_anfile; + AttributeName *si_anlist; + AttributeName *si_exanlist; + char **si_attrs; + char **si_exattrs; + int si_allattrs; + int si_allopattrs; + int si_schemachecking; + int si_type; /* the active type */ + int si_ctype; /* the configured type */ + time_t si_interval; + time_t *si_retryinterval; + int *si_retrynum_init; + int *si_retrynum; + struct sync_cookie si_syncCookie; + cookie_state *si_cookieState; + int si_cookieAge; + int si_manageDSAit; + int si_slimit; + int si_tlimit; + int si_refreshDelete; + int si_refreshPresent; + int si_refreshDone; + int si_syncdata; + int si_logstate; + int si_got; + int si_strict_refresh; /* stop listening during fallback refresh */ + int si_too_old; + int si_is_configdb; + ber_int_t si_msgid; + Avlnode *si_presentlist; + LDAP *si_ld; + Connection *si_conn; + LDAP_LIST_HEAD(np, nonpresent_entry) si_nonpresentlist; +#ifdef ENABLE_REWRITE + struct rewrite_info *si_rewrite; + struct berval si_suffixm; +#endif + ldap_pvt_thread_mutex_t si_mutex; +} syncinfo_t; + +static int syncuuid_cmp( const void *, const void * ); +static int presentlist_insert( syncinfo_t* si, struct berval *syncUUID ); +static void presentlist_delete( Avlnode **av, struct berval *syncUUID ); +static char *presentlist_find( Avlnode *av, struct berval *syncUUID ); +static int presentlist_free( Avlnode *av ); +static void syncrepl_del_nonpresent( Operation *, syncinfo_t *, BerVarray, struct sync_cookie *, int ); +static int syncrepl_message_to_op( + syncinfo_t *, Operation *, LDAPMessage *, int ); +static int syncrepl_message_to_entry( + syncinfo_t *, Operation *, LDAPMessage *, + Modifications **, Entry **, int, struct berval* ); +static int syncrepl_entry( + syncinfo_t *, Operation*, Entry*, + Modifications**,int, struct berval*, + struct berval *cookieCSN ); +static int syncrepl_updateCookie( + syncinfo_t *, Operation *, + struct sync_cookie *, int save ); +static struct berval * slap_uuidstr_from_normalized( + struct berval *, struct berval *, void * ); +static int syncrepl_add_glue_ancestors( + Operation* op, Entry *e ); + +/* delta-mpr overlay handler */ +static int syncrepl_op_modify( Operation *op, SlapReply *rs ); + +/* callback functions */ +static int dn_callback( Operation *, SlapReply * ); +static int nonpresent_callback( Operation *, SlapReply * ); + +static AttributeDescription *sync_descs[4]; + +/* delta-mpr */ +static AttributeDescription *ad_reqMod, *ad_reqDN; + +typedef struct logschema { + struct berval ls_dn; + struct berval ls_req; + struct berval ls_mod; + struct berval ls_newRdn; + struct berval ls_delRdn; + struct berval ls_newSup; + struct berval ls_controls; +} logschema; + +static logschema changelog_sc = { + BER_BVC("targetDN"), + BER_BVC("changeType"), + BER_BVC("changes"), + BER_BVC("newRDN"), + BER_BVC("deleteOldRDN"), + BER_BVC("newSuperior"), + BER_BVC("controls") +}; + +static logschema accesslog_sc = { + BER_BVC("reqDN"), + BER_BVC("reqType"), + BER_BVC("reqMod"), + BER_BVC("reqNewRDN"), + BER_BVC("reqDeleteOldRDN"), + BER_BVC("reqNewSuperior"), + BER_BVC("reqControls") +}; + +static const char * +syncrepl_state2str( int state ) +{ + switch ( state ) { + case LDAP_SYNC_PRESENT: + return "PRESENT"; + + case LDAP_SYNC_ADD: + return "ADD"; + + case LDAP_SYNC_MODIFY: + return "MODIFY"; + + case LDAP_SYNC_DELETE: + return "DELETE"; + } + + return "UNKNOWN"; +} + +static slap_overinst syncrepl_ov; + +static void +init_syncrepl(syncinfo_t *si) +{ + int i, j, k, l, n; + char **attrs, **exattrs; + + if ( !syncrepl_ov.on_bi.bi_type ) { + syncrepl_ov.on_bi.bi_type = "syncrepl"; + syncrepl_ov.on_bi.bi_op_modify = syncrepl_op_modify; + overlay_register( &syncrepl_ov ); + } + + /* delta-MPR needs the overlay, nothing else does. + * This must happen before accesslog overlay is configured. + */ + if ( si->si_syncdata && + !overlay_is_inst( si->si_be, syncrepl_ov.on_bi.bi_type )) { + overlay_config( si->si_be, syncrepl_ov.on_bi.bi_type, -1, NULL, NULL ); + if ( !ad_reqMod ) { + const char *text; + logschema *ls = &accesslog_sc; + + slap_bv2ad( &ls->ls_mod, &ad_reqMod, &text ); + slap_bv2ad( &ls->ls_dn, &ad_reqDN, &text ); + } + } + + if ( !sync_descs[0] ) { + sync_descs[0] = slap_schema.si_ad_objectClass; + sync_descs[1] = slap_schema.si_ad_structuralObjectClass; + sync_descs[2] = slap_schema.si_ad_entryCSN; + sync_descs[3] = NULL; + } + + if ( si->si_allattrs && si->si_allopattrs ) + attrs = NULL; + else + attrs = anlist2attrs( si->si_anlist ); + + if ( attrs ) { + if ( si->si_allattrs ) { + i = 0; + while ( attrs[i] ) { + if ( !is_at_operational( at_find( attrs[i] ) ) ) { + for ( j = i; attrs[j] != NULL; j++ ) { + if ( j == i ) + ch_free( attrs[i] ); + attrs[j] = attrs[j+1]; + } + } else { + i++; + } + } + attrs = ( char ** ) ch_realloc( attrs, (i + 2)*sizeof( char * ) ); + attrs[i] = ch_strdup("*"); + attrs[i + 1] = NULL; + + } else if ( si->si_allopattrs ) { + i = 0; + while ( attrs[i] ) { + if ( is_at_operational( at_find( attrs[i] ) ) ) { + for ( j = i; attrs[j] != NULL; j++ ) { + if ( j == i ) + ch_free( attrs[i] ); + attrs[j] = attrs[j+1]; + } + } else { + i++; + } + } + attrs = ( char ** ) ch_realloc( attrs, (i + 2)*sizeof( char * ) ); + attrs[i] = ch_strdup("+"); + attrs[i + 1] = NULL; + } + + for ( i = 0; sync_descs[i] != NULL; i++ ) { + j = 0; + while ( attrs[j] ) { + if ( !strcmp( attrs[j], sync_descs[i]->ad_cname.bv_val ) ) { + for ( k = j; attrs[k] != NULL; k++ ) { + if ( k == j ) + ch_free( attrs[k] ); + attrs[k] = attrs[k+1]; + } + } else { + j++; + } + } + } + + for ( n = 0; attrs[ n ] != NULL; n++ ) /* empty */; + + if ( si->si_allopattrs ) { + attrs = ( char ** ) ch_realloc( attrs, (n + 2)*sizeof( char * ) ); + } else { + attrs = ( char ** ) ch_realloc( attrs, (n + 4)*sizeof( char * ) ); + } + + /* Add Attributes */ + if ( si->si_allopattrs ) { + attrs[n++] = ch_strdup( sync_descs[0]->ad_cname.bv_val ); + } else { + for ( i = 0; sync_descs[ i ] != NULL; i++ ) { + attrs[ n++ ] = ch_strdup ( sync_descs[i]->ad_cname.bv_val ); + } + } + attrs[ n ] = NULL; + + } else { + + i = 0; + if ( si->si_allattrs == si->si_allopattrs ) { + attrs = (char**) ch_malloc( 3 * sizeof(char*) ); + attrs[i++] = ch_strdup( "*" ); + attrs[i++] = ch_strdup( "+" ); + si->si_allattrs = si->si_allopattrs = 1; + } else if ( si->si_allattrs && !si->si_allopattrs ) { + for ( n = 0; sync_descs[ n ] != NULL; n++ ) ; + attrs = (char**) ch_malloc( (n+1)* sizeof(char*) ); + attrs[i++] = ch_strdup( "*" ); + for ( j = 1; sync_descs[ j ] != NULL; j++ ) { + attrs[i++] = ch_strdup ( sync_descs[j]->ad_cname.bv_val ); + } + } else if ( !si->si_allattrs && si->si_allopattrs ) { + attrs = (char**) ch_malloc( 3 * sizeof(char*) ); + attrs[i++] = ch_strdup( "+" ); + attrs[i++] = ch_strdup( sync_descs[0]->ad_cname.bv_val ); + } + attrs[i] = NULL; + } + + si->si_attrs = attrs; + + exattrs = anlist2attrs( si->si_exanlist ); + + if ( exattrs ) { + for ( n = 0; exattrs[n] != NULL; n++ ) ; + + for ( i = 0; sync_descs[i] != NULL; i++ ) { + j = 0; + while ( exattrs[j] != NULL ) { + if ( !strcmp( exattrs[j], sync_descs[i]->ad_cname.bv_val ) ) { + ch_free( exattrs[j] ); + for ( k = j; exattrs[k] != NULL; k++ ) { + exattrs[k] = exattrs[k+1]; + } + } else { + j++; + } + } + } + + for ( i = 0; exattrs[i] != NULL; i++ ) { + for ( j = 0; si->si_anlist[j].an_name.bv_val; j++ ) { + ObjectClass *oc; + if ( ( oc = si->si_anlist[j].an_oc ) ) { + k = 0; + while ( oc->soc_required[k] ) { + if ( !strcmp( exattrs[i], + oc->soc_required[k]->sat_cname.bv_val ) ) { + ch_free( exattrs[i] ); + for ( l = i; exattrs[l]; l++ ) { + exattrs[l] = exattrs[l+1]; + } + } else { + k++; + } + } + } + } + } + + for ( i = 0; exattrs[i] != NULL; i++ ) ; + + if ( i != n ) + exattrs = (char **) ch_realloc( exattrs, (i + 1)*sizeof(char *) ); + } + + si->si_exattrs = exattrs; +} + +static int +ldap_sync_search( + syncinfo_t *si, + void *ctx ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + LDAPControl c[3], *ctrls[4]; + int rc; + int rhint; + char *base; + char **attrs, *lattrs[9]; + char *filter; + int attrsonly; + int scope; + + /* setup LDAP SYNC control */ + ber_init2( ber, NULL, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &ctx ); + + /* If we're using a log but we have no state, then fallback to + * normal mode for a full refresh. + */ + if ( si->si_syncdata && !si->si_syncCookie.numcsns && !si->si_refreshDone ) { + si->si_logstate = SYNCLOG_FALLBACK; + } + + /* Use the log parameters if we're in log mode */ + if ( si->si_syncdata && si->si_logstate == SYNCLOG_LOGGING ) { + logschema *ls; + if ( si->si_syncdata == SYNCDATA_ACCESSLOG ) + ls = &accesslog_sc; + else + ls = &changelog_sc; + lattrs[0] = ls->ls_dn.bv_val; + lattrs[1] = ls->ls_req.bv_val; + lattrs[2] = ls->ls_mod.bv_val; + lattrs[3] = ls->ls_newRdn.bv_val; + lattrs[4] = ls->ls_delRdn.bv_val; + lattrs[5] = ls->ls_newSup.bv_val; + lattrs[6] = ls->ls_controls.bv_val; + lattrs[7] = slap_schema.si_ad_entryCSN->ad_cname.bv_val; + lattrs[8] = NULL; + + rhint = 0; + base = si->si_logbase.bv_val; + filter = si->si_logfilterstr.bv_val; + attrs = lattrs; + attrsonly = 0; + scope = LDAP_SCOPE_SUBTREE; + } else { + rhint = 1; + base = si->si_base.bv_val; + filter = si->si_filterstr.bv_val; + attrs = si->si_attrs; + attrsonly = si->si_attrsonly; + scope = si->si_scope; + } + if ( si->si_syncdata && si->si_logstate == SYNCLOG_FALLBACK ) { + si->si_type = LDAP_SYNC_REFRESH_ONLY; + } else { + si->si_type = si->si_ctype; + } + + if ( !BER_BVISNULL( &si->si_syncCookie.octet_str ) ) + { + ber_printf( ber, "{eOb}", + abs(si->si_type), &si->si_syncCookie.octet_str, rhint ); + } else { + ber_printf( ber, "{eb}", + abs(si->si_type), rhint ); + } + + if ( (rc = ber_flatten2( ber, &c[0].ldctl_value, 0 ) ) == -1 ) { + ber_free_buf( ber ); + return rc; + } + + c[0].ldctl_oid = LDAP_CONTROL_SYNC; + c[0].ldctl_iscritical = si->si_type < 0; + ctrls[0] = &c[0]; + + c[1].ldctl_oid = LDAP_CONTROL_MANAGEDSAIT; + BER_BVZERO( &c[1].ldctl_value ); + c[1].ldctl_iscritical = 1; + ctrls[1] = &c[1]; + + if ( !BER_BVISNULL( &si->si_bindconf.sb_authzId ) ) { + c[2].ldctl_oid = LDAP_CONTROL_PROXY_AUTHZ; + c[2].ldctl_value = si->si_bindconf.sb_authzId; + c[2].ldctl_iscritical = 1; + ctrls[2] = &c[2]; + ctrls[3] = NULL; + } else { + ctrls[2] = NULL; + } + + rc = ldap_search_ext( si->si_ld, base, scope, filter, attrs, attrsonly, + ctrls, NULL, NULL, si->si_slimit, &si->si_msgid ); + ber_free_buf( ber ); + return rc; +} + +/* #define DEBUG_MERGE_STATE 1 */ + +static int +merge_state( syncinfo_t *si, struct sync_cookie *sc1, struct sync_cookie *sc2 ) +{ + int i, j, k, changed = 0; + int ei, ej; + int *newsids; + struct berval *newcsns; + + ei = sc1->numcsns; + ej = sc2->numcsns; +#ifdef DEBUG_MERGE_STATE + for ( i=0; i<ei; i++ ) { + fprintf(stderr, "merge_state: %s si_syncCookie [%d] %d %s\n", + si->si_ridtxt, i, sc1->sids[i], sc1->ctxcsn[i].bv_val ); + } + for ( i=0; i<ej; i++ ) { + fprintf(stderr, "merge_state: %s si_cookieState [%d] %d %s\n", + si->si_ridtxt, i, sc2->sids[i], sc2->ctxcsn[i].bv_val ); + } +#endif + /* see if they cover the same SIDs */ + if ( ei == ej ) { + for ( i = 0; i < ei; i++ ) { + if ( sc1->sids[i] != sc2->sids[i] ) { + changed = 1; + break; + } + } + /* SIDs are the same, take fast path */ + if ( !changed ) { + for ( i = 0; i > ei; i++ ) { + if ( ber_bvcmp( &sc1->ctxcsn[i], &sc2->ctxcsn[i] ) < 0 ) { + ber_bvreplace( &sc1->ctxcsn[i], &sc2->ctxcsn[i] ); + changed = 1; + } + } + return changed; + } + changed = 0; + } + + i = ei + ej; + newsids = ch_malloc( sizeof(int) * i ); + newcsns = ch_malloc( sizeof(struct berval) * ( i + 1 )); + + for ( i=0, j=0, k=0; i < ei || j < ej ; ) { + if ( sc1->sids[i] == -1 ) { + i++; + continue; + } + if ( j >= ej || (i < ei && sc1->sids[i] < sc2->sids[j] )) { + newsids[k] = sc1->sids[i]; + ber_dupbv( &newcsns[k], &sc1->ctxcsn[i] ); + i++; k++; + continue; + } + if ( i < ei && sc1->sids[i] == sc2->sids[j] ) { + newsids[k] = sc1->sids[i]; + if ( ber_bvcmp( &sc1->ctxcsn[i], &sc2->ctxcsn[j] ) < 0 ) { + changed = 1; + ber_dupbv( &newcsns[k], &sc2->ctxcsn[j] ); + } else { + ber_dupbv( &newcsns[k], &sc1->ctxcsn[i] ); + } + i++; j++; k++; + continue; + } + if ( j < ej ) { + if ( sc2->sids[j] == -1 ) { + j++; + continue; + } + newsids[k] = sc2->sids[j]; + ber_dupbv( &newcsns[k], &sc2->ctxcsn[j] ); + changed = 1; + j++; k++; + } + } + + ber_bvarray_free( sc1->ctxcsn ); + ch_free( sc1->sids ); + sc1->numcsns = k; + sc1->sids = ch_realloc( newsids, sizeof(int) * k ); + sc1->ctxcsn = ch_realloc( newcsns, sizeof(struct berval) * (k+1) ); + BER_BVZERO( &sc1->ctxcsn[k] ); +#ifdef DEBUG_MERGE_STATE + for ( i=0; i<sc1->numcsns; i++ ) { + fprintf(stderr, "merge_state: %s si_syncCookie2 [%d] %d %s\n", + si->si_ridtxt, i, sc1->sids[i], sc1->ctxcsn[i].bv_val ); + } +#endif + + return changed; +} + +#ifdef DEBUG_MERGE_STATE +static void +merge_test( syncinfo_t *si ) { + struct sync_cookie sc1, sc2; + int ret; + + sc1.numcsns = 4; + sc1.sids = malloc( sizeof( int ) * sc1.numcsns ); + sc1.ctxcsn = malloc( sizeof( struct berval ) * ( sc1.numcsns + 1 )); + sc1.sids[0] = 1; + sc1.sids[1] = 3; + sc1.sids[2] = 4; + sc1.sids[3] = 5; + { struct berval bv = BER_BVC("20200101000000.100000Z#sc1#001#000000"); /* unique */ + ber_dupbv( &sc1.ctxcsn[0], &bv ); } + { struct berval bv = BER_BVC("20200101000000.100000Z#sc1#003#000000"); /* lower */ + ber_dupbv( &sc1.ctxcsn[1], &bv ); } + { struct berval bv = BER_BVC("20201231000000.100000Z#sc1#004#000000"); /* higher */ + ber_dupbv( &sc1.ctxcsn[2], &bv ); } + { struct berval bv = BER_BVC("20200228000000.100000Z#sc1#005#000000"); /* unique */ + ber_dupbv( &sc1.ctxcsn[3], &bv ); } + BER_BVZERO( &sc1.ctxcsn[sc1.numcsns] ); + + sc2.numcsns = 4; + sc2.sids = malloc( sizeof( int ) * sc2.numcsns ); + sc2.ctxcsn = malloc( sizeof( struct berval ) * ( sc2.numcsns + 1 )); + sc2.sids[0] = 2; + sc2.sids[1] = 3; + sc2.sids[2] = 4; + sc2.sids[3] = 6; + { struct berval bv = BER_BVC("20200101000000.100000Z#sc2#002#000000"); /* unique */ + ber_dupbv( &sc2.ctxcsn[0], &bv ); } + { struct berval bv = BER_BVC("20200331000000.100000Z#sc2#003#000000"); /* higher */ + ber_dupbv( &sc2.ctxcsn[1], &bv ); } + { struct berval bv = BER_BVC("20200501000000.100000Z#sc2#004#000000"); /* lower */ + ber_dupbv( &sc2.ctxcsn[2], &bv ); } + { struct berval bv = BER_BVC("20200628000000.100000Z#sc2#006#000000"); /* unique */ + ber_dupbv( &sc2.ctxcsn[3], &bv ); } + BER_BVZERO( &sc2.ctxcsn[sc2.numcsns] ); + + ret = merge_state( si, &sc1, &sc2 ); +} +#endif + +static int +check_syncprov( + Operation *op, + syncinfo_t *si ) +{ + AttributeName at[2]; + Attribute a = {0}; + Entry e = {0}; + SlapReply rs = {REP_SEARCH}; + int i, j, changed = 0; + + /* Look for contextCSN from syncprov overlay. If + * there's no overlay, this will be a no-op. That means + * this is a pure consumer, so local changes will not be + * allowed, and all changes will already be reflected in + * the cookieState. + */ + a.a_desc = slap_schema.si_ad_contextCSN; + e.e_attrs = &a; + e.e_name = si->si_contextdn; + e.e_nname = si->si_contextdn; + at[0].an_name = a.a_desc->ad_cname; + at[0].an_desc = a.a_desc; + BER_BVZERO( &at[1].an_name ); + rs.sr_entry = &e; + rs.sr_flags = REP_ENTRY_MODIFIABLE; + rs.sr_attrs = at; + op->o_req_dn = e.e_name; + op->o_req_ndn = e.e_nname; + + ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_mutex ); + i = backend_operational( op, &rs ); + if ( i == LDAP_SUCCESS && a.a_nvals ) { + int num = a.a_numvals; + /* check for differences */ + if ( num != si->si_cookieState->cs_num ) { + changed = 1; + } else { + for ( i=0; i<num; i++ ) { + if ( ber_bvcmp( &a.a_nvals[i], + &si->si_cookieState->cs_vals[i] )) { + changed = 1; + break; + } + } + } + if ( changed ) { + ber_bvarray_free( si->si_cookieState->cs_vals ); + ch_free( si->si_cookieState->cs_sids ); + si->si_cookieState->cs_num = num; + si->si_cookieState->cs_vals = a.a_nvals; + si->si_cookieState->cs_sids = slap_parse_csn_sids( a.a_nvals, + num, NULL ); + si->si_cookieState->cs_age++; + } else { + ber_bvarray_free( a.a_nvals ); + } + ber_bvarray_free( a.a_vals ); + } + /* See if the cookieState has changed due to anything outside + * this particular consumer. That includes other consumers in + * the same context, or local changes detected above. + */ + if ( si->si_cookieState->cs_num > 0 && si->si_cookieAge != + si->si_cookieState->cs_age ) { + if ( !si->si_syncCookie.numcsns ) { + ber_bvarray_free( si->si_syncCookie.ctxcsn ); + ber_bvarray_dup_x( &si->si_syncCookie.ctxcsn, + si->si_cookieState->cs_vals, NULL ); + changed = 1; + } else { + changed = merge_state( si, &si->si_syncCookie, + (struct sync_cookie *)&si->si_cookieState->cs_vals ); + } + } + if ( changed ) { + si->si_cookieAge = si->si_cookieState->cs_age; + ch_free( si->si_syncCookie.octet_str.bv_val ); + slap_compose_sync_cookie( NULL, &si->si_syncCookie.octet_str, + si->si_syncCookie.ctxcsn, si->si_syncCookie.rid, + si->si_syncCookie.sid ); + ch_free( si->si_syncCookie.sids ); + slap_reparse_sync_cookie( &si->si_syncCookie, op->o_tmpmemctx ); + } + ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex ); + return changed; +} + +static int +do_syncrep1( + Operation *op, + syncinfo_t *si ) +{ + int rc; + int cmdline_cookie_found = 0; + + struct sync_cookie *sc = NULL; +#ifdef HAVE_TLS + void *ssl; +#endif + + rc = slap_client_connect( &si->si_ld, &si->si_bindconf ); + if ( rc != LDAP_SUCCESS ) { + goto done; + } + op->o_protocol = LDAP_VERSION3; + + /* Set SSF to strongest of TLS, SASL SSFs */ + op->o_sasl_ssf = 0; + op->o_tls_ssf = 0; + op->o_transport_ssf = 0; +#ifdef HAVE_TLS + if ( ldap_get_option( si->si_ld, LDAP_OPT_X_TLS_SSL_CTX, &ssl ) + == LDAP_SUCCESS && ssl != NULL ) + { + op->o_tls_ssf = ldap_pvt_tls_get_strength( ssl ); + } +#endif /* HAVE_TLS */ + { + ber_len_t ssf; /* ITS#5403, 3864 LDAP_OPT_X_SASL_SSF probably ought + to use sasl_ssf_t but currently uses ber_len_t */ + if ( ldap_get_option( si->si_ld, LDAP_OPT_X_SASL_SSF, &ssf ) + == LDAP_SUCCESS ) + op->o_sasl_ssf = ssf; + } + op->o_ssf = ( op->o_sasl_ssf > op->o_tls_ssf ) + ? op->o_sasl_ssf : op->o_tls_ssf; + + ldap_set_option( si->si_ld, LDAP_OPT_TIMELIMIT, &si->si_tlimit ); + + rc = LDAP_DEREF_NEVER; /* actually could allow DEREF_FINDING */ + ldap_set_option( si->si_ld, LDAP_OPT_DEREF, &rc ); + + ldap_set_option( si->si_ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF ); + + si->si_syncCookie.rid = si->si_rid; + + /* whenever there are multiple data sources possible, advertise sid */ + si->si_syncCookie.sid = ( SLAP_MULTIMASTER( si->si_be ) || si->si_be != si->si_wbe ) ? + slap_serverID : -1; + + /* We've just started up, or the remote server hasn't sent us + * any meaningful state. + */ + if ( !si->si_syncCookie.ctxcsn ) { + int i; + + LDAP_STAILQ_FOREACH( sc, &slap_sync_cookie, sc_next ) { + if ( si->si_rid == sc->rid ) { + cmdline_cookie_found = 1; + break; + } + } + + if ( cmdline_cookie_found ) { + /* cookie is supplied in the command line */ + + LDAP_STAILQ_REMOVE( &slap_sync_cookie, sc, sync_cookie, sc_next ); + + slap_sync_cookie_free( &si->si_syncCookie, 0 ); + si->si_syncCookie.octet_str = sc->octet_str; + ch_free( sc ); + /* ctxcsn wasn't parsed yet, do it now */ + slap_parse_sync_cookie( &si->si_syncCookie, NULL ); + } else { + ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_mutex ); + if ( !si->si_cookieState->cs_num ) { + /* get contextCSN shadow replica from database */ + BerVarray csn = NULL; + void *ctx = op->o_tmpmemctx; + + op->o_req_ndn = si->si_contextdn; + op->o_req_dn = op->o_req_ndn; + + /* try to read stored contextCSN */ + op->o_tmpmemctx = NULL; + backend_attribute( op, NULL, &op->o_req_ndn, + slap_schema.si_ad_contextCSN, &csn, ACL_READ ); + op->o_tmpmemctx = ctx; + if ( csn ) { + si->si_cookieState->cs_vals = csn; + for (i=0; !BER_BVISNULL( &csn[i] ); i++); + si->si_cookieState->cs_num = i; + si->si_cookieState->cs_sids = slap_parse_csn_sids( csn, i, NULL ); + slap_sort_csn_sids( csn, si->si_cookieState->cs_sids, i, NULL ); + } + } + if ( si->si_cookieState->cs_num ) { + ber_bvarray_free( si->si_syncCookie.ctxcsn ); + if ( ber_bvarray_dup_x( &si->si_syncCookie.ctxcsn, + si->si_cookieState->cs_vals, NULL )) { + rc = LDAP_NO_MEMORY; + ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex ); + goto done; + } + si->si_syncCookie.numcsns = si->si_cookieState->cs_num; + si->si_syncCookie.sids = ch_malloc( si->si_cookieState->cs_num * + sizeof(int) ); + for ( i=0; i<si->si_syncCookie.numcsns; i++ ) + si->si_syncCookie.sids[i] = si->si_cookieState->cs_sids[i]; + } + ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex ); + } + } + + if ( !cmdline_cookie_found ) { + /* ITS#6367: recreate the cookie so it has our SID, not our peer's */ + ch_free( si->si_syncCookie.octet_str.bv_val ); + BER_BVZERO( &si->si_syncCookie.octet_str ); + /* Look for contextCSN from syncprov overlay. */ + check_syncprov( op, si ); + if ( BER_BVISNULL( &si->si_syncCookie.octet_str )) + slap_compose_sync_cookie( NULL, &si->si_syncCookie.octet_str, + si->si_syncCookie.ctxcsn, si->si_syncCookie.rid, + si->si_syncCookie.sid ); + } + + si->si_refreshDone = 0; + Debug( LDAP_DEBUG_SYNC, "do_syncrep1: %s starting refresh (sending cookie=%s)\n", + si->si_ridtxt, si->si_syncCookie.octet_str.bv_val, 0 ); + + rc = ldap_sync_search( si, op->o_tmpmemctx ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "do_syncrep1: %s " + "ldap_search_ext: %s (%d)\n", + si->si_ridtxt, ldap_err2string( rc ), rc ); + } + +done: + if ( rc ) { + if ( si->si_ld ) { + ldap_unbind_ext( si->si_ld, NULL, NULL ); + si->si_ld = NULL; + } + } + + return rc; +} + +static int +compare_csns( struct sync_cookie *sc1, struct sync_cookie *sc2, int *which ) +{ + int i, j, match = 0; + const char *text; + + *which = 0; + + if ( sc1->numcsns < sc2->numcsns ) { + *which = sc1->numcsns; + return -1; + } + + for (j=0; j<sc2->numcsns; j++) { + for (i=0; i<sc1->numcsns; i++) { + if ( sc1->sids[i] != sc2->sids[j] ) + continue; + value_match( &match, slap_schema.si_ad_entryCSN, + slap_schema.si_ad_entryCSN->ad_type->sat_ordering, + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + &sc1->ctxcsn[i], &sc2->ctxcsn[j], &text ); + if ( match < 0 ) { + *which = j; + return match; + } + break; + } + if ( i == sc1->numcsns ) { + /* sc2 has a sid sc1 lacks */ + *which = j; + return -1; + } + } + return match; +} + +#define CV_CSN_OK 0 +#define CV_CSN_OLD 1 +#define CV_SID_NEW 2 + +static int +check_csn_age( + syncinfo_t *si, + struct berval *dn, + struct berval *csn, + int sid, + cookie_vals *cv, + int *slot ) +{ + int i, rc = CV_SID_NEW; + + for ( i =0; i<cv->cv_num; i++ ) { +#ifdef CHATTY_SYNCLOG + Debug( LDAP_DEBUG_SYNC, "do_syncrep2: %s CSN for sid %d: %s\n", + si->si_ridtxt, i, cv->cv_vals[i].bv_val ); +#endif + /* new SID */ + if ( sid < cv->cv_sids[i] ) + break; + if ( cv->cv_sids[i] == sid ) { + if ( ber_bvcmp( csn, &cv->cv_vals[i] ) <= 0 ) { + dn->bv_val[dn->bv_len] = '\0'; + Debug( LDAP_DEBUG_SYNC, "do_syncrep2: %s CSN too old, ignoring %s (%s)\n", + si->si_ridtxt, csn->bv_val, dn->bv_val ); + return CV_CSN_OLD; + } + rc = CV_CSN_OK; + break; + } + } + if ( slot ) + *slot = i; + return rc; +} + +#define SYNC_TIMEOUT 0 +#define SYNC_SHUTDOWN -100 +#define SYNC_ERROR -101 +#define SYNC_REPOLL -102 +#define SYNC_PAUSED -103 + +static int +get_pmutex( + syncinfo_t *si +) +{ + if ( !si->si_is_configdb ) { + ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_pmutex ); + } else { + /* avoid deadlock when replicating cn=config */ + while ( ldap_pvt_thread_mutex_trylock( &si->si_cookieState->cs_pmutex )) { + if ( slapd_shutdown ) + return SYNC_SHUTDOWN; + if ( !ldap_pvt_thread_pool_pausecheck( &connection_pool )) + ldap_pvt_thread_yield(); + } + } + + return 0; +} + +static int +do_syncrep2( + Operation *op, + syncinfo_t *si ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + + LDAPMessage *msg = NULL; + + struct sync_cookie syncCookie = { NULL }; + struct sync_cookie syncCookie_req = { NULL }; + + int rc, + err = LDAP_SUCCESS; + + Modifications *modlist = NULL; + + int m; + + struct timeval tout = { 0, 0 }; + + int refreshDeletes = 0; + char empty[6] = "empty"; + + if ( slapd_shutdown ) { + rc = SYNC_SHUTDOWN; + goto done; + } + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, "=>do_syncrep2 %s\n", si->si_ridtxt, 0, 0 ); + + slap_dup_sync_cookie( &syncCookie_req, &si->si_syncCookie ); + + while ( ( rc = ldap_result( si->si_ld, si->si_msgid, LDAP_MSG_ONE, + &tout, &msg ) ) > 0 ) + { + int match, punlock, syncstate; + struct berval *retdata, syncUUID[2], cookie = BER_BVNULL; + char *retoid; + LDAPControl **rctrls = NULL, *rctrlp = NULL; + BerVarray syncUUIDs; + ber_len_t len; + ber_tag_t si_tag; + Entry *entry; + struct berval bdn; + + if ( slapd_shutdown ) { + rc = SYNC_SHUTDOWN; + goto done; + } + switch( ldap_msgtype( msg ) ) { + case LDAP_RES_SEARCH_ENTRY: + ldap_get_entry_controls( si->si_ld, msg, &rctrls ); + ldap_get_dn_ber( si->si_ld, msg, NULL, &bdn ); + if (!bdn.bv_len) { + bdn.bv_val = empty; + bdn.bv_len = sizeof(empty)-1; + } + /* we can't work without the control */ + if ( rctrls ) { + LDAPControl **next = NULL; + /* NOTE: make sure we use the right one; + * a better approach would be to run thru + * the whole list and take care of all */ + /* NOTE: since we issue the search request, + * we should know what controls to expect, + * and there should be none apart from the + * sync-related control */ + rctrlp = ldap_control_find( LDAP_CONTROL_SYNC_STATE, rctrls, &next ); + if ( next && ldap_control_find( LDAP_CONTROL_SYNC_STATE, next, NULL ) ) + { + bdn.bv_val[bdn.bv_len] = '\0'; + Debug( LDAP_DEBUG_ANY, "do_syncrep2: %s " + "got search entry with multiple " + "Sync State control (%s)\n", si->si_ridtxt, bdn.bv_val, 0 ); + ldap_controls_free( rctrls ); + rc = -1; + goto done; + } + } + if ( rctrlp == NULL ) { + bdn.bv_val[bdn.bv_len] = '\0'; + Debug( LDAP_DEBUG_ANY, "do_syncrep2: %s " + "got search entry without " + "Sync State control (%s)\n", si->si_ridtxt, bdn.bv_val, 0 ); + rc = -1; + goto done; + } + ber_init2( ber, &rctrlp->ldctl_value, LBER_USE_DER ); + if ( ber_scanf( ber, "{em" /*"}"*/, &syncstate, &syncUUID[0] ) + == LBER_ERROR ) { + bdn.bv_val[bdn.bv_len] = '\0'; + Debug( LDAP_DEBUG_ANY, "do_syncrep2: %s malformed message (%s)\n", + si->si_ridtxt, bdn.bv_val, 0 ); + ldap_controls_free( rctrls ); + rc = -1; + goto done; + } + /* FIXME: what if syncUUID is NULL or empty? + * (happens with back-sql...) */ + if ( syncUUID[0].bv_len != UUIDLEN ) { + bdn.bv_val[bdn.bv_len] = '\0'; + Debug( LDAP_DEBUG_ANY, "do_syncrep2: %s " + "got empty or invalid syncUUID with LDAP_SYNC_%s (%s)\n", + si->si_ridtxt, + syncrepl_state2str( syncstate ), bdn.bv_val ); + ldap_controls_free( rctrls ); + rc = -1; + goto done; + } + punlock = -1; + if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) { + if ( ber_scanf( ber, /*"{"*/ "m}", &cookie ) != LBER_ERROR ) { + + Debug( LDAP_DEBUG_SYNC, "do_syncrep2: %s cookie=%s\n", + si->si_ridtxt, + BER_BVISNULL( &cookie ) ? "" : cookie.bv_val, 0 ); + + if ( !BER_BVISNULL( &cookie ) ) { + ch_free( syncCookie.octet_str.bv_val ); + ber_dupbv( &syncCookie.octet_str, &cookie ); + } + if ( !BER_BVISNULL( &syncCookie.octet_str ) ) + { + slap_parse_sync_cookie( &syncCookie, NULL ); + if ( syncCookie.ctxcsn ) { + int i, slot, sid = slap_parse_csn_sid( syncCookie.ctxcsn ); + check_syncprov( op, si ); + ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_mutex ); + i = check_csn_age( si, &bdn, syncCookie.ctxcsn, sid, (cookie_vals *)&si->si_cookieState->cs_vals, NULL ); + ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex ); + if ( i == CV_CSN_OLD ) { + si->si_too_old = 1; + ldap_controls_free( rctrls ); + rc = 0; + goto done; + } + si->si_too_old = 0; + + /* check pending CSNs too */ + if (( rc = get_pmutex( si ))) + goto done; + + i = check_csn_age( si, &bdn, syncCookie.ctxcsn, sid, (cookie_vals *)&si->si_cookieState->cs_pvals, &slot ); + if ( i == CV_CSN_OK ) { + ber_bvreplace( &si->si_cookieState->cs_pvals[slot], + syncCookie.ctxcsn ); + } else if ( i == CV_CSN_OLD ) { + ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_pmutex ); + ldap_controls_free( rctrls ); + rc = 0; + goto done; + } else { + /* new SID, add it */ + slap_insert_csn_sids( + (struct sync_cookie *)&si->si_cookieState->cs_pvals, + slot, sid, syncCookie.ctxcsn ); + } + assert( punlock < 0 ); + punlock = slot; + } else if (si->si_too_old) { + bdn.bv_val[bdn.bv_len] = '\0'; + Debug( LDAP_DEBUG_SYNC, "do_syncrep2: %s CSN too old, ignoring (%s)\n", + si->si_ridtxt, bdn.bv_val, 0 ); + ldap_controls_free( rctrls ); + rc = 0; + goto done; + } + op->o_controls[slap_cids.sc_LDAPsync] = &syncCookie; + } + } + } + rc = 0; + if ( si->si_syncdata && si->si_logstate == SYNCLOG_LOGGING ) { + modlist = NULL; + if ( ( rc = syncrepl_message_to_op( si, op, msg, punlock < 0 ) ) == LDAP_SUCCESS && + syncCookie.ctxcsn ) + { + rc = syncrepl_updateCookie( si, op, &syncCookie, 0 ); + } else switch ( rc ) { + case LDAP_ALREADY_EXISTS: + case LDAP_NO_SUCH_OBJECT: + case LDAP_NO_SUCH_ATTRIBUTE: + case LDAP_TYPE_OR_VALUE_EXISTS: + case LDAP_NOT_ALLOWED_ON_NONLEAF: + rc = LDAP_SYNC_REFRESH_REQUIRED; + si->si_logstate = SYNCLOG_FALLBACK; + ldap_abandon_ext( si->si_ld, si->si_msgid, NULL, NULL ); + bdn.bv_val[bdn.bv_len] = '\0'; + Debug( LDAP_DEBUG_SYNC, "do_syncrep2: %s delta-sync lost sync on (%s), switching to REFRESH\n", + si->si_ridtxt, bdn.bv_val, 0 ); + if (si->si_strict_refresh) { + slap_suspend_listeners(); + connections_drop(); + } + break; + default: + break; + } + } else if ( ( rc = syncrepl_message_to_entry( si, op, msg, + &modlist, &entry, syncstate, syncUUID ) ) == LDAP_SUCCESS ) + { + if ( punlock < 0 ) { + if (( rc = get_pmutex( si ))) + goto done; + } + if ( ( rc = syncrepl_entry( si, op, entry, &modlist, + syncstate, syncUUID, syncCookie.ctxcsn ) ) == LDAP_SUCCESS && + syncCookie.ctxcsn ) + { + rc = syncrepl_updateCookie( si, op, &syncCookie, 0 ); + } + if ( punlock < 0 ) + ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_pmutex ); + } + if ( punlock >= 0 ) { + /* on failure, revert pending CSN */ + if ( rc != LDAP_SUCCESS ) { + int i; + ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_mutex ); + for ( i = 0; i<si->si_cookieState->cs_num; i++ ) { + if ( si->si_cookieState->cs_sids[i] == si->si_cookieState->cs_psids[punlock] ) { + ber_bvreplace( &si->si_cookieState->cs_pvals[punlock], + &si->si_cookieState->cs_vals[i] ); + break; + } + } + if ( i == si->si_cookieState->cs_num ) + si->si_cookieState->cs_pvals[punlock].bv_val[0] = '\0'; + ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex ); + } + ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_pmutex ); + } + ldap_controls_free( rctrls ); + if ( modlist ) { + slap_mods_free( modlist, 1 ); + } + if ( rc ) + goto done; + break; + + case LDAP_RES_SEARCH_REFERENCE: + Debug( LDAP_DEBUG_ANY, + "do_syncrep2: %s reference received error\n", + si->si_ridtxt, 0, 0 ); + break; + + case LDAP_RES_SEARCH_RESULT: + Debug( LDAP_DEBUG_SYNC, + "do_syncrep2: %s LDAP_RES_SEARCH_RESULT\n", + si->si_ridtxt, 0, 0 ); + err = LDAP_OTHER; /* FIXME check parse result properly */ + ldap_parse_result( si->si_ld, msg, &err, NULL, NULL, NULL, + &rctrls, 0 ); +#ifdef LDAP_X_SYNC_REFRESH_REQUIRED + if ( err == LDAP_X_SYNC_REFRESH_REQUIRED ) { + /* map old result code to registered code */ + err = LDAP_SYNC_REFRESH_REQUIRED; + } +#endif + if ( err == LDAP_SYNC_REFRESH_REQUIRED ) { + if ( si->si_logstate == SYNCLOG_LOGGING ) { + si->si_logstate = SYNCLOG_FALLBACK; + Debug( LDAP_DEBUG_SYNC, "do_syncrep2: %s delta-sync lost sync, switching to REFRESH\n", + si->si_ridtxt, 0, 0 ); + if (si->si_strict_refresh) { + slap_suspend_listeners(); + connections_drop(); + } + } + rc = err; + goto done; + } + if ( err ) { + Debug( LDAP_DEBUG_ANY, + "do_syncrep2: %s LDAP_RES_SEARCH_RESULT (%d) %s\n", + si->si_ridtxt, err, ldap_err2string( err ) ); + } + if ( rctrls ) { + LDAPControl **next = NULL; + /* NOTE: make sure we use the right one; + * a better approach would be to run thru + * the whole list and take care of all */ + /* NOTE: since we issue the search request, + * we should know what controls to expect, + * and there should be none apart from the + * sync-related control */ + rctrlp = ldap_control_find( LDAP_CONTROL_SYNC_DONE, rctrls, &next ); + if ( next && ldap_control_find( LDAP_CONTROL_SYNC_DONE, next, NULL ) ) + { + Debug( LDAP_DEBUG_ANY, "do_syncrep2: %s " + "got search result with multiple " + "Sync State control\n", si->si_ridtxt, 0, 0 ); + ldap_controls_free( rctrls ); + rc = SYNC_ERROR; + goto done; + } + } + if ( rctrlp ) { + ber_init2( ber, &rctrlp->ldctl_value, LBER_USE_DER ); + + ber_scanf( ber, "{" /*"}"*/); + if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) { + ber_scanf( ber, "m", &cookie ); + + Debug( LDAP_DEBUG_SYNC, "do_syncrep2: %s cookie=%s\n", + si->si_ridtxt, + BER_BVISNULL( &cookie ) ? "" : cookie.bv_val, 0 ); + + if ( !BER_BVISNULL( &cookie ) ) { + ch_free( syncCookie.octet_str.bv_val ); + ber_dupbv( &syncCookie.octet_str, &cookie); + } + if ( !BER_BVISNULL( &syncCookie.octet_str ) ) + { + slap_parse_sync_cookie( &syncCookie, NULL ); + op->o_controls[slap_cids.sc_LDAPsync] = &syncCookie; + } + } + if ( ber_peek_tag( ber, &len ) == LDAP_TAG_REFRESHDELETES ) + { + ber_scanf( ber, "b", &refreshDeletes ); + } + ber_scanf( ber, /*"{"*/ "}" ); + } + if ( SLAP_MULTIMASTER( op->o_bd ) && check_syncprov( op, si )) { + slap_sync_cookie_free( &syncCookie_req, 0 ); + slap_dup_sync_cookie( &syncCookie_req, &si->si_syncCookie ); + } + if ( !syncCookie.ctxcsn ) { + match = 1; + } else if ( !syncCookie_req.ctxcsn ) { + match = -1; + m = 0; + } else { + match = compare_csns( &syncCookie_req, &syncCookie, &m ); + } + if ( rctrls ) { + ldap_controls_free( rctrls ); + } + if (si->si_type != LDAP_SYNC_REFRESH_AND_PERSIST) { + /* FIXME : different error behaviors according to + * 1) err code : LDAP_BUSY ... + * 2) on err policy : stop service, stop sync, retry + */ + if ( refreshDeletes == 0 && match < 0 && err == LDAP_SUCCESS ) + { + syncrepl_del_nonpresent( op, si, NULL, + &syncCookie, m ); + } else if ( si->si_presentlist ) { + presentlist_free( si->si_presentlist ); + si->si_presentlist = NULL; + } + } + if ( syncCookie.ctxcsn && match < 0 && err == LDAP_SUCCESS ) + { + rc = syncrepl_updateCookie( si, op, &syncCookie, 1 ); + } + if ( err == LDAP_SUCCESS + && si->si_logstate == SYNCLOG_FALLBACK ) { + si->si_logstate = SYNCLOG_LOGGING; + si->si_refreshDone = 1; + rc = LDAP_SYNC_REFRESH_REQUIRED; + slap_resume_listeners(); + } else { + /* for persist, we shouldn't get a SearchResult so this is an error */ + if ( si->si_type == LDAP_SYNC_REFRESH_AND_PERSIST ) + rc = SYNC_ERROR; + else + rc = SYNC_REPOLL; + } + goto done; + + case LDAP_RES_INTERMEDIATE: + retoid = NULL; + retdata = NULL; + rc = ldap_parse_intermediate( si->si_ld, msg, + &retoid, &retdata, NULL, 0 ); + if ( !rc && !strcmp( retoid, LDAP_SYNC_INFO ) ) { + ber_init2( ber, retdata, LBER_USE_DER ); + + switch ( si_tag = ber_peek_tag( ber, &len ) ) { + ber_tag_t tag; + case LDAP_TAG_SYNC_NEW_COOKIE: + Debug( LDAP_DEBUG_SYNC, + "do_syncrep2: %s %s - %s\n", + si->si_ridtxt, + "LDAP_RES_INTERMEDIATE", + "NEW_COOKIE" ); + ber_scanf( ber, "tm", &tag, &cookie ); + Debug( LDAP_DEBUG_SYNC, + "do_syncrep2: %s NEW_COOKIE: %s\n", + si->si_ridtxt, + cookie.bv_val, 0); + if ( !BER_BVISNULL( &cookie ) ) { + ch_free( syncCookie.octet_str.bv_val ); + ber_dupbv( &syncCookie.octet_str, &cookie ); + } + if (!BER_BVISNULL( &syncCookie.octet_str ) ) { + slap_parse_sync_cookie( &syncCookie, NULL ); + op->o_controls[slap_cids.sc_LDAPsync] = &syncCookie; + } + break; + case LDAP_TAG_SYNC_REFRESH_DELETE: + case LDAP_TAG_SYNC_REFRESH_PRESENT: + Debug( LDAP_DEBUG_SYNC, + "do_syncrep2: %s %s - %s\n", + si->si_ridtxt, + "LDAP_RES_INTERMEDIATE", + si_tag == LDAP_TAG_SYNC_REFRESH_PRESENT ? + "REFRESH_PRESENT" : "REFRESH_DELETE" ); + if ( si_tag == LDAP_TAG_SYNC_REFRESH_DELETE ) { + si->si_refreshDelete = 1; + } else { + si->si_refreshPresent = 1; + } + ber_scanf( ber, "t{" /*"}"*/, &tag ); + if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) + { + ber_scanf( ber, "m", &cookie ); + + Debug( LDAP_DEBUG_SYNC, "do_syncrep2: %s cookie=%s\n", + si->si_ridtxt, + BER_BVISNULL( &cookie ) ? "" : cookie.bv_val, 0 ); + + if ( !BER_BVISNULL( &cookie ) ) { + ch_free( syncCookie.octet_str.bv_val ); + ber_dupbv( &syncCookie.octet_str, &cookie ); + } + if ( !BER_BVISNULL( &syncCookie.octet_str ) ) + { + slap_parse_sync_cookie( &syncCookie, NULL ); + op->o_controls[slap_cids.sc_LDAPsync] = &syncCookie; + } + } + /* Defaults to TRUE */ + if ( ber_peek_tag( ber, &len ) == + LDAP_TAG_REFRESHDONE ) + { + ber_scanf( ber, "b", &si->si_refreshDone ); + } else + { + si->si_refreshDone = 1; + } + ber_scanf( ber, /*"{"*/ "}" ); + break; + case LDAP_TAG_SYNC_ID_SET: + Debug( LDAP_DEBUG_SYNC, + "do_syncrep2: %s %s - %s\n", + si->si_ridtxt, + "LDAP_RES_INTERMEDIATE", + "SYNC_ID_SET" ); + ber_scanf( ber, "t{" /*"}"*/, &tag ); + if ( ber_peek_tag( ber, &len ) == + LDAP_TAG_SYNC_COOKIE ) + { + ber_scanf( ber, "m", &cookie ); + + Debug( LDAP_DEBUG_SYNC, "do_syncrep2: %s cookie=%s\n", + si->si_ridtxt, + BER_BVISNULL( &cookie ) ? "" : cookie.bv_val, 0 ); + + if ( !BER_BVISNULL( &cookie ) ) { + ch_free( syncCookie.octet_str.bv_val ); + ber_dupbv( &syncCookie.octet_str, &cookie ); + } + if ( !BER_BVISNULL( &syncCookie.octet_str ) ) + { + slap_parse_sync_cookie( &syncCookie, NULL ); + op->o_controls[slap_cids.sc_LDAPsync] = &syncCookie; + compare_csns( &syncCookie_req, &syncCookie, &m ); + } + } + if ( ber_peek_tag( ber, &len ) == + LDAP_TAG_REFRESHDELETES ) + { + ber_scanf( ber, "b", &refreshDeletes ); + } + syncUUIDs = NULL; + rc = ber_scanf( ber, "[W]", &syncUUIDs ); + ber_scanf( ber, /*"{"*/ "}" ); + if ( rc != LBER_ERROR ) { + if ( refreshDeletes ) { + syncrepl_del_nonpresent( op, si, syncUUIDs, + &syncCookie, m ); + ber_bvarray_free_x( syncUUIDs, op->o_tmpmemctx ); + } else { + int i; + for ( i = 0; !BER_BVISNULL( &syncUUIDs[i] ); i++ ) { + (void)presentlist_insert( si, &syncUUIDs[i] ); + slap_sl_free( syncUUIDs[i].bv_val, op->o_tmpmemctx ); + } + slap_sl_free( syncUUIDs, op->o_tmpmemctx ); + } + } + rc = 0; + slap_sync_cookie_free( &syncCookie, 0 ); + break; + default: + Debug( LDAP_DEBUG_ANY, + "do_syncrep2: %s unknown syncinfo tag (%ld)\n", + si->si_ridtxt, (long) si_tag, 0 ); + ldap_memfree( retoid ); + ber_bvfree( retdata ); + continue; + } + + if ( SLAP_MULTIMASTER( op->o_bd ) && check_syncprov( op, si )) { + slap_sync_cookie_free( &syncCookie_req, 0 ); + slap_dup_sync_cookie( &syncCookie_req, &si->si_syncCookie ); + } + if ( !syncCookie.ctxcsn ) { + match = 1; + } else if ( !syncCookie_req.ctxcsn ) { + match = -1; + m = 0; + } else { + match = compare_csns( &syncCookie_req, &syncCookie, &m ); + } + + if ( match < 0 ) { + if ( si->si_refreshPresent == 1 && + si_tag != LDAP_TAG_SYNC_NEW_COOKIE ) { + syncrepl_del_nonpresent( op, si, NULL, + &syncCookie, m ); + } + + if ( syncCookie.ctxcsn ) + { + rc = syncrepl_updateCookie( si, op, &syncCookie, 1 ); + } + if ( si->si_presentlist ) { + presentlist_free( si->si_presentlist ); + si->si_presentlist = NULL; + } + } + + ldap_memfree( retoid ); + ber_bvfree( retdata ); + + if ( rc ) + goto done; + + } else { + Debug( LDAP_DEBUG_ANY, "do_syncrep2: %s " + "unknown intermediate response (%d)\n", + si->si_ridtxt, rc, 0 ); + ldap_memfree( retoid ); + ber_bvfree( retdata ); + } + break; + + default: + Debug( LDAP_DEBUG_ANY, "do_syncrep2: %s " + "unknown message (0x%02lx)\n", + si->si_ridtxt, + (unsigned long)ldap_msgtype( msg ), 0 ); + break; + + } + if ( !BER_BVISNULL( &syncCookie.octet_str ) ) { + slap_sync_cookie_free( &syncCookie_req, 0 ); + syncCookie_req = syncCookie; + memset( &syncCookie, 0, sizeof( syncCookie )); + } + ldap_msgfree( msg ); + msg = NULL; + if ( ldap_pvt_thread_pool_pausing( &connection_pool )) { + slap_sync_cookie_free( &syncCookie, 0 ); + slap_sync_cookie_free( &syncCookie_req, 0 ); + return SYNC_PAUSED; + } + } + + if ( rc == SYNC_ERROR ) { + rc = LDAP_OTHER; + ldap_get_option( si->si_ld, LDAP_OPT_ERROR_NUMBER, &rc ); + err = rc; + } + +done: + if ( err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "do_syncrep2: %s (%d) %s\n", + si->si_ridtxt, err, ldap_err2string( err ) ); + } + + slap_sync_cookie_free( &syncCookie, 0 ); + slap_sync_cookie_free( &syncCookie_req, 0 ); + + if ( msg ) ldap_msgfree( msg ); + + if ( rc ) { + /* never reuse existing connection */ + if ( si->si_conn ) { + connection_client_stop( si->si_conn ); + si->si_conn = NULL; + } + ldap_unbind_ext( si->si_ld, NULL, NULL ); + si->si_ld = NULL; + } + + return rc; +} + +static void * +do_syncrepl( + void *ctx, + void *arg ) +{ + struct re_s* rtask = arg; + syncinfo_t *si = ( syncinfo_t * ) rtask->arg; + Connection conn = {0}; + OperationBuffer opbuf; + Operation *op; + int rc = LDAP_SUCCESS; + int dostop = 0; + ber_socket_t s; + int i, fail = 0, freeinfo = 0; + Backend *be; + + if ( si == NULL ) + return NULL; + if ( slapd_shutdown ) + return NULL; + + Debug( LDAP_DEBUG_TRACE, "=>do_syncrepl %s\n", si->si_ridtxt, 0, 0 ); + + /* Don't get stuck here while a pause is initiated */ + while ( ldap_pvt_thread_mutex_trylock( &si->si_mutex )) { + if ( slapd_shutdown ) + return NULL; + if ( !ldap_pvt_thread_pool_pausecheck( &connection_pool )) + ldap_pvt_thread_yield(); + } + + si->si_too_old = 0; + + if ( si->si_ctype < 1 ) { + goto deleted; + } + + switch( abs( si->si_type ) ) { + case LDAP_SYNC_REFRESH_ONLY: + case LDAP_SYNC_REFRESH_AND_PERSIST: + break; + default: + ldap_pvt_thread_mutex_unlock( &si->si_mutex ); + return NULL; + } + + if ( slapd_shutdown ) { + if ( si->si_ld ) { + if ( si->si_conn ) { + connection_client_stop( si->si_conn ); + si->si_conn = NULL; + } + ldap_unbind_ext( si->si_ld, NULL, NULL ); + si->si_ld = NULL; + } + ldap_pvt_thread_mutex_unlock( &si->si_mutex ); + return NULL; + } + + connection_fake_init( &conn, &opbuf, ctx ); + op = &opbuf.ob_op; + /* o_connids must be unique for slap_graduate_commit_csn */ + op->o_connid = SLAPD_SYNC_RID2SYNCCONN(si->si_rid); + + op->o_managedsait = SLAP_CONTROL_NONCRITICAL; + be = si->si_be; + + /* Coordinate contextCSN updates with any syncprov overlays + * in use. This may be complicated by the use of the glue + * overlay. + * + * Typically there is a single syncprov controlling the entire + * glued tree. In that case, our contextCSN updates should + * go to the primary DB. But if there is no syncprov on the + * primary DB, then nothing special is needed here. + * + * Alternatively, there may be individual syncprov overlays + * on each glued branch. In that case, each syncprov only + * knows about changes within its own branch. And so our + * contextCSN updates should only go to the local DB. + */ + if ( !si->si_wbe ) { + if ( SLAP_GLUE_SUBORDINATE( be ) && !overlay_is_inst( be, "syncprov" )) { + BackendDB * top_be = select_backend( &be->be_nsuffix[0], 1 ); + if ( overlay_is_inst( top_be, "syncprov" )) + si->si_wbe = top_be; + else + si->si_wbe = be; + } else { + si->si_wbe = be; + } + if ( SLAP_SYNC_SUBENTRY( si->si_wbe )) { + build_new_dn( &si->si_contextdn, &si->si_wbe->be_nsuffix[0], + (struct berval *)&slap_ldapsync_cn_bv, NULL ); + } else { + si->si_contextdn = si->si_wbe->be_nsuffix[0]; + } + } + if ( !si->si_schemachecking ) + op->o_no_schema_check = 1; + +reload: + /* Establish session, do search */ + if ( !si->si_ld ) { + si->si_refreshDelete = 0; + si->si_refreshPresent = 0; + + if ( si->si_presentlist ) { + presentlist_free( si->si_presentlist ); + si->si_presentlist = NULL; + } + + /* use main DB when retrieving contextCSN */ + op->o_bd = si->si_wbe; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + rc = do_syncrep1( op, si ); + } + + /* Process results */ + if ( rc == LDAP_SUCCESS ) { + ldap_get_option( si->si_ld, LDAP_OPT_DESC, &s ); + + /* use current DB */ + op->o_bd = be; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + rc = do_syncrep2( op, si ); + if ( rc == LDAP_SYNC_REFRESH_REQUIRED ) { + goto reload; + } + +deleted: + /* We got deleted while running on cn=config */ + if ( si->si_ctype < 1 ) { + if ( si->si_ctype == -1 ) { + si->si_ctype = 0; + freeinfo = 1; + } + if ( si->si_conn ) + dostop = 1; + rc = SYNC_SHUTDOWN; + } + + if ( rc != SYNC_PAUSED ) { + if ( rc == SYNC_TIMEOUT ) { + /* there was nothing to read, try to listen for more */ + if ( si->si_conn ) { + connection_client_enable( si->si_conn ); + } else { + si->si_conn = connection_client_setup( s, do_syncrepl, arg ); + } + } else if ( si->si_conn ) { + dostop = 1; + } + } + } + + /* At this point, we have 5 cases: + * 1) for any hard failure, give up and remove this task + * 2) for ServerDown, reschedule this task to run later + * 3) for threadpool pause, reschedule to run immediately + * 4) for SYNC_REPOLL, reschedule to run later + * 5) for SYNC_TIMEOUT, reschedule to defer + */ + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, rtask ) ) { + ldap_pvt_runqueue_stoptask( &slapd_rq, rtask ); + } + + if ( dostop ) { + connection_client_stop( si->si_conn ); + si->si_conn = NULL; + } + + if ( rc == SYNC_PAUSED ) { + rtask->interval.tv_sec = 0; + ldap_pvt_runqueue_resched( &slapd_rq, rtask, 0 ); + rtask->interval.tv_sec = si->si_interval; + rc = 0; + } else if ( rc == SYNC_TIMEOUT ) { + ldap_pvt_runqueue_resched( &slapd_rq, rtask, 1 ); + } else if ( rc == SYNC_REPOLL ) { + rtask->interval.tv_sec = si->si_interval; + ldap_pvt_runqueue_resched( &slapd_rq, rtask, 0 ); + if ( si->si_retrynum ) { + for ( i = 0; si->si_retrynum_init[i] != RETRYNUM_TAIL; i++ ) { + si->si_retrynum[i] = si->si_retrynum_init[i]; + } + si->si_retrynum[i] = RETRYNUM_TAIL; + } + slap_wake_listener(); + rc = 0; + } else { + for ( i = 0; si->si_retrynum && si->si_retrynum[i] <= 0; i++ ) { + if ( si->si_retrynum[i] == RETRYNUM_FOREVER || si->si_retrynum[i] == RETRYNUM_TAIL ) + break; + } + + if ( si->si_ctype < 1 || rc == SYNC_SHUTDOWN + || !si->si_retrynum || si->si_retrynum[i] == RETRYNUM_TAIL ) { + if ( si->si_re ) { + ldap_pvt_runqueue_remove( &slapd_rq, rtask ); + si->si_re = NULL; + } + fail = RETRYNUM_TAIL; + } else if ( RETRYNUM_VALID( si->si_retrynum[i] ) ) { + if ( si->si_retrynum[i] > 0 ) + si->si_retrynum[i]--; + fail = si->si_retrynum[i]; + rtask->interval.tv_sec = si->si_retryinterval[i]; + ldap_pvt_runqueue_resched( &slapd_rq, rtask, 0 ); + slap_wake_listener(); + } + } + + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + ldap_pvt_thread_mutex_unlock( &si->si_mutex ); + + if ( rc ) { + if ( fail == RETRYNUM_TAIL ) { + Debug( LDAP_DEBUG_ANY, + "do_syncrepl: %s rc %d quitting\n", + si->si_ridtxt, rc, 0 ); + } else if ( fail > 0 ) { + Debug( LDAP_DEBUG_ANY, + "do_syncrepl: %s rc %d retrying (%d retries left)\n", + si->si_ridtxt, rc, fail ); + } else { + Debug( LDAP_DEBUG_ANY, + "do_syncrepl: %s rc %d retrying\n", + si->si_ridtxt, rc, 0 ); + } + } + + /* Do final delete cleanup */ + if ( freeinfo ) { + syncinfo_free( si, 0 ); + } + return NULL; +} + +#ifdef ENABLE_REWRITE +static int +syncrepl_rewrite_dn( + syncinfo_t *si, + struct berval *dn, + struct berval *sdn ) +{ + char nul; + int rc; + + nul = dn->bv_val[dn->bv_len]; + dn->bv_val[dn->bv_len] = 0; + rc = rewrite( si->si_rewrite, SUFFIXM_CTX, dn->bv_val, &sdn->bv_val ); + dn->bv_val[dn->bv_len] = nul; + + if ( sdn->bv_val == dn->bv_val ) + sdn->bv_val = NULL; + else if ( rc == REWRITE_REGEXEC_OK && sdn->bv_val ) + sdn->bv_len = strlen( sdn->bv_val ); + return rc; +} +#define REWRITE_VAL(si, ad, bv, bv2) \ + BER_BVZERO( &bv2 ); \ + if ( si->si_rewrite && ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName) \ + syncrepl_rewrite_dn( si, &bv, &bv2); \ + if ( BER_BVISNULL( &bv2 )) \ + ber_dupbv( &bv2, &bv ) +#define REWRITE_DN(si, bv, bv2, dn, ndn) \ + BER_BVZERO( &bv2 ); \ + if (si->si_rewrite) \ + syncrepl_rewrite_dn(si, &bv, &bv2); \ + rc = dnPrettyNormal( NULL, bv2.bv_val ? &bv2 : &bv, &dn, &ndn, op->o_tmpmemctx ); \ + ch_free(bv2.bv_val) +#else +#define REWRITE_VAL(si, ad, bv, bv2) ber_dupbv(&bv2, &bv) +#define REWRITE_DN(si, bv, bv2, dn, ndn) \ + rc = dnPrettyNormal( NULL, &bv, &dn, &ndn, op->o_tmpmemctx ) +#endif + + +static slap_verbmasks modops[] = { + { BER_BVC("add"), LDAP_REQ_ADD }, + { BER_BVC("delete"), LDAP_REQ_DELETE }, + { BER_BVC("modify"), LDAP_REQ_MODIFY }, + { BER_BVC("modrdn"), LDAP_REQ_MODRDN}, + { BER_BVNULL, 0 } +}; + +static int +syncrepl_accesslog_mods( + syncinfo_t *si, + struct berval *vals, + struct Modifications **modres +) +{ + char *colon; + const char *text; + AttributeDescription *ad; + struct berval bv, bv2; + short op; + Modifications *mod = NULL, *modlist = NULL, **modtail; + int i, rc = 0; + + modtail = &modlist; + + for (i=0; !BER_BVISNULL( &vals[i] ); i++) { + ad = NULL; + bv = vals[i]; + + colon = ber_bvchr( &bv, ':' ); + if ( !colon ) { + /* Invalid */ + continue; + } else if ( colon == bv.bv_val ) { + /* ITS#6545: An empty attribute signals that a new mod + * is about to start */ + mod = NULL; + continue; + } + + bv.bv_len = colon - bv.bv_val; + if ( slap_bv2ad( &bv, &ad, &text ) ) { + /* Invalid */ + Debug( LDAP_DEBUG_ANY, "syncrepl_accesslog_mods: %s " + "Invalid attribute %s, %s\n", + si->si_ridtxt, bv.bv_val, text ); + slap_mods_free( modlist, 1 ); + modlist = NULL; + rc = -1; + break; + } + + /* Ignore dynamically generated attrs */ + if ( ad->ad_type->sat_flags & SLAP_AT_DYNAMIC ) { + continue; + } + + /* Ignore excluded attrs */ + if ( ldap_charray_inlist( si->si_exattrs, + ad->ad_type->sat_cname.bv_val ) ) + { + continue; + } + + switch(colon[1]) { + case '+': op = LDAP_MOD_ADD; break; + case '-': op = LDAP_MOD_DELETE; break; + case '=': op = LDAP_MOD_REPLACE; break; + case '#': op = LDAP_MOD_INCREMENT; break; + default: continue; + } + + if ( !mod || ad != mod->sml_desc || op != mod->sml_op ) { + mod = (Modifications *) ch_malloc( sizeof( Modifications ) ); + mod->sml_flags = 0; + mod->sml_op = op; + mod->sml_next = NULL; + mod->sml_desc = ad; + mod->sml_type = ad->ad_cname; + mod->sml_values = NULL; + mod->sml_nvalues = NULL; + mod->sml_numvals = 0; + + /* Keep 'op' to reflect what we read out from accesslog */ + if ( op == LDAP_MOD_ADD && is_at_single_value( ad->ad_type )) + mod->sml_op = LDAP_MOD_REPLACE; + + *modtail = mod; + modtail = &mod->sml_next; + } + if ( colon[2] == ' ' ) { + bv.bv_val = colon + 3; + bv.bv_len = vals[i].bv_len - ( bv.bv_val - vals[i].bv_val ); + REWRITE_VAL( si, ad, bv, bv2 ); + ber_bvarray_add( &mod->sml_values, &bv2 ); + mod->sml_numvals++; + } + } + *modres = modlist; + return rc; +} + +static int +syncrepl_changelog_mods( + syncinfo_t *si, + struct berval *vals, + struct Modifications **modres +) +{ + return -1; /* FIXME */ +} + +typedef struct OpExtraSync { + OpExtra oe; + syncinfo_t *oe_si; +} OpExtraSync; + +/* Copy the original modlist, split Replace ops into Delete/Add, + * and drop mod opattrs since this modification is in the past. + */ +static Modifications *mods_dup( Operation *op, Modifications *modlist, int match ) +{ + Modifications *mod, *modnew = NULL, *modtail = NULL; + int size; + for ( ; modlist; modlist = modlist->sml_next ) { + /* older ops */ + if ( match < 0 ) { + if ( modlist->sml_desc == slap_schema.si_ad_modifiersName || + modlist->sml_desc == slap_schema.si_ad_modifyTimestamp || + modlist->sml_desc == slap_schema.si_ad_entryCSN ) + continue; + if ( modlist->sml_values == NULL && modlist->sml_op == LDAP_MOD_REPLACE ) { + /* ITS#9359 This adds no values, just change to a delete op */ + modlist->sml_op = LDAP_MOD_DELETE; + } else if ( modlist->sml_op == LDAP_MOD_REPLACE ) { + mod = op->o_tmpalloc( sizeof(Modifications), op->o_tmpmemctx ); + mod->sml_desc = modlist->sml_desc; + mod->sml_values = NULL; + mod->sml_nvalues = NULL; + mod->sml_op = LDAP_MOD_DELETE; + mod->sml_numvals = 0; + mod->sml_flags = 0; + if ( !modnew ) + modnew = mod; + if ( modtail ) + modtail->sml_next = mod; + modtail = mod; + } + } + if ( modlist->sml_numvals ) { + size = (modlist->sml_numvals+1) * sizeof(struct berval); + if ( modlist->sml_nvalues ) size *= 2; + } else { + size = 0; + } + size += sizeof(Modifications); + mod = op->o_tmpalloc( size, op->o_tmpmemctx ); + if ( !modnew ) + modnew = mod; + if ( modtail ) + modtail->sml_next = mod; + modtail = mod; + mod->sml_desc = modlist->sml_desc; + mod->sml_numvals = modlist->sml_numvals; + mod->sml_flags = 0; + if ( modlist->sml_numvals ) { + int i; + mod->sml_values = (BerVarray)(mod+1); + for (i=0; i<mod->sml_numvals; i++) + mod->sml_values[i] = modlist->sml_values[i]; + BER_BVZERO(&mod->sml_values[i]); + if ( modlist->sml_nvalues ) { + mod->sml_nvalues = mod->sml_values + mod->sml_numvals + 1; + for (i=0; i<mod->sml_numvals; i++) + mod->sml_nvalues[i] = modlist->sml_nvalues[i]; + BER_BVZERO(&mod->sml_nvalues[i]); + } else { + mod->sml_nvalues = NULL; + } + } else { + mod->sml_values = NULL; + mod->sml_nvalues = NULL; + } + if ( match < 0 && modlist->sml_op == LDAP_MOD_REPLACE ) + mod->sml_op = LDAP_MOD_ADD; + else + mod->sml_op = modlist->sml_op; + mod->sml_next = NULL; + } + return modnew; +} + +typedef struct resolve_ctxt { + syncinfo_t *rx_si; + Modifications *rx_mods; +} resolve_ctxt; + +static void +compare_vals( Modifications *m1, Modifications *m2 ) +{ + int i, j; + struct berval *bv1, *bv2; + + if ( m2->sml_nvalues ) { + bv2 = m2->sml_nvalues; + bv1 = m1->sml_nvalues; + } else { + bv2 = m2->sml_values; + bv1 = m1->sml_values; + } + for ( j=0; j<m2->sml_numvals; j++ ) { + for ( i=0; i<m1->sml_numvals; i++ ) { + if ( !ber_bvcmp( &bv1[i], &bv2[j] )) { + int k; + for ( k=i; k<m1->sml_numvals-1; k++ ) { + m1->sml_values[k] = m1->sml_values[k+1]; + if ( m1->sml_nvalues ) + m1->sml_nvalues[k] = m1->sml_nvalues[k+1]; + } + BER_BVZERO(&m1->sml_values[k]); + if ( m1->sml_nvalues ) { + BER_BVZERO(&m1->sml_nvalues[k]); + } + m1->sml_numvals--; + i--; + } + } + } +} + +static int +syncrepl_resolve_cb( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH ) { + resolve_ctxt *rx = op->o_callback->sc_private; + Attribute *a = attr_find( rs->sr_entry->e_attrs, ad_reqMod ); + if ( a ) { + Modifications *oldmods, *newmods, *m1, *m2, **prev; + oldmods = rx->rx_mods; + syncrepl_accesslog_mods( rx->rx_si, a->a_vals, &newmods ); + for ( m2 = newmods; m2; m2=m2->sml_next ) { + for ( prev = &oldmods, m1 = *prev; m1; m1 = *prev ) { + if ( m1->sml_desc != m2->sml_desc ) { + prev = &m1->sml_next; + continue; + } + if ( m2->sml_op == LDAP_MOD_DELETE || + m2->sml_op == LDAP_MOD_REPLACE ) { + int numvals = m2->sml_numvals; + if ( m2->sml_op == LDAP_MOD_REPLACE ) + numvals = 0; + /* New delete All cancels everything */ + if ( numvals == 0 ) { +drop: + *prev = m1->sml_next; + op->o_tmpfree( m1, op->o_tmpmemctx ); + continue; + } + if ( m1->sml_op == LDAP_MOD_DELETE ) { + if ( m1->sml_numvals == 0 ) { + /* turn this to SOFTDEL later */ + m1->sml_flags = SLAP_MOD_INTERNAL; + } else { + compare_vals( m1, m2 ); + if ( !m1->sml_numvals ) + goto drop; + } + } else if ( m1->sml_op == LDAP_MOD_ADD ) { + compare_vals( m1, m2 ); + if ( !m1->sml_numvals ) + goto drop; + } + } + + if ( m2->sml_op == LDAP_MOD_ADD || + m2->sml_op == LDAP_MOD_REPLACE ) { + if ( m1->sml_op == LDAP_MOD_DELETE ) { + if ( !m1->sml_numvals ) goto drop; + compare_vals( m1, m2 ); + if ( !m1->sml_numvals ) + goto drop; + } + if ( m2->sml_desc->ad_type->sat_atype.at_single_value ) + goto drop; + compare_vals( m1, m2 ); + if ( !m1->sml_numvals ) + goto drop; + } + prev = &m1->sml_next; + } + } + slap_mods_free( newmods, 1 ); + rx->rx_mods = oldmods; + } + } + return LDAP_SUCCESS; +} + +typedef struct modify_ctxt { + Modifications *mx_orig; + Modifications *mx_free; +} modify_ctxt; + +static int +syncrepl_modify_cb( Operation *op, SlapReply *rs ) +{ + slap_callback *sc = op->o_callback; + modify_ctxt *mx = sc->sc_private; + Modifications *ml; + + op->orm_no_opattrs = 0; + op->orm_modlist = mx->mx_orig; + for ( ml = mx->mx_free; ml; ml = mx->mx_free ) { + mx->mx_free = ml->sml_next; + op->o_tmpfree( ml, op->o_tmpmemctx ); + } + op->o_callback = sc->sc_next; + op->o_tmpfree( sc, op->o_tmpmemctx ); + return SLAP_CB_CONTINUE; +} + +static int +syncrepl_op_modify( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + OpExtra *oex; + syncinfo_t *si; + Entry *e; + int rc, match = 0; + Modifications *mod, *newlist; + + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == (void *)syncrepl_message_to_op ) + break; + } + if ( !oex ) + return SLAP_CB_CONTINUE; + + si = ((OpExtraSync *)oex)->oe_si; + + /* Check if entryCSN in modlist is newer than entryCSN in entry. + * We do it here because the op has been serialized by accesslog + * by the time we get here. If the CSN is new enough, just do the + * mod. If not, we need to resolve conflicts. + */ + + for ( mod = op->orm_modlist; mod; mod=mod->sml_next ) { + if ( mod->sml_desc == slap_schema.si_ad_entryCSN ) break; + } + /* FIXME: what should we do if entryCSN is missing from the mod? */ + if ( !mod ) + return SLAP_CB_CONTINUE; + + { + int sid = slap_parse_csn_sid( &mod->sml_nvalues[0] ); + ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_mutex ); + rc = check_csn_age( si, &op->o_req_dn, &mod->sml_nvalues[0], + sid, (cookie_vals *)&si->si_cookieState->cs_vals, NULL ); + ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex ); + if ( rc == CV_CSN_OLD ) { + slap_graduate_commit_csn( op ); + /* tell accesslog this was a failure */ + rs->sr_err = LDAP_TYPE_OR_VALUE_EXISTS; + return LDAP_SUCCESS; + } + } + + rc = overlay_entry_get_ov( op, &op->o_req_ndn, NULL, NULL, 0, &e, on ); + if ( rc == 0 ) { + Attribute *a; + const char *text; + a = attr_find( e->e_attrs, slap_schema.si_ad_entryCSN ); + if ( a ) { + value_match( &match, slap_schema.si_ad_entryCSN, + slap_schema.si_ad_entryCSN->ad_type->sat_ordering, + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + &mod->sml_nvalues[0], &a->a_nvals[0], &text ); + } else { + /* no entryCSN? shouldn't happen. assume mod is newer. */ + match = 1; + } + overlay_entry_release_ov( op, e, 0, on ); + } else { + return SLAP_CB_CONTINUE; + } + + /* equal? Should never happen */ + if ( match == 0 ) { + slap_graduate_commit_csn( op ); + /* tell accesslog this was a failure */ + rs->sr_err = LDAP_TYPE_OR_VALUE_EXISTS; + return LDAP_SUCCESS; + } + + /* mod is older: resolve conflicts... + * 1. Save/copy original modlist. Split Replace to Del/Add. + * 2. Find all mods to this reqDN newer than the mod stamp. + * 3. Resolve any mods in this request that affect attributes + * touched by newer mods. + * old new + * delete all delete all drop + * delete all delete X SOFTDEL + * delete X delete all drop + * delete X delete X drop + * delete X delete Y OK + * delete all add X drop + * delete X add X drop + * delete X add Y OK + * add X delete all drop + * add X delete X drop + * add X add X drop + * add X add Y if SV, drop else OK + * + * 4. Swap original modlist back in response callback so + * that accesslog logs the original mod. + * + * Even if the mod is newer, other out-of-order changes may + * have been committed, forcing us to tweak the modlist: + * 1. Save/copy original modlist. + * 2. Change deletes to soft deletes. + * 3. Change Adds of single-valued attrs to Replace. + */ + + newlist = mods_dup( op, op->orm_modlist, match ); + + /* mod is older */ + if ( match < 0 ) { + Operation op2 = *op; + AttributeName an[2]; + struct berval bv; + int size; + SlapReply rs1 = {0}; + resolve_ctxt rx; + slap_callback cb = { NULL, syncrepl_resolve_cb, NULL, NULL }; + Filter lf[3] = {0}; + AttributeAssertion aa[2] = {0}; + + rx.rx_si = si; + rx.rx_mods = newlist; + cb.sc_private = ℞ + + op2.o_tag = LDAP_REQ_SEARCH; + op2.ors_scope = LDAP_SCOPE_SUBTREE; + op2.ors_deref = LDAP_DEREF_NEVER; + op2.o_req_dn = si->si_logbase; + op2.o_req_ndn = si->si_logbase; + op2.ors_tlimit = SLAP_NO_LIMIT; + op2.ors_slimit = SLAP_NO_LIMIT; + op2.ors_limit = NULL; + memset( an, 0, sizeof(an)); + an[0].an_desc = ad_reqMod; + an[0].an_name = ad_reqMod->ad_cname; + op2.ors_attrs = an; + op2.ors_attrsonly = 0; + + bv = mod->sml_nvalues[0]; + + size = sizeof("(&(entryCSN>=)(reqDN=))"); + size += bv.bv_len + op->o_req_ndn.bv_len + si->si_logfilterstr.bv_len; + op2.ors_filterstr.bv_val = op->o_tmpalloc( size, op->o_tmpmemctx ); + op2.ors_filterstr.bv_len = sprintf(op2.ors_filterstr.bv_val, + "(&(entryCSN>=%s)(reqDN=%s)%s)", + bv.bv_val, op->o_req_ndn.bv_val, si->si_logfilterstr.bv_val ); + + lf[0].f_choice = LDAP_FILTER_AND; + lf[0].f_and = lf+1; + lf[1].f_choice = LDAP_FILTER_GE; + lf[1].f_ava = aa; + lf[1].f_av_desc = slap_schema.si_ad_entryCSN; + lf[1].f_av_value = bv; + lf[1].f_next = lf+2; + lf[2].f_choice = LDAP_FILTER_EQUALITY; + lf[2].f_ava = aa+1; + lf[2].f_av_desc = ad_reqDN; + lf[2].f_av_value = op->o_req_ndn; + lf[2].f_next = si->si_logfilter; + + op2.ors_filter = lf; + + op2.o_callback = &cb; + op2.o_bd = select_backend( &op2.o_req_ndn, 1 ); + op2.o_bd->be_search( &op2, &rs1 ); + newlist = rx.rx_mods; + } + + { + slap_callback *sc = op->o_tmpalloc( sizeof(slap_callback) + + sizeof(modify_ctxt), op->o_tmpmemctx ); + modify_ctxt *mx = (modify_ctxt *)(sc+1); + Modifications *ml; + + sc->sc_response = syncrepl_modify_cb; + sc->sc_private = mx; + sc->sc_next = op->o_callback; + sc->sc_cleanup = NULL; + sc->sc_writewait = NULL; + op->o_callback = sc; + op->orm_no_opattrs = 1; + mx->mx_orig = op->orm_modlist; + mx->mx_free = newlist; + for ( ml = newlist; ml; ml=ml->sml_next ) { + if ( ml->sml_flags == SLAP_MOD_INTERNAL ) { + ml->sml_flags = 0; + ml->sml_op = SLAP_MOD_SOFTDEL; + } + else if ( ml->sml_op == LDAP_MOD_DELETE ) + ml->sml_op = SLAP_MOD_SOFTDEL; + else if ( ml->sml_op == LDAP_MOD_ADD && + ml->sml_desc->ad_type->sat_atype.at_single_value ) + ml->sml_op = LDAP_MOD_REPLACE; + } + op->orm_modlist = newlist; + op->o_csn = mod->sml_nvalues[0]; + } + return SLAP_CB_CONTINUE; +} + +static int +syncrepl_null_callback( + Operation *op, + SlapReply *rs ) +{ + /* If we're not the last callback in the chain, move to the end */ + if ( op->o_callback->sc_next ) { + slap_callback **sc, *s1; + s1 = op->o_callback; + op->o_callback = op->o_callback->sc_next; + for ( sc = &op->o_callback; *sc; sc = &(*sc)->sc_next ) ; + *sc = s1; + s1->sc_next = NULL; + return SLAP_CB_CONTINUE; + } + if ( rs->sr_err != LDAP_SUCCESS && + rs->sr_err != LDAP_REFERRAL && + rs->sr_err != LDAP_ALREADY_EXISTS && + rs->sr_err != LDAP_NO_SUCH_OBJECT && + rs->sr_err != LDAP_NOT_ALLOWED_ON_NONLEAF ) + { + Debug( LDAP_DEBUG_ANY, + "syncrepl_null_callback : error code 0x%x\n", + rs->sr_err, 0, 0 ); + } + return LDAP_SUCCESS; +} + +static int +syncrepl_message_to_op( + syncinfo_t *si, + Operation *op, + LDAPMessage *msg, + int do_lock +) +{ + BerElement *ber = NULL; + Modifications *modlist = NULL; + logschema *ls; + SlapReply rs = { REP_RESULT }; + slap_callback cb = { NULL, syncrepl_null_callback, NULL, NULL }; + + const char *text; + char txtbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof txtbuf; + + struct berval bdn, dn = BER_BVNULL, ndn; + struct berval bv, bv2, *bvals = NULL; + struct berval rdn = BER_BVNULL, sup = BER_BVNULL, + prdn = BER_BVNULL, nrdn = BER_BVNULL, + psup = BER_BVNULL, nsup = BER_BVNULL; + int rc, deleteOldRdn = 0, freeReqDn = 0; + int do_graduate = 0, do_unlock = 0; + + if ( ldap_msgtype( msg ) != LDAP_RES_SEARCH_ENTRY ) { + Debug( LDAP_DEBUG_ANY, "syncrepl_message_to_op: %s " + "Message type should be entry (%d)", + si->si_ridtxt, ldap_msgtype( msg ), 0 ); + return -1; + } + + if ( si->si_syncdata == SYNCDATA_ACCESSLOG ) + ls = &accesslog_sc; + else + ls = &changelog_sc; + + rc = ldap_get_dn_ber( si->si_ld, msg, &ber, &bdn ); + + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "syncrepl_message_to_op: %s dn get failed (%d)", + si->si_ridtxt, rc, 0 ); + return rc; + } + + op->o_tag = LBER_DEFAULT; + op->o_bd = si->si_wbe; + + if ( BER_BVISEMPTY( &bdn )) { + Debug( LDAP_DEBUG_ANY, + "syncrepl_message_to_op: %s got empty dn", + si->si_ridtxt, 0, 0 ); + return LDAP_OTHER; + } + + while (( rc = ldap_get_attribute_ber( si->si_ld, msg, ber, &bv, &bvals ) ) + == LDAP_SUCCESS ) { + if ( bv.bv_val == NULL ) + break; + + if ( !ber_bvstrcasecmp( &bv, &ls->ls_dn ) ) { + bdn = bvals[0]; + REWRITE_DN( si, bdn, bv2, dn, ndn ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "syncrepl_message_to_op: %s " + "dn \"%s\" normalization failed (%d)", + si->si_ridtxt, bdn.bv_val, rc ); + rc = -1; + ch_free( bvals ); + goto done; + } + ber_dupbv( &op->o_req_dn, &dn ); + ber_dupbv( &op->o_req_ndn, &ndn ); + slap_sl_free( ndn.bv_val, op->o_tmpmemctx ); + slap_sl_free( dn.bv_val, op->o_tmpmemctx ); + freeReqDn = 1; + } else if ( !ber_bvstrcasecmp( &bv, &ls->ls_req ) ) { + int i = verb_to_mask( bvals[0].bv_val, modops ); + if ( i < 0 ) { + Debug( LDAP_DEBUG_ANY, + "syncrepl_message_to_op: %s unknown op %s", + si->si_ridtxt, bvals[0].bv_val, 0 ); + ch_free( bvals ); + rc = -1; + goto done; + } + op->o_tag = modops[i].mask; + } else if ( !ber_bvstrcasecmp( &bv, &ls->ls_mod ) ) { + /* Parse attribute into modlist */ + if ( si->si_syncdata == SYNCDATA_ACCESSLOG ) { + rc = syncrepl_accesslog_mods( si, bvals, &modlist ); + } else { + rc = syncrepl_changelog_mods( si, bvals, &modlist ); + } + if ( rc ) goto done; + } else if ( !ber_bvstrcasecmp( &bv, &ls->ls_newRdn ) ) { + rdn = bvals[0]; + } else if ( !ber_bvstrcasecmp( &bv, &ls->ls_delRdn ) ) { + if ( !ber_bvstrcasecmp( &slap_true_bv, bvals ) ) { + deleteOldRdn = 1; + } + } else if ( !ber_bvstrcasecmp( &bv, &ls->ls_newSup ) ) { + sup = bvals[0]; + } else if ( !ber_bvstrcasecmp( &bv, &ls->ls_controls ) ) { + int i; + struct berval rel_ctrl_bv; + + (void)ber_str2bv( "{" LDAP_CONTROL_RELAX, 0, 0, &rel_ctrl_bv ); + for ( i = 0; bvals[i].bv_val; i++ ) { + struct berval cbv, tmp; + + ber_bvchr_post( &cbv, &bvals[i], '}' ); + ber_bvchr_post( &tmp, &cbv, '{' ); + ber_bvchr_pre( &cbv, &tmp, ' ' ); + if ( cbv.bv_len == tmp.bv_len ) /* control w/o value */ + ber_bvchr_pre( &cbv, &tmp, '}' ); + if ( !ber_bvcmp( &cbv, &rel_ctrl_bv ) ) + op->o_relax = SLAP_CONTROL_CRITICAL; + } + } else if ( !ber_bvstrcasecmp( &bv, + &slap_schema.si_ad_entryCSN->ad_cname ) ) + { + slap_queue_csn( op, bvals ); + do_graduate = 1; + } + ch_free( bvals ); + } + + /* If we didn't get a mod type or a target DN, bail out */ + if ( op->o_tag == LBER_DEFAULT || BER_BVISNULL( &dn ) ) { + rc = -1; + goto done; + } + + if ( do_lock ) { + if (( rc = get_pmutex( si ))) + goto done; + do_unlock = 1; + } + + op->o_callback = &cb; + slap_op_time( &op->o_time, &op->o_tincr ); + + Debug( LDAP_DEBUG_SYNC, "syncrepl_message_to_op: %s tid %x\n", + si->si_ridtxt, op->o_tid, 0 ); + + switch( op->o_tag ) { + case LDAP_REQ_ADD: + case LDAP_REQ_MODIFY: + /* If we didn't get required data, bail */ + if ( !modlist ) goto done; + + rc = slap_mods_check( op, modlist, &text, txtbuf, textlen, NULL ); + + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "syncrepl_message_to_op: %s " + "mods check (%s)\n", + si->si_ridtxt, text, 0 ); + goto done; + } + + if ( op->o_tag == LDAP_REQ_ADD ) { + Entry *e = entry_alloc(); + op->ora_e = e; + op->ora_e->e_name = op->o_req_dn; + op->ora_e->e_nname = op->o_req_ndn; + freeReqDn = 0; + rc = slap_mods2entry( modlist, &op->ora_e, 1, 0, &text, txtbuf, textlen); + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "syncrepl_message_to_op: %s " + "mods2entry (%s)\n", + si->si_ridtxt, text, 0 ); + } else { + rc = op->o_bd->be_add( op, &rs ); + Debug( LDAP_DEBUG_SYNC, + "syncrepl_message_to_op: %s be_add %s (%d)\n", + si->si_ridtxt, op->o_req_dn.bv_val, rc ); + do_graduate = 0; + if ( rc == LDAP_ALREADY_EXISTS ) { + Attribute *a = attr_find( e->e_attrs, slap_schema.si_ad_entryCSN ); + struct berval *vals; + if ( a && backend_attribute( op, NULL, &op->o_req_ndn, + slap_schema.si_ad_entryCSN, &vals, ACL_READ ) == LDAP_SUCCESS ) { + if ( ber_bvcmp( &vals[0], &a->a_vals[0] ) >= 0 ) + rc = LDAP_SUCCESS; + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + } + } + if ( e == op->ora_e ) + be_entry_release_w( op, op->ora_e ); + } else { + OpExtraSync oes; + op->orm_modlist = modlist; + op->o_bd = si->si_wbe; + /* delta-mpr needs additional checks in syncrepl_op_modify */ + if ( SLAP_MULTIMASTER( op->o_bd )) { + oes.oe.oe_key = (void *)syncrepl_message_to_op; + oes.oe_si = si; + LDAP_SLIST_INSERT_HEAD( &op->o_extra, &oes.oe, oe_next ); + } + rc = op->o_bd->be_modify( op, &rs ); + if ( SLAP_MULTIMASTER( op->o_bd )) { + LDAP_SLIST_REMOVE( &op->o_extra, &oes.oe, OpExtra, oe_next ); + BER_BVZERO( &op->o_csn ); + } + modlist = op->orm_modlist; + Debug( rc ? LDAP_DEBUG_ANY : LDAP_DEBUG_SYNC, + "syncrepl_message_to_op: %s be_modify %s (%d)\n", + si->si_ridtxt, op->o_req_dn.bv_val, rc ); + op->o_bd = si->si_be; + do_graduate = 0; + } + break; + case LDAP_REQ_MODRDN: + if ( BER_BVISNULL( &rdn ) ) goto done; + + if ( rdnPretty( NULL, &rdn, &prdn, NULL ) ) { + goto done; + } + if ( rdnNormalize( 0, NULL, NULL, &rdn, &nrdn, NULL ) ) { + goto done; + } + if ( !BER_BVISNULL( &sup ) ) { + REWRITE_DN( si, sup, bv2, psup, nsup ); + if ( rc ) + goto done; + op->orr_newSup = &psup; + op->orr_nnewSup = ⊅ + } else { + op->orr_newSup = NULL; + op->orr_nnewSup = NULL; + } + op->orr_newrdn = prdn; + op->orr_nnewrdn = nrdn; + op->orr_deleteoldrdn = deleteOldRdn; + op->orr_modlist = NULL; + if ( slap_modrdn2mods( op, &rs ) ) { + goto done; + } + + /* Append modlist for operational attrs */ + { + Modifications *m; + + for ( m = op->orr_modlist; m->sml_next; m = m->sml_next ) + ; + m->sml_next = modlist; + modlist = NULL; + } + rc = op->o_bd->be_modrdn( op, &rs ); + slap_mods_free( op->orr_modlist, 1 ); + Debug( rc ? LDAP_DEBUG_ANY : LDAP_DEBUG_SYNC, + "syncrepl_message_to_op: %s be_modrdn %s (%d)\n", + si->si_ridtxt, op->o_req_dn.bv_val, rc ); + do_graduate = 0; + break; + case LDAP_REQ_DELETE: + rc = op->o_bd->be_delete( op, &rs ); + Debug( rc ? LDAP_DEBUG_ANY : LDAP_DEBUG_SYNC, + "syncrepl_message_to_op: %s be_delete %s (%d)\n", + si->si_ridtxt, op->o_req_dn.bv_val, rc ); + /* silently ignore this */ + if ( rc == LDAP_NO_SUCH_OBJECT ) + rc = LDAP_SUCCESS; + do_graduate = 0; + break; + } +done: + if ( do_graduate ) + slap_graduate_commit_csn( op ); + if ( do_unlock ) + ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_pmutex ); + op->o_bd = si->si_be; + op->o_tmpfree( op->o_csn.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &op->o_csn ); + if ( modlist ) { + slap_mods_free( modlist, op->o_tag != LDAP_REQ_ADD ); + } + if ( !BER_BVISNULL( &rdn ) ) { + if ( !BER_BVISNULL( &nsup ) ) { + ch_free( nsup.bv_val ); + } + if ( !BER_BVISNULL( &psup ) ) { + ch_free( psup.bv_val ); + } + if ( !BER_BVISNULL( &nrdn ) ) { + ch_free( nrdn.bv_val ); + } + if ( !BER_BVISNULL( &prdn ) ) { + ch_free( prdn.bv_val ); + } + } + if ( freeReqDn ) { + ch_free( op->o_req_ndn.bv_val ); + ch_free( op->o_req_dn.bv_val ); + } + ber_free( ber, 0 ); + return rc; +} + +static int +syncrepl_message_to_entry( + syncinfo_t *si, + Operation *op, + LDAPMessage *msg, + Modifications **modlist, + Entry **entry, + int syncstate, + struct berval *syncUUID +) +{ + Entry *e = NULL; + BerElement *ber = NULL; + Modifications tmp; + Modifications *mod; + Modifications **modtail = modlist; + + const char *text; + char txtbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof txtbuf; + + struct berval bdn = BER_BVNULL, dn, ndn, bv2; + int rc, is_ctx; + + *modlist = NULL; + + if ( ldap_msgtype( msg ) != LDAP_RES_SEARCH_ENTRY ) { + Debug( LDAP_DEBUG_ANY, "syncrepl_message_to_entry: %s " + "Message type should be entry (%d)", + si->si_ridtxt, ldap_msgtype( msg ), 0 ); + return -1; + } + + op->o_tag = LDAP_REQ_ADD; + + rc = ldap_get_dn_ber( si->si_ld, msg, &ber, &bdn ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "syncrepl_message_to_entry: %s dn get failed (%d)", + si->si_ridtxt, rc, 0 ); + return rc; + } + + if ( BER_BVISEMPTY( &bdn ) && !BER_BVISEMPTY( &op->o_bd->be_nsuffix[0] ) ) { + Debug( LDAP_DEBUG_ANY, + "syncrepl_message_to_entry: %s got empty dn", + si->si_ridtxt, 0, 0 ); + return LDAP_OTHER; + } + + /* syncUUID[0] is normalized UUID received over the wire + * syncUUID[1] is denormalized UUID, generated here + */ + (void)slap_uuidstr_from_normalized( &syncUUID[1], &syncUUID[0], op->o_tmpmemctx ); + Debug( LDAP_DEBUG_SYNC, + "syncrepl_message_to_entry: %s DN: %s, UUID: %s\n", + si->si_ridtxt, bdn.bv_val, syncUUID[1].bv_val ); + + if ( syncstate == LDAP_SYNC_PRESENT || syncstate == LDAP_SYNC_DELETE ) { + /* NOTE: this could be done even before decoding the DN, + * although encoding errors wouldn't be detected */ + rc = LDAP_SUCCESS; + goto done; + } + + if ( entry == NULL ) { + return -1; + } + + REWRITE_DN( si, bdn, bv2, dn, ndn ); + if ( rc != LDAP_SUCCESS ) { + /* One of the things that could happen is that the schema + * is not lined-up; this could result in unknown attributes. + * A value non conformant to the syntax should be unlikely, + * except when replicating between different versions + * of the software, or when syntax validation bugs are fixed + */ + Debug( LDAP_DEBUG_ANY, + "syncrepl_message_to_entry: " + "%s dn \"%s\" normalization failed (%d)", + si->si_ridtxt, bdn.bv_val, rc ); + return rc; + } + + ber_dupbv( &op->o_req_dn, &dn ); + ber_dupbv( &op->o_req_ndn, &ndn ); + slap_sl_free( ndn.bv_val, op->o_tmpmemctx ); + slap_sl_free( dn.bv_val, op->o_tmpmemctx ); + + is_ctx = dn_match( &op->o_req_ndn, &op->o_bd->be_nsuffix[0] ); + + e = entry_alloc(); + e->e_name = op->o_req_dn; + e->e_nname = op->o_req_ndn; + + while ( ber_remaining( ber ) ) { + if ( (ber_scanf( ber, "{mW}", &tmp.sml_type, &tmp.sml_values ) == + LBER_ERROR ) || BER_BVISNULL( &tmp.sml_type ) ) + { + break; + } + + /* Drop all updates to the contextCSN of the context entry + * (ITS#4622, etc.) + */ + if ( is_ctx && !strcasecmp( tmp.sml_type.bv_val, + slap_schema.si_ad_contextCSN->ad_cname.bv_val )) { + ber_bvarray_free( tmp.sml_values ); + continue; + } + + mod = (Modifications *) ch_malloc( sizeof( Modifications ) ); + + mod->sml_op = LDAP_MOD_REPLACE; + mod->sml_flags = 0; + mod->sml_next = NULL; + mod->sml_desc = NULL; + mod->sml_type = tmp.sml_type; + mod->sml_values = tmp.sml_values; + mod->sml_nvalues = NULL; + mod->sml_numvals = 0; /* slap_mods_check will set this */ + +#ifdef ENABLE_REWRITE + if (si->si_rewrite) { + AttributeDescription *ad = NULL; + slap_bv2ad( &tmp.sml_type, &ad, &text ); + if ( ad ) { + mod->sml_desc = ad; + mod->sml_type = ad->ad_cname; + if ( ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) { + int i; + for ( i = 0; tmp.sml_values[i].bv_val; i++ ) { + syncrepl_rewrite_dn( si, &tmp.sml_values[i], &bv2); + if ( !BER_BVISNULL( &bv2 )) { + ber_memfree( tmp.sml_values[i].bv_val ); + tmp.sml_values[i] = bv2; + } + } + } + } + } +#endif + *modtail = mod; + modtail = &mod->sml_next; + } + + if ( *modlist == NULL ) { + Debug( LDAP_DEBUG_ANY, "syncrepl_message_to_entry: %s no attributes\n", + si->si_ridtxt, 0, 0 ); + rc = -1; + goto done; + } + + rc = slap_mods_check( op, *modlist, &text, txtbuf, textlen, NULL ); + + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "syncrepl_message_to_entry: %s mods check (%s)\n", + si->si_ridtxt, text, 0 ); + goto done; + } + + /* Strip out dynamically generated attrs */ + for ( modtail = modlist; *modtail ; ) { + mod = *modtail; + if ( mod->sml_desc->ad_type->sat_flags & SLAP_AT_DYNAMIC ) { + *modtail = mod->sml_next; + slap_mod_free( &mod->sml_mod, 0 ); + ch_free( mod ); + } else { + modtail = &mod->sml_next; + } + } + + /* Strip out attrs in exattrs list */ + for ( modtail = modlist; *modtail ; ) { + mod = *modtail; + if ( ldap_charray_inlist( si->si_exattrs, + mod->sml_desc->ad_type->sat_cname.bv_val ) ) + { + *modtail = mod->sml_next; + slap_mod_free( &mod->sml_mod, 0 ); + ch_free( mod ); + } else { + modtail = &mod->sml_next; + } + } + + rc = slap_mods2entry( *modlist, &e, 1, 1, &text, txtbuf, textlen); + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "syncrepl_message_to_entry: %s mods2entry (%s)\n", + si->si_ridtxt, text, 0 ); + } + +done: + ber_free( ber, 0 ); + if ( rc != LDAP_SUCCESS ) { + if ( e ) { + entry_free( e ); + e = NULL; + } + } + if ( entry ) + *entry = e; + + return rc; +} + +static struct berval generic_filterstr = BER_BVC("(objectclass=*)"); + +/* During a refresh, we may get an LDAP_SYNC_ADD for an already existing + * entry if a previous refresh was interrupted before sending us a new + * context state. We try to compare the new entry to the existing entry + * and ignore the new entry if they are the same. + * + * Also, we may get an update where the entryDN has changed, due to + * a ModDn on the provider. We detect this as well, so we can issue + * the corresponding operation locally. + * + * In the case of a modify, we get a list of all the attributes + * in the original entry. Rather than deleting the entry and re-adding it, + * we issue a Modify request that deletes all the attributes and adds all + * the new ones. This avoids the issue of trying to delete/add a non-leaf + * entry. + * + * We otherwise distinguish ModDN from Modify; in the case of + * a ModDN we just use the CSN, modifyTimestamp and modifiersName + * operational attributes from the entry, and do a regular ModDN. + */ +typedef struct dninfo { + syncinfo_t *si; + Entry *new_entry; + struct berval dn; + struct berval ndn; + struct berval nnewSup; + int renamed; /* Was an existing entry renamed? */ + int delOldRDN; /* Was old RDN deleted? */ + Modifications **modlist; /* the modlist we received */ + Modifications *mods; /* the modlist we compared */ + int oldNcount; /* #values of old naming attr */ + AttributeDescription *oldDesc; /* for renames */ + AttributeDescription *newDesc; /* for renames */ +} dninfo; + +#define HASHUUID 1 + +/* return 1 if inserted, 0 otherwise */ +static int +presentlist_insert( + syncinfo_t* si, + struct berval *syncUUID ) +{ + char *val; + +#ifdef HASHUUID + Avlnode **av; + unsigned short s; + + if ( !si->si_presentlist ) + si->si_presentlist = ch_calloc(65536, sizeof( Avlnode * )); + + av = (Avlnode **)si->si_presentlist; + + val = ch_malloc(UUIDLEN-2); + memcpy(&s, syncUUID->bv_val, 2); + memcpy(val, syncUUID->bv_val+2, UUIDLEN-2); + + if ( avl_insert( &av[s], val, + syncuuid_cmp, avl_dup_error ) ) + { + ch_free( val ); + return 0; + } +#else + val = ch_malloc(UUIDLEN); + + AC_MEMCPY( val, syncUUID->bv_val, UUIDLEN ); + + if ( avl_insert( &si->si_presentlist, val, + syncuuid_cmp, avl_dup_error ) ) + { + ch_free( val ); + return 0; + } +#endif + + return 1; +} + +static char * +presentlist_find( + Avlnode *av, + struct berval *val ) +{ +#ifdef HASHUUID + Avlnode **a2 = (Avlnode **)av; + unsigned short s; + + if (!av) + return NULL; + + memcpy(&s, val->bv_val, 2); + return avl_find( a2[s], val->bv_val+2, syncuuid_cmp ); +#else + return avl_find( av, val->bv_val, syncuuid_cmp ); +#endif +} + +static int +presentlist_free( Avlnode *av ) +{ +#ifdef HASHUUID + Avlnode **a2 = (Avlnode **)av; + int i, count = 0; + + if ( av ) { + for (i=0; i<65536; i++) { + if (a2[i]) + count += avl_free( a2[i], ch_free ); + } + ch_free( av ); + } + return count; +#else + return avl_free( av, ch_free ); +#endif +} + +static void +presentlist_delete( + Avlnode **av, + struct berval *val ) +{ +#ifdef HASHUUID + Avlnode **a2 = *(Avlnode ***)av; + unsigned short s; + + memcpy(&s, val->bv_val, 2); + avl_delete( &a2[s], val->bv_val+2, syncuuid_cmp ); +#else + avl_delete( av, val->bv_val, syncuuid_cmp ); +#endif +} + +static int +syncrepl_entry( + syncinfo_t* si, + Operation *op, + Entry* entry, + Modifications** modlist, + int syncstate, + struct berval* syncUUID, + struct berval* syncCSN ) +{ + Backend *be = op->o_bd; + slap_callback cb = { NULL, NULL, NULL, NULL }; + int syncuuid_inserted = 0; + + SlapReply rs_search = {REP_RESULT}; + Filter f = {0}; + AttributeAssertion ava = ATTRIBUTEASSERTION_INIT; + int rc = LDAP_SUCCESS; + + struct berval pdn = BER_BVNULL; + dninfo dni = {0}; + int retry = 1; + int freecsn = 1; + + Log4( LDAP_DEBUG_SYNC, ldap_syslog_level, + "syncrepl_entry: %s LDAP_RES_SEARCH_ENTRY(LDAP_SYNC_%s) csn=%s tid %x\n", + si->si_ridtxt, syncrepl_state2str( syncstate ), syncCSN ? syncCSN->bv_val : "(none)", op->o_tid ); + + if (( syncstate == LDAP_SYNC_PRESENT || syncstate == LDAP_SYNC_ADD ) ) { + if ( !si->si_refreshPresent && !si->si_refreshDone ) { + syncuuid_inserted = presentlist_insert( si, syncUUID ); + } + } + + if ( syncstate == LDAP_SYNC_PRESENT ) { + return 0; + } else if ( syncstate != LDAP_SYNC_DELETE ) { + if ( entry == NULL ) { + return 0; + } + } + + if ( syncstate != LDAP_SYNC_DELETE ) { + Attribute *a = attr_find( entry->e_attrs, slap_schema.si_ad_entryUUID ); + + if ( a == NULL ) { + /* add if missing */ + attr_merge_one( entry, slap_schema.si_ad_entryUUID, + &syncUUID[1], syncUUID ); + + } else if ( !bvmatch( &a->a_nvals[0], syncUUID ) ) { + /* replace only if necessary */ + if ( a->a_nvals != a->a_vals ) { + ber_memfree( a->a_nvals[0].bv_val ); + ber_dupbv( &a->a_nvals[0], syncUUID ); + } + ber_memfree( a->a_vals[0].bv_val ); + ber_dupbv( &a->a_vals[0], &syncUUID[1] ); + } + } + + f.f_choice = LDAP_FILTER_EQUALITY; + f.f_ava = &ava; + ava.aa_desc = slap_schema.si_ad_entryUUID; + ava.aa_value = *syncUUID; + + if ( syncuuid_inserted ) { + Debug( LDAP_DEBUG_SYNC, "syncrepl_entry: %s inserted UUID %s\n", + si->si_ridtxt, syncUUID[1].bv_val, 0 ); + } + op->ors_filter = &f; + + op->ors_filterstr.bv_len = STRLENOF( "(entryUUID=)" ) + syncUUID[1].bv_len; + op->ors_filterstr.bv_val = (char *) slap_sl_malloc( + op->ors_filterstr.bv_len + 1, op->o_tmpmemctx ); + AC_MEMCPY( op->ors_filterstr.bv_val, "(entryUUID=", STRLENOF( "(entryUUID=" ) ); + AC_MEMCPY( &op->ors_filterstr.bv_val[STRLENOF( "(entryUUID=" )], + syncUUID[1].bv_val, syncUUID[1].bv_len ); + op->ors_filterstr.bv_val[op->ors_filterstr.bv_len - 1] = ')'; + op->ors_filterstr.bv_val[op->ors_filterstr.bv_len] = '\0'; + + op->o_tag = LDAP_REQ_SEARCH; + op->ors_scope = LDAP_SCOPE_SUBTREE; + op->ors_deref = LDAP_DEREF_NEVER; + + /* get the entry for this UUID */ +#ifdef ENABLE_REWRITE + if ( si->si_rewrite ) { + op->o_req_dn = si->si_suffixm; + op->o_req_ndn = si->si_suffixm; + } else +#endif + { + op->o_req_dn = si->si_base; + op->o_req_ndn = si->si_base; + } + + op->o_time = slap_get_time(); + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_slimit = 1; + op->ors_limit = NULL; + + op->ors_attrs = slap_anlist_all_attributes; + op->ors_attrsonly = 0; + + /* set callback function */ + op->o_callback = &cb; + cb.sc_response = dn_callback; + cb.sc_private = &dni; + dni.si = si; + dni.new_entry = entry; + dni.modlist = modlist; + + rc = be->be_search( op, &rs_search ); + Debug( LDAP_DEBUG_SYNC, + "syncrepl_entry: %s be_search (%d)\n", + si->si_ridtxt, rc, 0 ); + + if ( !BER_BVISNULL( &op->ors_filterstr ) ) { + slap_sl_free( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + } + + cb.sc_response = syncrepl_null_callback; + cb.sc_private = si; + + if ( entry && !BER_BVISNULL( &entry->e_name ) ) { + Debug( LDAP_DEBUG_SYNC, + "syncrepl_entry: %s %s\n", + si->si_ridtxt, entry->e_name.bv_val, 0 ); + } else { + Debug( LDAP_DEBUG_SYNC, + "syncrepl_entry: %s %s\n", + si->si_ridtxt, dni.dn.bv_val ? dni.dn.bv_val : "(null)", 0 ); + } + + assert( BER_BVISNULL( &op->o_csn ) ); + if ( syncCSN ) { + slap_queue_csn( op, syncCSN ); + } + + slap_op_time( &op->o_time, &op->o_tincr ); + switch ( syncstate ) { + case LDAP_SYNC_ADD: + case LDAP_SYNC_MODIFY: + if ( BER_BVISNULL( &op->o_csn )) + { + + Attribute *a = attr_find( entry->e_attrs, slap_schema.si_ad_entryCSN ); + if ( a ) { + /* FIXME: op->o_csn is assumed to be + * on the thread's slab; this needs + * to be cleared ASAP. + */ + op->o_csn = a->a_vals[0]; + freecsn = 0; + } + } +retry_add:; + if ( BER_BVISNULL( &dni.dn ) ) { + SlapReply rs_add = {REP_RESULT}; + + op->o_req_dn = entry->e_name; + op->o_req_ndn = entry->e_nname; + op->o_tag = LDAP_REQ_ADD; + op->ora_e = entry; + op->o_bd = si->si_wbe; + + rc = op->o_bd->be_add( op, &rs_add ); + Debug( LDAP_DEBUG_SYNC, + "syncrepl_entry: %s be_add %s (%d)\n", + si->si_ridtxt, op->o_req_dn.bv_val, rc ); + switch ( rs_add.sr_err ) { + case LDAP_SUCCESS: + if ( op->ora_e == entry ) { + be_entry_release_w( op, entry ); + } + entry = NULL; + break; + + case LDAP_REFERRAL: + /* we assume that LDAP_NO_SUCH_OBJECT is returned + * only if the suffix entry is not present. + * This should not happen during Persist phase. + */ + case LDAP_NO_SUCH_OBJECT: + if ( abs(si->si_type) == LDAP_SYNC_REFRESH_AND_PERSIST && + si->si_refreshDone ) { + /* Something's wrong, start over */ + ber_bvarray_free( si->si_syncCookie.ctxcsn ); + si->si_syncCookie.ctxcsn = NULL; + entry_free( entry ); + ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_mutex ); + ber_bvarray_free( si->si_cookieState->cs_vals ); + ch_free( si->si_cookieState->cs_sids ); + si->si_cookieState->cs_vals = NULL; + si->si_cookieState->cs_sids = 0; + si->si_cookieState->cs_num = 0; + ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex ); + return LDAP_NO_SUCH_OBJECT; + } + rc = syncrepl_add_glue( op, entry ); + entry = NULL; + break; + + /* if an entry was added via syncrepl_add_glue(), + * it likely has no entryUUID, so the previous + * be_search() doesn't find it. In this case, + * give syncrepl a chance to modify it. Also + * allow for entries that were recreated with the + * same DN but a different entryUUID. + */ + case LDAP_ALREADY_EXISTS: + if ( retry ) { + Operation op2 = *op; + SlapReply rs2 = { REP_RESULT }; + slap_callback cb2 = { 0 }; + + op2.o_bd = be; + op2.o_tag = LDAP_REQ_SEARCH; + op2.o_req_dn = entry->e_name; + op2.o_req_ndn = entry->e_nname; + op2.ors_scope = LDAP_SCOPE_BASE; + op2.ors_deref = LDAP_DEREF_NEVER; + op2.ors_attrs = slap_anlist_all_attributes; + op2.ors_attrsonly = 0; + op2.ors_limit = NULL; + op2.ors_slimit = 1; + op2.ors_tlimit = SLAP_NO_LIMIT; + + f.f_choice = LDAP_FILTER_PRESENT; + f.f_desc = slap_schema.si_ad_objectClass; + op2.ors_filter = &f; + op2.ors_filterstr = generic_filterstr; + + op2.o_callback = &cb2; + cb2.sc_response = dn_callback; + cb2.sc_private = &dni; + + rc = be->be_search( &op2, &rs2 ); + if ( rc ) goto done; + + retry = 0; + slap_op_time( &op->o_time, &op->o_tincr ); + goto retry_add; + } + /* FALLTHRU */ + + default: + Debug( LDAP_DEBUG_ANY, + "syncrepl_entry: %s be_add %s failed (%d)\n", + si->si_ridtxt, op->o_req_dn.bv_val, rs_add.sr_err ); + break; + } + syncCSN = NULL; + op->o_bd = be; + goto done; + } + /* FALLTHRU */ + op->o_req_dn = dni.dn; + op->o_req_ndn = dni.ndn; + if ( dni.renamed ) { + struct berval noldp, newp; + Modifications *mod, **modtail, **ml, *m2; + int i, got_replace = 0, just_rename = 0; + SlapReply rs_modify = {REP_RESULT}; + + op->o_tag = LDAP_REQ_MODRDN; + dnRdn( &entry->e_name, &op->orr_newrdn ); + dnRdn( &entry->e_nname, &op->orr_nnewrdn ); + + if ( !BER_BVISNULL( &dni.nnewSup )) { + dnParent( &entry->e_name, &newp ); + op->orr_newSup = &newp; + op->orr_nnewSup = &dni.nnewSup; + } else { + op->orr_newSup = NULL; + op->orr_nnewSup = NULL; + } + op->orr_deleteoldrdn = dni.delOldRDN; + op->orr_modlist = NULL; + if ( ( rc = slap_modrdn2mods( op, &rs_modify ) ) ) { + goto done; + } + + /* Drop the RDN-related mods from this op, because their + * equivalents were just setup by slap_modrdn2mods. + * + * If delOldRDN is TRUE then we should see a delete modop + * for oldDesc. We might see a replace instead. + * delete with no values: therefore newDesc != oldDesc. + * if oldNcount == 1, then Drop this op. + * delete with 1 value: can only be the oldRDN value. Drop op. + * delete with N values: Drop oldRDN value, keep remainder. + * replace with 1 value: if oldNcount == 1 and + * newDesc == oldDesc, Drop this op. + * Any other cases must be left intact. + * + * We should also see an add modop for newDesc. (But not if + * we got a replace modop due to delOldRDN.) If it has + * multiple values, we'll have to drop the new RDN value. + */ + modtail = &op->orr_modlist; + if ( dni.delOldRDN ) { + for ( ml = &dni.mods; *ml; ml = &(*ml)->sml_next ) { + if ( (*ml)->sml_desc == dni.oldDesc ) { + mod = *ml; + if ( mod->sml_op == LDAP_MOD_REPLACE && + dni.oldDesc != dni.newDesc ) { + /* This Replace is due to other Mods. + * Just let it ride. + */ + continue; + } + if ( mod->sml_numvals <= 1 && + dni.oldNcount == 1 && + ( mod->sml_op == LDAP_MOD_DELETE || + mod->sml_op == LDAP_MOD_REPLACE )) { + if ( mod->sml_op == LDAP_MOD_REPLACE ) + got_replace = 1; + /* Drop this op */ + *ml = mod->sml_next; + mod->sml_next = NULL; + slap_mods_free( mod, 1 ); + break; + } + if ( mod->sml_op != LDAP_MOD_DELETE || mod->sml_numvals == 0 ) + continue; + for ( m2 = op->orr_modlist; m2; m2=m2->sml_next ) { + if ( m2->sml_desc == dni.oldDesc && + m2->sml_op == LDAP_MOD_DELETE ) break; + } + for ( i=0; i<mod->sml_numvals; i++ ) { + if ( bvmatch( &mod->sml_values[i], &m2->sml_values[0] )) { + mod->sml_numvals--; + ch_free( mod->sml_values[i].bv_val ); + mod->sml_values[i] = mod->sml_values[mod->sml_numvals]; + BER_BVZERO( &mod->sml_values[mod->sml_numvals] ); + if ( mod->sml_nvalues ) { + ch_free( mod->sml_nvalues[i].bv_val ); + mod->sml_nvalues[i] = mod->sml_nvalues[mod->sml_numvals]; + BER_BVZERO( &mod->sml_nvalues[mod->sml_numvals] ); + } + break; + } + } + if ( !mod->sml_numvals ) { + /* Drop this op */ + *ml = mod->sml_next; + mod->sml_next = NULL; + slap_mods_free( mod, 1 ); + } + break; + } + } + } + if ( !got_replace ) { + for ( ml = &dni.mods; *ml; ml = &(*ml)->sml_next ) { + if ( (*ml)->sml_desc == dni.newDesc ) { + mod = *ml; + if ( mod->sml_op != LDAP_MOD_ADD ) + continue; + if ( mod->sml_numvals == 1 ) { + /* Drop this op */ + *ml = mod->sml_next; + mod->sml_next = NULL; + slap_mods_free( mod, 1 ); + break; + } + for ( m2 = op->orr_modlist; m2; m2=m2->sml_next ) { + if ( m2->sml_desc == dni.oldDesc && + m2->sml_op == SLAP_MOD_SOFTADD ) break; + } + for ( i=0; i<mod->sml_numvals; i++ ) { + if ( bvmatch( &mod->sml_values[i], &m2->sml_values[0] )) { + mod->sml_numvals--; + ch_free( mod->sml_values[i].bv_val ); + mod->sml_values[i] = mod->sml_values[mod->sml_numvals]; + BER_BVZERO( &mod->sml_values[mod->sml_numvals] ); + if ( mod->sml_nvalues ) { + ch_free( mod->sml_nvalues[i].bv_val ); + mod->sml_nvalues[i] = mod->sml_nvalues[mod->sml_numvals]; + BER_BVZERO( &mod->sml_nvalues[mod->sml_numvals] ); + } + break; + } + } + break; + } + } + } + + /* RDNs must be NUL-terminated for back-ldap */ + noldp = op->orr_newrdn; + ber_dupbv_x( &op->orr_newrdn, &noldp, op->o_tmpmemctx ); + noldp = op->orr_nnewrdn; + ber_dupbv_x( &op->orr_nnewrdn, &noldp, op->o_tmpmemctx ); + + /* Setup opattrs too */ + { + static AttributeDescription *nullattr = NULL; + static AttributeDescription **const opattrs[] = { + &slap_schema.si_ad_entryCSN, + &slap_schema.si_ad_modifiersName, + &slap_schema.si_ad_modifyTimestamp, + &nullattr + }; + AttributeDescription *opattr; + int i; + + modtail = &m2; + /* pull mod off incoming modlist */ + for ( i = 0; (opattr = *opattrs[i]) != NULL; i++ ) { + for ( ml = &dni.mods; *ml; ml = &(*ml)->sml_next ) + { + if ( (*ml)->sml_desc == opattr ) { + mod = *ml; + *ml = mod->sml_next; + mod->sml_next = NULL; + *modtail = mod; + modtail = &mod->sml_next; + break; + } + } + } + /* If there are still Modifications left, put the opattrs + * back, and let be_modify run. Otherwise, append the opattrs + * to the orr_modlist. + */ + if ( dni.mods ) { + mod = dni.mods; + /* don't set a CSN for the rename op */ + if ( syncCSN ) + slap_graduate_commit_csn( op ); + } else { + mod = op->orr_modlist; + just_rename = 1; + } + for ( ; mod->sml_next; mod=mod->sml_next ); + mod->sml_next = m2; + } + op->o_bd = si->si_wbe; +retry_modrdn:; + rs_reinit( &rs_modify, REP_RESULT ); + rc = op->o_bd->be_modrdn( op, &rs_modify ); + + /* NOTE: noSuchObject should result because the new superior + * has not been added yet (ITS#6472) */ + if ( rc == LDAP_NO_SUCH_OBJECT && op->orr_nnewSup != NULL ) { + Operation op2 = *op; + rc = syncrepl_add_glue_ancestors( &op2, entry ); + if ( rc == LDAP_SUCCESS ) { + goto retry_modrdn; + } + } + + op->o_tmpfree( op->orr_nnewrdn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( op->orr_newrdn.bv_val, op->o_tmpmemctx ); + + slap_mods_free( op->orr_modlist, 1 ); + Debug( LDAP_DEBUG_SYNC, + "syncrepl_entry: %s be_modrdn %s (%d)\n", + si->si_ridtxt, op->o_req_dn.bv_val, rc ); + op->o_bd = be; + /* Renamed entries may still have other mods so just fallthru */ + op->o_req_dn = entry->e_name; + op->o_req_ndn = entry->e_nname; + /* Use CSN on the modify */ + if ( just_rename ) + syncCSN = NULL; + else if ( syncCSN ) + slap_queue_csn( op, syncCSN ); + } + if ( dni.mods ) { + SlapReply rs_modify = {REP_RESULT}; + + op->o_tag = LDAP_REQ_MODIFY; + op->orm_modlist = dni.mods; + op->orm_no_opattrs = 1; + op->o_bd = si->si_wbe; + + rc = op->o_bd->be_modify( op, &rs_modify ); + slap_mods_free( op->orm_modlist, 1 ); + op->orm_no_opattrs = 0; + Debug( LDAP_DEBUG_SYNC, + "syncrepl_entry: %s be_modify %s (%d)\n", + si->si_ridtxt, op->o_req_dn.bv_val, rc ); + if ( rs_modify.sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "syncrepl_entry: %s be_modify failed (%d)\n", + si->si_ridtxt, rs_modify.sr_err, 0 ); + } + syncCSN = NULL; + op->o_bd = be; + } else if ( !dni.renamed ) { + Debug( LDAP_DEBUG_SYNC, + "syncrepl_entry: %s entry unchanged, ignored (%s)\n", + si->si_ridtxt, op->o_req_dn.bv_val, 0 ); + if ( syncCSN ) { + slap_graduate_commit_csn( op ); + syncCSN = NULL; + } + } + goto done; + case LDAP_SYNC_DELETE : + if ( !BER_BVISNULL( &dni.dn ) ) { + SlapReply rs_delete = {REP_RESULT}; + op->o_req_dn = dni.dn; + op->o_req_ndn = dni.ndn; + op->o_tag = LDAP_REQ_DELETE; + op->o_bd = si->si_wbe; + if ( !syncCSN ) { + slap_queue_csn( op, si->si_syncCookie.ctxcsn ); + } + rc = op->o_bd->be_delete( op, &rs_delete ); + Debug( LDAP_DEBUG_SYNC, + "syncrepl_entry: %s be_delete %s (%d)\n", + si->si_ridtxt, op->o_req_dn.bv_val, rc ); + if ( rc == LDAP_NO_SUCH_OBJECT ) + rc = LDAP_SUCCESS; + + while ( rs_delete.sr_err == LDAP_SUCCESS + && op->o_delete_glue_parent ) { + op->o_delete_glue_parent = 0; + if ( !be_issuffix( be, &op->o_req_ndn ) ) { + slap_callback cb = { NULL }; + cb.sc_response = syncrepl_null_callback; + dnParent( &op->o_req_ndn, &pdn ); + op->o_req_dn = pdn; + op->o_req_ndn = pdn; + op->o_callback = &cb; + rs_reinit( &rs_delete, REP_RESULT ); + op->o_bd->be_delete( op, &rs_delete ); + } else { + break; + } + } + syncCSN = NULL; + op->o_bd = be; + } + goto done; + + default : + Debug( LDAP_DEBUG_ANY, + "syncrepl_entry: %s unknown syncstate\n", si->si_ridtxt, 0, 0 ); + goto done; + } + +done: + slap_sl_free( syncUUID[1].bv_val, op->o_tmpmemctx ); + BER_BVZERO( &syncUUID[1] ); + if ( !BER_BVISNULL( &dni.ndn ) ) { + op->o_tmpfree( dni.ndn.bv_val, op->o_tmpmemctx ); + } + if ( !BER_BVISNULL( &dni.dn ) ) { + op->o_tmpfree( dni.dn.bv_val, op->o_tmpmemctx ); + } + if ( entry ) { + entry_free( entry ); + } + if ( syncCSN ) { + slap_graduate_commit_csn( op ); + } + if ( !BER_BVISNULL( &op->o_csn ) && freecsn ) { + op->o_tmpfree( op->o_csn.bv_val, op->o_tmpmemctx ); + } + BER_BVZERO( &op->o_csn ); + return rc; +} + +static struct berval gcbva[] = { + BER_BVC("top"), + BER_BVC("glue"), + BER_BVNULL +}; + +#define NP_DELETE_ONE 2 + +static void +syncrepl_del_nonpresent( + Operation *op, + syncinfo_t *si, + BerVarray uuids, + struct sync_cookie *sc, + int m ) +{ + Backend* be = op->o_bd; + slap_callback cb = { NULL }; + struct nonpresent_entry *np_list, *np_prev; + int rc; + AttributeName an[3]; /* entryUUID, entryCSN, NULL */ + + struct berval pdn = BER_BVNULL; + struct berval csn; + +#ifdef ENABLE_REWRITE + if ( si->si_rewrite ) { + op->o_req_dn = si->si_suffixm; + op->o_req_ndn = si->si_suffixm; + } else +#endif + { + op->o_req_dn = si->si_base; + op->o_req_ndn = si->si_base; + } + + cb.sc_response = nonpresent_callback; + cb.sc_private = si; + + op->o_callback = &cb; + op->o_tag = LDAP_REQ_SEARCH; + op->ors_scope = si->si_scope; + op->ors_deref = LDAP_DEREF_NEVER; + op->o_time = slap_get_time(); + op->ors_tlimit = SLAP_NO_LIMIT; + + + if ( uuids ) { + Filter uf; + AttributeAssertion eq = ATTRIBUTEASSERTION_INIT; + int i; + + op->ors_attrsonly = 1; + op->ors_attrs = slap_anlist_no_attrs; + op->ors_limit = NULL; + op->ors_filter = &uf; + + uf.f_ava = &eq; + uf.f_av_desc = slap_schema.si_ad_entryUUID; + uf.f_next = NULL; + uf.f_choice = LDAP_FILTER_EQUALITY; + si->si_refreshDelete |= NP_DELETE_ONE; + + for (i=0; uuids[i].bv_val; i++) { + SlapReply rs_search = {REP_RESULT}; + + op->ors_slimit = 1; + uf.f_av_value = uuids[i]; + filter2bv_x( op, op->ors_filter, &op->ors_filterstr ); + Debug( LDAP_DEBUG_SYNC, "syncrepl_del_nonpresent: %s " + "checking non-present filter=%s\n", + si->si_ridtxt, op->ors_filterstr.bv_val, 0 ); + rc = be->be_search( op, &rs_search ); + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + } + si->si_refreshDelete ^= NP_DELETE_ONE; + } else { + Filter *cf, *of; + Filter mmf[2]; + AttributeAssertion mmaa; + SlapReply rs_search = {REP_RESULT}; + + memset( &an[0], 0, 3 * sizeof( AttributeName ) ); + an[0].an_name = slap_schema.si_ad_entryUUID->ad_cname; + an[0].an_desc = slap_schema.si_ad_entryUUID; + an[1].an_name = slap_schema.si_ad_entryCSN->ad_cname; + an[1].an_desc = slap_schema.si_ad_entryCSN; + op->ors_attrs = an; + op->ors_slimit = SLAP_NO_LIMIT; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_limit = NULL; + op->ors_attrsonly = 0; + op->ors_filter = filter_dup( si->si_filter, op->o_tmpmemctx ); + /* In multi-provider, updates can continue to arrive while + * we're searching. Limit the search result to entries + * older than our newest cookie CSN. + */ + if ( SLAP_MULTIMASTER( op->o_bd )) { + Filter *f; + int i; + + f = mmf; + f->f_choice = LDAP_FILTER_AND; + f->f_next = op->ors_filter; + f->f_and = f+1; + of = f->f_and; + f = of; + f->f_choice = LDAP_FILTER_LE; + f->f_ava = &mmaa; + f->f_av_desc = slap_schema.si_ad_entryCSN; + f->f_next = NULL; + BER_BVZERO( &f->f_av_value ); + for ( i=0; i<sc->numcsns; i++ ) { + if ( ber_bvcmp( &sc->ctxcsn[i], &f->f_av_value ) > 0 ) + f->f_av_value = sc->ctxcsn[i]; + } + of = op->ors_filter; + op->ors_filter = mmf; + filter2bv_x( op, op->ors_filter, &op->ors_filterstr ); + } else { + cf = NULL; + op->ors_filterstr = si->si_filterstr; + } + op->o_nocaching = 1; + + + rc = be->be_search( op, &rs_search ); + if ( SLAP_MULTIMASTER( op->o_bd )) { + op->ors_filter = of; + } + if ( op->ors_filter ) filter_free_x( op, op->ors_filter, 1 ); + if ( op->ors_filterstr.bv_val != si->si_filterstr.bv_val ) { + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + } + + } + + op->o_nocaching = 0; + + if ( !LDAP_LIST_EMPTY( &si->si_nonpresentlist ) ) { + + if ( sc->ctxcsn && !BER_BVISNULL( &sc->ctxcsn[m] ) ) { + csn = sc->ctxcsn[m]; + } else { + csn = si->si_syncCookie.ctxcsn[0]; + } + + op->o_bd = si->si_wbe; + slap_queue_csn( op, &csn ); + + np_list = LDAP_LIST_FIRST( &si->si_nonpresentlist ); + while ( np_list != NULL ) { + SlapReply rs_delete = {REP_RESULT}; + + LDAP_LIST_REMOVE( np_list, npe_link ); + np_prev = np_list; + np_list = LDAP_LIST_NEXT( np_list, npe_link ); + op->o_tag = LDAP_REQ_DELETE; + op->o_callback = &cb; + cb.sc_response = syncrepl_null_callback; + cb.sc_private = si; + op->o_req_dn = *np_prev->npe_name; + op->o_req_ndn = *np_prev->npe_nname; + rc = op->o_bd->be_delete( op, &rs_delete ); + Debug( LDAP_DEBUG_SYNC, + "syncrepl_del_nonpresent: %s be_delete %s (%d)\n", + si->si_ridtxt, op->o_req_dn.bv_val, rc ); + + if ( rs_delete.sr_err == LDAP_NOT_ALLOWED_ON_NONLEAF ) { + SlapReply rs_modify = {REP_RESULT}; + Modifications mod1, mod2; + mod1.sml_op = LDAP_MOD_REPLACE; + mod1.sml_flags = 0; + mod1.sml_desc = slap_schema.si_ad_objectClass; + mod1.sml_type = mod1.sml_desc->ad_cname; + mod1.sml_numvals = 2; + mod1.sml_values = &gcbva[0]; + mod1.sml_nvalues = NULL; + mod1.sml_next = &mod2; + + mod2.sml_op = LDAP_MOD_REPLACE; + mod2.sml_flags = 0; + mod2.sml_desc = slap_schema.si_ad_structuralObjectClass; + mod2.sml_type = mod2.sml_desc->ad_cname; + mod2.sml_numvals = 1; + mod2.sml_values = &gcbva[1]; + mod2.sml_nvalues = NULL; + mod2.sml_next = NULL; + + op->o_tag = LDAP_REQ_MODIFY; + op->orm_modlist = &mod1; + + rc = op->o_bd->be_modify( op, &rs_modify ); + if ( mod2.sml_next ) slap_mods_free( mod2.sml_next, 1 ); + } + + while ( rs_delete.sr_err == LDAP_SUCCESS && + op->o_delete_glue_parent ) { + op->o_delete_glue_parent = 0; + if ( !be_issuffix( be, &op->o_req_ndn ) ) { + slap_callback cb = { NULL }; + cb.sc_response = syncrepl_null_callback; + dnParent( &op->o_req_ndn, &pdn ); + op->o_req_dn = pdn; + op->o_req_ndn = pdn; + op->o_callback = &cb; + rs_reinit( &rs_delete, REP_RESULT ); + /* give it a root privil ? */ + op->o_bd->be_delete( op, &rs_delete ); + } else { + break; + } + } + + op->o_delete_glue_parent = 0; + + ber_bvfree( np_prev->npe_name ); + ber_bvfree( np_prev->npe_nname ); + ch_free( np_prev ); + + if ( slapd_shutdown ) { + break; + } + } + + slap_graduate_commit_csn( op ); + op->o_bd = be; + + op->o_tmpfree( op->o_csn.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &op->o_csn ); + } + + return; +} + +static int +syncrepl_add_glue_ancestors( + Operation* op, + Entry *e ) +{ + Backend *be = op->o_bd; + slap_callback cb = { NULL }; + Attribute *a; + int rc = LDAP_SUCCESS; + int suffrdns; + int i; + struct berval dn = BER_BVNULL; + struct berval ndn = BER_BVNULL; + Entry *glue; + struct berval ptr, nptr; + char *comma; + + op->o_tag = LDAP_REQ_ADD; + op->o_callback = &cb; + cb.sc_response = syncrepl_null_callback; + cb.sc_private = NULL; + + dn = e->e_name; + ndn = e->e_nname; + + /* count RDNs in suffix */ + if ( !BER_BVISEMPTY( &be->be_nsuffix[0] ) ) { + for ( i = 0, ptr = be->be_nsuffix[0], comma = ptr.bv_val; comma != NULL; comma = ber_bvchr( &ptr, ',' ) ) { + comma++; + ptr.bv_len -= comma - ptr.bv_val; + ptr.bv_val = comma; + i++; + } + suffrdns = i; + } else { + /* suffix is "" */ + suffrdns = 0; + } + + /* Start with BE suffix */ + ptr = dn; + for ( i = 0; i < suffrdns; i++ ) { + comma = ber_bvrchr( &ptr, ',' ); + if ( comma != NULL ) { + ptr.bv_len = comma - ptr.bv_val; + } else { + ptr.bv_len = 0; + break; + } + } + + if ( !BER_BVISEMPTY( &ptr ) ) { + dn.bv_len -= ptr.bv_len + ( suffrdns != 0 ); + dn.bv_val += ptr.bv_len + ( suffrdns != 0 ); + } + + /* the normalizedDNs are always the same length, no counting + * required. + */ + nptr = ndn; + if ( ndn.bv_len > be->be_nsuffix[0].bv_len ) { + ndn.bv_val += ndn.bv_len - be->be_nsuffix[0].bv_len; + ndn.bv_len = be->be_nsuffix[0].bv_len; + + nptr.bv_len = ndn.bv_val - nptr.bv_val - 1; + + } else { + nptr.bv_len = 0; + } + + while ( ndn.bv_val > e->e_nname.bv_val ) { + SlapReply rs_add = {REP_RESULT}; + + glue = entry_alloc(); + ber_dupbv( &glue->e_name, &dn ); + ber_dupbv( &glue->e_nname, &ndn ); + + a = attr_alloc( slap_schema.si_ad_objectClass ); + + a->a_numvals = 2; + a->a_vals = ch_calloc( 3, sizeof( struct berval ) ); + ber_dupbv( &a->a_vals[0], &gcbva[0] ); + ber_dupbv( &a->a_vals[1], &gcbva[1] ); + ber_dupbv( &a->a_vals[2], &gcbva[2] ); + + a->a_nvals = a->a_vals; + + a->a_next = glue->e_attrs; + glue->e_attrs = a; + + a = attr_alloc( slap_schema.si_ad_structuralObjectClass ); + + a->a_numvals = 1; + a->a_vals = ch_calloc( 2, sizeof( struct berval ) ); + ber_dupbv( &a->a_vals[0], &gcbva[1] ); + ber_dupbv( &a->a_vals[1], &gcbva[2] ); + + a->a_nvals = a->a_vals; + + a->a_next = glue->e_attrs; + glue->e_attrs = a; + + op->o_req_dn = glue->e_name; + op->o_req_ndn = glue->e_nname; + op->ora_e = glue; + rc = be->be_add ( op, &rs_add ); + if ( rs_add.sr_err == LDAP_SUCCESS ) { + if ( op->ora_e == glue ) + be_entry_release_w( op, glue ); + } else { + /* incl. ALREADY EXIST */ + entry_free( glue ); + if ( rs_add.sr_err != LDAP_ALREADY_EXISTS ) { + entry_free( e ); + return rc; + } + } + + /* Move to next child */ + comma = ber_bvrchr( &ptr, ',' ); + if ( comma == NULL ) { + break; + } + ptr.bv_len = comma - ptr.bv_val; + + dn.bv_val = ++comma; + dn.bv_len = e->e_name.bv_len - (dn.bv_val - e->e_name.bv_val); + + comma = ber_bvrchr( &nptr, ',' ); + assert( comma != NULL ); + nptr.bv_len = comma - nptr.bv_val; + + ndn.bv_val = ++comma; + ndn.bv_len = e->e_nname.bv_len - (ndn.bv_val - e->e_nname.bv_val); + } + + return rc; +} + +int +syncrepl_add_glue( + Operation* op, + Entry *e ) +{ + slap_callback cb = { NULL }; + int rc; + Backend *be = op->o_bd; + SlapReply rs_add = {REP_RESULT}; + + rc = syncrepl_add_glue_ancestors( op, e ); + switch ( rc ) { + case LDAP_SUCCESS: + case LDAP_ALREADY_EXISTS: + break; + + default: + return rc; + } + + op->o_tag = LDAP_REQ_ADD; + op->o_callback = &cb; + cb.sc_response = syncrepl_null_callback; + cb.sc_private = NULL; + + op->o_req_dn = e->e_name; + op->o_req_ndn = e->e_nname; + op->ora_e = e; + rc = be->be_add ( op, &rs_add ); + if ( rs_add.sr_err == LDAP_SUCCESS ) { + if ( op->ora_e == e ) + be_entry_release_w( op, e ); + } else { + entry_free( e ); + } + + return rc; +} + +static int +syncrepl_updateCookie( + syncinfo_t *si, + Operation *op, + struct sync_cookie *syncCookie, + int save ) +{ + Backend *be = op->o_bd; + Modifications mod; + struct berval first = BER_BVNULL; + struct sync_cookie sc; +#ifdef CHECK_CSN + Syntax *syn = slap_schema.si_ad_contextCSN->ad_type->sat_syntax; +#endif + + int rc, i, j, changed = 0; + ber_len_t len; + + slap_callback cb = { NULL }; + SlapReply rs_modify = {REP_RESULT}; + + mod.sml_op = LDAP_MOD_REPLACE; + mod.sml_desc = slap_schema.si_ad_contextCSN; + mod.sml_type = mod.sml_desc->ad_cname; + mod.sml_flags = SLAP_MOD_INTERNAL; + mod.sml_nvalues = NULL; + mod.sml_next = NULL; + + ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_mutex ); + while ( si->si_cookieState->cs_updating ) + ldap_pvt_thread_cond_wait( &si->si_cookieState->cs_cond, &si->si_cookieState->cs_mutex ); + +#ifdef CHECK_CSN + for ( i=0; i<syncCookie->numcsns; i++ ) { + assert( !syn->ssyn_validate( syn, syncCookie->ctxcsn+i )); + } + for ( i=0; i<si->si_cookieState->cs_num; i++ ) { + assert( !syn->ssyn_validate( syn, si->si_cookieState->cs_vals+i )); + } +#endif + + /* clone the cookieState CSNs so we can Replace the whole thing */ + sc.numcsns = si->si_cookieState->cs_num; + if ( sc.numcsns ) { + ber_bvarray_dup_x( &sc.ctxcsn, si->si_cookieState->cs_vals, NULL ); + sc.sids = ch_malloc( sc.numcsns * sizeof(int)); + for ( i=0; i<sc.numcsns; i++ ) + sc.sids[i] = si->si_cookieState->cs_sids[i]; + } else { + sc.ctxcsn = NULL; + sc.sids = NULL; + } + + /* find any CSNs in the syncCookie that are newer than the cookieState */ + for ( i=0; i<syncCookie->numcsns; i++ ) { + for ( j=0; j<sc.numcsns; j++ ) { + if ( syncCookie->sids[i] < sc.sids[j] ) + break; + if ( syncCookie->sids[i] != sc.sids[j] ) + continue; + len = syncCookie->ctxcsn[i].bv_len; + if ( len > sc.ctxcsn[j].bv_len ) + len = sc.ctxcsn[j].bv_len; + if ( memcmp( syncCookie->ctxcsn[i].bv_val, + sc.ctxcsn[j].bv_val, len ) > 0 ) { + ber_bvreplace( &sc.ctxcsn[j], &syncCookie->ctxcsn[i] ); + changed = 1; + if ( BER_BVISNULL( &first ) || + memcmp( syncCookie->ctxcsn[i].bv_val, first.bv_val, first.bv_len ) > 0 ) { + first = syncCookie->ctxcsn[i]; + } + } + break; + } + /* there was no match for this SID, it's a new CSN */ + if ( j == sc.numcsns || + syncCookie->sids[i] != sc.sids[j] ) { + slap_insert_csn_sids( &sc, j, syncCookie->sids[i], + &syncCookie->ctxcsn[i] ); + if ( BER_BVISNULL( &first ) || + memcmp( syncCookie->ctxcsn[i].bv_val, first.bv_val, first.bv_len ) > 0 ) { + first = syncCookie->ctxcsn[i]; + } + changed = 1; + } + } + /* Should never happen, ITS#5065 */ + if ( BER_BVISNULL( &first ) || !changed ) { + ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex ); + ber_bvarray_free( sc.ctxcsn ); + ch_free( sc.sids ); + return 0; + } + + si->si_cookieState->cs_updating = 1; + ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex ); + + op->o_bd = si->si_wbe; + slap_queue_csn( op, &first ); + + op->o_tag = LDAP_REQ_MODIFY; + + cb.sc_response = syncrepl_null_callback; + cb.sc_private = si; + + op->o_callback = &cb; + op->o_req_dn = si->si_contextdn; + op->o_req_ndn = si->si_contextdn; + + /* update contextCSN */ + op->o_dont_replicate = !save; + + /* avoid timestamp collisions */ + if ( save ) + slap_op_time( &op->o_time, &op->o_tincr ); + + mod.sml_numvals = sc.numcsns; + mod.sml_values = sc.ctxcsn; + + op->orm_modlist = &mod; + op->orm_no_opattrs = 1; + rc = op->o_bd->be_modify( op, &rs_modify ); + + if ( rs_modify.sr_err == LDAP_NO_SUCH_OBJECT && + SLAP_SYNC_SUBENTRY( op->o_bd )) { + const char *text; + char txtbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof txtbuf; + Entry *e = slap_create_context_csn_entry( op->o_bd, NULL ); + rs_reinit( &rs_modify, REP_RESULT ); + rc = slap_mods2entry( &mod, &e, 0, 1, &text, txtbuf, textlen); + slap_queue_csn( op, &first ); + op->o_tag = LDAP_REQ_ADD; + op->ora_e = e; + rc = op->o_bd->be_add( op, &rs_modify ); + if ( e == op->ora_e ) + be_entry_release_w( op, op->ora_e ); + } + + op->orm_no_opattrs = 0; + op->o_dont_replicate = 0; + ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_mutex ); + + if ( rs_modify.sr_err == LDAP_SUCCESS ) { + slap_sync_cookie_free( &si->si_syncCookie, 0 ); + ber_bvarray_free( si->si_cookieState->cs_vals ); + ch_free( si->si_cookieState->cs_sids ); + si->si_cookieState->cs_vals = sc.ctxcsn; + si->si_cookieState->cs_sids = sc.sids; + si->si_cookieState->cs_num = sc.numcsns; + + /* Don't just dup the provider's cookie, recreate it */ + si->si_syncCookie.numcsns = si->si_cookieState->cs_num; + ber_bvarray_dup_x( &si->si_syncCookie.ctxcsn, si->si_cookieState->cs_vals, NULL ); + si->si_syncCookie.sids = ch_malloc( si->si_cookieState->cs_num * sizeof(int) ); + for ( i=0; i<si->si_cookieState->cs_num; i++ ) + si->si_syncCookie.sids[i] = si->si_cookieState->cs_sids[i]; + + si->si_cookieState->cs_age++; + si->si_cookieAge = si->si_cookieState->cs_age; + } else { + Debug( LDAP_DEBUG_ANY, + "syncrepl_updateCookie: %s be_modify failed (%d)\n", + si->si_ridtxt, rs_modify.sr_err, 0 ); + ch_free( sc.sids ); + ber_bvarray_free( sc.ctxcsn ); + } + +#ifdef CHECK_CSN + for ( i=0; i<si->si_cookieState->cs_num; i++ ) { + assert( !syn->ssyn_validate( syn, si->si_cookieState->cs_vals+i )); + } +#endif + + si->si_cookieState->cs_updating = 0; + ldap_pvt_thread_cond_broadcast( &si->si_cookieState->cs_cond ); + ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex ); + + op->o_bd = be; + op->o_tmpfree( op->o_csn.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &op->o_csn ); + if ( mod.sml_next ) slap_mods_free( mod.sml_next, 1 ); + + return rc; +} + +/* Compare the attribute from the old entry to the one in the new + * entry. The Modifications from the new entry will either be left + * in place, or changed to an Add or Delete as needed. + */ +static void +attr_cmp( Operation *op, Attribute *old, Attribute *new, + Modifications ***mret, Modifications ***mcur ) +{ + int i, j; + Modifications *mod, **modtail; + + modtail = *mret; + + if ( old ) { + int n, o, nn, no; + struct berval **adds, **dels; + /* count old and new */ + for ( o=0; old->a_vals[o].bv_val; o++ ) ; + for ( n=0; new->a_vals[n].bv_val; n++ ) ; + + /* there MUST be both old and new values */ + assert( o != 0 ); + assert( n != 0 ); + j = 0; + + adds = op->o_tmpalloc( sizeof(struct berval *) * n, op->o_tmpmemctx ); + dels = op->o_tmpalloc( sizeof(struct berval *) * o, op->o_tmpmemctx ); + + for ( i=0; i<o; i++ ) dels[i] = &old->a_vals[i]; + for ( i=0; i<n; i++ ) adds[i] = &new->a_vals[i]; + + nn = n; no = o; + + for ( i=0; i<o; i++ ) { + for ( j=0; j<n; j++ ) { + if ( !adds[j] ) + continue; + if ( bvmatch( dels[i], adds[j] ) ) { + no--; + nn--; + adds[j] = NULL; + dels[i] = NULL; + break; + } + } + } + + /* Don't delete/add an objectClass, always use the replace op. + * Modify would fail if provider has replaced entry with a new, + * and the new explicitly includes a superior of a class that was + * only included implicitly in the old entry. Ref ITS#5517. + * + * Also use replace op if attr has no equality matching rule. + * (ITS#5781) + */ + if ( ( nn || ( no > 0 && no < o ) ) && + ( old->a_desc == slap_schema.si_ad_objectClass || + !old->a_desc->ad_type->sat_equality ) ) + { + no = o; + } + + i = j; + /* all old values were deleted, just use the replace op */ + if ( no == o ) { + i = j-1; + } else if ( no ) { + /* delete some values */ + mod = ch_malloc( sizeof( Modifications ) ); + mod->sml_op = LDAP_MOD_DELETE; + mod->sml_flags = 0; + mod->sml_desc = old->a_desc; + mod->sml_type = mod->sml_desc->ad_cname; + mod->sml_numvals = no; + mod->sml_values = ch_malloc( ( no + 1 ) * sizeof(struct berval) ); + if ( old->a_vals != old->a_nvals ) { + mod->sml_nvalues = ch_malloc( ( no + 1 ) * sizeof(struct berval) ); + } else { + mod->sml_nvalues = NULL; + } + j = 0; + for ( i = 0; i < o; i++ ) { + if ( !dels[i] ) continue; + ber_dupbv( &mod->sml_values[j], &old->a_vals[i] ); + if ( mod->sml_nvalues ) { + ber_dupbv( &mod->sml_nvalues[j], &old->a_nvals[i] ); + } + j++; + } + BER_BVZERO( &mod->sml_values[j] ); + if ( mod->sml_nvalues ) { + BER_BVZERO( &mod->sml_nvalues[j] ); + } + *modtail = mod; + modtail = &mod->sml_next; + i = j; + } + op->o_tmpfree( dels, op->o_tmpmemctx ); + /* some values were added */ + if ( nn && no < o ) { + mod = ch_malloc( sizeof( Modifications ) ); + if ( is_at_single_value( old->a_desc->ad_type )) + mod->sml_op = LDAP_MOD_REPLACE; + else + mod->sml_op = LDAP_MOD_ADD; + mod->sml_flags = 0; + mod->sml_desc = old->a_desc; + mod->sml_type = mod->sml_desc->ad_cname; + mod->sml_numvals = nn; + mod->sml_values = ch_malloc( ( nn + 1 ) * sizeof(struct berval) ); + if ( old->a_vals != old->a_nvals ) { + mod->sml_nvalues = ch_malloc( ( nn + 1 ) * sizeof(struct berval) ); + } else { + mod->sml_nvalues = NULL; + } + j = 0; + for ( i = 0; i < n; i++ ) { + if ( !adds[i] ) continue; + ber_dupbv( &mod->sml_values[j], &new->a_vals[i] ); + if ( mod->sml_nvalues ) { + ber_dupbv( &mod->sml_nvalues[j], &new->a_nvals[i] ); + } + j++; + } + BER_BVZERO( &mod->sml_values[j] ); + if ( mod->sml_nvalues ) { + BER_BVZERO( &mod->sml_nvalues[j] ); + } + *modtail = mod; + modtail = &mod->sml_next; + i = j; + } + op->o_tmpfree( adds, op->o_tmpmemctx ); + } else { + /* new attr, just use the new mod */ + i = 0; + j = 1; + } + /* advance to next element */ + mod = **mcur; + if ( mod ) { + if ( i != j ) { + **mcur = mod->sml_next; + *modtail = mod; + modtail = &mod->sml_next; + } else { + *mcur = &mod->sml_next; + } + } + *mret = modtail; +} + +/* Generate a set of modifications to change the old entry into the + * new one. On input ml is a list of modifications equivalent to + * the new entry. It will be massaged and the result will be stored + * in mods. + */ +void syncrepl_diff_entry( Operation *op, Attribute *old, Attribute *new, + Modifications **mods, Modifications **ml, int is_ctx) +{ + Modifications **modtail = mods; + + /* We assume that attributes are saved in the same order + * in the remote and local databases. So if we walk through + * the attributeDescriptions one by one they should match in + * lock step. If not, look for an add or delete. + */ + while ( old && new ) + { + /* If we've seen this before, use its mod now */ + if ( new->a_flags & SLAP_ATTR_IXADD ) { + attr_cmp( op, NULL, new, &modtail, &ml ); + new = new->a_next; + continue; + } + /* Skip contextCSN */ + if ( is_ctx && old->a_desc == + slap_schema.si_ad_contextCSN ) { + old = old->a_next; + continue; + } + + if ( old->a_desc != new->a_desc ) { + Modifications *mod; + Attribute *tmp; + + /* If it's just been re-added later, + * remember that we've seen it. + */ + tmp = attr_find( new, old->a_desc ); + if ( tmp ) { + tmp->a_flags |= SLAP_ATTR_IXADD; + } else { + /* If it's a new attribute, pull it in. + */ + tmp = attr_find( old, new->a_desc ); + if ( !tmp ) { + attr_cmp( op, NULL, new, &modtail, &ml ); + new = new->a_next; + continue; + } + /* Delete old attr */ + mod = ch_malloc( sizeof( Modifications ) ); + mod->sml_op = LDAP_MOD_DELETE; + mod->sml_flags = 0; + mod->sml_desc = old->a_desc; + mod->sml_type = mod->sml_desc->ad_cname; + mod->sml_numvals = 0; + mod->sml_values = NULL; + mod->sml_nvalues = NULL; + *modtail = mod; + modtail = &mod->sml_next; + } + old = old->a_next; + continue; + } + /* kludge - always update modifiersName so that it + * stays co-located with the other mod opattrs. But only + * if we know there are other valid mods. + */ + if ( *mods && ( old->a_desc == slap_schema.si_ad_modifiersName || + old->a_desc == slap_schema.si_ad_modifyTimestamp )) + attr_cmp( op, NULL, new, &modtail, &ml ); + else + attr_cmp( op, old, new, &modtail, &ml ); + new = new->a_next; + old = old->a_next; + } + *modtail = *ml; + *ml = NULL; +} + +/* shallow copy attrs, excluding non-replicated attrs */ +static Attribute * +attrs_exdup( Operation *op, dninfo *dni, Attribute *attrs ) +{ + int i; + Attribute *tmp, *anew; + + if ( attrs == NULL ) return NULL; + + /* count attrs */ + for ( tmp = attrs,i=0; tmp; tmp=tmp->a_next ) i++; + + anew = op->o_tmpalloc( i * sizeof(Attribute), op->o_tmpmemctx ); + for ( tmp = anew; attrs; attrs=attrs->a_next ) { + int flag = is_at_operational( attrs->a_desc->ad_type ) ? dni->si->si_allopattrs : + dni->si->si_allattrs; + if ( !flag && !ad_inlist( attrs->a_desc, dni->si->si_anlist )) + continue; + if ( dni->si->si_exattrs && ad_inlist( attrs->a_desc, dni->si->si_exanlist )) + continue; + *tmp = *attrs; + tmp->a_next = tmp+1; + tmp++; + } + if ( tmp == anew ) { + /* excluded everything */ + op->o_tmpfree( anew, op->o_tmpmemctx ); + return NULL; + } + tmp[-1].a_next = NULL; + return anew; +} + +static int +dn_callback( + Operation* op, + SlapReply* rs ) +{ + dninfo *dni = op->o_callback->sc_private; + + if ( rs->sr_type == REP_SEARCH ) { + if ( !BER_BVISNULL( &dni->dn ) ) { + Debug( LDAP_DEBUG_ANY, + "dn_callback : consistency error - " + "entryUUID is not unique\n", 0, 0, 0 ); + } else { + ber_dupbv_x( &dni->dn, &rs->sr_entry->e_name, op->o_tmpmemctx ); + ber_dupbv_x( &dni->ndn, &rs->sr_entry->e_nname, op->o_tmpmemctx ); + /* If there is a new entry, see if it differs from the old. + * We compare the non-normalized values so that cosmetic changes + * in the provider are always propagated. + */ + if ( dni->new_entry ) { + Attribute *old, *new; + struct berval old_rdn, new_rdn; + struct berval old_p, new_p; + int is_ctx, new_sup = 0; + + /* If old entry is not a glue entry, make sure new entry + * is actually newer than old entry + */ + if ( !is_entry_glue( rs->sr_entry )) { + old = attr_find( rs->sr_entry->e_attrs, + slap_schema.si_ad_entryCSN ); + new = attr_find( dni->new_entry->e_attrs, + slap_schema.si_ad_entryCSN ); + if ( new && old ) { + int rc; + ber_len_t len = old->a_vals[0].bv_len; + if ( len > new->a_vals[0].bv_len ) + len = new->a_vals[0].bv_len; + rc = memcmp( old->a_vals[0].bv_val, + new->a_vals[0].bv_val, len ); + if ( rc > 0 ) { + Debug( LDAP_DEBUG_SYNC, + "dn_callback : new entry is older than ours " + "%s ours %s, new %s\n", + rs->sr_entry->e_name.bv_val, + old->a_vals[0].bv_val, + new->a_vals[0].bv_val ); + return LDAP_SUCCESS; + } else if ( rc == 0 ) { + Debug( LDAP_DEBUG_SYNC, + "dn_callback : entries have identical CSN " + "%s %s\n", + rs->sr_entry->e_name.bv_val, + old->a_vals[0].bv_val, 0 ); + return LDAP_SUCCESS; + } + } + } + + is_ctx = dn_match( &rs->sr_entry->e_nname, + &op->o_bd->be_nsuffix[0] ); + + /* Did the DN change? + * case changes in the parent are ignored, + * we only want to know if the RDN was + * actually changed. + */ + dnRdn( &rs->sr_entry->e_name, &old_rdn ); + dnRdn( &dni->new_entry->e_name, &new_rdn ); + dnParent( &rs->sr_entry->e_nname, &old_p ); + dnParent( &dni->new_entry->e_nname, &new_p ); + + new_sup = !dn_match( &old_p, &new_p ); + if ( !dn_match( &old_rdn, &new_rdn ) || new_sup ) + { + struct berval oldRDN, oldVal; + AttributeDescription *ad = NULL; + int oldpos, newpos; + Attribute *a; + + dni->renamed = 1; + if ( new_sup ) + dni->nnewSup = new_p; + + /* See if the oldRDN was deleted */ + dnRdn( &rs->sr_entry->e_nname, &oldRDN ); + oldVal.bv_val = strchr(oldRDN.bv_val, '=') + 1; + oldVal.bv_len = oldRDN.bv_len - ( oldVal.bv_val - + oldRDN.bv_val ); + oldRDN.bv_len -= oldVal.bv_len + 1; + slap_bv2ad( &oldRDN, &ad, &rs->sr_text ); + dni->oldDesc = ad; + for ( oldpos=0, a=rs->sr_entry->e_attrs; + a && a->a_desc != ad; oldpos++, a=a->a_next ); + /* a should not be NULL but apparently it happens. + * ITS#7144 + */ + dni->oldNcount = a ? a->a_numvals : 0; + for ( newpos=0, a=dni->new_entry->e_attrs; + a && a->a_desc != ad; newpos++, a=a->a_next ); + if ( !a || oldpos != newpos || attr_valfind( a, + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH | + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_VALUE_OF_SYNTAX, + &oldVal, NULL, op->o_tmpmemctx ) != LDAP_SUCCESS ) + { + dni->delOldRDN = 1; + } + /* Get the newRDN's desc */ + dnRdn( &dni->new_entry->e_nname, &oldRDN ); + oldVal.bv_val = strchr(oldRDN.bv_val, '='); + oldRDN.bv_len = oldVal.bv_val - oldRDN.bv_val; + ad = NULL; + slap_bv2ad( &oldRDN, &ad, &rs->sr_text ); + dni->newDesc = ad; + + /* A ModDN has happened, but in Refresh mode other + * changes may have occurred before we picked it up. + * So fallthru to regular Modify processing. + */ + } + + { + Attribute *old = attrs_exdup( op, dni, rs->sr_entry->e_attrs ); + syncrepl_diff_entry( op, old, + dni->new_entry->e_attrs, &dni->mods, dni->modlist, + is_ctx ); + op->o_tmpfree( old, op->o_tmpmemctx ); + } + } + } + } else if ( rs->sr_type == REP_RESULT ) { + if ( rs->sr_err == LDAP_SIZELIMIT_EXCEEDED ) { + Debug( LDAP_DEBUG_ANY, + "dn_callback : consistency error - " + "entryUUID is not unique\n", 0, 0, 0 ); + } + } + + return LDAP_SUCCESS; +} + +static int +nonpresent_callback( + Operation* op, + SlapReply* rs ) +{ + syncinfo_t *si = op->o_callback->sc_private; + Attribute *a; + int count = 0; + char *present_uuid = NULL; + struct nonpresent_entry *np_entry; + struct sync_cookie *syncCookie = op->o_controls[slap_cids.sc_LDAPsync]; + + if ( rs->sr_type == REP_RESULT ) { + count = presentlist_free( si->si_presentlist ); + si->si_presentlist = NULL; + Debug( LDAP_DEBUG_SYNC, "nonpresent_callback: %s " + "had %d items left in the list\n", si->si_ridtxt, count, 0 ); + + } else if ( rs->sr_type == REP_SEARCH ) { + if ( !( si->si_refreshDelete & NP_DELETE_ONE ) ) { + a = attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryUUID ); + + if ( a ) { + present_uuid = presentlist_find( si->si_presentlist, &a->a_nvals[0] ); + } + + Log4(LDAP_DEBUG_SYNC, ldap_syslog_level, "nonpresent_callback: " + "%s %spresent UUID %s, dn %s\n", + si->si_ridtxt, + present_uuid ? "" : "non", + a ? a->a_vals[0].bv_val : "<missing>", + rs->sr_entry->e_name.bv_val ); + + if ( a == NULL ) return 0; + } + + if ( present_uuid == NULL ) { + int covered = 1; /* covered by our new contextCSN? */ + + if ( !syncCookie ) + syncCookie = &si->si_syncCookie; + + /* TODO: This can go once we can build a filter that takes care of + * the check for us */ + a = attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryCSN ); + if ( a ) { + int i, sid = slap_parse_csn_sid( &a->a_nvals[0] ); + if ( sid != -1 ) { + covered = 0; + for ( i=0; i < syncCookie->numcsns && syncCookie->sids[i] <= sid; i++ ) { + if ( syncCookie->sids[i] == sid && + ber_bvcmp( &a->a_nvals[0], &syncCookie->ctxcsn[i] ) <= 0 ) { + covered = 1; + } + } + } + } + + if ( covered ) { + np_entry = (struct nonpresent_entry *) + ch_calloc( 1, sizeof( struct nonpresent_entry ) ); + np_entry->npe_name = ber_dupbv( NULL, &rs->sr_entry->e_name ); + np_entry->npe_nname = ber_dupbv( NULL, &rs->sr_entry->e_nname ); + LDAP_LIST_INSERT_HEAD( &si->si_nonpresentlist, np_entry, npe_link ); + Debug( LDAP_DEBUG_SYNC, "nonpresent_callback: %s " + "adding entry %s to non-present list\n", + si->si_ridtxt, np_entry->npe_name->bv_val, 0 ); + } + + } else { + presentlist_delete( &si->si_presentlist, &a->a_nvals[0] ); + ch_free( present_uuid ); + } + } + return LDAP_SUCCESS; +} + +static struct berval * +slap_uuidstr_from_normalized( + struct berval* uuidstr, + struct berval* normalized, + void *ctx ) +{ +#if 0 + struct berval *new; + unsigned char nibble; + int i, d = 0; + + if ( normalized == NULL ) return NULL; + if ( normalized->bv_len != 16 ) return NULL; + + if ( uuidstr ) { + new = uuidstr; + } else { + new = (struct berval *)slap_sl_malloc( sizeof(struct berval), ctx ); + if ( new == NULL ) { + return NULL; + } + } + + new->bv_len = 36; + + if ( ( new->bv_val = slap_sl_malloc( new->bv_len + 1, ctx ) ) == NULL ) { + if ( new != uuidstr ) { + slap_sl_free( new, ctx ); + } + return NULL; + } + + for ( i = 0; i < 16; i++ ) { + if ( i == 4 || i == 6 || i == 8 || i == 10 ) { + new->bv_val[(i<<1)+d] = '-'; + d += 1; + } + + nibble = (normalized->bv_val[i] >> 4) & 0xF; + if ( nibble < 10 ) { + new->bv_val[(i<<1)+d] = nibble + '0'; + } else { + new->bv_val[(i<<1)+d] = nibble - 10 + 'a'; + } + + nibble = (normalized->bv_val[i]) & 0xF; + if ( nibble < 10 ) { + new->bv_val[(i<<1)+d+1] = nibble + '0'; + } else { + new->bv_val[(i<<1)+d+1] = nibble - 10 + 'a'; + } + } + + new->bv_val[new->bv_len] = '\0'; + return new; +#endif + + struct berval *new; + int rc = 0; + + if ( normalized == NULL ) return NULL; + if ( normalized->bv_len != 16 ) return NULL; + + if ( uuidstr ) { + new = uuidstr; + + } else { + new = (struct berval *)slap_sl_malloc( sizeof(struct berval), ctx ); + if ( new == NULL ) { + return NULL; + } + } + + new->bv_len = 36; + + if ( ( new->bv_val = slap_sl_malloc( new->bv_len + 1, ctx ) ) == NULL ) { + rc = 1; + goto done; + } + + rc = lutil_uuidstr_from_normalized( normalized->bv_val, + normalized->bv_len, new->bv_val, new->bv_len + 1 ); + +done:; + if ( rc == -1 ) { + if ( new != NULL ) { + if ( new->bv_val != NULL ) { + slap_sl_free( new->bv_val, ctx ); + } + + if ( new != uuidstr ) { + slap_sl_free( new, ctx ); + } + } + new = NULL; + + } else { + new->bv_len = rc; + } + + return new; +} + +static int +syncuuid_cmp( const void* v_uuid1, const void* v_uuid2 ) +{ +#ifdef HASHUUID + return ( memcmp( v_uuid1, v_uuid2, UUIDLEN-2 )); +#else + return ( memcmp( v_uuid1, v_uuid2, UUIDLEN )); +#endif +} + +void +syncinfo_free( syncinfo_t *sie, int free_all ) +{ + syncinfo_t *si_next; + + Debug( LDAP_DEBUG_TRACE, "syncinfo_free: %s\n", + sie->si_ridtxt, 0, 0 ); + + do { + si_next = sie->si_next; + + if ( sie->si_ld ) { + if ( sie->si_conn ) { + connection_client_stop( sie->si_conn ); + sie->si_conn = NULL; + } + ldap_unbind_ext( sie->si_ld, NULL, NULL ); + } + + if ( sie->si_re ) { + struct re_s *re = sie->si_re; + sie->si_re = NULL; + + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re ) ) + ldap_pvt_runqueue_stoptask( &slapd_rq, re ); + ldap_pvt_runqueue_remove( &slapd_rq, re ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + + ldap_pvt_thread_mutex_destroy( &sie->si_mutex ); + + bindconf_free( &sie->si_bindconf ); + + if ( sie->si_filterstr.bv_val ) { + ch_free( sie->si_filterstr.bv_val ); + } + if ( sie->si_filter ) { + filter_free( sie->si_filter ); + } + if ( sie->si_logfilterstr.bv_val ) { + ch_free( sie->si_logfilterstr.bv_val ); + } + if ( sie->si_logfilter ) { + filter_free( sie->si_logfilter ); + } + if ( sie->si_base.bv_val ) { + ch_free( sie->si_base.bv_val ); + } + if ( sie->si_logbase.bv_val ) { + ch_free( sie->si_logbase.bv_val ); + } + if ( sie->si_be && SLAP_SYNC_SUBENTRY( sie->si_be )) { + ch_free( sie->si_contextdn.bv_val ); + } + if ( sie->si_attrs ) { + int i = 0; + while ( sie->si_attrs[i] != NULL ) { + ch_free( sie->si_attrs[i] ); + i++; + } + ch_free( sie->si_attrs ); + } + if ( sie->si_exattrs ) { + int i = 0; + while ( sie->si_exattrs[i] != NULL ) { + ch_free( sie->si_exattrs[i] ); + i++; + } + ch_free( sie->si_exattrs ); + } + if ( sie->si_anlist ) { + int i = 0; + while ( sie->si_anlist[i].an_name.bv_val != NULL ) { + ch_free( sie->si_anlist[i].an_name.bv_val ); + i++; + } + ch_free( sie->si_anlist ); + } + if ( sie->si_exanlist ) { + int i = 0; + while ( sie->si_exanlist[i].an_name.bv_val != NULL ) { + ch_free( sie->si_exanlist[i].an_name.bv_val ); + i++; + } + ch_free( sie->si_exanlist ); + } + if ( sie->si_retryinterval ) { + ch_free( sie->si_retryinterval ); + } + if ( sie->si_retrynum ) { + ch_free( sie->si_retrynum ); + } + if ( sie->si_retrynum_init ) { + ch_free( sie->si_retrynum_init ); + } + slap_sync_cookie_free( &sie->si_syncCookie, 0 ); + if ( sie->si_presentlist ) { + presentlist_free( sie->si_presentlist ); + } + while ( !LDAP_LIST_EMPTY( &sie->si_nonpresentlist ) ) { + struct nonpresent_entry* npe; + npe = LDAP_LIST_FIRST( &sie->si_nonpresentlist ); + LDAP_LIST_REMOVE( npe, npe_link ); + if ( npe->npe_name ) { + if ( npe->npe_name->bv_val ) { + ch_free( npe->npe_name->bv_val ); + } + ch_free( npe->npe_name ); + } + if ( npe->npe_nname ) { + if ( npe->npe_nname->bv_val ) { + ch_free( npe->npe_nname->bv_val ); + } + ch_free( npe->npe_nname ); + } + ch_free( npe ); + } + if ( sie->si_cookieState ) { + sie->si_cookieState->cs_ref--; + if ( !sie->si_cookieState->cs_ref ) { + ch_free( sie->si_cookieState->cs_sids ); + ber_bvarray_free( sie->si_cookieState->cs_vals ); + ldap_pvt_thread_cond_destroy( &sie->si_cookieState->cs_cond ); + ldap_pvt_thread_mutex_destroy( &sie->si_cookieState->cs_mutex ); + ch_free( sie->si_cookieState->cs_psids ); + ber_bvarray_free( sie->si_cookieState->cs_pvals ); + ldap_pvt_thread_mutex_destroy( &sie->si_cookieState->cs_pmutex ); + ch_free( sie->si_cookieState ); + } + } +#ifdef ENABLE_REWRITE + if ( sie->si_rewrite ) + rewrite_info_delete( &sie->si_rewrite ); + if ( sie->si_suffixm.bv_val ) + ch_free( sie->si_suffixm.bv_val ); +#endif + ch_free( sie ); + sie = si_next; + } while ( free_all && si_next ); +} + +#ifdef ENABLE_REWRITE +static int +config_suffixm( ConfigArgs *c, syncinfo_t *si ) +{ + char *argvEngine[] = { "rewriteEngine", "on", NULL }; + char *argvContext[] = { "rewriteContext", SUFFIXM_CTX, NULL }; + char *argvRule[] = { "rewriteRule", NULL, NULL, ":", NULL }; + char *vnc, *rnc; + int rc; + + if ( si->si_rewrite ) + rewrite_info_delete( &si->si_rewrite ); + si->si_rewrite = rewrite_info_init( REWRITE_MODE_USE_DEFAULT ); + + rc = rewrite_parse( si->si_rewrite, c->fname, c->lineno, 2, argvEngine ); + if ( rc != LDAP_SUCCESS ) + return rc; + + rc = rewrite_parse( si->si_rewrite, c->fname, c->lineno, 2, argvContext ); + if ( rc != LDAP_SUCCESS ) + return rc; + + vnc = ch_malloc( si->si_base.bv_len + 6 ); + strcpy( vnc, "(.*)" ); + lutil_strcopy( lutil_strcopy( vnc+4, si->si_base.bv_val ), "$" ); + argvRule[1] = vnc; + + rnc = ch_malloc( si->si_suffixm.bv_len + 3 ); + strcpy( rnc, "%1" ); + strcpy( rnc+2, si->si_suffixm.bv_val ); + argvRule[2] = rnc; + + rc = rewrite_parse( si->si_rewrite, c->fname, c->lineno, 4, argvRule ); + ch_free( vnc ); + ch_free( rnc ); + return rc; +} +#endif + +/* NOTE: used & documented in slapd.conf(5) */ +#define IDSTR "rid" +#define PROVIDERSTR "provider" +#define SCHEMASTR "schemachecking" +#define FILTERSTR "filter" +#define SEARCHBASESTR "searchbase" +#define SCOPESTR "scope" +#define ATTRSONLYSTR "attrsonly" +#define ATTRSSTR "attrs" +#define TYPESTR "type" +#define INTERVALSTR "interval" +#define RETRYSTR "retry" +#define SLIMITSTR "sizelimit" +#define TLIMITSTR "timelimit" +#define SYNCDATASTR "syncdata" +#define LOGBASESTR "logbase" +#define LOGFILTERSTR "logfilter" +#define SUFFIXMSTR "suffixmassage" +#define STRICT_REFRESH "strictrefresh" + +/* FIXME: undocumented */ +#define EXATTRSSTR "exattrs" +#define MANAGEDSAITSTR "manageDSAit" + +/* mandatory */ +enum { + GOT_RID = 0x00000001U, + GOT_PROVIDER = 0x00000002U, + GOT_SCHEMACHECKING = 0x00000004U, + GOT_FILTER = 0x00000008U, + GOT_SEARCHBASE = 0x00000010U, + GOT_SCOPE = 0x00000020U, + GOT_ATTRSONLY = 0x00000040U, + GOT_ATTRS = 0x00000080U, + GOT_TYPE = 0x00000100U, + GOT_INTERVAL = 0x00000200U, + GOT_RETRY = 0x00000400U, + GOT_SLIMIT = 0x00000800U, + GOT_TLIMIT = 0x00001000U, + GOT_SYNCDATA = 0x00002000U, + GOT_LOGBASE = 0x00004000U, + GOT_LOGFILTER = 0x00008000U, + GOT_EXATTRS = 0x00010000U, + GOT_MANAGEDSAIT = 0x00020000U, + GOT_BINDCONF = 0x00040000U, + GOT_SUFFIXM = 0x00080000U, + +/* check */ + GOT_REQUIRED = (GOT_RID|GOT_PROVIDER|GOT_SEARCHBASE) +}; + +static slap_verbmasks datamodes[] = { + { BER_BVC("default"), SYNCDATA_DEFAULT }, + { BER_BVC("accesslog"), SYNCDATA_ACCESSLOG }, + { BER_BVC("changelog"), SYNCDATA_CHANGELOG }, + { BER_BVNULL, 0 } +}; + +static int +parse_syncrepl_retry( + ConfigArgs *c, + char *arg, + syncinfo_t *si ) +{ + char **retry_list; + int j, k, n; + int use_default = 0; + + char *val = arg + STRLENOF( RETRYSTR "=" ); + if ( strcasecmp( val, "undefined" ) == 0 ) { + val = "3600 +"; + use_default = 1; + } + + retry_list = (char **) ch_calloc( 1, sizeof( char * ) ); + retry_list[0] = NULL; + + slap_str2clist( &retry_list, val, " ,\t" ); + + for ( k = 0; retry_list && retry_list[k]; k++ ) ; + n = k / 2; + if ( k % 2 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Error: incomplete syncrepl retry list" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + for ( k = 0; retry_list && retry_list[k]; k++ ) { + ch_free( retry_list[k] ); + } + ch_free( retry_list ); + return 1; + } + si->si_retryinterval = (time_t *) ch_calloc( n + 1, sizeof( time_t ) ); + si->si_retrynum = (int *) ch_calloc( n + 1, sizeof( int ) ); + si->si_retrynum_init = (int *) ch_calloc( n + 1, sizeof( int ) ); + for ( j = 0; j < n; j++ ) { + unsigned long t; + if ( lutil_atoul( &t, retry_list[j*2] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Error: invalid retry interval \"%s\" (#%d)", + retry_list[j*2], j ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + /* do some cleanup */ + return 1; + } + si->si_retryinterval[j] = (time_t)t; + if ( *retry_list[j*2+1] == '+' ) { + si->si_retrynum_init[j] = RETRYNUM_FOREVER; + si->si_retrynum[j] = RETRYNUM_FOREVER; + j++; + break; + } else { + if ( lutil_atoi( &si->si_retrynum_init[j], retry_list[j*2+1] ) != 0 + || si->si_retrynum_init[j] <= 0 ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Error: invalid initial retry number \"%s\" (#%d)", + retry_list[j*2+1], j ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + /* do some cleanup */ + return 1; + } + if ( lutil_atoi( &si->si_retrynum[j], retry_list[j*2+1] ) != 0 + || si->si_retrynum[j] <= 0 ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Error: invalid retry number \"%s\" (#%d)", + retry_list[j*2+1], j ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + /* do some cleanup */ + return 1; + } + } + } + if ( j < 1 || si->si_retrynum_init[j-1] != RETRYNUM_FOREVER ) { + Debug( LDAP_DEBUG_CONFIG, + "%s: syncrepl will eventually stop retrying; the \"retry\" parameter should end with a '+'.\n", + c->log, 0, 0 ); + } + + si->si_retrynum_init[j] = RETRYNUM_TAIL; + si->si_retrynum[j] = RETRYNUM_TAIL; + si->si_retryinterval[j] = 0; + + for ( k = 0; retry_list && retry_list[k]; k++ ) { + ch_free( retry_list[k] ); + } + ch_free( retry_list ); + if ( !use_default ) { + si->si_got |= GOT_RETRY; + } + + return 0; +} + +static int +parse_syncrepl_line( + ConfigArgs *c, + syncinfo_t *si ) +{ + int i; + char *val; + + for ( i = 1; i < c->argc; i++ ) { + if ( !strncasecmp( c->argv[ i ], IDSTR "=", + STRLENOF( IDSTR "=" ) ) ) + { + int tmp; + /* '\0' string terminator accounts for '=' */ + val = c->argv[ i ] + STRLENOF( IDSTR "=" ); + if ( lutil_atoi( &tmp, val ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Error: parse_syncrepl_line: " + "unable to parse syncrepl id \"%s\"", val ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return -1; + } + if ( tmp > SLAP_SYNC_RID_MAX || tmp < 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Error: parse_syncrepl_line: " + "syncrepl id %d is out of range [0..%d]", tmp, SLAP_SYNC_RID_MAX ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return -1; + } + si->si_rid = tmp; + sprintf( si->si_ridtxt, IDSTR "=%03d", si->si_rid ); + si->si_got |= GOT_RID; + } else if ( !strncasecmp( c->argv[ i ], PROVIDERSTR "=", + STRLENOF( PROVIDERSTR "=" ) ) ) + { + val = c->argv[ i ] + STRLENOF( PROVIDERSTR "=" ); + ber_str2bv( val, 0, 1, &si->si_bindconf.sb_uri ); +#ifdef HAVE_TLS + if ( ldap_is_ldaps_url( val )) + si->si_bindconf.sb_tls_do_init = 1; +#endif + si->si_got |= GOT_PROVIDER; + } else if ( !strncasecmp( c->argv[ i ], SCHEMASTR "=", + STRLENOF( SCHEMASTR "=" ) ) ) + { + val = c->argv[ i ] + STRLENOF( SCHEMASTR "=" ); + if ( !strncasecmp( val, "on", STRLENOF( "on" ) ) ) { + si->si_schemachecking = 1; + } else if ( !strncasecmp( val, "off", STRLENOF( "off" ) ) ) { + si->si_schemachecking = 0; + } else { + si->si_schemachecking = 1; + } + si->si_got |= GOT_SCHEMACHECKING; + } else if ( !strncasecmp( c->argv[ i ], FILTERSTR "=", + STRLENOF( FILTERSTR "=" ) ) ) + { + val = c->argv[ i ] + STRLENOF( FILTERSTR "=" ); + if ( si->si_filterstr.bv_val ) + ch_free( si->si_filterstr.bv_val ); + ber_str2bv( val, 0, 1, &si->si_filterstr ); + si->si_got |= GOT_FILTER; + } else if ( !strncasecmp( c->argv[ i ], LOGFILTERSTR "=", + STRLENOF( LOGFILTERSTR "=" ) ) ) + { + val = c->argv[ i ] + STRLENOF( LOGFILTERSTR "=" ); + if ( si->si_logfilterstr.bv_val ) + ch_free( si->si_logfilterstr.bv_val ); + ber_str2bv( val, 0, 1, &si->si_logfilterstr ); + si->si_got |= GOT_LOGFILTER; + } else if ( !strncasecmp( c->argv[ i ], SEARCHBASESTR "=", + STRLENOF( SEARCHBASESTR "=" ) ) ) + { + struct berval bv; + int rc; + + val = c->argv[ i ] + STRLENOF( SEARCHBASESTR "=" ); + if ( si->si_base.bv_val ) { + ch_free( si->si_base.bv_val ); + } + ber_str2bv( val, 0, 0, &bv ); + rc = dnNormalize( 0, NULL, NULL, &bv, &si->si_base, NULL ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Invalid base DN \"%s\": %d (%s)", + val, rc, ldap_err2string( rc ) ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return -1; + } + si->si_got |= GOT_SEARCHBASE; +#ifdef ENABLE_REWRITE + } else if ( !strncasecmp( c->argv[ i ], SUFFIXMSTR "=", + STRLENOF( SUFFIXMSTR "=" ) ) ) + { + struct berval bv; + int rc; + + val = c->argv[ i ] + STRLENOF( SUFFIXMSTR "=" ); + if ( si->si_suffixm.bv_val ) { + ch_free( si->si_suffixm.bv_val ); + } + ber_str2bv( val, 0, 0, &bv ); + rc = dnNormalize( 0, NULL, NULL, &bv, &si->si_suffixm, NULL ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Invalid massage DN \"%s\": %d (%s)", + val, rc, ldap_err2string( rc ) ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return -1; + } + if ( !be_issubordinate( c->be, &si->si_suffixm )) { + ch_free( si->si_suffixm.bv_val ); + BER_BVZERO( &si->si_suffixm ); + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Massage DN \"%s\" is not within the database naming context", + val ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return -1; + } + si->si_got |= GOT_SUFFIXM; +#endif + } else if ( !strncasecmp( c->argv[ i ], LOGBASESTR "=", + STRLENOF( LOGBASESTR "=" ) ) ) + { + struct berval bv; + int rc; + + val = c->argv[ i ] + STRLENOF( LOGBASESTR "=" ); + if ( si->si_logbase.bv_val ) { + ch_free( si->si_logbase.bv_val ); + } + ber_str2bv( val, 0, 0, &bv ); + rc = dnNormalize( 0, NULL, NULL, &bv, &si->si_logbase, NULL ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Invalid logbase DN \"%s\": %d (%s)", + val, rc, ldap_err2string( rc ) ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return -1; + } + si->si_got |= GOT_LOGBASE; + } else if ( !strncasecmp( c->argv[ i ], SCOPESTR "=", + STRLENOF( SCOPESTR "=" ) ) ) + { + int j; + val = c->argv[ i ] + STRLENOF( SCOPESTR "=" ); + j = ldap_pvt_str2scope( val ); + if ( j < 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Error: parse_syncrepl_line: " + "unknown scope \"%s\"", val); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return -1; + } + si->si_scope = j; + si->si_got |= GOT_SCOPE; + } else if ( !strncasecmp( c->argv[ i ], ATTRSONLYSTR, + STRLENOF( ATTRSONLYSTR ) ) ) + { + si->si_attrsonly = 1; + si->si_got |= GOT_ATTRSONLY; + } else if ( !strncasecmp( c->argv[ i ], ATTRSSTR "=", + STRLENOF( ATTRSSTR "=" ) ) ) + { + val = c->argv[ i ] + STRLENOF( ATTRSSTR "=" ); + if ( !strncasecmp( val, ":include:", STRLENOF(":include:") ) ) { + char *attr_fname; + attr_fname = ch_strdup( val + STRLENOF(":include:") ); + si->si_anlist = file2anlist( si->si_anlist, attr_fname, " ,\t" ); + if ( si->si_anlist == NULL ) { + ch_free( attr_fname ); + return -1; + } + si->si_anfile = attr_fname; + } else { + char *str, *s, *next; + const char *delimstr = " ,\t"; + str = ch_strdup( val ); + for ( s = ldap_pvt_strtok( str, delimstr, &next ); + s != NULL; + s = ldap_pvt_strtok( NULL, delimstr, &next ) ) + { + if ( strlen(s) == 1 && *s == '*' ) { + si->si_allattrs = 1; + val[ s - str ] = delimstr[0]; + } + if ( strlen(s) == 1 && *s == '+' ) { + si->si_allopattrs = 1; + val [ s - str ] = delimstr[0]; + } + } + ch_free( str ); + si->si_anlist = str2anlist( si->si_anlist, val, " ,\t" ); + if ( si->si_anlist == NULL ) { + return -1; + } + } + si->si_got |= GOT_ATTRS; + } else if ( !strncasecmp( c->argv[ i ], EXATTRSSTR "=", + STRLENOF( EXATTRSSTR "=" ) ) ) + { + val = c->argv[ i ] + STRLENOF( EXATTRSSTR "=" ); + if ( !strncasecmp( val, ":include:", STRLENOF(":include:") ) ) { + char *attr_fname; + attr_fname = ch_strdup( val + STRLENOF(":include:") ); + si->si_exanlist = file2anlist( + si->si_exanlist, attr_fname, " ,\t" ); + if ( si->si_exanlist == NULL ) { + ch_free( attr_fname ); + return -1; + } + ch_free( attr_fname ); + } else { + si->si_exanlist = str2anlist( si->si_exanlist, val, " ,\t" ); + if ( si->si_exanlist == NULL ) { + return -1; + } + } + si->si_got |= GOT_EXATTRS; + } else if ( !strncasecmp( c->argv[ i ], TYPESTR "=", + STRLENOF( TYPESTR "=" ) ) ) + { + val = c->argv[ i ] + STRLENOF( TYPESTR "=" ); + if ( !strncasecmp( val, "refreshOnly", + STRLENOF("refreshOnly") ) ) + { + si->si_type = si->si_ctype = LDAP_SYNC_REFRESH_ONLY; + } else if ( !strncasecmp( val, "refreshAndPersist", + STRLENOF("refreshAndPersist") ) ) + { + si->si_type = si->si_ctype = LDAP_SYNC_REFRESH_AND_PERSIST; + si->si_interval = 60; + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Error: parse_syncrepl_line: " + "unknown sync type \"%s\"", val); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return -1; + } + si->si_got |= GOT_TYPE; + } else if ( !strncasecmp( c->argv[ i ], INTERVALSTR "=", + STRLENOF( INTERVALSTR "=" ) ) ) + { + val = c->argv[ i ] + STRLENOF( INTERVALSTR "=" ); + if ( si->si_type == LDAP_SYNC_REFRESH_AND_PERSIST ) { + si->si_interval = 0; + } else if ( strchr( val, ':' ) != NULL ) { + char *next, *ptr = val; + int dd, hh, mm, ss; + + dd = strtol( ptr, &next, 10 ); + if ( next == ptr || next[0] != ':' || dd < 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Error: parse_syncrepl_line: " + "invalid interval \"%s\", unable to parse days", val ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return -1; + } + ptr = next + 1; + hh = strtol( ptr, &next, 10 ); + if ( next == ptr || next[0] != ':' || hh < 0 || hh > 24 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Error: parse_syncrepl_line: " + "invalid interval \"%s\", unable to parse hours", val ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return -1; + } + ptr = next + 1; + mm = strtol( ptr, &next, 10 ); + if ( next == ptr || next[0] != ':' || mm < 0 || mm > 60 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Error: parse_syncrepl_line: " + "invalid interval \"%s\", unable to parse minutes", val ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return -1; + } + ptr = next + 1; + ss = strtol( ptr, &next, 10 ); + if ( next == ptr || next[0] != '\0' || ss < 0 || ss > 60 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Error: parse_syncrepl_line: " + "invalid interval \"%s\", unable to parse seconds", val ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return -1; + } + si->si_interval = (( dd * 24 + hh ) * 60 + mm ) * 60 + ss; + } else { + unsigned long t; + + if ( lutil_parse_time( val, &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Error: parse_syncrepl_line: " + "invalid interval \"%s\"", val ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return -1; + } + si->si_interval = (time_t)t; + } + if ( si->si_interval < 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Error: parse_syncrepl_line: " + "invalid interval \"%ld\"", + (long) si->si_interval); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return -1; + } + si->si_got |= GOT_INTERVAL; + } else if ( !strncasecmp( c->argv[ i ], RETRYSTR "=", + STRLENOF( RETRYSTR "=" ) ) ) + { + if ( parse_syncrepl_retry( c, c->argv[ i ], si ) ) { + return 1; + } + } else if ( !strncasecmp( c->argv[ i ], MANAGEDSAITSTR "=", + STRLENOF( MANAGEDSAITSTR "=" ) ) ) + { + val = c->argv[ i ] + STRLENOF( MANAGEDSAITSTR "=" ); + if ( lutil_atoi( &si->si_manageDSAit, val ) != 0 + || si->si_manageDSAit < 0 || si->si_manageDSAit > 1 ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "invalid manageDSAit value \"%s\".\n", + val ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + si->si_got |= GOT_MANAGEDSAIT; + } else if ( !strncasecmp( c->argv[ i ], SLIMITSTR "=", + STRLENOF( SLIMITSTR "=") ) ) + { + val = c->argv[ i ] + STRLENOF( SLIMITSTR "=" ); + if ( strcasecmp( val, "unlimited" ) == 0 ) { + si->si_slimit = 0; + + } else if ( lutil_atoi( &si->si_slimit, val ) != 0 || si->si_slimit < 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "invalid size limit value \"%s\".\n", + val ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + si->si_got |= GOT_SLIMIT; + } else if ( !strncasecmp( c->argv[ i ], TLIMITSTR "=", + STRLENOF( TLIMITSTR "=" ) ) ) + { + val = c->argv[ i ] + STRLENOF( TLIMITSTR "=" ); + if ( strcasecmp( val, "unlimited" ) == 0 ) { + si->si_tlimit = 0; + + } else if ( lutil_atoi( &si->si_tlimit, val ) != 0 || si->si_tlimit < 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "invalid time limit value \"%s\".\n", + val ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + si->si_got |= GOT_TLIMIT; + } else if ( !strncasecmp( c->argv[ i ], SYNCDATASTR "=", + STRLENOF( SYNCDATASTR "=" ) ) ) + { + val = c->argv[ i ] + STRLENOF( SYNCDATASTR "=" ); + si->si_syncdata = verb_to_mask( val, datamodes ); + si->si_got |= GOT_SYNCDATA; + } else if ( !strncasecmp( c->argv[ i ], STRICT_REFRESH, + STRLENOF( STRICT_REFRESH ) ) ) + { + si->si_strict_refresh = 1; + } else if ( !bindconf_parse( c->argv[i], &si->si_bindconf ) ) { + si->si_got |= GOT_BINDCONF; + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Error: parse_syncrepl_line: " + "unable to parse \"%s\"\n", c->argv[ i ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return -1; + } + } + + if ( ( si->si_got & GOT_REQUIRED ) != GOT_REQUIRED ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Error: Malformed \"syncrepl\" line in slapd config file, missing%s%s%s", + si->si_got & GOT_RID ? "" : " "IDSTR, + si->si_got & GOT_PROVIDER ? "" : " "PROVIDERSTR, + si->si_got & GOT_SEARCHBASE ? "" : " "SEARCHBASESTR ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return -1; + } + + if ( !be_issubordinate( c->be, &si->si_base ) && !( si->si_got & GOT_SUFFIXM )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Base DN \"%s\" is not within the database naming context", + si->si_base.bv_val ); + ch_free( si->si_base.bv_val ); + BER_BVZERO( &si->si_base ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return -1; + } + +#ifdef ENABLE_REWRITE + if ( si->si_got & GOT_SUFFIXM ) { + if (config_suffixm( c, si )) { + ch_free( si->si_suffixm.bv_val ); + BER_BVZERO( &si->si_suffixm ); + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "Error configuring rewrite engine" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return -1; + } + } +#endif + + if ( !( si->si_got & GOT_RETRY ) ) { + Debug( LDAP_DEBUG_ANY, "syncrepl %s " SEARCHBASESTR "=\"%s\": no retry defined, using default\n", + si->si_ridtxt, c->be->be_suffix ? c->be->be_suffix[ 0 ].bv_val : "(null)", 0 ); + if ( si->si_retryinterval == NULL ) { + if ( parse_syncrepl_retry( c, "retry=undefined", si ) ) { + return 1; + } + } + } + + si->si_filter = str2filter( si->si_filterstr.bv_val ); + if ( si->si_filter == NULL ) { + Debug( LDAP_DEBUG_ANY, "syncrepl %s " SEARCHBASESTR "=\"%s\": unable to parse filter=\"%s\"\n", + si->si_ridtxt, c->be->be_suffix ? c->be->be_suffix[ 0 ].bv_val : "(null)", si->si_filterstr.bv_val ); + return 1; + } + + if ( si->si_got & GOT_LOGFILTER ) { + si->si_logfilter = str2filter( si->si_logfilterstr.bv_val ); + if ( si->si_logfilter == NULL ) { + Debug( LDAP_DEBUG_ANY, "syncrepl %s " SEARCHBASESTR "=\"%s\": unable to parse logfilter=\"%s\"\n", + si->si_ridtxt, c->be->be_suffix ? c->be->be_suffix[ 0 ].bv_val : "(null)", si->si_logfilterstr.bv_val ); + return 1; + } + } + + return 0; +} + +static int +add_syncrepl( + ConfigArgs *c ) +{ + syncinfo_t *si; + int rc = 0; + + if ( !( c->be->be_search && c->be->be_add && c->be->be_modify && c->be->be_delete ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), "database %s does not support " + "operations required for syncrepl", c->be->be_type ); + Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg, 0 ); + return 1; + } + if ( BER_BVISEMPTY( &c->be->be_rootdn ) ) { + strcpy( c->cr_msg, "rootDN must be defined before syncrepl may be used" ); + Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg, 0 ); + return 1; + } + si = (syncinfo_t *) ch_calloc( 1, sizeof( syncinfo_t ) ); + + if ( si == NULL ) { + Debug( LDAP_DEBUG_ANY, "out of memory in add_syncrepl\n", 0, 0, 0 ); + return 1; + } + + si->si_bindconf.sb_tls = SB_TLS_OFF; + si->si_bindconf.sb_method = LDAP_AUTH_SIMPLE; + si->si_schemachecking = 0; + ber_str2bv( "(objectclass=*)", STRLENOF("(objectclass=*)"), 1, + &si->si_filterstr ); + si->si_base.bv_val = NULL; + si->si_scope = LDAP_SCOPE_SUBTREE; + si->si_attrsonly = 0; + si->si_anlist = (AttributeName *) ch_calloc( 1, sizeof( AttributeName ) ); + si->si_exanlist = (AttributeName *) ch_calloc( 1, sizeof( AttributeName ) ); + si->si_attrs = NULL; + si->si_allattrs = 0; + si->si_allopattrs = 0; + si->si_exattrs = NULL; + si->si_type = si->si_ctype = LDAP_SYNC_REFRESH_ONLY; + si->si_interval = 86400; + si->si_retryinterval = NULL; + si->si_retrynum_init = NULL; + si->si_retrynum = NULL; + si->si_manageDSAit = 0; + si->si_tlimit = 0; + si->si_slimit = 0; + + si->si_presentlist = NULL; + LDAP_LIST_INIT( &si->si_nonpresentlist ); + ldap_pvt_thread_mutex_init( &si->si_mutex ); + + si->si_is_configdb = strcmp( c->be->be_suffix[0].bv_val, "cn=config" ) == 0; + + rc = parse_syncrepl_line( c, si ); + + if ( rc == 0 ) { + LDAPURLDesc *lud; + + /* Must be LDAPv3 because we need controls */ + switch ( si->si_bindconf.sb_version ) { + case 0: + /* not explicitly set */ + si->si_bindconf.sb_version = LDAP_VERSION3; + break; + case 3: + /* explicitly set */ + break; + default: + Debug( LDAP_DEBUG_ANY, + "version %d incompatible with syncrepl\n", + si->si_bindconf.sb_version, 0, 0 ); + syncinfo_free( si, 0 ); + return 1; + } + + if ( ldap_url_parse( si->si_bindconf.sb_uri.bv_val, &lud )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> invalid URL", c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s %s\n", + c->log, c->cr_msg, si->si_bindconf.sb_uri.bv_val ); + return 1; + } + + si->si_be = c->be; + if ( slapMode & SLAP_SERVER_MODE ) { + int isMe = 0; + /* check if consumer points to current server and database. + * If so, ignore this configuration. + */ + if ( !SLAP_DBHIDDEN( c->be ) ) { + int i; + /* if searchbase doesn't match current DB suffix, + * assume it's different + */ + for ( i=0; !BER_BVISNULL( &c->be->be_nsuffix[i] ); i++ ) { + if ( bvmatch( &si->si_base, &c->be->be_nsuffix[i] )) { + isMe = 1; + break; + } + } + /* if searchbase matches, see if URLs match */ + if ( isMe && config_check_my_url( si->si_bindconf.sb_uri.bv_val, + lud ) == NULL ) + isMe = 0; + } + + if ( !isMe ) { + init_syncrepl( si ); + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + si->si_re = ldap_pvt_runqueue_insert( &slapd_rq, + si->si_interval, do_syncrepl, si, "do_syncrepl", + si->si_ridtxt ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + if ( si->si_re ) + rc = config_sync_shadow( c ) ? -1 : 0; + else + rc = -1; + } + } else { + /* mirrormode still needs to see this flag in tool mode */ + rc = config_sync_shadow( c ) ? -1 : 0; + } + ldap_free_urldesc( lud ); + } + +#ifdef HAVE_TLS + /* Use main slapd defaults */ + bindconf_tls_defaults( &si->si_bindconf ); +#endif + if ( rc < 0 ) { + Debug( LDAP_DEBUG_ANY, "failed to add syncinfo\n", 0, 0, 0 ); + syncinfo_free( si, 0 ); + return 1; + } else { + Debug( LDAP_DEBUG_CONFIG, + "Config: ** successfully added syncrepl %s \"%s\"\n", + si->si_ridtxt, + BER_BVISNULL( &si->si_bindconf.sb_uri ) ? + "(null)" : si->si_bindconf.sb_uri.bv_val, 0 ); + if ( c->be->be_syncinfo ) { + syncinfo_t *sip; + + si->si_cookieState = c->be->be_syncinfo->si_cookieState; + + /* add new syncrepl to end of list (same order as when deleting) */ + for ( sip = c->be->be_syncinfo; sip->si_next; sip = sip->si_next ); + sip->si_next = si; + } else { + si->si_cookieState = ch_calloc( 1, sizeof( cookie_state )); + ldap_pvt_thread_mutex_init( &si->si_cookieState->cs_mutex ); + ldap_pvt_thread_mutex_init( &si->si_cookieState->cs_pmutex ); + ldap_pvt_thread_cond_init( &si->si_cookieState->cs_cond ); + + c->be->be_syncinfo = si; + } + si->si_cookieState->cs_ref++; + + si->si_next = NULL; + + return 0; + } +} + +static void +syncrepl_unparse( syncinfo_t *si, struct berval *bv ) +{ + struct berval bc, uri, bs; + char buf[BUFSIZ*2], *ptr; + ber_len_t len; + int i; +# define WHATSLEFT ((ber_len_t) (&buf[sizeof( buf )] - ptr)) + + BER_BVZERO( bv ); + + /* temporarily inhibit bindconf from printing URI */ + uri = si->si_bindconf.sb_uri; + BER_BVZERO( &si->si_bindconf.sb_uri ); + si->si_bindconf.sb_version = 0; + bindconf_unparse( &si->si_bindconf, &bc ); + si->si_bindconf.sb_uri = uri; + si->si_bindconf.sb_version = LDAP_VERSION3; + + ptr = buf; + assert( si->si_rid >= 0 && si->si_rid <= SLAP_SYNC_RID_MAX ); + len = snprintf( ptr, WHATSLEFT, IDSTR "=%03d " PROVIDERSTR "=%s", + si->si_rid, si->si_bindconf.sb_uri.bv_val ); + if ( len >= sizeof( buf ) ) return; + ptr += len; + if ( !BER_BVISNULL( &bc ) ) { + if ( WHATSLEFT <= bc.bv_len ) { + free( bc.bv_val ); + return; + } + ptr = lutil_strcopy( ptr, bc.bv_val ); + free( bc.bv_val ); + } + if ( !BER_BVISEMPTY( &si->si_filterstr ) ) { + if ( WHATSLEFT <= STRLENOF( " " FILTERSTR "=\"" "\"" ) + si->si_filterstr.bv_len ) return; + ptr = lutil_strcopy( ptr, " " FILTERSTR "=\"" ); + ptr = lutil_strcopy( ptr, si->si_filterstr.bv_val ); + *ptr++ = '"'; + } + if ( !BER_BVISNULL( &si->si_base ) ) { + if ( WHATSLEFT <= STRLENOF( " " SEARCHBASESTR "=\"" "\"" ) + si->si_base.bv_len ) return; + ptr = lutil_strcopy( ptr, " " SEARCHBASESTR "=\"" ); + ptr = lutil_strcopy( ptr, si->si_base.bv_val ); + *ptr++ = '"'; + } +#ifdef ENABLE_REWRITE + if ( !BER_BVISNULL( &si->si_suffixm ) ) { + if ( WHATSLEFT <= STRLENOF( " " SUFFIXMSTR "=\"" "\"" ) + si->si_suffixm.bv_len ) return; + ptr = lutil_strcopy( ptr, " " SUFFIXMSTR "=\"" ); + ptr = lutil_strcopy( ptr, si->si_suffixm.bv_val ); + *ptr++ = '"'; + } +#endif + if ( !BER_BVISEMPTY( &si->si_logfilterstr ) ) { + if ( WHATSLEFT <= STRLENOF( " " LOGFILTERSTR "=\"" "\"" ) + si->si_logfilterstr.bv_len ) return; + ptr = lutil_strcopy( ptr, " " LOGFILTERSTR "=\"" ); + ptr = lutil_strcopy( ptr, si->si_logfilterstr.bv_val ); + *ptr++ = '"'; + } + if ( !BER_BVISNULL( &si->si_logbase ) ) { + if ( WHATSLEFT <= STRLENOF( " " LOGBASESTR "=\"" "\"" ) + si->si_logbase.bv_len ) return; + ptr = lutil_strcopy( ptr, " " LOGBASESTR "=\"" ); + ptr = lutil_strcopy( ptr, si->si_logbase.bv_val ); + *ptr++ = '"'; + } + if ( ldap_pvt_scope2bv( si->si_scope, &bs ) == LDAP_SUCCESS ) { + if ( WHATSLEFT <= STRLENOF( " " SCOPESTR "=" ) + bs.bv_len ) return; + ptr = lutil_strcopy( ptr, " " SCOPESTR "=" ); + ptr = lutil_strcopy( ptr, bs.bv_val ); + } + if ( si->si_attrsonly ) { + if ( WHATSLEFT <= STRLENOF( " " ATTRSONLYSTR "=\"" "\"" ) ) return; + ptr = lutil_strcopy( ptr, " " ATTRSONLYSTR ); + } + if ( si->si_anfile ) { + if ( WHATSLEFT <= STRLENOF( " " ATTRSSTR "=\":include:" "\"" ) + strlen( si->si_anfile ) ) return; + ptr = lutil_strcopy( ptr, " " ATTRSSTR "=:include:\"" ); + ptr = lutil_strcopy( ptr, si->si_anfile ); + *ptr++ = '"'; + } else if ( si->si_allattrs || si->si_allopattrs || + ( si->si_anlist && !BER_BVISNULL(&si->si_anlist[0].an_name) ) ) + { + char *old; + + if ( WHATSLEFT <= STRLENOF( " " ATTRSONLYSTR "=\"" "\"" ) ) return; + ptr = lutil_strcopy( ptr, " " ATTRSSTR "=\"" ); + old = ptr; + ptr = anlist_unparse( si->si_anlist, ptr, WHATSLEFT ); + if ( ptr == NULL ) return; + if ( si->si_allattrs ) { + if ( WHATSLEFT <= STRLENOF( ",*\"" ) ) return; + if ( old != ptr ) *ptr++ = ','; + *ptr++ = '*'; + } + if ( si->si_allopattrs ) { + if ( WHATSLEFT <= STRLENOF( ",+\"" ) ) return; + if ( old != ptr ) *ptr++ = ','; + *ptr++ = '+'; + } + *ptr++ = '"'; + } + if ( si->si_exanlist && !BER_BVISNULL(&si->si_exanlist[0].an_name) ) { + if ( WHATSLEFT <= STRLENOF( " " EXATTRSSTR "=" ) ) return; + ptr = lutil_strcopy( ptr, " " EXATTRSSTR "=" ); + ptr = anlist_unparse( si->si_exanlist, ptr, WHATSLEFT ); + if ( ptr == NULL ) return; + } + if ( WHATSLEFT <= STRLENOF( " " SCHEMASTR "=" ) + STRLENOF( "off" ) ) return; + ptr = lutil_strcopy( ptr, " " SCHEMASTR "=" ); + ptr = lutil_strcopy( ptr, si->si_schemachecking ? "on" : "off" ); + + if ( WHATSLEFT <= STRLENOF( " " TYPESTR "=" ) + STRLENOF( "refreshAndPersist" ) ) return; + ptr = lutil_strcopy( ptr, " " TYPESTR "=" ); + ptr = lutil_strcopy( ptr, si->si_type == LDAP_SYNC_REFRESH_AND_PERSIST ? + "refreshAndPersist" : "refreshOnly" ); + + if ( si->si_type == LDAP_SYNC_REFRESH_ONLY ) { + int dd, hh, mm, ss; + + dd = si->si_interval; + ss = dd % 60; + dd /= 60; + mm = dd % 60; + dd /= 60; + hh = dd % 24; + dd /= 24; + len = snprintf( ptr, WHATSLEFT, " %s=%02d:%02d:%02d:%02d", + INTERVALSTR, dd, hh, mm, ss ); + if ( len >= WHATSLEFT ) return; + ptr += len; + } + + if ( si->si_got & GOT_RETRY ) { + const char *space = ""; + if ( WHATSLEFT <= STRLENOF( " " RETRYSTR "=\"" "\"" ) ) return; + ptr = lutil_strcopy( ptr, " " RETRYSTR "=\"" ); + for (i=0; si->si_retryinterval[i]; i++) { + len = snprintf( ptr, WHATSLEFT, "%s%ld ", space, + (long) si->si_retryinterval[i] ); + space = " "; + if ( WHATSLEFT - 1 <= len ) return; + ptr += len; + if ( si->si_retrynum_init[i] == RETRYNUM_FOREVER ) + *ptr++ = '+'; + else { + len = snprintf( ptr, WHATSLEFT, "%d", si->si_retrynum_init[i] ); + if ( WHATSLEFT <= len ) return; + ptr += len; + } + } + if ( WHATSLEFT <= STRLENOF( "\"" ) ) return; + *ptr++ = '"'; + } else { + ptr = lutil_strcopy( ptr, " " RETRYSTR "=undefined" ); + } + + if ( si->si_slimit ) { + len = snprintf( ptr, WHATSLEFT, " " SLIMITSTR "=%d", si->si_slimit ); + if ( WHATSLEFT <= len ) return; + ptr += len; + } + + if ( si->si_tlimit ) { + len = snprintf( ptr, WHATSLEFT, " " TLIMITSTR "=%d", si->si_tlimit ); + if ( WHATSLEFT <= len ) return; + ptr += len; + } + + if ( si->si_syncdata ) { + if ( enum_to_verb( datamodes, si->si_syncdata, &bc ) >= 0 ) { + if ( WHATSLEFT <= STRLENOF( " " SYNCDATASTR "=" ) + bc.bv_len ) return; + ptr = lutil_strcopy( ptr, " " SYNCDATASTR "=" ); + ptr = lutil_strcopy( ptr, bc.bv_val ); + } + } + bc.bv_len = ptr - buf; + bc.bv_val = buf; + ber_dupbv( bv, &bc ); +} + +int +syncrepl_config( ConfigArgs *c ) +{ + if (c->op == SLAP_CONFIG_EMIT) { + if ( c->be->be_syncinfo ) { + struct berval bv; + syncinfo_t *si; + + for ( si = c->be->be_syncinfo; si; si=si->si_next ) { + syncrepl_unparse( si, &bv ); + ber_bvarray_add( &c->rvalue_vals, &bv ); + } + return 0; + } + return 1; + } else if ( c->op == LDAP_MOD_DELETE ) { + int isrunning = 0; + if ( c->be->be_syncinfo ) { + syncinfo_t *si, **sip; + int i; + + for ( sip = &c->be->be_syncinfo, i=0; *sip; i++ ) { + si = *sip; + if ( c->valx == -1 || i == c->valx ) { + *sip = si->si_next; + si->si_ctype = -1; + si->si_next = NULL; + /* If the task is currently active, we have to leave + * it running. It will exit on its own. This will only + * happen when running on the cn=config DB. + */ + if ( si->si_re ) { + if ( si->si_be == c->be || ldap_pvt_thread_mutex_trylock( &si->si_mutex )) { + isrunning = 1; + } else { + /* There is no active thread, but we must still + * ensure that no thread is (or will be) queued + * while we removes the task. + */ + struct re_s *re = si->si_re; + si->si_re = NULL; + + if ( si->si_conn ) { + connection_client_stop( si->si_conn ); + si->si_conn = NULL; + } + + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re ) ) { + ldap_pvt_runqueue_stoptask( &slapd_rq, re ); + isrunning = 1; + } + if ( ldap_pvt_thread_pool_retract( &connection_pool, + re->routine, re ) > 0 ) + isrunning = 0; + + ldap_pvt_runqueue_remove( &slapd_rq, re ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + ldap_pvt_thread_mutex_unlock( &si->si_mutex ); + } + } + if ( !isrunning ) { + syncinfo_free( si, 0 ); + } + if ( i == c->valx ) + break; + } else { + sip = &si->si_next; + } + } + } + if ( !c->be->be_syncinfo ) { + SLAP_DBFLAGS( c->be ) &= ~SLAP_DBFLAG_SHADOW_MASK; + } + return 0; + } + if ( SLAP_SLURP_SHADOW( c->be ) ) { + Debug(LDAP_DEBUG_ANY, "%s: " + "syncrepl: database already shadowed.\n", + c->log, 0, 0); + return(1); + } else { + return add_syncrepl( c ); + } +} diff --git a/servers/slapd/syntax.c b/servers/slapd/syntax.c new file mode 100644 index 0000000..39cf43f --- /dev/null +++ b/servers/slapd/syntax.c @@ -0,0 +1,457 @@ +/* syntax.c - routines to manage syntax definitions */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" + +struct sindexrec { + char *sir_name; + Syntax *sir_syn; +}; + +static Avlnode *syn_index = NULL; +static LDAP_STAILQ_HEAD(SyntaxList, Syntax) syn_list + = LDAP_STAILQ_HEAD_INITIALIZER(syn_list); + +/* Last hardcoded attribute registered */ +Syntax *syn_sys_tail; + +static int +syn_index_cmp( + const void *v_sir1, + const void *v_sir2 +) +{ + const struct sindexrec *sir1 = v_sir1, *sir2 = v_sir2; + return (strcmp( sir1->sir_name, sir2->sir_name )); +} + +static int +syn_index_name_cmp( + const void *name, + const void *sir +) +{ + return (strcmp( name, ((const struct sindexrec *)sir)->sir_name )); +} + +Syntax * +syn_find( const char *synname ) +{ + struct sindexrec *sir = NULL; + + if ( (sir = avl_find( syn_index, synname, syn_index_name_cmp )) != NULL ) { + return( sir->sir_syn ); + } + return( NULL ); +} + +Syntax * +syn_find_desc( const char *syndesc, int *len ) +{ + Syntax *synp; + + LDAP_STAILQ_FOREACH(synp, &syn_list, ssyn_next) { + if ((*len = dscompare( synp->ssyn_syn.syn_desc, syndesc, '{' /*'}'*/ ))) { + return synp; + } + } + return( NULL ); +} + +int +syn_is_sup( Syntax *syn, Syntax *sup ) +{ + int i; + + assert( syn != NULL ); + assert( sup != NULL ); + + if ( syn == sup ) { + return 1; + } + + if ( syn->ssyn_sups == NULL ) { + return 0; + } + + for ( i = 0; syn->ssyn_sups[i]; i++ ) { + if ( syn->ssyn_sups[i] == sup ) { + return 1; + } + + if ( syn_is_sup( syn->ssyn_sups[i], sup ) ) { + return 1; + } + } + + return 0; +} + +void +syn_destroy( void ) +{ + Syntax *s; + + avl_free( syn_index, ldap_memfree ); + while( !LDAP_STAILQ_EMPTY( &syn_list ) ) { + s = LDAP_STAILQ_FIRST( &syn_list ); + LDAP_STAILQ_REMOVE_HEAD( &syn_list, ssyn_next ); + if ( s->ssyn_sups ) { + SLAP_FREE( s->ssyn_sups ); + } + ldap_syntax_free( (LDAPSyntax *)s ); + } +} + +static int +syn_insert( + Syntax *ssyn, + Syntax *prev, + const char **err ) +{ + struct sindexrec *sir; + + LDAP_STAILQ_NEXT( ssyn, ssyn_next ) = NULL; + + if ( ssyn->ssyn_oid ) { + sir = (struct sindexrec *) + SLAP_CALLOC( 1, sizeof(struct sindexrec) ); + if( sir == NULL ) { + Debug( LDAP_DEBUG_ANY, "SLAP_CALLOC Error\n", 0, 0, 0 ); + return LDAP_OTHER; + } + sir->sir_name = ssyn->ssyn_oid; + sir->sir_syn = ssyn; + if ( avl_insert( &syn_index, (caddr_t) sir, + syn_index_cmp, avl_dup_error ) ) { + *err = ssyn->ssyn_oid; + ldap_memfree(sir); + return SLAP_SCHERR_SYN_DUP; + } + /* FIX: temporal consistency check */ + syn_find(sir->sir_name); + } + + if ( ssyn->ssyn_flags & SLAP_AT_HARDCODE ) { + prev = syn_sys_tail; + syn_sys_tail = ssyn; + } + + if ( prev ) { + LDAP_STAILQ_INSERT_AFTER( &syn_list, prev, ssyn, ssyn_next ); + } else { + LDAP_STAILQ_INSERT_TAIL( &syn_list, ssyn, ssyn_next ); + } + return 0; +} + +int +syn_add( + LDAPSyntax *syn, + int user, + slap_syntax_defs_rec *def, + Syntax **ssynp, + Syntax *prev, + const char **err ) +{ + Syntax *ssyn; + int code = 0; + + if ( ssynp != NULL ) { + *ssynp = NULL; + } + + ssyn = (Syntax *) SLAP_CALLOC( 1, sizeof(Syntax) ); + if ( ssyn == NULL ) { + Debug( LDAP_DEBUG_ANY, "SLAP_CALLOC Error\n", 0, 0, 0 ); + return SLAP_SCHERR_OUTOFMEM; + } + + AC_MEMCPY( &ssyn->ssyn_syn, syn, sizeof(LDAPSyntax) ); + + LDAP_STAILQ_NEXT(ssyn,ssyn_next) = NULL; + + /* + * note: ssyn_bvoid uses the same memory of ssyn_syn.syn_oid; + * ssyn_oidlen is #defined as ssyn_bvoid.bv_len + */ + ssyn->ssyn_bvoid.bv_val = ssyn->ssyn_syn.syn_oid; + ssyn->ssyn_oidlen = strlen(syn->syn_oid); + ssyn->ssyn_flags = def->sd_flags; + ssyn->ssyn_validate = def->sd_validate; + ssyn->ssyn_pretty = def->sd_pretty; + + ssyn->ssyn_sups = NULL; + +#ifdef SLAPD_BINARY_CONVERSION + ssyn->ssyn_ber2str = def->sd_ber2str; + ssyn->ssyn_str2ber = def->sd_str2ber; +#endif + + if ( def->sd_validate == NULL && def->sd_pretty == NULL && syn->syn_extensions != NULL ) { + LDAPSchemaExtensionItem **lsei; + Syntax *subst = NULL; + + for ( lsei = syn->syn_extensions; *lsei != NULL; lsei++) { + if ( strcmp( (*lsei)->lsei_name, "X-SUBST" ) != 0 ) { + continue; + } + + assert( (*lsei)->lsei_values != NULL ); + if ( (*lsei)->lsei_values[0] == NULL + || (*lsei)->lsei_values[1] != NULL ) + { + Debug( LDAP_DEBUG_ANY, "syn_add(%s): exactly one substitute syntax must be present\n", + ssyn->ssyn_syn.syn_oid, 0, 0 ); + SLAP_FREE( ssyn ); + return SLAP_SCHERR_SYN_SUBST_NOT_SPECIFIED; + } + + subst = syn_find( (*lsei)->lsei_values[0] ); + if ( subst == NULL ) { + Debug( LDAP_DEBUG_ANY, "syn_add(%s): substitute syntax %s not found\n", + ssyn->ssyn_syn.syn_oid, (*lsei)->lsei_values[0], 0 ); + SLAP_FREE( ssyn ); + return SLAP_SCHERR_SYN_SUBST_NOT_FOUND; + } + break; + } + + if ( subst != NULL ) { + ssyn->ssyn_flags = subst->ssyn_flags; + ssyn->ssyn_validate = subst->ssyn_validate; + ssyn->ssyn_pretty = subst->ssyn_pretty; + + ssyn->ssyn_sups = NULL; + +#ifdef SLAPD_BINARY_CONVERSION + ssyn->ssyn_ber2str = subst->ssyn_ber2str; + ssyn->ssyn_str2ber = subst->ssyn_str2ber; +#endif + } + } + + if ( def->sd_sups != NULL ) { + int cnt; + + for ( cnt = 0; def->sd_sups[cnt] != NULL; cnt++ ) + ; + + ssyn->ssyn_sups = (Syntax **)SLAP_CALLOC( cnt + 1, + sizeof( Syntax * ) ); + if ( ssyn->ssyn_sups == NULL ) { + Debug( LDAP_DEBUG_ANY, "SLAP_CALLOC Error\n", 0, 0, 0 ); + code = SLAP_SCHERR_OUTOFMEM; + + } else { + for ( cnt = 0; def->sd_sups[cnt] != NULL; cnt++ ) { + ssyn->ssyn_sups[cnt] = syn_find( def->sd_sups[cnt] ); + if ( ssyn->ssyn_sups[cnt] == NULL ) { + *err = def->sd_sups[cnt]; + code = SLAP_SCHERR_SYN_SUP_NOT_FOUND; + } + } + } + } + + if ( !user ) + ssyn->ssyn_flags |= SLAP_SYNTAX_HARDCODE; + + if ( code == 0 ) { + code = syn_insert( ssyn, prev, err ); + } + + if ( code != 0 && ssyn != NULL ) { + if ( ssyn->ssyn_sups != NULL ) { + SLAP_FREE( ssyn->ssyn_sups ); + } + SLAP_FREE( ssyn ); + ssyn = NULL; + } + + if (ssynp ) { + *ssynp = ssyn; + } + + return code; +} + +int +register_syntax( + slap_syntax_defs_rec *def ) +{ + LDAPSyntax *syn; + int code; + const char *err; + + syn = ldap_str2syntax( def->sd_desc, &code, &err, LDAP_SCHEMA_ALLOW_ALL); + if ( !syn ) { + Debug( LDAP_DEBUG_ANY, "Error in register_syntax: %s before %s in %s\n", + ldap_scherr2str(code), err, def->sd_desc ); + + return( -1 ); + } + + code = syn_add( syn, 0, def, NULL, NULL, &err ); + + if ( code ) { + Debug( LDAP_DEBUG_ANY, "Error in register_syntax: %s %s in %s\n", + scherr2str(code), err, def->sd_desc ); + ldap_syntax_free( syn ); + + return( -1 ); + } + + ldap_memfree( syn ); + + return( 0 ); +} + +int +syn_schema_info( Entry *e ) +{ + AttributeDescription *ad_ldapSyntaxes = slap_schema.si_ad_ldapSyntaxes; + Syntax *syn; + struct berval val; + struct berval nval; + + LDAP_STAILQ_FOREACH(syn, &syn_list, ssyn_next ) { + if ( ! syn->ssyn_validate ) { + /* skip syntaxes without validators */ + continue; + } + if ( syn->ssyn_flags & SLAP_SYNTAX_HIDE ) { + /* hide syntaxes */ + continue; + } + + if ( ldap_syntax2bv( &syn->ssyn_syn, &val ) == NULL ) { + return -1; + } +#if 0 + Debug( LDAP_DEBUG_TRACE, "Merging syn [%ld] %s\n", + (long) val.bv_len, val.bv_val, 0 ); +#endif + + nval.bv_val = syn->ssyn_oid; + nval.bv_len = strlen(syn->ssyn_oid); + + if( attr_merge_one( e, ad_ldapSyntaxes, &val, &nval ) ) + { + return -1; + } + ldap_memfree( val.bv_val ); + } + return 0; +} + +void +syn_delete( Syntax *syn ) +{ + LDAP_STAILQ_REMOVE(&syn_list, syn, Syntax, ssyn_next); +} + +int +syn_start( Syntax **syn ) +{ + assert( syn != NULL ); + + *syn = LDAP_STAILQ_FIRST(&syn_list); + + return (*syn != NULL); +} + +int +syn_next( Syntax **syn ) +{ + assert( syn != NULL ); + +#if 0 /* pedantic check: don't use this */ + { + Syntax *tmp = NULL; + + LDAP_STAILQ_FOREACH(tmp,&syn_list,ssyn_next) { + if ( tmp == *syn ) { + break; + } + } + + assert( tmp != NULL ); + } +#endif + + *syn = LDAP_STAILQ_NEXT(*syn,ssyn_next); + + return (*syn != NULL); +} + +void +syn_unparse( BerVarray *res, Syntax *start, Syntax *end, int sys ) +{ + Syntax *syn; + int i, num; + struct berval bv, *bva = NULL, idx; + char ibuf[32]; + + if ( !start ) + start = LDAP_STAILQ_FIRST( &syn_list ); + + /* count the result size */ + i = 0; + for ( syn = start; syn; syn = LDAP_STAILQ_NEXT( syn, ssyn_next ) ) { + if ( sys && !( syn->ssyn_flags & SLAP_SYNTAX_HARDCODE ) ) break; + i++; + if ( syn == end ) break; + } + if ( !i ) return; + + num = i; + bva = ch_malloc( (num+1) * sizeof(struct berval) ); + BER_BVZERO( bva ); + idx.bv_val = ibuf; + if ( sys ) { + idx.bv_len = 0; + ibuf[0] = '\0'; + } + i = 0; + for ( syn = start; syn; syn = LDAP_STAILQ_NEXT( syn, ssyn_next ) ) { + if ( sys && !( syn->ssyn_flags & SLAP_SYNTAX_HARDCODE ) ) break; + if ( ldap_syntax2bv( &syn->ssyn_syn, &bv ) == NULL ) { + ber_bvarray_free( bva ); + } + if ( !sys ) { + idx.bv_len = sprintf(idx.bv_val, "{%d}", i); + } + bva[i].bv_len = idx.bv_len + bv.bv_len; + bva[i].bv_val = ch_malloc( bva[i].bv_len + 1 ); + strcpy( bva[i].bv_val, ibuf ); + strcpy( bva[i].bv_val + idx.bv_len, bv.bv_val ); + i++; + bva[i].bv_val = NULL; + ldap_memfree( bv.bv_val ); + if ( syn == end ) break; + } + *res = bva; +} + diff --git a/servers/slapd/txn.c b/servers/slapd/txn.c new file mode 100644 index 0000000..0afce26 --- /dev/null +++ b/servers/slapd/txn.c @@ -0,0 +1,217 @@ +/* txn.c - LDAP Transactions */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/unistd.h> + +#include "slap.h" + +#include <lber_pvt.h> +#include <lutil.h> + +#ifdef LDAP_X_TXN +const struct berval slap_EXOP_TXN_START = BER_BVC(LDAP_EXOP_X_TXN_START); +const struct berval slap_EXOP_TXN_END = BER_BVC(LDAP_EXOP_X_TXN_END); + +int txn_start_extop( + Operation *op, SlapReply *rs ) +{ + int rc; + struct berval *bv; + + Statslog( LDAP_DEBUG_STATS, "%s TXN START\n", + op->o_log_prefix, 0, 0, 0, 0 ); + + if( op->ore_reqdata != NULL ) { + rs->sr_text = "no request data expected"; + return LDAP_PROTOCOL_ERROR; + } + + op->o_bd = op->o_conn->c_authz_backend; + if( backend_check_restrictions( op, rs, + (struct berval *)&slap_EXOP_TXN_START ) != LDAP_SUCCESS ) + { + return rs->sr_err; + } + + /* acquire connection lock */ + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + + if( op->o_conn->c_txn != CONN_TXN_INACTIVE ) { + rs->sr_text = "Too many transactions"; + rc = LDAP_BUSY; + goto done; + } + + assert( op->o_conn->c_txn_backend == NULL ); + op->o_conn->c_txn = CONN_TXN_SPECIFY; + + bv = (struct berval *) ch_malloc( sizeof (struct berval) ); + bv->bv_len = 0; + bv->bv_val = NULL; + + rs->sr_rspdata = bv; + rc = LDAP_SUCCESS; + +done: + /* release connection lock */ + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + return rc; +} + +int txn_spec_ctrl( + Operation *op, SlapReply *rs, LDAPControl *ctrl ) +{ + if ( !ctrl->ldctl_iscritical ) { + rs->sr_text = "txnSpec control must be marked critical"; + return LDAP_PROTOCOL_ERROR; + } + if( op->o_txnSpec ) { + rs->sr_text = "txnSpec control provided multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( ctrl->ldctl_value.bv_val == NULL ) { + rs->sr_text = "no transaction identifier provided"; + return LDAP_PROTOCOL_ERROR; + } + if ( ctrl->ldctl_value.bv_len != 0 ) { + rs->sr_text = "invalid transaction identifier"; + return LDAP_X_TXN_ID_INVALID; + } + + if ( op->o_preread ) { /* temporary limitation */ + rs->sr_text = "cannot perform pre-read in transaction"; + return LDAP_UNWILLING_TO_PERFORM; + } + if ( op->o_postread ) { /* temporary limitation */ + rs->sr_text = "cannot perform post-read in transaction"; + return LDAP_UNWILLING_TO_PERFORM; + } + + op->o_txnSpec = SLAP_CONTROL_CRITICAL; + return LDAP_SUCCESS; +} + +int txn_end_extop( + Operation *op, SlapReply *rs ) +{ + int rc; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_tag_t tag; + ber_len_t len; + ber_int_t commit=1; + struct berval txnid; + + Statslog( LDAP_DEBUG_STATS, "%s TXN END\n", + op->o_log_prefix, 0, 0, 0, 0 ); + + if( op->ore_reqdata == NULL ) { + rs->sr_text = "request data expected"; + return LDAP_PROTOCOL_ERROR; + } + if( op->ore_reqdata->bv_len == 0 ) { + rs->sr_text = "empty request data"; + return LDAP_PROTOCOL_ERROR; + } + + op->o_bd = op->o_conn->c_authz_backend; + if( backend_check_restrictions( op, rs, + (struct berval *)&slap_EXOP_TXN_END ) != LDAP_SUCCESS ) + { + return rs->sr_err; + } + + ber_init2( ber, op->ore_reqdata, 0 ); + + tag = ber_scanf( ber, "{" /*}*/ ); + if( tag == LBER_ERROR ) { + rs->sr_text = "request data decoding error"; + return LDAP_PROTOCOL_ERROR; + } + + tag = ber_peek_tag( ber, &len ); + if( tag == LBER_BOOLEAN ) { + tag = ber_scanf( ber, "b", &commit ); + if( tag == LBER_ERROR ) { + rs->sr_text = "request data decoding error"; + return LDAP_PROTOCOL_ERROR; + } + } + + tag = ber_scanf( ber, /*{*/ "m}", &txnid ); + if( tag == LBER_ERROR ) { + rs->sr_text = "request data decoding error"; + return LDAP_PROTOCOL_ERROR; + } + + if( txnid.bv_len ) { + rs->sr_text = "invalid transaction identifier"; + return LDAP_X_TXN_ID_INVALID; + } + + /* acquire connection lock */ + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + + if( op->o_conn->c_txn != CONN_TXN_SPECIFY ) { + rs->sr_text = "invalid transaction identifier"; + rc = LDAP_X_TXN_ID_INVALID; + goto done; + } + op->o_conn->c_txn = CONN_TXN_SETTLE; + + if( commit ) { + if ( op->o_abandon ) { + } + + if( LDAP_STAILQ_EMPTY(&op->o_conn->c_txn_ops) ) { + /* no updates to commit */ + rs->sr_text = "no updates to commit"; + rc = LDAP_OPERATIONS_ERROR; + goto settled; + } + + rs->sr_text = "not yet implemented"; + rc = LDAP_UNWILLING_TO_PERFORM; + + } else { + rs->sr_text = "transaction aborted"; + rc = LDAP_SUCCESS;; + } + +drain: + /* drain txn ops list */ + +settled: + assert( LDAP_STAILQ_EMPTY(&op->o_conn->c_txn_ops) ); + assert( op->o_conn->c_txn == CONN_TXN_SETTLE ); + op->o_conn->c_txn = CONN_TXN_INACTIVE; + op->o_conn->c_txn_backend = NULL; + +done: + /* release connection lock */ + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + + return rc; +} + +#endif /* LDAP_X_TXN */ diff --git a/servers/slapd/unbind.c b/servers/slapd/unbind.c new file mode 100644 index 0000000..c30f260 --- /dev/null +++ b/servers/slapd/unbind.c @@ -0,0 +1,62 @@ +/* unbind.c - decode an ldap unbind operation and pass it to a backend db */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + * + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> + +#include "slap.h" + +int +do_unbind( Operation *op, SlapReply *rs ) +{ + Debug( LDAP_DEBUG_TRACE, "%s do_unbind\n", + op->o_log_prefix, 0, 0 ); + + /* + * Parse the unbind request. It looks like this: + * + * UnBindRequest ::= NULL + */ + + Statslog( LDAP_DEBUG_STATS, "%s UNBIND\n", op->o_log_prefix, + 0, 0, 0, 0 ); + + if ( frontendDB->be_unbind ) { + op->o_bd = frontendDB; + (void)frontendDB->be_unbind( op, rs ); + op->o_bd = NULL; + } + + /* pass the unbind to all backends */ + (void)backend_unbind( op, rs ); + + return 0; +} + diff --git a/servers/slapd/user.c b/servers/slapd/user.c new file mode 100644 index 0000000..54a9da2 --- /dev/null +++ b/servers/slapd/user.c @@ -0,0 +1,176 @@ +/* user.c - set user id, group id and group access list */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * Portions Copyright 1999 PM Lashley. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#if defined(HAVE_SETUID) && defined(HAVE_SETGID) + +#include <stdio.h> + +#include <ac/stdlib.h> + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#ifdef HAVE_GRP_H +#include <grp.h> +#endif + +#include <ac/ctype.h> +#include <ac/unistd.h> + +#include "slap.h" +#include "lutil.h" + +/* + * Set real and effective user id and group id, and group access list + * The user and group arguments are freed. + */ + +void +slap_init_user( char *user, char *group ) +{ + uid_t uid = 0; + gid_t gid = 0; + int got_uid = 0, got_gid = 0; + + if ( user ) { + struct passwd *pwd; + if ( isdigit( (unsigned char) *user ) ) { + unsigned u; + + got_uid = 1; + if ( lutil_atou( &u, user ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "Unble to parse user %s\n", + user, 0, 0 ); + + exit( EXIT_FAILURE ); + } + uid = (uid_t)u; +#ifdef HAVE_GETPWUID + pwd = getpwuid( uid ); + goto did_getpw; +#else + free( user ); + user = NULL; +#endif + } else { + pwd = getpwnam( user ); + did_getpw: + if ( pwd == NULL ) { + Debug( LDAP_DEBUG_ANY, "No passwd entry for user %s\n", + user, 0, 0 ); + + exit( EXIT_FAILURE ); + } + if ( got_uid ) { + free( user ); + user = (pwd != NULL ? ch_strdup( pwd->pw_name ) : NULL); + } else { + got_uid = 1; + uid = pwd->pw_uid; + } + got_gid = 1; + gid = pwd->pw_gid; +#ifdef HAVE_ENDPWENT + endpwent(); +#endif + } + } + + if ( group ) { + struct group *grp; + if ( isdigit( (unsigned char) *group )) { + unsigned g; + + if ( lutil_atou( &g, group ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "Unble to parse group %s\n", + group, 0, 0 ); + + exit( EXIT_FAILURE ); + } + gid = (uid_t)g; +#ifdef HAVE_GETGRGID + grp = getgrgid( gid ); + goto did_group; +#endif + } else { + grp = getgrnam( group ); + if ( grp != NULL ) + gid = grp->gr_gid; + did_group: + if ( grp == NULL ) { + Debug( LDAP_DEBUG_ANY, "No group entry for group %s\n", + group, 0, 0 ); + + exit( EXIT_FAILURE ); + } + } + free( group ); + got_gid = 1; + } + + if ( user ) { + if ( getuid() == 0 && initgroups( user, gid ) != 0 ) { + Debug( LDAP_DEBUG_ANY, + "Could not set the group access (gid) list\n", 0, 0, 0 ); + + exit( EXIT_FAILURE ); + } + free( user ); + } + +#ifdef HAVE_ENDGRENT + endgrent(); +#endif + + if ( got_gid ) { + if ( setgid( gid ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "Could not set real group id to %d\n", + (int) gid, 0, 0 ); + + exit( EXIT_FAILURE ); + } +#ifdef HAVE_SETEGID + if ( setegid( gid ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "Could not set effective group id to %d\n", + (int) gid, 0, 0 ); + + exit( EXIT_FAILURE ); + } +#endif + } + + if ( got_uid ) { + if ( setuid( uid ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "Could not set real user id to %d\n", + (int) uid, 0, 0 ); + + exit( EXIT_FAILURE ); + } +#ifdef HAVE_SETEUID + if ( seteuid( uid ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "Could not set effective user id to %d\n", + (int) uid, 0, 0 ); + + exit( EXIT_FAILURE ); + } +#endif + } +} + +#endif /* HAVE_PWD_H && HAVE_GRP_H */ diff --git a/servers/slapd/value.c b/servers/slapd/value.c new file mode 100644 index 0000000..ecc645b --- /dev/null +++ b/servers/slapd/value.c @@ -0,0 +1,798 @@ +/* value.c - routines for dealing with values */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* + * Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include <sys/stat.h> + +#include "slap.h" + +int +value_add( + BerVarray *vals, + BerVarray addvals ) +{ + int n, nn = 0; + BerVarray v2; + + if ( addvals != NULL ) { + for ( ; !BER_BVISNULL( &addvals[nn] ); nn++ ) + ; /* NULL */ + } + + if ( *vals == NULL ) { + *vals = (BerVarray) SLAP_MALLOC( (nn + 1) + * sizeof(struct berval) ); + if( *vals == NULL ) { + Debug(LDAP_DEBUG_TRACE, + "value_add: SLAP_MALLOC failed.\n", 0, 0, 0 ); + return LBER_ERROR_MEMORY; + } + n = 0; + + } else { + for ( n = 0; !BER_BVISNULL( &(*vals)[n] ); n++ ) { + ; /* Empty */ + } + *vals = (BerVarray) SLAP_REALLOC( (char *) *vals, + (n + nn + 1) * sizeof(struct berval) ); + if( *vals == NULL ) { + Debug(LDAP_DEBUG_TRACE, + "value_add: SLAP_MALLOC failed.\n", 0, 0, 0 ); + return LBER_ERROR_MEMORY; + } + } + + v2 = &(*vals)[n]; + for ( n = 0 ; n < nn; v2++, addvals++ ) { + ber_dupbv( v2, addvals ); + if ( BER_BVISNULL( v2 ) ) break; + } + BER_BVZERO( v2 ); + + return LDAP_SUCCESS; +} + +int +value_add_one( + BerVarray *vals, + struct berval *addval ) +{ + int n; + BerVarray v2; + + if ( *vals == NULL ) { + *vals = (BerVarray) SLAP_MALLOC( 2 * sizeof(struct berval) ); + if( *vals == NULL ) { + Debug(LDAP_DEBUG_TRACE, + "value_add_one: SLAP_MALLOC failed.\n", 0, 0, 0 ); + return LBER_ERROR_MEMORY; + } + n = 0; + + } else { + for ( n = 0; !BER_BVISNULL( &(*vals)[n] ); n++ ) { + ; /* Empty */ + } + *vals = (BerVarray) SLAP_REALLOC( (char *) *vals, + (n + 2) * sizeof(struct berval) ); + if( *vals == NULL ) { + Debug(LDAP_DEBUG_TRACE, + "value_add_one: SLAP_MALLOC failed.\n", 0, 0, 0 ); + return LBER_ERROR_MEMORY; + } + } + + v2 = &(*vals)[n]; + ber_dupbv(v2, addval); + + v2++; + BER_BVZERO( v2 ); + + return LDAP_SUCCESS; +} + +int asserted_value_validate_normalize( + AttributeDescription *ad, + MatchingRule *mr, + unsigned usage, + struct berval *in, + struct berval *out, + const char ** text, + void *ctx ) +{ + int rc; + struct berval pval; + pval.bv_val = NULL; + + /* we expect the value to be in the assertion syntax */ + assert( !SLAP_MR_IS_VALUE_OF_ATTRIBUTE_SYNTAX(usage) ); + + if( mr == NULL ) { + *text = "inappropriate matching request"; + return LDAP_INAPPROPRIATE_MATCHING; + } + + if( !mr->smr_match ) { + *text = "requested matching rule not supported"; + return LDAP_INAPPROPRIATE_MATCHING; + } + + if( mr->smr_syntax->ssyn_pretty ) { + rc = (mr->smr_syntax->ssyn_pretty)( mr->smr_syntax, in, &pval, ctx ); + in = &pval; + + } else if ( mr->smr_syntax->ssyn_validate ) { + rc = (mr->smr_syntax->ssyn_validate)( mr->smr_syntax, in ); + + } else { + *text = "inappropriate matching request"; + return LDAP_INAPPROPRIATE_MATCHING; + } + + if( rc != LDAP_SUCCESS ) { + *text = "value does not conform to assertion syntax"; + return LDAP_INVALID_SYNTAX; + } + + if( mr->smr_normalize ) { + rc = (mr->smr_normalize)( + usage|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX, + ad ? ad->ad_type->sat_syntax : NULL, + mr, in, out, ctx ); + + if( pval.bv_val ) ber_memfree_x( pval.bv_val, ctx ); + + if( rc != LDAP_SUCCESS ) { + *text = "unable to normalize value for matching"; + return LDAP_INVALID_SYNTAX; + } + + } else if ( pval.bv_val != NULL ) { + *out = pval; + + } else { + ber_dupbv_x( out, in, ctx ); + } + + return LDAP_SUCCESS; +} + +int +value_match( + int *match, + AttributeDescription *ad, + MatchingRule *mr, + unsigned flags, + struct berval *v1, /* stored value */ + void *v2, /* assertion */ + const char ** text ) +{ + int rc; + + assert( mr != NULL ); + + if( !mr->smr_match ) { + return LDAP_INAPPROPRIATE_MATCHING; + } + + rc = (mr->smr_match)( match, flags, + ad->ad_type->sat_syntax, mr, v1, v2 ); + + return rc; +} + +int value_find_ex( + AttributeDescription *ad, + unsigned flags, + BerVarray vals, + struct berval *val, + void *ctx ) +{ + int i; + int rc; + struct berval nval = BER_BVNULL; + MatchingRule *mr = ad->ad_type->sat_equality; + + if( mr == NULL || !mr->smr_match ) { + return LDAP_INAPPROPRIATE_MATCHING; + } + + assert( SLAP_IS_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH( flags ) != 0 ); + + if( !SLAP_IS_MR_ASSERTED_VALUE_NORMALIZED_MATCH( flags ) && + mr->smr_normalize ) + { + rc = (mr->smr_normalize)( + flags & (SLAP_MR_TYPE_MASK|SLAP_MR_SUBTYPE_MASK|SLAP_MR_VALUE_OF_SYNTAX), + ad->ad_type->sat_syntax, + mr, val, &nval, ctx ); + + if( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + } + + for ( i = 0; vals[i].bv_val != NULL; i++ ) { + int match; + const char *text; + + rc = value_match( &match, ad, mr, flags, + &vals[i], nval.bv_val == NULL ? val : &nval, &text ); + + if( rc == LDAP_SUCCESS && match == 0 ) { + slap_sl_free( nval.bv_val, ctx ); + return rc; + } + } + + slap_sl_free( nval.bv_val, ctx ); + return LDAP_NO_SUCH_ATTRIBUTE; +} + +/* assign new indexes to an attribute's ordered values */ +void +ordered_value_renumber( Attribute *a ) +{ + char *ptr, ibuf[64]; /* many digits */ + struct berval ibv, tmp, vtmp; + unsigned i; + + ibv.bv_val = ibuf; + + for (i=0; i<a->a_numvals; i++) { + ibv.bv_len = sprintf(ibv.bv_val, "{%u}", i); + vtmp = a->a_vals[i]; + if ( vtmp.bv_val[0] == '{' ) { + ptr = ber_bvchr(&vtmp, '}'); + assert( ptr != NULL ); + ++ptr; + vtmp.bv_len -= ptr - vtmp.bv_val; + vtmp.bv_val = ptr; + } + tmp.bv_len = ibv.bv_len + vtmp.bv_len; + tmp.bv_val = ch_malloc( tmp.bv_len + 1 ); + strcpy( tmp.bv_val, ibv.bv_val ); + AC_MEMCPY( tmp.bv_val + ibv.bv_len, vtmp.bv_val, vtmp.bv_len ); + tmp.bv_val[tmp.bv_len] = '\0'; + ch_free( a->a_vals[i].bv_val ); + a->a_vals[i] = tmp; + + if ( a->a_nvals && a->a_nvals != a->a_vals ) { + vtmp = a->a_nvals[i]; + if ( vtmp.bv_val[0] == '{' ) { + ptr = ber_bvchr(&vtmp, '}'); + assert( ptr != NULL ); + ++ptr; + vtmp.bv_len -= ptr - vtmp.bv_val; + vtmp.bv_val = ptr; + } + tmp.bv_len = ibv.bv_len + vtmp.bv_len; + tmp.bv_val = ch_malloc( tmp.bv_len + 1 ); + strcpy( tmp.bv_val, ibv.bv_val ); + AC_MEMCPY( tmp.bv_val + ibv.bv_len, vtmp.bv_val, vtmp.bv_len ); + tmp.bv_val[tmp.bv_len] = '\0'; + ch_free( a->a_nvals[i].bv_val ); + a->a_nvals[i] = tmp; + } + } +} + +/* Sort the values in an X-ORDERED VALUES attribute. + * If the values have no index, index them in their given order. + * If the values have indexes, sort them. + * If some are indexed and some are not, return Error. + */ +int +ordered_value_sort( Attribute *a, int do_renumber ) +{ + int i, vals; + int index = 0, noindex = 0, renumber = 0, gotnvals = 0; + struct berval tmp; + + if ( a->a_nvals && a->a_nvals != a->a_vals ) + gotnvals = 1; + + /* count attrs, look for index */ + for (i=0; a->a_vals[i].bv_val; i++) { + if ( a->a_vals[i].bv_val[0] == '{' ) { + char *ptr; + index = 1; + ptr = ber_bvchr( &a->a_vals[i], '}' ); + if ( !ptr ) + return LDAP_INVALID_SYNTAX; + if ( noindex ) + return LDAP_INVALID_SYNTAX; + } else { + noindex = 1; + if ( index ) + return LDAP_INVALID_SYNTAX; + } + } + vals = i; + + /* If values have indexes, sort the values */ + if ( index ) { + int *indexes, j, idx; + struct berval ntmp; + +#if 0 + /* Strip index from normalized values */ + if ( !a->a_nvals || a->a_vals == a->a_nvals ) { + a->a_nvals = ch_malloc( (vals+1)*sizeof(struct berval)); + BER_BVZERO(a->a_nvals+vals); + for ( i=0; i<vals; i++ ) { + char *ptr = ber_bvchr(&a->a_vals[i], '}') + 1; + a->a_nvals[i].bv_len = a->a_vals[i].bv_len - + (ptr - a->a_vals[i].bv_val); + a->a_nvals[i].bv_val = ch_malloc( a->a_nvals[i].bv_len + 1); + strcpy(a->a_nvals[i].bv_val, ptr ); + } + } else { + for ( i=0; i<vals; i++ ) { + char *ptr = ber_bvchr(&a->a_nvals[i], '}') + 1; + a->a_nvals[i].bv_len -= ptr - a->a_nvals[i].bv_val; + strcpy(a->a_nvals[i].bv_val, ptr); + } + } +#endif + + indexes = ch_malloc( vals * sizeof(int) ); + for ( i=0; i<vals; i++) { + char *ptr; + indexes[i] = strtol(a->a_vals[i].bv_val+1, &ptr, 0); + if ( *ptr != '}' ) { + ch_free( indexes ); + return LDAP_INVALID_SYNTAX; + } + } + + /* Insertion sort */ + for ( i=1; i<vals; i++ ) { + idx = indexes[i]; + tmp = a->a_vals[i]; + if ( gotnvals ) ntmp = a->a_nvals[i]; + j = i; + while ((j > 0) && (indexes[j-1] > idx)) { + indexes[j] = indexes[j-1]; + a->a_vals[j] = a->a_vals[j-1]; + if ( gotnvals ) a->a_nvals[j] = a->a_nvals[j-1]; + j--; + } + indexes[j] = idx; + a->a_vals[j] = tmp; + if ( gotnvals ) a->a_nvals[j] = ntmp; + } + + /* If range is not contiguous, must renumber */ + if ( indexes[0] != 0 || indexes[vals-1] != vals-1 ) { + renumber = 1; + } + ch_free( indexes ); + } else { + renumber = 1; + } + + if ( do_renumber && renumber ) + ordered_value_renumber( a ); + + return 0; +} + +/* + * wrapper for validate function + * uses the validate function of the syntax after removing + * the index, if allowed and present + */ +int +ordered_value_validate( + AttributeDescription *ad, + struct berval *in, + int mop ) +{ + struct berval bv = *in; + + assert( ad->ad_type->sat_syntax != NULL ); + assert( ad->ad_type->sat_syntax->ssyn_validate != NULL ); + + if ( ad->ad_type->sat_flags & SLAP_AT_ORDERED ) { + + /* Skip past the assertion index */ + if ( bv.bv_val[0] == '{' ) { + char *ptr; + + ptr = ber_bvchr( &bv, '}' ); + if ( ptr != NULL ) { + struct berval ns; + + ns.bv_val = bv.bv_val + 1; + ns.bv_len = ptr - ns.bv_val; + + if ( numericStringValidate( NULL, &ns ) == LDAP_SUCCESS ) { + ptr++; + bv.bv_len -= ptr - bv.bv_val; + bv.bv_val = ptr; + in = &bv; + /* If deleting by index, just succeed */ + if ( mop == LDAP_MOD_DELETE && BER_BVISEMPTY( &bv ) ) { + return LDAP_SUCCESS; + } + } + } + } + } + + return ad->ad_type->sat_syntax->ssyn_validate( ad->ad_type->sat_syntax, in ); +} + +/* + * wrapper for pretty function + * uses the pretty function of the syntax after removing + * the index, if allowed and present; in case, it's prepended + * to the pretty value + */ +int +ordered_value_pretty( + AttributeDescription *ad, + struct berval *val, + struct berval *out, + void *ctx ) +{ + struct berval bv, + idx = BER_BVNULL; + int rc; + + assert( ad->ad_type->sat_syntax != NULL ); + assert( ad->ad_type->sat_syntax->ssyn_pretty != NULL ); + assert( val != NULL ); + assert( out != NULL ); + + bv = *val; + + if ( ad->ad_type->sat_flags & SLAP_AT_ORDERED ) { + + /* Skip past the assertion index */ + if ( bv.bv_val[0] == '{' ) { + char *ptr; + + ptr = ber_bvchr( &bv, '}' ); + if ( ptr != NULL ) { + struct berval ns; + + ns.bv_val = bv.bv_val + 1; + ns.bv_len = ptr - ns.bv_val; + + if ( numericStringValidate( NULL, &ns ) == LDAP_SUCCESS ) { + ptr++; + + idx = bv; + idx.bv_len = ptr - bv.bv_val; + + bv.bv_len -= idx.bv_len; + bv.bv_val = ptr; + + val = &bv; + } + } + } + } + + rc = ad->ad_type->sat_syntax->ssyn_pretty( ad->ad_type->sat_syntax, val, out, ctx ); + + if ( rc == LDAP_SUCCESS && !BER_BVISNULL( &idx ) ) { + bv = *out; + + out->bv_len = idx.bv_len + bv.bv_len; + out->bv_val = ber_memalloc_x( out->bv_len + 1, ctx ); + + AC_MEMCPY( out->bv_val, idx.bv_val, idx.bv_len ); + AC_MEMCPY( &out->bv_val[ idx.bv_len ], bv.bv_val, bv.bv_len + 1 ); + + ber_memfree_x( bv.bv_val, ctx ); + } + + return rc; +} + +/* + * wrapper for normalize function + * uses the normalize function of the attribute description equality rule + * after removing the index, if allowed and present; in case, it's + * prepended to the value + */ +int +ordered_value_normalize( + slap_mask_t usage, + AttributeDescription *ad, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + struct berval bv, + idx = BER_BVNULL; + int rc; + + assert( ad->ad_type->sat_equality != NULL ); + assert( ad->ad_type->sat_equality->smr_normalize != NULL ); + assert( val != NULL ); + assert( normalized != NULL ); + + bv = *val; + + if ( ad->ad_type->sat_flags & SLAP_AT_ORDERED ) { + + /* Skip past the assertion index */ + if ( bv.bv_val[ 0 ] == '{' ) { + char *ptr; + + ptr = ber_bvchr( &bv, '}' ); + if ( ptr != NULL ) { + struct berval ns; + + ns.bv_val = bv.bv_val + 1; + ns.bv_len = ptr - ns.bv_val; + + if ( numericStringValidate( NULL, &ns ) == LDAP_SUCCESS ) { + ptr++; + + idx = bv; + idx.bv_len = ptr - bv.bv_val; + + bv.bv_len -= idx.bv_len; + bv.bv_val = ptr; + + /* validator will already prevent this for Adds */ + if ( BER_BVISEMPTY( &bv )) { + ber_dupbv_x( normalized, &idx, ctx ); + return LDAP_SUCCESS; + } + val = &bv; + } + } + } + } + + rc = ad->ad_type->sat_equality->smr_normalize( usage, + ad->ad_type->sat_syntax, mr, val, normalized, ctx ); + + if ( rc == LDAP_SUCCESS && !BER_BVISNULL( &idx ) ) { + bv = *normalized; + + normalized->bv_len = idx.bv_len + bv.bv_len; + normalized->bv_val = ber_memalloc_x( normalized->bv_len + 1, ctx ); + + AC_MEMCPY( normalized->bv_val, idx.bv_val, idx.bv_len ); + AC_MEMCPY( &normalized->bv_val[ idx.bv_len ], bv.bv_val, bv.bv_len + 1 ); + + ber_memfree_x( bv.bv_val, ctx ); + } + + return rc; +} + +/* A wrapper for value match, handles Equality matches for attributes + * with ordered values. + */ +int +ordered_value_match( + int *match, + AttributeDescription *ad, + MatchingRule *mr, + unsigned flags, + struct berval *v1, /* stored value */ + struct berval *v2, /* assertion */ + const char ** text ) +{ + struct berval bv1, bv2; + + /* X-ORDERED VALUES equality matching: + * If (SLAP_MR_IS_VALUE_OF_ATTRIBUTE_SYNTAX) that means we are + * comparing two attribute values. In this case, we want to ignore + * the ordering index of both values, we just want to know if their + * main values are equal. + * + * If (SLAP_MR_IS_VALUE_OF_ASSERTION_SYNTAX) then we are comparing + * an assertion against an attribute value. + * If the assertion has no index, the index of the value is ignored. + * If the assertion has only an index, the remainder of the value is + * ignored. + * If the assertion has index and value, both are compared. + */ + if ( ad->ad_type->sat_flags & SLAP_AT_ORDERED ) { + char *ptr; + struct berval ns1 = BER_BVNULL, ns2 = BER_BVNULL; + + bv1 = *v1; + bv2 = *v2; + + /* Skip past the assertion index */ + if ( bv2.bv_val[0] == '{' ) { + ptr = ber_bvchr( &bv2, '}' ); + if ( ptr != NULL ) { + ns2.bv_val = bv2.bv_val + 1; + ns2.bv_len = ptr - ns2.bv_val; + + if ( numericStringValidate( NULL, &ns2 ) == LDAP_SUCCESS ) { + ptr++; + bv2.bv_len -= ptr - bv2.bv_val; + bv2.bv_val = ptr; + v2 = &bv2; + } + } + } + + /* Skip past the attribute index */ + if ( bv1.bv_val[0] == '{' ) { + ptr = ber_bvchr( &bv1, '}' ); + if ( ptr != NULL ) { + ns1.bv_val = bv1.bv_val + 1; + ns1.bv_len = ptr - ns1.bv_val; + + if ( numericStringValidate( NULL, &ns1 ) == LDAP_SUCCESS ) { + ptr++; + bv1.bv_len -= ptr - bv1.bv_val; + bv1.bv_val = ptr; + v1 = &bv1; + } + } + } + + if ( SLAP_MR_IS_VALUE_OF_ASSERTION_SYNTAX( flags )) { + if ( !BER_BVISNULL( &ns2 ) && !BER_BVISNULL( &ns1 ) ) { + /* compare index values first */ + (void)octetStringOrderingMatch( match, 0, NULL, NULL, &ns1, &ns2 ); + + /* If not equal, or we're only comparing the index, + * return result now. + */ + if ( *match != 0 || BER_BVISEMPTY( &bv2 ) ) { + return LDAP_SUCCESS; + } + } + } + + } + + if ( !mr || !mr->smr_match ) { + *match = ber_bvcmp( v1, v2 ); + return LDAP_SUCCESS; + } + + return value_match( match, ad, mr, flags, v1, v2, text ); +} + +int +ordered_value_add( + Entry *e, + AttributeDescription *ad, + Attribute *a, + BerVarray vals, + BerVarray nvals +) +{ + int i, j, k, anum, vnum; + BerVarray new, nnew = NULL; + + /* count new vals */ + for (i=0; !BER_BVISNULL( vals+i ); i++) ; + vnum = i; + + if ( a ) { + ordered_value_sort( a, 0 ); + } else { + Attribute **ap; + for ( ap=&e->e_attrs; *ap; ap = &(*ap)->a_next ) ; + a = attr_alloc( ad ); + *ap = a; + } + anum = a->a_numvals; + + new = ch_malloc( (anum+vnum+1) * sizeof(struct berval)); + + /* sanity check: if normalized modifications come in, either + * no values are present or normalized existing values differ + * from non-normalized; if no normalized modifications come in, + * either no values are present or normalized existing values + * don't differ from non-normalized */ + if ( nvals != NULL ) { + assert( nvals != vals ); + assert( a->a_nvals == NULL || a->a_nvals != a->a_vals ); + + } else { + assert( a->a_nvals == NULL || a->a_nvals == a->a_vals ); + } + + if ( ( a->a_nvals && a->a_nvals != a->a_vals ) || nvals != NULL ) { + nnew = ch_malloc( (anum+vnum+1) * sizeof(struct berval)); + /* Shouldn't happen... */ + if ( !nvals ) nvals = vals; + } + if ( anum ) { + AC_MEMCPY( new, a->a_vals, anum * sizeof(struct berval)); + if ( nnew && a->a_nvals ) + AC_MEMCPY( nnew, a->a_nvals, anum * sizeof(struct berval)); + } + + for (i=0; i<vnum; i++) { + char *next; + + k = -1; + if ( vals[i].bv_val[0] == '{' ) { + /* FIXME: strtol() could go past end... */ + k = strtol( vals[i].bv_val + 1, &next, 0 ); + if ( next == vals[i].bv_val + 1 || + next[ 0 ] != '}' || + (ber_len_t) (next - vals[i].bv_val) > vals[i].bv_len ) + { + ch_free( nnew ); + ch_free( new ); + return -1; + } + if ( k > anum ) k = -1; + } + /* No index, or index is greater than current number of + * values, just tack onto the end + */ + if ( k < 0 ) { + ber_dupbv( new+anum, vals+i ); + if ( nnew ) ber_dupbv( nnew+anum, nvals+i ); + + /* Indexed, push everything else down one and insert */ + } else { + for (j=anum; j>k; j--) { + new[j] = new[j-1]; + if ( nnew ) nnew[j] = nnew[j-1]; + } + ber_dupbv( new+k, vals+i ); + if ( nnew ) ber_dupbv( nnew+k, nvals+i ); + } + anum++; + } + BER_BVZERO( new+anum ); + ch_free( a->a_vals ); + a->a_vals = new; + if ( nnew ) { + BER_BVZERO( nnew+anum ); + ch_free( a->a_nvals ); + a->a_nvals = nnew; + } else { + a->a_nvals = a->a_vals; + } + + a->a_numvals = anum; + ordered_value_renumber( a ); + + return 0; +} diff --git a/servers/slapd/zn_malloc.c b/servers/slapd/zn_malloc.c new file mode 100644 index 0000000..4bdecfc --- /dev/null +++ b/servers/slapd/zn_malloc.c @@ -0,0 +1,972 @@ +/* zn_malloc.c - zone-based malloc routines */ +/* $OpenLDAP$*/ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright 2004 IBM Corporation + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + */ +/* ACKNOWLEDGEMENTS + * This work originally developed by Jong-Hyuk Choi for inclusion in + * OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <sys/types.h> +#include <fcntl.h> + +#include "slap.h" + +#ifdef SLAP_ZONE_ALLOC + +#include <sys/mman.h> + +static int slap_zone_cmp(const void *v1, const void *v2); +void * slap_replenish_zopool(void *ctx); + +static void +slap_zo_release(void *data) +{ + struct zone_object *zo = (struct zone_object *)data; + ch_free( zo ); +} + +void +slap_zn_mem_destroy( + void *ctx +) +{ + struct zone_heap *zh = ctx; + int pad = 2*sizeof(int)-1, pad_shift; + int order_start = -1, i, j; + struct zone_object *zo; + + pad_shift = pad - 1; + do { + order_start++; + } while (pad_shift >>= 1); + + ldap_pvt_thread_mutex_lock( &zh->zh_mutex ); + for (i = 0; i < zh->zh_zoneorder - order_start + 1; i++) { + zo = LDAP_LIST_FIRST(&zh->zh_free[i]); + while (zo) { + struct zone_object *zo_tmp = zo; + zo = LDAP_LIST_NEXT(zo, zo_link); + LDAP_LIST_REMOVE(zo_tmp, zo_link); + LDAP_LIST_INSERT_HEAD(&zh->zh_zopool, zo_tmp, zo_link); + } + } + ch_free(zh->zh_free); + + for (i = 0; i < zh->zh_numzones; i++) { + for (j = 0; j < zh->zh_zoneorder - order_start + 1; j++) { + ch_free(zh->zh_maps[i][j]); + } + ch_free(zh->zh_maps[i]); + munmap(zh->zh_zones[i], zh->zh_zonesize); + ldap_pvt_thread_rdwr_destroy(&zh->zh_znlock[i]); + } + ch_free(zh->zh_maps); + ch_free(zh->zh_zones); + ch_free(zh->zh_seqno); + ch_free(zh->zh_znlock); + + avl_free(zh->zh_zonetree, slap_zo_release); + + zo = LDAP_LIST_FIRST(&zh->zh_zopool); + while (zo) { + struct zone_object *zo_tmp = zo; + zo = LDAP_LIST_NEXT(zo, zo_link); + if (!zo_tmp->zo_blockhead) { + LDAP_LIST_REMOVE(zo_tmp, zo_link); + } + } + zo = LDAP_LIST_FIRST(&zh->zh_zopool); + while (zo) { + struct zone_object *zo_tmp = zo; + zo = LDAP_LIST_NEXT(zo, zo_link); + ch_free(zo_tmp); + } + ldap_pvt_thread_mutex_unlock(&zh->zh_mutex); + ldap_pvt_thread_rdwr_destroy(&zh->zh_lock); + ldap_pvt_thread_mutex_destroy(&zh->zh_mutex); + ch_free(zh); +} + +void * +slap_zn_mem_create( + ber_len_t initsize, + ber_len_t maxsize, + ber_len_t deltasize, + ber_len_t zonesize +) +{ + struct zone_heap *zh = NULL; + ber_len_t zpad; + int pad = 2*sizeof(int)-1, pad_shift; + int size_shift; + int order = -1, order_start = -1, order_end = -1; + int i, j; + struct zone_object *zo; + + Debug(LDAP_DEBUG_NONE, + "--> slap_zn_mem_create: initsize=%d, maxsize=%d\n", + initsize, maxsize, 0); + Debug(LDAP_DEBUG_NONE, + "++> slap_zn_mem_create: deltasize=%d, zonesize=%d\n", + deltasize, zonesize, 0); + + zh = (struct zone_heap *)ch_calloc(1, sizeof(struct zone_heap)); + + zh->zh_fd = open("/dev/zero", O_RDWR); + + if ( zonesize ) { + zh->zh_zonesize = zonesize; + } else { + zh->zh_zonesize = SLAP_ZONE_SIZE; + } + + zpad = zh->zh_zonesize - 1; + zh->zh_numzones = ((initsize + zpad) & ~zpad) / zh->zh_zonesize; + + if ( maxsize && maxsize >= initsize ) { + zh->zh_maxzones = ((maxsize + zpad) & ~zpad) / zh->zh_zonesize; + } else { + zh->zh_maxzones = ((initsize + zpad) & ~zpad) / zh->zh_zonesize; + } + + if ( deltasize ) { + zh->zh_deltazones = ((deltasize + zpad) & ~zpad) / zh->zh_zonesize; + } else { + zh->zh_deltazones = ((SLAP_ZONE_DELTA+zpad) & ~zpad) / zh->zh_zonesize; + } + + size_shift = zh->zh_zonesize - 1; + do { + order_end++; + } while (size_shift >>= 1); + + pad_shift = pad - 1; + do { + order_start++; + } while (pad_shift >>= 1); + + order = order_end - order_start + 1; + + zh->zh_zones = (void **)ch_malloc(zh->zh_maxzones * sizeof(void*)); + zh->zh_znlock = (ldap_pvt_thread_rdwr_t *)ch_malloc( + zh->zh_maxzones * sizeof(ldap_pvt_thread_rdwr_t *)); + zh->zh_maps = (unsigned char ***)ch_malloc( + zh->zh_maxzones * sizeof(unsigned char**)); + + zh->zh_zoneorder = order_end; + zh->zh_free = (struct zh_freelist *) + ch_malloc(order * sizeof(struct zh_freelist)); + zh->zh_seqno = (unsigned long *)ch_calloc(zh->zh_maxzones, + sizeof(unsigned long)); + for (i = 0; i < order; i++) { + LDAP_LIST_INIT(&zh->zh_free[i]); + } + LDAP_LIST_INIT(&zh->zh_zopool); + + for (i = 0; i < zh->zh_numzones; i++) { + zh->zh_zones[i] = mmap(0, zh->zh_zonesize, PROT_READ | PROT_WRITE, + MAP_PRIVATE, zh->zh_fd, 0); + zh->zh_maps[i] = (unsigned char **) + ch_malloc(order * sizeof(unsigned char *)); + for (j = 0; j < order; j++) { + int shiftamt = order_start + 1 + j; + int nummaps = zh->zh_zonesize >> shiftamt; + assert(nummaps); + nummaps >>= 3; + if (!nummaps) nummaps = 1; + zh->zh_maps[i][j] = (unsigned char *)ch_malloc(nummaps); + memset(zh->zh_maps[i][j], 0, nummaps); + } + + if (LDAP_LIST_EMPTY(&zh->zh_zopool)) { + slap_replenish_zopool(zh); + } + zo = LDAP_LIST_FIRST(&zh->zh_zopool); + LDAP_LIST_REMOVE(zo, zo_link); + zo->zo_ptr = zh->zh_zones[i]; + zo->zo_idx = i; + LDAP_LIST_INSERT_HEAD(&zh->zh_free[order-1], zo, zo_link); + + if (LDAP_LIST_EMPTY(&zh->zh_zopool)) { + slap_replenish_zopool(zh); + } + zo = LDAP_LIST_FIRST(&zh->zh_zopool); + LDAP_LIST_REMOVE(zo, zo_link); + zo->zo_ptr = zh->zh_zones[i]; + zo->zo_siz = zh->zh_zonesize; + zo->zo_idx = i; + avl_insert(&zh->zh_zonetree, zo, slap_zone_cmp, avl_dup_error); + ldap_pvt_thread_rdwr_init(&zh->zh_znlock[i]); + } + + LDAP_STAILQ_INIT(&zh->zh_latency_history_queue); + ldap_pvt_thread_mutex_init(&zh->zh_mutex); + ldap_pvt_thread_rdwr_init(&zh->zh_lock); + + return zh; +} + +void * +slap_zn_malloc( + ber_len_t size, + void *ctx +) +{ + struct zone_heap *zh = ctx; + ber_len_t size_shift; + int pad = 2*sizeof(int)-1, pad_shift; + int order = -1, order_start = -1; + struct zone_object *zo, *zo_new, *zo_left, *zo_right; + ber_len_t *ptr, *new; + int idx; + unsigned long diff; + int i, j, k; + + Debug(LDAP_DEBUG_NONE, + "--> slap_zn_malloc: size=%d\n", size, 0, 0); + + if (!zh) return ber_memalloc_x(size, NULL); + + /* round up to doubleword boundary */ + size += 2*sizeof(ber_len_t) + pad; + size &= ~pad; + + size_shift = size - 1; + do { + order++; + } while (size_shift >>= 1); + + pad_shift = pad - 1; + do { + order_start++; + } while (pad_shift >>= 1); + +retry: + + ldap_pvt_thread_mutex_lock( &zh->zh_mutex ); + for (i = order; i <= zh->zh_zoneorder && + LDAP_LIST_EMPTY(&zh->zh_free[i-order_start]); i++); + + if (i == order) { + zo_new = LDAP_LIST_FIRST(&zh->zh_free[i-order_start]); + LDAP_LIST_REMOVE(zo_new, zo_link); + ptr = zo_new->zo_ptr; + idx = zo_new->zo_idx; + diff = (unsigned long)((char*)ptr - + (char*)zh->zh_zones[idx]) >> (order + 1); + zh->zh_maps[idx][order-order_start][diff>>3] |= (1 << (diff & 0x7)); + *ptr++ = zh->zh_seqno[idx]; + *ptr++ = size - 2*sizeof(ber_len_t); + zo_new->zo_ptr = NULL; + zo_new->zo_idx = -1; + LDAP_LIST_INSERT_HEAD(&zh->zh_zopool, zo_new, zo_link); + ldap_pvt_thread_mutex_unlock( &zh->zh_mutex ); + Debug(LDAP_DEBUG_NONE, "slap_zn_malloc: returning 0x%x, 0x%x\n", + ptr, (int)ptr>>(zh->zh_zoneorder+1), 0); + return((void*)ptr); + } else if (i <= zh->zh_zoneorder) { + for (j = i; j > order; j--) { + zo_left = LDAP_LIST_FIRST(&zh->zh_free[j-order_start]); + LDAP_LIST_REMOVE(zo_left, zo_link); + if (LDAP_LIST_EMPTY(&zh->zh_zopool)) { + slap_replenish_zopool(zh); + } + zo_right = LDAP_LIST_FIRST(&zh->zh_zopool); + LDAP_LIST_REMOVE(zo_right, zo_link); + zo_right->zo_ptr = zo_left->zo_ptr + (1 << j); + zo_right->zo_idx = zo_left->zo_idx; + Debug(LDAP_DEBUG_NONE, + "slap_zn_malloc: split (left=0x%x, right=0x%x)\n", + zo_left->zo_ptr, zo_right->zo_ptr, 0); + if (j == order + 1) { + ptr = zo_left->zo_ptr; + diff = (unsigned long)((char*)ptr - + (char*)zh->zh_zones[zo_left->zo_idx]) >> (order+1); + zh->zh_maps[zo_left->zo_idx][order-order_start][diff>>3] |= + (1 << (diff & 0x7)); + *ptr++ = zh->zh_seqno[zo_left->zo_idx]; + *ptr++ = size - 2*sizeof(ber_len_t); + LDAP_LIST_INSERT_HEAD( + &zh->zh_free[j-1-order_start], zo_right, zo_link); + LDAP_LIST_INSERT_HEAD(&zh->zh_zopool, zo_left, zo_link); + ldap_pvt_thread_mutex_unlock( &zh->zh_mutex ); + Debug(LDAP_DEBUG_NONE, + "slap_zn_malloc: returning 0x%x, 0x%x\n", + ptr, (int)ptr>>(zh->zh_zoneorder+1), 0); + return((void*)ptr); + } else { + LDAP_LIST_INSERT_HEAD( + &zh->zh_free[j-1-order_start], zo_right, zo_link); + LDAP_LIST_INSERT_HEAD( + &zh->zh_free[j-1-order_start], zo_left, zo_link); + } + } + assert(0); + } else { + + if ( zh->zh_maxzones < zh->zh_numzones + zh->zh_deltazones ) { + ldap_pvt_thread_mutex_unlock( &zh->zh_mutex ); + Debug( LDAP_DEBUG_TRACE, + "zn_malloc %lu: ch_malloc\n", + (long)size, 0, 0); + Debug(LDAP_DEBUG_NONE, + "slap_zn_malloc: returning 0x%x, 0x%x\n", + ptr, (int)ptr>>(zh->zh_zoneorder+1), 0); + return (void*)ch_malloc(size); + } + + for (i = zh->zh_numzones; i < zh->zh_numzones+zh->zh_deltazones; i++) { + zh->zh_zones[i] = mmap(0, zh->zh_zonesize, PROT_READ | PROT_WRITE, + MAP_PRIVATE, zh->zh_fd, 0); + zh->zh_maps[i] = (unsigned char **) + ch_malloc((zh->zh_zoneorder - order_start + 1) * + sizeof(unsigned char *)); + for (j = 0; j < zh->zh_zoneorder-order_start+1; j++) { + int shiftamt = order_start + 1 + j; + int nummaps = zh->zh_zonesize >> shiftamt; + assert(nummaps); + nummaps >>= 3; + if (!nummaps) nummaps = 1; + zh->zh_maps[i][j] = (unsigned char *)ch_malloc(nummaps); + memset(zh->zh_maps[i][j], 0, nummaps); + } + + if (LDAP_LIST_EMPTY(&zh->zh_zopool)) { + slap_replenish_zopool(zh); + } + zo = LDAP_LIST_FIRST(&zh->zh_zopool); + LDAP_LIST_REMOVE(zo, zo_link); + zo->zo_ptr = zh->zh_zones[i]; + zo->zo_idx = i; + LDAP_LIST_INSERT_HEAD(&zh-> + zh_free[zh->zh_zoneorder-order_start],zo,zo_link); + + if (LDAP_LIST_EMPTY(&zh->zh_zopool)) { + slap_replenish_zopool(zh); + } + zo = LDAP_LIST_FIRST(&zh->zh_zopool); + LDAP_LIST_REMOVE(zo, zo_link); + zo->zo_ptr = zh->zh_zones[i]; + zo->zo_siz = zh->zh_zonesize; + zo->zo_idx = i; + avl_insert(&zh->zh_zonetree, zo, slap_zone_cmp, avl_dup_error); + ldap_pvt_thread_rdwr_init(&zh->zh_znlock[i]); + } + zh->zh_numzones += zh->zh_deltazones; + ldap_pvt_thread_mutex_unlock( &zh->zh_mutex ); + goto retry; + } +} + +void * +slap_zn_calloc( ber_len_t n, ber_len_t size, void *ctx ) +{ + void *new; + + new = slap_zn_malloc( n*size, ctx ); + if ( new ) { + memset( new, 0, n*size ); + } + return new; +} + +void * +slap_zn_realloc(void *ptr, ber_len_t size, void *ctx) +{ + struct zone_heap *zh = ctx; + int pad = 2*sizeof(int)-1, pad_shift; + int order_start = -1, order = -1; + struct zone_object zoi, *zoo; + ber_len_t *p = (ber_len_t *)ptr, *new; + unsigned long diff; + int i; + void *newptr = NULL; + struct zone_heap *zone = NULL; + + Debug(LDAP_DEBUG_NONE, + "--> slap_zn_realloc: ptr=0x%x, size=%d\n", ptr, size, 0); + + if (ptr == NULL) + return slap_zn_malloc(size, zh); + + zoi.zo_ptr = p; + zoi.zo_idx = -1; + + if (zh) { + ldap_pvt_thread_mutex_lock( &zh->zh_mutex ); + zoo = avl_find(zh->zh_zonetree, &zoi, slap_zone_cmp); + ldap_pvt_thread_mutex_unlock( &zh->zh_mutex ); + } + + /* Not our memory? */ + if (!zoo) { + /* duplicate of realloc behavior, oh well */ + new = ber_memrealloc_x(ptr, size, NULL); + if (new) { + return new; + } + Debug(LDAP_DEBUG_ANY, "ch_realloc of %lu bytes failed\n", + (long) size, 0, 0); + assert(0); + exit( EXIT_FAILURE ); + } + + assert(zoo->zo_idx != -1); + + zone = zh->zh_zones[zoo->zo_idx]; + + if (size == 0) { + slap_zn_free(ptr, zh); + return NULL; + } + + newptr = slap_zn_malloc(size, zh); + if (size < p[-1]) { + AC_MEMCPY(newptr, ptr, size); + } else { + AC_MEMCPY(newptr, ptr, p[-1]); + } + slap_zn_free(ptr, zh); + return newptr; +} + +void +slap_zn_free(void *ptr, void *ctx) +{ + struct zone_heap *zh = ctx; + int size, size_shift, order_size; + int pad = 2*sizeof(int)-1, pad_shift; + ber_len_t *p = (ber_len_t *)ptr, *tmpp; + int order_start = -1, order = -1; + struct zone_object zoi, *zoo, *zo; + unsigned long diff; + int i, k, inserted = 0, idx; + struct zone_heap *zone = NULL; + + zoi.zo_ptr = p; + zoi.zo_idx = -1; + + Debug(LDAP_DEBUG_NONE, "--> slap_zn_free: ptr=0x%x\n", ptr, 0, 0); + + if (zh) { + ldap_pvt_thread_mutex_lock( &zh->zh_mutex ); + zoo = avl_find(zh->zh_zonetree, &zoi, slap_zone_cmp); + ldap_pvt_thread_mutex_unlock( &zh->zh_mutex ); + } + + if (!zoo) { + ber_memfree_x(ptr, NULL); + } else { + idx = zoo->zo_idx; + assert(idx != -1); + zone = zh->zh_zones[idx]; + + size = *(--p); + size_shift = size + 2*sizeof(ber_len_t) - 1; + do { + order++; + } while (size_shift >>= 1); + + pad_shift = pad - 1; + do { + order_start++; + } while (pad_shift >>= 1); + + ldap_pvt_thread_mutex_lock( &zh->zh_mutex ); + for (i = order, tmpp = p; i <= zh->zh_zoneorder; i++) { + order_size = 1 << (i+1); + diff = (unsigned long)((char*)tmpp - (char*)zone) >> (i+1); + zh->zh_maps[idx][i-order_start][diff>>3] &= (~(1 << (diff & 0x7))); + if (diff == ((diff>>1)<<1)) { + if (!(zh->zh_maps[idx][i-order_start][(diff+1)>>3] & + (1<<((diff+1)&0x7)))) { + zo = LDAP_LIST_FIRST(&zh->zh_free[i-order_start]); + while (zo) { + if ((char*)zo->zo_ptr == (char*)tmpp) { + LDAP_LIST_REMOVE( zo, zo_link ); + } else if ((char*)zo->zo_ptr == + (char*)tmpp + order_size) { + LDAP_LIST_REMOVE(zo, zo_link); + break; + } + zo = LDAP_LIST_NEXT(zo, zo_link); + } + if (zo) { + if (i < zh->zh_zoneorder) { + inserted = 1; + zo->zo_ptr = tmpp; + Debug(LDAP_DEBUG_NONE, + "slap_zn_free: merging 0x%x\n", + zo->zo_ptr, 0, 0); + LDAP_LIST_INSERT_HEAD(&zh->zh_free[i-order_start+1], + zo, zo_link); + } + continue; + } else { + if (LDAP_LIST_EMPTY(&zh->zh_zopool)) { + slap_replenish_zopool(zh); + } + zo = LDAP_LIST_FIRST(&zh->zh_zopool); + LDAP_LIST_REMOVE(zo, zo_link); + zo->zo_ptr = tmpp; + zo->zo_idx = idx; + Debug(LDAP_DEBUG_NONE, + "slap_zn_free: merging 0x%x\n", + zo->zo_ptr, 0, 0); + LDAP_LIST_INSERT_HEAD(&zh->zh_free[i-order_start], + zo, zo_link); + break; + + Debug(LDAP_DEBUG_ANY, "slap_zn_free: " + "free object not found while bit is clear.\n", + 0, 0, 0); + assert(zo != NULL); + + } + } else { + if (!inserted) { + if (LDAP_LIST_EMPTY(&zh->zh_zopool)) { + slap_replenish_zopool(zh); + } + zo = LDAP_LIST_FIRST(&zh->zh_zopool); + LDAP_LIST_REMOVE(zo, zo_link); + zo->zo_ptr = tmpp; + zo->zo_idx = idx; + Debug(LDAP_DEBUG_NONE, + "slap_zn_free: merging 0x%x\n", + zo->zo_ptr, 0, 0); + LDAP_LIST_INSERT_HEAD(&zh->zh_free[i-order_start], + zo, zo_link); + } + break; + } + } else { + if (!(zh->zh_maps[idx][i-order_start][(diff-1)>>3] & + (1<<((diff-1)&0x7)))) { + zo = LDAP_LIST_FIRST(&zh->zh_free[i-order_start]); + while (zo) { + if ((char*)zo->zo_ptr == (char*)tmpp) { + LDAP_LIST_REMOVE(zo, zo_link); + } else if ((char*)tmpp == zo->zo_ptr + order_size) { + LDAP_LIST_REMOVE(zo, zo_link); + tmpp = zo->zo_ptr; + break; + } + zo = LDAP_LIST_NEXT(zo, zo_link); + } + if (zo) { + if (i < zh->zh_zoneorder) { + inserted = 1; + Debug(LDAP_DEBUG_NONE, + "slap_zn_free: merging 0x%x\n", + zo->zo_ptr, 0, 0); + LDAP_LIST_INSERT_HEAD(&zh->zh_free[i-order_start+1], + zo, zo_link); + continue; + } + } else { + if (LDAP_LIST_EMPTY(&zh->zh_zopool)) { + slap_replenish_zopool(zh); + } + zo = LDAP_LIST_FIRST(&zh->zh_zopool); + LDAP_LIST_REMOVE(zo, zo_link); + zo->zo_ptr = tmpp; + zo->zo_idx = idx; + Debug(LDAP_DEBUG_NONE, + "slap_zn_free: merging 0x%x\n", + zo->zo_ptr, 0, 0); + LDAP_LIST_INSERT_HEAD(&zh->zh_free[i-order_start], + zo, zo_link); + break; + + Debug(LDAP_DEBUG_ANY, "slap_zn_free: " + "free object not found while bit is clear.\n", + 0, 0, 0 ); + assert(zo != NULL); + + } + } else { + if ( !inserted ) { + if (LDAP_LIST_EMPTY(&zh->zh_zopool)) { + slap_replenish_zopool(zh); + } + zo = LDAP_LIST_FIRST(&zh->zh_zopool); + LDAP_LIST_REMOVE(zo, zo_link); + zo->zo_ptr = tmpp; + zo->zo_idx = idx; + Debug(LDAP_DEBUG_NONE, + "slap_zn_free: merging 0x%x\n", + zo->zo_ptr, 0, 0); + LDAP_LIST_INSERT_HEAD(&zh->zh_free[i-order_start], + zo, zo_link); + } + break; + } + } + } + ldap_pvt_thread_mutex_unlock( &zh->zh_mutex ); + } +} + +static int +slap_zone_cmp(const void *v1, const void *v2) +{ + const struct zone_object *zo1 = v1; + const struct zone_object *zo2 = v2; + char *ptr1; + char *ptr2; + ber_len_t zpad; + + zpad = zo2->zo_siz - 1; + ptr1 = (char*)(((unsigned long)zo1->zo_ptr + zpad) & ~zpad); + ptr2 = (char*)zo2->zo_ptr + ((char*)ptr1 - (char*)zo1->zo_ptr); + ptr2 = (char*)(((unsigned long)ptr2 + zpad) & ~zpad); + return (int)((char*)ptr1 - (char*)ptr2); +} + +void * +slap_replenish_zopool( + void *ctx +) +{ + struct zone_heap* zh = ctx; + struct zone_object *zo_block; + int i; + + zo_block = (struct zone_object *)ch_malloc( + SLAP_ZONE_ZOBLOCK * sizeof(struct zone_object)); + + if ( zo_block == NULL ) { + return NULL; + } + + zo_block[0].zo_blockhead = 1; + LDAP_LIST_INSERT_HEAD(&zh->zh_zopool, &zo_block[0], zo_link); + for (i = 1; i < SLAP_ZONE_ZOBLOCK; i++) { + zo_block[i].zo_blockhead = 0; + LDAP_LIST_INSERT_HEAD(&zh->zh_zopool, &zo_block[i], zo_link ); + } + + return zo_block; +} + +int +slap_zn_invalidate( + void *ctx, + void *ptr +) +{ + struct zone_heap* zh = ctx; + struct zone_object zoi, *zoo; + struct zone_heap *zone = NULL; + int seqno = *((ber_len_t*)ptr - 2); + int idx = -1, rc = 0; + int pad = 2*sizeof(int)-1, pad_shift; + int order_start = -1, i; + struct zone_object *zo; + + pad_shift = pad - 1; + do { + order_start++; + } while (pad_shift >>= 1); + + zoi.zo_ptr = ptr; + zoi.zo_idx = -1; + + ldap_pvt_thread_mutex_lock( &zh->zh_mutex ); + zoo = avl_find(zh->zh_zonetree, &zoi, slap_zone_cmp); + + if (zoo) { + idx = zoo->zo_idx; + assert(idx != -1); + madvise(zh->zh_zones[idx], zh->zh_zonesize, MADV_DONTNEED); + for (i = 0; i < zh->zh_zoneorder - order_start + 1; i++) { + int shiftamt = order_start + 1 + i; + int nummaps = zh->zh_zonesize >> shiftamt; + assert(nummaps); + nummaps >>= 3; + if (!nummaps) nummaps = 1; + memset(zh->zh_maps[idx][i], 0, nummaps); + zo = LDAP_LIST_FIRST(&zh->zh_free[i]); + while (zo) { + struct zone_object *zo_tmp = zo; + zo = LDAP_LIST_NEXT(zo, zo_link); + if (zo_tmp && zo_tmp->zo_idx == idx) { + LDAP_LIST_REMOVE(zo_tmp, zo_link); + LDAP_LIST_INSERT_HEAD(&zh->zh_zopool, zo_tmp, zo_link); + } + } + } + if (LDAP_LIST_EMPTY(&zh->zh_zopool)) { + slap_replenish_zopool(zh); + } + zo = LDAP_LIST_FIRST(&zh->zh_zopool); + LDAP_LIST_REMOVE(zo, zo_link); + zo->zo_ptr = zh->zh_zones[idx]; + zo->zo_idx = idx; + LDAP_LIST_INSERT_HEAD(&zh->zh_free[zh->zh_zoneorder-order_start], + zo, zo_link); + zh->zh_seqno[idx]++; + } else { + Debug(LDAP_DEBUG_NONE, "zone not found for (ctx=0x%x, ptr=0x%x) !\n", + ctx, ptr, 0); + } + + ldap_pvt_thread_mutex_unlock( &zh->zh_mutex ); + Debug(LDAP_DEBUG_NONE, "zone %d invalidate\n", idx, 0, 0); + return rc; +} + +int +slap_zn_validate( + void *ctx, + void *ptr, + int seqno +) +{ + struct zone_heap* zh = ctx; + struct zone_object zoi, *zoo; + struct zone_heap *zone = NULL; + int idx, rc = 0; + + zoi.zo_ptr = ptr; + zoi.zo_idx = -1; + + zoo = avl_find(zh->zh_zonetree, &zoi, slap_zone_cmp); + + if (zoo) { + idx = zoo->zo_idx; + assert(idx != -1); + assert(seqno <= zh->zh_seqno[idx]); + rc = (seqno == zh->zh_seqno[idx]); + } + + return rc; +} + +int slap_zh_rlock( + void *ctx +) +{ + struct zone_heap* zh = ctx; + ldap_pvt_thread_rdwr_rlock(&zh->zh_lock); +} + +int slap_zh_runlock( + void *ctx +) +{ + struct zone_heap* zh = ctx; + ldap_pvt_thread_rdwr_runlock(&zh->zh_lock); +} + +int slap_zh_wlock( + void *ctx +) +{ + struct zone_heap* zh = ctx; + ldap_pvt_thread_rdwr_wlock(&zh->zh_lock); +} + +int slap_zh_wunlock( + void *ctx +) +{ + struct zone_heap* zh = ctx; + ldap_pvt_thread_rdwr_wunlock(&zh->zh_lock); +} + +int slap_zn_rlock( + void *ctx, + void *ptr +) +{ + struct zone_heap* zh = ctx; + struct zone_object zoi, *zoo; + struct zone_heap *zone = NULL; + int idx; + + zoi.zo_ptr = ptr; + zoi.zo_idx = -1; + + ldap_pvt_thread_mutex_lock( &zh->zh_mutex ); + zoo = avl_find(zh->zh_zonetree, &zoi, slap_zone_cmp); + ldap_pvt_thread_mutex_unlock( &zh->zh_mutex ); + + if (zoo) { + idx = zoo->zo_idx; + assert(idx != -1); + ldap_pvt_thread_rdwr_rlock(&zh->zh_znlock[idx]); + } +} + +int slap_zn_runlock( + void *ctx, + void *ptr +) +{ + struct zone_heap* zh = ctx; + struct zone_object zoi, *zoo; + struct zone_heap *zone = NULL; + int idx; + + zoi.zo_ptr = ptr; + zoi.zo_idx = -1; + + ldap_pvt_thread_mutex_lock( &zh->zh_mutex ); + zoo = avl_find(zh->zh_zonetree, &zoi, slap_zone_cmp); + ldap_pvt_thread_mutex_unlock( &zh->zh_mutex ); + + if (zoo) { + idx = zoo->zo_idx; + assert(idx != -1); + ldap_pvt_thread_rdwr_runlock(&zh->zh_znlock[idx]); + } +} + +int slap_zn_wlock( + void *ctx, + void *ptr +) +{ + struct zone_heap* zh = ctx; + struct zone_object zoi, *zoo; + struct zone_heap *zone = NULL; + int idx; + + zoi.zo_ptr = ptr; + zoi.zo_idx = -1; + + ldap_pvt_thread_mutex_lock( &zh->zh_mutex ); + zoo = avl_find(zh->zh_zonetree, &zoi, slap_zone_cmp); + ldap_pvt_thread_mutex_unlock( &zh->zh_mutex ); + + if (zoo) { + idx = zoo->zo_idx; + assert(idx != -1); + ldap_pvt_thread_rdwr_wlock(&zh->zh_znlock[idx]); + } +} + +int slap_zn_wunlock( + void *ctx, + void *ptr +) +{ + struct zone_heap* zh = ctx; + struct zone_object zoi, *zoo; + struct zone_heap *zone = NULL; + int idx; + + zoi.zo_ptr = ptr; + zoi.zo_idx = -1; + + ldap_pvt_thread_mutex_lock( &zh->zh_mutex ); + zoo = avl_find(zh->zh_zonetree, &zoi, slap_zone_cmp); + ldap_pvt_thread_mutex_unlock( &zh->zh_mutex ); + + if (zoo) { + idx = zoo->zo_idx; + assert(idx != -1); + ldap_pvt_thread_rdwr_wunlock(&zh->zh_znlock[idx]); + } +} + +#define T_SEC_IN_USEC 1000000 + +static int +slap_timediff(struct timeval *tv_begin, struct timeval *tv_end) +{ + uint64_t t_begin, t_end, t_diff; + + t_begin = T_SEC_IN_USEC * tv_begin->tv_sec + tv_begin->tv_usec; + t_end = T_SEC_IN_USEC * tv_end->tv_sec + tv_end->tv_usec; + t_diff = t_end - t_begin; + + if ( t_diff < 0 ) + t_diff = 0; + + return (int)t_diff; +} + +void +slap_set_timing(struct timeval *tv_set) +{ + gettimeofday(tv_set, (struct timezone *)NULL); +} + +int +slap_measure_timing(struct timeval *tv_set, struct timeval *tv_measure) +{ + gettimeofday(tv_measure, (struct timezone *)NULL); + return(slap_timediff(tv_set, tv_measure)); +} + +#define EMA_WEIGHT 0.999000 +#define SLAP_ZN_LATENCY_HISTORY_QLEN 500 +int +slap_zn_latency_history(void* ctx, int ea_latency) +{ +/* TODO: monitor /proc/stat (swap) as well */ + struct zone_heap* zh = ctx; + double t_diff = 0.0; + + zh->zh_ema_latency = (double)ea_latency * (1.0 - EMA_WEIGHT) + + zh->zh_ema_latency * EMA_WEIGHT; + if (!zh->zh_swapping && zh->zh_ema_samples++ % 100 == 99) { + struct zone_latency_history *zlh_entry; + zlh_entry = ch_calloc(1, sizeof(struct zone_latency_history)); + zlh_entry->zlh_latency = zh->zh_ema_latency; + LDAP_STAILQ_INSERT_TAIL( + &zh->zh_latency_history_queue, zlh_entry, zlh_next); + zh->zh_latency_history_qlen++; + while (zh->zh_latency_history_qlen > SLAP_ZN_LATENCY_HISTORY_QLEN) { + struct zone_latency_history *zlh; + zlh = LDAP_STAILQ_FIRST(&zh->zh_latency_history_queue); + LDAP_STAILQ_REMOVE_HEAD( + &zh->zh_latency_history_queue, zlh_next); + zh->zh_latency_history_qlen--; + ch_free(zlh); + } + if (zh->zh_latency_history_qlen == SLAP_ZN_LATENCY_HISTORY_QLEN) { + struct zone_latency_history *zlh_first, *zlh_last; + zlh_first = LDAP_STAILQ_FIRST(&zh->zh_latency_history_queue); + zlh_last = LDAP_STAILQ_LAST(&zh->zh_latency_history_queue, + zone_latency_history, zlh_next); + t_diff = zlh_last->zlh_latency - zlh_first->zlh_latency; + } + if (t_diff >= 2000) { + zh->zh_latency_jump++; + } else { + zh->zh_latency_jump = 0; + } + if (zh->zh_latency_jump > 3) { + zh->zh_latency_jump = 0; + zh->zh_swapping = 1; + } + } + return zh->zh_swapping; +} +#endif /* SLAP_ZONE_ALLOC */ |